/* ==========================================================================
   Haitso 2.3 — feuille de style unique
   1 Fondations · 2 Éléments de base · 3 Connexion · 4 Coquille applicative
   5 Composants de page · 6 Overlays · 7 Responsive

   Règle de maintenance : les media queries closent le fichier. N'ajoutez
   jamais de règle après la section 7, sinon elle annule le responsive.
   ========================================================================== */

/* ============================ 1. FONDATIONS ============================== */

/* Inter, la police de Kooly.html, auto-hébergée dans static/fonts (SIL Open
   Font License 1.1) : la politique de contenu n'autorise que les polices servies
   par Haitso, donc aucun appel à Google Fonts. Quatre graisses, 400 à 700, les
   seules que dessine la maquette ; un 800 demandé ici retombe sur le 700.
   Chaque fichier ne garde que les lettres latines, accents compris. */
@font-face {
  font-family: Inter;
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/inter-regular.woff2") format("woff2");
}
@font-face {
  font-family: Inter;
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/static/fonts/inter-medium.woff2") format("woff2");
}
@font-face {
  font-family: Inter;
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/static/fonts/inter-semibold.woff2") format("woff2");
}
@font-face {
  font-family: Inter;
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/static/fonts/inter-bold.woff2") format("woff2");
}

:root {
  color-scheme: light;
  /* Palette de Haitso. Elle ne bouge jamais : l'application a la même allure
     dans toutes les écoles. Les couleurs de l'établissement ne touchent que
     --doc-primaire et --doc-secondaire, plus bas, qui ne servent qu'aux pièces
     imprimées (bulletin, reçu, fiche). */
  /* Les valeurs sont celles de Kooly.html, la maquette validée. */
  --navy: #2f3140;
  --navy-2: #2F3140;
  --navy-3: #2F3140;
  --ink: #1d2129;
  --ink-soft: #4e5969;
  /* Le gris de la maquette. Il tient 4,8 de contraste sur une carte blanche,
     où il porte presque toujours ses mots — « Sans classe », le rappel sous un
     titre, le mois d'un paiement —, mais seulement 4,1 posé à même le fond. */
  --muted: #6b7280;
  --line: #e5e6eb;
  --line-soft: #f0f2f5;
  --bg: #f0f2f5;
  --card: #fff;
  /* Les pastilles de la barre du haut (école, année), d'après la maquette. */
  --pastille-fond: #f8fafc;
  --pastille-trait: #e9edf4;
  /* Le blanc d'un texte posé sur une couleur (bouton turquoise). */
  --blanc: #fff;
  /* Le fond gris très pâle qu'une ligne ou un bouton discret prend au survol. */
  --survol: #F7F8FA;
  /* Le fond gris doux d'un champ qu'on ne peut pas modifier. */
  --fond-doux: #F5F6F8;
  /* Le bord d'un champ ou d'un bouton discret au survol, un cran plus foncé que --line. */
  --trait-champ: #D1D5DB;
  /* La barre de défilement des listes longues, et son survol. Écrites en dur
     jusqu'ici : en sombre, elles restaient deux traits blancs. */
  --barre-defilement: #D5D8DE;
  --barre-defilement-survol: #A4A9B3;
  /* Le gris des titres de rubrique du menu (« ÉCOLE », « ARGENT »). */
  --titre-rubrique: #A4A9B3;
  /* La double flèche d'une colonne qu'on peut trier, tant qu'on ne l'a pas
     triée. Volontairement discrète : elle propose, elle n'annonce pas. */
  --fleche-tri: #C4C7CE;

  /* Le turquoise est la seule couleur d'action de Kooly.html : focus, survol,
     élément actif. --violet ne sert plus qu'aux repères qui doivent se
     distinguer du turquoise (deuxième chiffre du tableau de bord, papier). */
  --teal: #17c3b2;
  /* Le turquoise un cran plus foncé, au survol d'un bouton déjà turquoise. */
  --turquoise-fonce: #14A897;
  --violet: #7559c7;
  /* Le bleu de la maquette (--primary-blue), celui des Élèves. */
  --bleu: #2D9CDB;
  /* Le corail des Parents, dans les couleurs de rôle de Haitso. */
  --corail: #E07A5F;
  /* Le fond turquoise très pâle d'une pastille ou d'un élément actif (--badge-bg-green de la maquette). */
  --turquoise-pale: #E3FAF7;

  /* La charte de l'école, réécrite à l'exécution par applyIdentity(). Elle ne
     sert que sur le papier : filet de l'en-tête, cadre du logo, filet du pied.
     Sans école (espace éditeur), elle retombe sur les couleurs de Haitso. */
  --doc-primaire: #17c3b2;
  --doc-secondaire: #7559c7;

  /* Les gris du papier. Ils sont volontairement à part de ceux de
     l'application : un reçu et un bulletin sont des pièces officielles, et
     changer l'allure d'un écran ne doit pas changer celle d'un document déjà
     remis à une famille. Contrairement aux deux noms juste au-dessus, ceux-ci
     ne suivent jamais la charte de l'école : applyIdentity() n'y touche pas. */
  --papier-encre: #101828;
  --papier-texte: #475467;
  --papier-mention: #667085;
  --papier-grise: #98a2b3;
  --papier-fond: #f0f2f6;
  --papier-fond-somme: #eff2f6;
  --papier-trait: #d0d5dd;
  /* Le trait le plus pale du papier. Il vaut --line-soft, dont il vient, mais
     il vit ici : une feuille imprimee ne passe jamais en sombre, et un trait
     d'ecran sur un bulletin finirait noir sur blanc. */
  --papier-trait-pale: #f0f2f5;
  --papier-alerte: #b42318;
  --papier-alerte-fond: #fef3f2;
  --coral: #EF6461;
  --amber: #E38328;
  --green: #2FAE60;
  --red: #E5484D;
  --vert-note: #1F9254; /* le vert des bonnes notes, dans Kooly.html */
  /* Les treize couleurs des cours de l'emploi du temps, celles de Kooly.html :
     le trait à gauche de la carte, puis son fond pâle. */
  --cours-malagasy: #17C3B2; --cours-malagasy-fond: #E3FAF7;
  --cours-francais: #E07A5F; --cours-francais-fond: #FBEAE3;
  --cours-anglais: #4C6FFF; --cours-anglais-fond: #EEF1FF;
  --cours-maths: #2D9CDB; --cours-maths-fond: #EAF6FD;
  --cours-svt: #2FAE60; --cours-svt-fond: #E7F8EE;
  --cours-physique: #7559C7; --cours-physique-fond: #F1EDFB;
  --cours-histoire: #E38328; --cours-histoire-fond: #FDF3E7;
  --brun: #8B5E3C;
  --cours-ecm: var(--brun); --cours-ecm-fond: #F5EEE8;
  --cours-eps: #EF6461; --cours-eps-fond: #FDEAE9;
  --cours-info: #5B5FC7; --cours-info-fond: #EEEEFB;
  --cours-artistique: #D6409F; --cours-artistique-fond: #FBEAF4;
  --cours-philo: #556270; --cours-philo-fond: #EEF0F1;
  --cours-economie: #A98600; --cours-economie-fond: #FBF6E3;

  /* Typographie de Kooly.html : 12px est le plancher, 14px la base de lecture. */
  --fs-xs: 0.75rem;     /* 12px — badges, notes */
  --fs-sm: 0.8125rem;   /* 13px — libellés secondaires */
  --fs-base: 0.875rem;  /* 14px — texte courant */
  --fs-md: 1rem;        /* 16px — titres de panneau */
  --fs-lg: 1.125rem;    /* 18px */
  --fs-xl: 1.75rem;     /* 28px — titre de page */

  --radius: 20px;
  --small-radius: 12px;
  --pill: 999px;

  --shadow-sm: 0 2px 8px rgba(32, 45, 76, .06);
  --shadow: 0 12px 32px rgba(32, 45, 76, .08);
  --shadow-lg: 0 24px 60px rgba(32, 45, 76, .16);

  --sidebar: 280px;
  --topbar-h: 72px;
  --mobilenav-h: 74px;

  /* Cible tactile minimale (WCAG 2.5.5). */
  --tap: 44px;

  --ease: 180ms cubic-bezier(.4, 0, .2, 1);

  /* Le voile pose sous une fenetre. Il fonce avec le theme : un bleu nuit a
     55 % ne se voit plus sur un fond deja sombre. */
  --voile: rgba(15, 35, 60, 0.55);
}

/* ---- Le mode sombre -------------------------------------------------------
   Les memes quatorze valeurs que Kooly.html, sous les noms de l'application.
   Rien n'est invente : chaque teinte ci-dessous est copiee de la, ou elle a
   ete reglee ecran par ecran. Le choix se pose sur <html data-theme="dark">,
   depuis Mon profil > Preferences, et ne tient que sur cet appareil : on peut
   vouloir le sombre le soir sur son telephone et le clair au bureau.

   Ce qui NE change pas, et c'est voulu :
   - les couleurs d'action et de role (turquoise, bleu, violet, corail...) :
     elles identifient, et une couleur qui change de teinte n'identifie plus ;
   - le papier (--papier-*, --doc-*) : un bulletin sort blanc de l'imprimante,
     quel que soit l'ecran depuis lequel on l'a lance. */
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #1C1C1D;
  --card: #252728;
  --ink: #EDEDED;
  --ink-soft: #C2C2C2;
  --muted: #B8B8B8;
  --line: #333333;
  --line-soft: #2E2E2E;
  --pastille-fond: var(--card);
  --pastille-trait: var(--line);
  --trait-champ: #404040;
  --survol: #333333;
  --fond-doux: #232425;
  --turquoise-pale: #123330;
  --voile: rgba(0, 0, 0, 0.6);
  --barre-defilement: #9F9F9F;
  --barre-defilement-survol: #B8B8B8;
  --titre-rubrique: #8A8A8A;
  --fleche-tri: #8A8A8A;

  /* Les ombres etaient bleutees : sur un fond sombre, une ombre bleu nuit ne
     se voit plus et les cartes perdent leur relief. Elles passent au noir,
     comme celle de l'icone navy dans Kooly.html. */
  --shadow-sm: 0 2px 8px rgba(0, 0, 0, .30);
  --shadow: 0 12px 32px rgba(0, 0, 0, .40);
  --shadow-lg: 0 24px 60px rgba(0, 0, 0, .55);

  /* Les fonds des treize cours de l'emploi du temps. Le trait de couleur a
     gauche ne bouge pas : c'est lui qui nomme la matiere. */
  --cours-malagasy-fond: #16302E;
  --cours-francais-fond: #382823;
  --cours-anglais-fond: #232A42;
  --cours-maths-fond: #1D3546;
  --cours-svt-fond: #1F3327;
  --cours-physique-fond: #2B2740;
  --cours-histoire-fond: #3A2E1E;
  --cours-ecm-fond: #332822;
  --cours-eps-fond: #392325;
  --cours-info-fond: #262844;
  --cours-artistique-fond: #382233;
  --cours-philo-fond: #2A2D31;
  --cours-economie-fond: #332B18;
}

/* Le mot Haitso de la bande laterale est dessine dans l'encre du texte, comme
   dans Kooly.html : en sombre, le bleu marine disparaitrait sur la carte. */
:root[data-theme="dark"] .sidebar-icons .brand-logo { color: var(--ink); }

/* Les ombres colorees des icones de cartes. A 30 %, elles deviennent des
   halos lumineux sur un fond sombre ; Kooly.html les ramene a 16 %, et passe
   celle du bleu marine au noir, qui n'a pas de halo a donner. */
:root[data-theme="dark"] .stat-icon.icon-pink { box-shadow: 0 8px 16px rgba(23,195,178,0.16); }
:root[data-theme="dark"] .stat-icon.icon-blue { box-shadow: 0 8px 16px rgba(45,156,219,0.16); }
:root[data-theme="dark"] .stat-icon.icon-amber { box-shadow: 0 8px 16px rgba(227,131,40,0.16); }
:root[data-theme="dark"] .stat-icon.icon-purple { box-shadow: 0 8px 16px rgba(117,89,199,0.16); }
:root[data-theme="dark"] .stat-icon.icon-red { box-shadow: 0 8px 16px rgba(229,72,77,0.16); }
:root[data-theme="dark"] .stat-icon.icon-brown { box-shadow: 0 8px 16px rgba(139,94,60,0.16); }
:root[data-theme="dark"] .stat-icon.icon-corail { box-shadow: 0 8px 16px rgba(224,122,95,0.16); }
:root[data-theme="dark"] .stat-icon.icon-navy { box-shadow: 0 8px 16px rgba(0,0,0,0.4); }

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-size: var(--fs-base);
  line-height: 1.55;
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 { margin: 0; line-height: 1.25; font-weight: 700; }
p { margin: 0; }
button, input, select, textarea { font: inherit; }
button { cursor: pointer; }

/* ---- Icônes ----
   Tracés SVG au trait générés par icon() dans app.js. Deux principes :
   la taille suit la police environnante (em), la couleur suit currentColor.
   Il n'y a donc aucune règle de couleur d'icône à écrire au cas par cas :
   une icône posée sur la barre latérale devient blanche, la même dans un
   bouton actif prend la couleur de l'établissement. */
.ico {
  display: block;
  width: 1.35em;
  height: 1.35em;
  flex: 0 0 auto;
  overflow: visible;
}
.ico.spin { animation: ico-spin 900ms linear infinite; transform-origin: 50% 50%; }
@keyframes ico-spin { to { transform: rotate(360deg); } }

/* ---- Barres de défilement ----
   Les barres natives de Windows sont larges, grises et flanquées de flèches.
   On les remplace par un simple pouce arrondi, sans piste ni boutons.
   Déclarer ::-webkit-scrollbar suffit à supprimer les flèches. */
* { scrollbar-width: thin; scrollbar-color: rgba(107, 117, 144, .35) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track, ::-webkit-scrollbar-corner { background: transparent; }
::-webkit-scrollbar-thumb {
  border: 3px solid transparent;  /* marge intérieure : le pouce ne colle pas au bord */
  border-radius: var(--pill);
  background: rgba(107, 117, 144, .35);
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: rgba(107, 117, 144, .6); background-clip: content-box; }


/* Utilitaires */
.hidden { display: none !important; }
/* L'attribut hidden doit cacher, quoi qu'il arrive. Le navigateur ne lui donne
   qu'un display:none de base, que la moindre règle d'affichage écrase — un champ
   de formulaire masqué en JavaScript restait visible, .form-field étant en flex. */
[hidden] { display: none !important; }
.muted { color: var(--muted); font-size: var(--fs-sm); }
/* Une précision qui vit sous un chiffre de tableau : « 3 notes sur « Semestre 1 » »
   sous « 8 sur 9 ». Elle prend sa propre ligne au lieu de coller au compte. */
.sous-compte { display: block; margin-top: 2px; color: var(--muted); font-size: var(--fs-xs); }
.liste-laisses { list-style: none; margin: 8px 0 0; padding: 0; }
.liste-laisses li { padding: 8px 0; border-top: 1px solid var(--line-soft); }
/* .mobile-only est défini en section 2, après .icon-btn : à spécificité
   égale c'est l'ordre qui tranche, et un bouton peut porter les deux classes. */

/* Accessibilité : anneau de focus visible et homogène partout. */
:where(a, button, input, select, textarea):focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 2px;
  border-radius: 6px;
}

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

/* ========================= 2. ÉLÉMENTS DE BASE =========================== */

/* Les boutons reprennent .btn-primary et .btn-outline de Kooly.html. Sur un
   téléphone, ils gardent la hauteur d'un doigt (voir plus bas). */
.primary-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px 28px;
  border: 0;
  border-radius: 20px;
  background: var(--teal);
  color: var(--blanc);
  font-size: 14px;
  font-weight: 600;
  line-height: normal;
  box-shadow: 0 4px 12px rgba(23, 195, 178, .25);
  transition: transform var(--ease), box-shadow var(--ease), filter var(--ease);
}
.primary-btn:hover { transform: translateY(-1px); box-shadow: 0 6px 16px rgba(23, 195, 178, .35); }
.primary-btn:active { transform: translateY(1px); }
/* .full : bouton pleine largeur, libellé à gauche et flèche à droite. */
.primary-btn.full { width: 100%; justify-content: space-between; }

.secondary-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 20px;
  border: 1.5px solid var(--line);
  border-radius: 20px;
  background: var(--card);
  color: var(--ink);
  font-size: 13px;
  font-weight: 600;
  line-height: normal;
  transition: border-color var(--ease), color var(--ease);
}
.secondary-btn:hover { border-color: var(--teal); color: var(--teal); }
@media (max-width: 640px) {
  .primary-btn, .secondary-btn, .panel-link { min-height: var(--tap); }
  /* La hauteur d'un doigt suffit : un intitulé sur deux lignes ne doit pas gonfler le bouton. */
  .primary-btn, .secondary-btn { padding-top: 6px; padding-bottom: 6px; }
}

button:disabled { opacity: .55; cursor: not-allowed; filter: none; transform: none; }

.icon-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  flex: 0 0 auto;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--card);
  color: var(--ink-soft);
  font-size: var(--fs-md);
  line-height: 1;
  transition: border-color var(--ease), background var(--ease);
}
.icon-btn:hover { border-color: var(--teal); background: var(--line-soft); }

/* Masqué par défaut, réaffiché sous 900px (section 7). Doit rester après
   .icon-btn pour l'emporter sur son display:inline-flex. */
.mobile-only { display: none; }
/* La loupe du téléphone : masquée sur un ordinateur, où la case de recherche
   tient déjà dans la barre. Même raison de place dans le fichier que
   `.mobile-only` ci-dessus. */
.recherche-mobile { display: none; }

/* Une réponse que Haitso donne sous une case, sans qu'on ait rien demandé : ce
   que cet élève a déjà réglé, écrit sous son nom au moment d'encaisser. Elle
   prend sa propre ligne, se lit en gris comme une note, et laisse respirer la
   pastille qui signale un mois sauté. */
.note-champ { display: block; margin-top: 6px; line-height: 1.5; }
/* « Scolarité mensuelle : réglé jusqu'à juin 2026. » C'est la seule phrase qui
   empêche d'encaisser deux fois le même mois, et c'était la plus petite de
   l'écran : du gris clair, sous la case, là où l'œil ne va pas. Elle a
   maintenant son propre encadré, écrit à la taille du texte courant. */
.bloc-regle {
  margin-top: 8px;
  padding: 10px 12px;
  border-radius: var(--small-radius);
  background: var(--line-soft);
  color: var(--ink);
  font-size: var(--fs-base);
  line-height: 1.55;
}
.bloc-regle .ligne-tarif + .ligne-tarif { margin-top: 6px; }
.note-champ .status-badge { margin-left: 4px; vertical-align: middle; }

/* Une case que Haitso remplit à la place de la personne : elle se lit comme une
   réponse déjà donnée, pas comme une case oubliée. Fond adouci et curseur qui
   ne promet rien — la note en dessous dit d'où vient la valeur. */


/* Champs de formulaire — style commun à tous les contrôles saisissables. */
.login-form input,
.form-field input,
.form-field select,
.form-field textarea,
.doc-saisie,
.filter-control,
.grade-input,
.comment-input {
  width: 100%;
  min-height: var(--tap);
  padding: 10px 14px;
  border: 1px solid var(--line);
  border-radius: var(--small-radius);
  background: var(--card);
  color: var(--ink);
  font-size: var(--fs-base);
  transition: border-color var(--ease), box-shadow var(--ease);
}
.form-field textarea { min-height: 120px; resize: vertical; line-height: 1.6; }

.login-form input:focus,
.form-field input:focus,
.form-field select:focus,
.form-field textarea:focus,
.doc-saisie:focus,
.filter-control:focus,
.grade-input:focus,
.comment-input:focus {
  outline: none;
  border-color: var(--teal);
  box-shadow: 0 0 0 3px rgba(23, 195, 178, .14);
}
.form-field input:disabled { background: var(--line-soft); color: var(--muted); }
/* Une case que Haitso remplit à la place de la personne : elle se lit comme une
   réponse déjà donnée, pas comme une case oubliée. Le même fond qu'une case
   fermée, mais l'encre reste noire — ce n'est pas une case hors service, c'est
   une réponse. La note en dessous dit d'où elle vient. */
.form-field input.champ-deduit { background: var(--line-soft); cursor: default; }
/* Un montant : la devise est écrite à droite, dans la case elle-même, comme sur
   un cahier de caisse. Elle ne se saisit pas et ne se relit pas au moment
   d'envoyer le formulaire — c'est un rappel, pas une réponse à donner. Le
   rembourrage à droite laisse la place aux trois lettres, pour que le dernier
   chiffre d'un montant long ne passe pas dessous. */
.champ-montant { position: relative; display: block; }
.champ-montant input { padding-right: 52px; }
.champ-montant em {
  position: absolute;
  right: 14px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--muted);
  font-size: var(--fs-sm);
  font-style: normal;
  pointer-events: none;
}
/* Un texte à lire, pas un formulaire : lignes plus aérées et titres courts,
   pour que la page « Vos données » se parcoure des yeux au lieu de se subir. */
.texte-info { font-size: var(--fs-sm); color: var(--ink-soft); line-height: 1.6; }
.texte-info h4 { margin: 18px 0 6px; font-size: var(--fs-sm); font-weight: 700; color: var(--ink); }
.texte-info p { margin: 0; }
.texte-info ul { margin: 0; padding-left: 18px; display: grid; gap: 6px; }

/* Titre à l’intérieur d’un formulaire long : les paramètres de l’école
   mêlent la papeterie et l’argent, et sans ce trait on ne voit plus où
   commence l’un et où finit l’autre. */
.form-section { margin: 8px 0 0; padding-top: 14px; border-top: 1px solid var(--line);
  font-size: var(--fs-sm); font-weight: 700; color: var(--ink); }
/* Le premier titre d'un formulaire n'a rien à séparer de ce qui le précède :
   son trait ferait une seconde ligne juste sous l'en-tête de la fenêtre. */
.form-section.premier { margin-top: 0; padding-top: 0; border-top: 0; }

/* Précision sous un champ : la forme attendue d'un numéro étranger, par exemple.
   Elle se lit après la saisie, pas avant — d'où la taille réduite et le gris. */
.form-field small { margin-top: 4px; color: var(--muted); font-size: var(--fs-sm); }

/* Champ cherchable — un champ de saisie et sa liste de propositions. Le
   navigateur en propose une (datalist), mais il la dessine lui-même : fond
   noir, hors de la fenêtre, jusqu'en bas de l'écran. Celle-ci est à nous. */
.champ-cherche { position: relative; }
.suggestions {
  position: absolute;
  z-index: 20;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  margin: 0;
  padding: 6px;
  list-style: none;
  max-height: 50vh;
  overflow-y: auto;
  border: 1px solid var(--line);
  border-radius: var(--small-radius);
  background: var(--card);
  box-shadow: var(--shadow);
}
.suggestions li {
  padding: 9px 12px;
  border-radius: 10px;
  color: var(--ink);
  font-size: var(--fs-base);
  cursor: pointer;
}
.suggestions li:hover,
.suggestions li.vise { background: var(--line-soft); }
.form-field input[type="color"] { padding: 4px; cursor: pointer; }


.form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}
.form-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--ink-soft);
}
.form-field.full { grid-column: 1 / -1; }

/* ---- Le champ où l'on écrit un énoncé ----
   Une case de trois lignes convient pour « exercices 4 et 5 page 32 ». Elle ne
   convient pas quand l'élève n'a pas de livre et que le professeur doit écrire
   l'exercice en entier, comme il le ferait au tableau : il faut de la place, et
   de quoi mettre en avant la consigne. D'où une barre d'outils courte — la
   taille, le gras, l'italique, le souligné, le surligneur — et le trombone,
   pour le devoir déjà tapé sous Word ou écrit à la main puis scanné. */
.editeur-riche {
  border: 1px solid var(--line);
  border-radius: var(--small-radius);
  background: var(--card);
  overflow: hidden;
  transition: border-color var(--ease), box-shadow var(--ease);
}
.editeur-riche:focus-within { border-color: var(--teal); box-shadow: 0 0 0 3px rgba(23, 195, 178, .14); }
.editeur-barre {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 6px 8px;
  border-bottom: 1px solid var(--line);
  background: var(--line-soft);
}
.editeur-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-width: 32px;
  height: 32px;
  padding: 0 6px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--ink-soft);
  font-size: var(--fs-base);
  transition: background-color var(--ease), color var(--ease);
}
.editeur-btn:hover { background: rgba(24, 32, 51, .07); }
.editeur-btn.actif { background: rgba(23, 195, 178, .12); color: var(--teal); }
/* Les icônes de Kooly.html (Font Awesome) ; la taille s'écrit « T T », un
   grand et un petit, avec une flèche fine. */
.editeur-taille { display: flex; align-items: baseline; gap: 1px; font-weight: 600; line-height: 1; }
.editeur-taille span { font-size: 15px; }
.editeur-taille small { font-size: 10px; }
.editeur-caret { font-size: 9px; color: var(--muted); }
.editeur-sep { width: 1px; height: 18px; margin: 0 5px; background: var(--line); }

/* Les deux petits menus (taille, émoji) s'ouvrent sous leur bouton. */
.editeur-menu { position: relative; display: inline-flex; }
.editeur-liste {
  display: none;
  position: absolute;
  z-index: 20;
  top: calc(100% + 4px);
  left: 0;
  padding: 6px;
  border: 1px solid var(--line);
  border-radius: var(--small-radius);
  background: var(--card);
  box-shadow: var(--shadow);
}
.editeur-menu.ouvert .editeur-liste { display: block; }
.editeur-liste.emojis { display: none; grid-template-columns: repeat(6, 1fr); gap: 2px; }
.editeur-menu.ouvert .editeur-liste.emojis { display: grid; }
.editeur-choix {
  display: block;
  width: 100%;
  padding: 6px 12px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--ink);
  text-align: left;
  white-space: nowrap;
}
.editeur-liste.emojis .editeur-choix { width: 34px; padding: 4px; text-align: center; font-size: 18px; }
.editeur-choix:hover { background: var(--line-soft); }

/* Le nom du fichier joint reste sous les yeux, avec de quoi le retirer. */
.editeur-piece { display: flex; align-items: center; gap: 2px; margin-left: auto; min-width: 0; }
.editeur-piece b {
  max-width: 14rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--muted);
  font-size: var(--fs-sm);
  font-weight: 600;
}
.editeur-retirer {
  display: flex;
  padding: 4px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--muted);
}
.editeur-retirer:hover { background: rgba(24, 32, 51, .07); color: var(--red); }

/* Les hauteurs de Kooly.html : 160 px au départ, et la poignée du coin tire le
   champ de 100 à 600 px. L'ancien minimum de 260 px gonflait la fenêtre et
   empêchait de réduire le champ ; le plafond de 55 % de l'écran l'empêchait de
   grandir. */
.editeur-texte {
  height: 160px;
  min-height: 100px;
  max-height: 600px;
  padding: 14px 16px;
  overflow-y: auto;
  outline: none;
  color: var(--ink);
  font-size: var(--fs-base);
  font-weight: 400;
  line-height: 1.7;
  resize: vertical;
}
/* L'invite disparaît dès le premier caractère — et seulement à ce moment-là. */
.editeur-texte:empty::before { content: attr(data-invite); color: var(--muted); }

/* Le devoir tel qu'il a été écrit, quand on l'ouvre pour le lire. */
.texte-riche { line-height: 1.7; overflow-wrap: anywhere; }

/* ---- Explication repliée ----
   Une phrase sous chaque case finit par ne plus se lire : l'écran devient un
   mur de gris qu'on saute. Repliée derrière une icône, elle attend d'être
   demandée. L'étiquette et son icône tiennent sur la même ligne — .form-field
   empile ses enfants, il faut donc les réunir dans .etiquette. */
.etiquette { display: flex; align-items: center; gap: 5px; }
/* L'icône se tient en retrait de son étiquette : elle propose une explication,
   elle ne réclame pas l'attention. Elle ne se colore qu'au moment où la main
   arrive dessus. C'est elle qui porte la bulle : la pointe doit retomber sur
   l'icône que l'on survole, pas sur le début de l'étiquette. */
.infobulle {
  position: relative;
  display: inline-flex;
  flex: none;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  /* Le point d'interrogation des explications faisait 15 px à demi effacés : on
     ne le voyait pas, et au doigt on le manquait. La zone touchable fait
     maintenant 24 px — le retrait négatif la creuse vers l'extérieur, pour que
     l'étiquette du champ ne bouge pas d'un pixel. */
  opacity: .75;
  cursor: help;
  padding: 4px;
  margin: -4px;
  vertical-align: -4px;
  transition: color var(--ease), opacity var(--ease);
}
.infobulle .ico { width: 16px; height: 16px; }
.infobulle:hover, .infobulle:focus-visible { color: var(--teal); opacity: 1; }
/* La bulle s'ouvre **au-dessus** de l'icône, jamais en dessous : sous
   l'étiquette, elle se posait exactement sur la case que l'on est en train de
   remplir et cachait ce qu'on y écrivait. Au-dessus, elle ne recouvre que
   l'étiquette du champ précédent, déjà lue.
   Elle s'aligne sur le bord gauche de l'icône plutôt que de se centrer dessus :
   centrée, elle déborderait à gauche pour un champ collé au bord de la fenêtre,
   et la fenêtre modale coupe ce qui sort d'elle. */
/* Elle est sombre et compacte, comme partout ailleurs sur le web. En blanc
   bordé de vert avec une grande ombre, elle avait l'air d'une seconde carte
   posée sur la page : on la lisait comme un bloc du formulaire, et elle
   recouvrait le titre de la carte au-dessus au lieu de passer par-dessus
   discrètement. Une explication qu'on survole doit s'effacer d'elle-même. */
.infobulle-texte {
  position: absolute;
  bottom: calc(100% + 10px);
  left: -12px;
  z-index: 5;
  width: max-content;
  max-width: min(280px, 70vw);
  padding: 9px 12px;
  border-radius: 10px;
  background: var(--navy);
  color: rgba(255, 255, 255, .92);
  font-size: var(--fs-xs);
  font-weight: 400;
  line-height: 1.5;
  text-align: left;
  box-shadow: 0 8px 20px rgba(11, 23, 57, .22);
  opacity: 0;
  visibility: hidden;
  transform: translateY(4px);
  transition: opacity var(--ease), transform var(--ease), visibility var(--ease);
}
/* La pointe qui redescend vers l'icône : un seul triangle, de la couleur de la
   bulle. Il en fallait deux tant que la bulle avait un filet — le second
   servait à évider l'intérieur ; sans filet, il n'a plus lieu d'être. Elle
   s'arrête quatre pixels au-dessus de l'icône, sinon elle se plante dedans. */
.infobulle-texte::before {
  content: '';
  position: absolute;
  top: 100%;
  left: 13px;
  border: 6px solid transparent;
  border-top-color: var(--navy);
  /* Quand la bulle a dû glisser vers l'intérieur de l'écran (voir
     placeInfobulle), la pointe la suivrait et ne désignerait plus rien : on la
     ramène d'autant, pour qu'elle reste plantée sur son icône. */
  transform: translateX(calc(-1 * var(--glisse, 0px)));
}
/* Retournée vers la gauche quand l'icône est trop près du bord (voir
   placeInfobulle) : la pointe passe de l'autre côté pour rester sur l'icône. */
.infobulle.a-gauche .infobulle-texte { left: auto; right: -12px; }
.infobulle.a-gauche .infobulle-texte::before { left: auto; right: 13px; }
/* Retournée vers le bas quand il n'y a plus de place au-dessus (voir
   placeInfobulle). C'est le cas du premier champ d'une fenêtre modale : la
   bulle montait au-delà du haut de la fenêtre, qui la tranchait en plein mot —
   on lisait la fin d'une phrase dont le début avait disparu. Recouvrir la case
   qu'on explique le temps d'un survol vaut mieux qu'une explication coupée. */
.infobulle.en-dessous .infobulle-texte { bottom: auto; top: calc(100% + 10px); }
.infobulle.en-dessous .infobulle-texte::before { top: auto; bottom: 100%; border-top-color: transparent; border-bottom-color: var(--navy); }
/* Le survol pour la souris, le focus pour le clavier — et pour le doigt, qui
   n'a pas de survol : appuyer sur l'icône donne le focus, la bulle reste. */
.infobulle:hover .infobulle-texte,
.infobulle:focus .infobulle-texte { opacity: 1; visibility: visible; transform: none; }
/* Précision sous un champ : elle explique ce qu'on attend sans que l'on ait à
   ouvrir un mode d'emploi. Plus légère que l'étiquette, sinon elle la concurrence. */
.field-hint { font-weight: 400; font-size: var(--fs-xs, 0.75rem); color: var(--muted); }

/* ---- Groupe de cases à cocher ----
   Utilisé pour les cycles couverts par un établissement, où plusieurs réponses
   sont possibles. La règle .form-field input plus haut impose width:100% et une
   hauteur de cible tactile : il faut la neutraliser pour la case elle-même,
   sinon elle s'étire sur toute la ligne. La zone cliquable reste entière —
   c'est le <label> qui porte la hauteur, pas la case. */
.field-label { color: var(--ink-soft); }
.check-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 10px;
}
.check-item {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: var(--tap);
  padding: 10px 14px;
  border: 1px solid var(--line);
  border-radius: var(--small-radius);
  background: var(--card);
  font-weight: 500;
  cursor: pointer;
  transition: border-color var(--ease), background var(--ease);
}
.check-item:hover { border-color: var(--teal); }
.check-item input[type="checkbox"] {
  width: 18px;
  min-height: 0;
  height: 18px;
  flex: 0 0 auto;
  padding: 0;
  accent-color: var(--teal);
  cursor: pointer;
}
/* Cycle retenu : on le marque visuellement, la seule coche est trop discrète. */
.check-item:has(input:checked) {
  border-color: var(--teal);
  background: rgba(23, 195, 178, .06);
  color: var(--ink);
}

.form-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 12px;
  margin-top: 20px;
}

.filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-bottom: 18px;
}
/* Une barre qui ne porte que le bouton d'ajout : il se range à droite, au bout
   du tableau, là où l'œil finit sa ligne. À gauche il flottait seul au-dessus
   d'un intitulé de colonne. */
.filters.filtres-droite { justify-content: flex-end; }
.filter-control { width: auto; min-width: 10rem; flex: 0 1 auto; }
/* Un menu Haitso prend toute la largeur de la colonne qui le porte (l'écran
   Notes le pose sous son étiquette). Posé à même la ligne de filtres, il
   doit se comporter comme un champ : sinon chacun occupe une ligne entière et
   le bouton tombe tout seul en dessous. */
.filters > .menu-choix { width: auto; min-width: 10rem; flex: 0 1 auto; }

/* Une ligne de filtres se lit d'ordinaire par son contenu : « 6e A » ou « Mathématiques »
   n'ont besoin d'aucune explication, et les écrans qui n'en portent pas restent intacts.
   L'étiquette n'apparaît donc que là où le champ demande un nom plutôt qu'un choix
   évident — l'écran Notes, où se tromper d'évaluation ne provoque aucune erreur visible,
   seulement une moyenne fausse au bulletin trois mois plus tard.
   Le bouton s'aligne alors sur le bas des champs et non sur leur milieu, sinon il flotte
   à la hauteur des étiquettes. */
.filter-field { display: flex; flex-direction: column; gap: 6px; font-size: .74rem; font-weight: 700; color: var(--muted); letter-spacing: .03em; text-transform: uppercase; }
.filters:has(.filter-field) { align-items: flex-end; }

/* =========================== 3. CONNEXION ================================ */

.login-screen {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
  min-height: 100vh;
  min-height: 100dvh;
}

.login-showcase {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 40px;
  padding: 48px 5vw;
  overflow: hidden;
  background: linear-gradient(150deg, var(--navy), var(--navy-2) 55%, var(--navy-3));
  color: var(--blanc);
}
.showcase-orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(60px);
  opacity: .45;
  pointer-events: none;
}
.orb-one { width: 380px; height: 380px; top: -110px; right: -90px; background: var(--teal); }
.orb-two { width: 320px; height: 320px; bottom: -120px; left: -100px; background: var(--violet); }

.login-brand, .mobile-brand {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 14px;
}
.login-brand strong { display: block; font-size: var(--fs-md); }
.login-brand span { display: block; font-size: var(--fs-sm); color: rgba(255, 255, 255, .65); }

.brand-mark {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  flex: 0 0 auto;
  border-radius: 14px;
  background: var(--teal);
  color: var(--blanc);
  font-weight: 800;
  letter-spacing: .04em;
}

/* Le logo déposé occupe toute la pastille. `contain` plutôt que `cover` : un
   logo se lit en entier, on ne rogne pas un blason pour remplir un carré. */
.brand-mark.has-image { background: var(--blanc); padding: 3px; }
.brand-mark img { width: 100%; height: 100%; object-fit: contain; border-radius: 11px; }

/* ---- Dépôt du logo (Paramètres) ---- */
.logo-field {
  display: flex;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
  margin-bottom: 24px;
  padding: 18px;
  border: 1px solid var(--line);
  border-radius: var(--small-radius);
  background: var(--line-soft);
}
.logo-field.busy { opacity: .55; pointer-events: none; }
.logo-preview {
  display: grid;
  place-items: center;
  width: 72px;
  height: 72px;
  flex: 0 0 auto;
  overflow: hidden;
  border-radius: 18px;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
  font-weight: 800;
  font-size: var(--fs-lg);
}
.logo-preview img { width: 100%; height: 100%; object-fit: contain; }
/* Un logo doit tenir en entier dans son cadre ; un portrait, lui, se cadre —
   « contain » laisserait deux bandes blanches autour du visage. */
#photoField .logo-preview img { object-fit: cover; }
.logo-copy { flex: 1; min-width: 12rem; }
.logo-copy strong { display: block; font-size: var(--fs-base); }
.logo-copy span { display: block; margin-top: 2px; font-size: var(--fs-sm); color: var(--muted); }
/* Le bouton « Choisir une image » est un <label> : il doit donc se comporter
   comme un bouton, pas comme du texte posé à côté de la case de fichier. */
.logo-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 12px; }
.logo-actions label { cursor: pointer; }

.showcase-copy { position: relative; z-index: 1; max-width: 30rem; }
.showcase-copy h1 {
  margin: 0;
  font-size: clamp(1.9rem, 3.4vw, 3rem);
  line-height: 1.12;
  letter-spacing: -.02em;
}

/* La carte est accrochée en haut, pas centrée dans la hauteur. Centrée, elle
   remontait ou redescendait à chaque écran — connexion, mot de passe oublié,
   compte parent n'ont pas le même nombre de champs — et les cases se
   dérobaient sous la main entre deux gestes. Accrochée, le titre et le premier
   champ restent au même endroit, quel que soit l'écran affiché. */
.login-panel {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: min(12vh, 88px) 32px 40px;
  background: var(--bg);
  /* L'écran de gauche ne défile pas avec la carte : c'est cette moitié-ci qui
     défile toute seule quand un écran est plus long que la fenêtre. */
  max-height: 100dvh;
  overflow-y: auto;
}
.login-colonne { width: 100%; max-width: 27rem; }
.login-card {
  padding: 32px;
  border-radius: var(--radius);
  background: var(--card);
  box-shadow: var(--shadow);
}
.mobile-brand { display: none; margin-bottom: 24px; color: var(--ink); }
.mobile-brand strong { font-size: var(--fs-md); }
.login-card h2 { margin: 0 0 4px; font-size: var(--fs-xl); }
.login-card > p { margin-bottom: 4px; }

.login-form { display: flex; flex-direction: column; gap: 16px; margin-top: 24px; }
.login-form label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--ink-soft);
}

.password-wrap { position: relative; display: flex; }
.password-wrap input { padding-right: 52px; }
.password-wrap button {
  position: absolute;
  top: 50%;
  right: 6px;
  transform: translateY(-50%);
  width: 38px;
  height: 38px;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--muted);
  font-size: var(--fs-md);
}
.password-wrap button { display: flex; align-items: center; justify-content: center; }
.password-wrap button:hover { background: var(--line-soft); color: var(--ink); }

.divider {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 26px 0 16px;
  color: var(--muted);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: .08em;
}
.divider::before, .divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}

/* 3 colonnes sur la carte : les 7 profils tiennent en 3 rangées,
   ce qui évite que l'écran de connexion déborde sur un portable 900px. */
.demo-roles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(6.5rem, 1fr));
  gap: 8px;
}
.demo-role {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  min-height: var(--tap);
  padding: 8px 12px;
  border: 1px solid var(--line);
  border-radius: var(--small-radius);
  background: var(--card);
  text-align: left;
  transition: border-color var(--ease), background var(--ease);
}
.demo-role strong { font-size: var(--fs-sm); font-weight: 600; color: var(--ink); }
.demo-role span { font-size: var(--fs-xs); color: var(--muted); }
.demo-role:hover { border-color: var(--teal); background: var(--line-soft); }
.demo-role:hover strong { color: var(--teal); }

/* Les indications s'alignent sur les champs, à gauche. Centrées, elles
   faisaient un petit bloc de texte au milieu de la carte que l'œil prenait
   pour la suite du formulaire. */
.login-hint { margin-top: 18px; font-size: var(--fs-sm); color: var(--muted); }
/* « Mot de passe » à gauche, « Oublié ? » collé à droite sur la même ligne. */
.label-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
/* Sous la carte, plus discret que ce qu'elle contient. */
.login-pied { margin-top: 16px; text-align: center; font-size: var(--fs-sm); }
.login-pied .link-btn { color: var(--muted); }
.login-pied .link-btn:hover { color: var(--teal); }
/* Un bouton qui se lit comme un lien : la porte des familles ne doit pas
   concurrencer « Se connecter », qui reste le geste de tous les jours. Mais
   elle doit se voir — d'où la couleur de Haitso, et non le gris du texte
   autour, qui la faisait passer pour une simple phrase. */
.link-btn { padding: 0; border: 0; background: none; font: inherit; color: var(--teal); text-decoration: underline; cursor: pointer; }

/* Créer un compte parent, ouvrir celui du directeur, taper le code à six
   chiffres : ces gestes remplacent le contenu de la carte au lieu de flotter
   par-dessus. Sur un écran de connexion il n'y a rien derrière à garder sous
   les yeux, et une fenêtre qu'un doigt posé à côté referme effacerait le code
   que le parent vient de recopier sur son papier. */
.login-etape .retour { display: inline-block; margin-bottom: 18px; text-decoration: none; font-weight: 600; }
.login-etape h2 { margin: 0 0 8px; font-size: var(--fs-xl); }
/* Un champ par ligne. La carte d'entrée est étroite : à deux colonnes, chaque
   case tombait à une largeur où l'on ne voit plus ce qu'on écrit — le numéro
   de téléphone n'avait plus la place que de trois chiffres. Ailleurs dans
   l'application les formulaires sont larges et gardent leurs deux colonnes. */
.login-etape .form-grid { margin-top: 20px; grid-template-columns: 1fr; }
/* À qui demander son code, selon qui l'on est : trois lignes valent mieux qu'un
   paragraphe où chacun doit chercher la sienne. */
.login-etape .liste-aide { margin: 12px 0 0; padding-left: 18px; display: grid; gap: 4px; }
.login-etape .liste-aide strong { color: var(--ink-soft); }
.login-etape .form-actions { margin-top: 22px; }
.login-etape .form-actions .primary-btn { width: 100%; justify-content: center; }
.login-hint code {
  padding: 2px 8px;
  border-radius: 6px;
  background: var(--line-soft);
  color: var(--ink);
}

.login-error { margin-top: 14px; font-size: var(--fs-sm); font-weight: 600; color: var(--red); }
.login-error:not(:empty) {
  padding: 12px 14px;
  border-radius: var(--small-radius);
  background: rgba(229, 72, 77, .1);
}

/* ====================== 4. COQUILLE APPLICATIVE ========================== */

.app-shell {
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--bg);
  padding-left: var(--sidebar);
}

/* ---- Barre latérale ----
   Celle de Kooly.html : blanche, en deux bandes. L'étroite est à Haitso, la large
   à l'école. */
.sidebar {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 40;
  display: flex;
  width: var(--sidebar);
  height: 100vh;
  height: 100dvh;
  background: var(--card);
  border-right: 1px solid var(--line);
  color: var(--ink);
  transition: transform var(--ease);
}
/* Les règles qui suivent sont celles de Kooly.html, reprises telles quelles avec
   leurs noms : seules les couleurs passent par les variables de l'application,
   qui ont les mêmes valeurs. Un bouton remplace le <div> de Kooly.html, d'où la
   remise à zéro de sa bordure et de son fond. */
.sidebar-icons { width: 70px; flex-shrink: 0; display: flex; flex-direction: column; align-items: center; padding: 30px 0; border-right: 1px solid var(--line); position: relative; }
.sidebar-icons .brand-logo { display: flex; align-items: flex-end; gap: 4px; transform: rotate(-90deg); margin-bottom: 60px; margin-top: 20px; color: var(--navy); }
.sidebar-icons .brand-logo svg { display: block; width: 70px; height: auto; }
.sidebar-icons .brand-logo-dot { width: 5px; height: 5px; border-radius: 50%; background-color: var(--teal); flex-shrink: 0; margin-bottom: 6px; }
.sidebar-icons .icon-item { width: 40px; height: 40px; display: flex; justify-content: center; align-items: center; color: var(--muted); margin-bottom: 20px; border-radius: 50%; cursor: pointer; transition: all .2s; position: relative; padding: 0; border: 0; background: transparent; font-size: 16px; }
.sidebar-icons .icon-item:hover { color: var(--ink); background-color: var(--survol); }
.sidebar-icons .icon-item.active { color: var(--teal); }
.sidebar-icons .icon-item .ico { width: 20px; height: 20px; }
/* Icône dessinée pour Haitso (le portefeuille d'Argent) : elle prend la couleur du bouton. */
.sidebar-icons .icon-item .icone-dessin { width: 18px; height: 18px; display: block; }
.sidebar-icons .icon-item .icone-trait { width: 21px; height: 21px; display: block; stroke-width: 1.7; }
/* Support et Paramètres gardent Font Awesome, en 18 px : pleins, ils paraissent plus gros que les icônes au trait. */
.sidebar-icons [data-monde="support"] i, .sidebar-icons [data-monde="reglages"] i { font-size: 18px; }
/* Les quatre rectangles du tableau de bord paraissent plus petits que les autres dessins au même format. */
.sidebar-icons [data-monde="ecole"] .icone-trait { width: 24px; height: 24px; }
.dots-grid { display: grid; grid-template-columns: repeat(3, 4px); grid-template-rows: repeat(3, 4px); gap: 4px; width: 20px; height: 20px; place-content: center; }
.dots-grid span { width: 4px; height: 4px; border-radius: 50%; background-color: currentColor; }
.active-indicator { display: none; position: absolute; left: -15px; top: 50%; transform: translateY(-50%); width: 4px; height: 28px; background-color: var(--teal); border-radius: 0 4px 4px 0; }
.icon-item.active .active-indicator { display: block; }
/* La boîte que remplit buildMenu() s'efface : les icônes sont posées dans la
   bande comme sur Kooly.html. */
.rail-icones { display: contents; }
.bottom-icons { margin-top: auto; display: flex; flex-direction: column; align-items: center; gap: 20px; }
/* La photo, en haut à droite : un clic ouvre la page Mon profil, où se trouve
   aussi Déconnexion, en bas. */
.side-user-wrap { position: relative; }
.side-user {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
}
.side-user { position: relative; }
/* Au survol de la photo, le nom et le rôle : la même bulle sombre que les
   explications, ouverte en dessous et calée à droite, la photo étant au bord. */
.side-user .infobulle-texte { top: calc(100% + 10px); bottom: auto; left: auto; right: 0; pointer-events: none; white-space: nowrap; }
.side-user .infobulle-texte::before { top: auto; bottom: 100%; left: auto; right: 10px; border-top-color: transparent; border-bottom-color: var(--navy); transform: none; }
.side-user:hover .infobulle-texte, .side-user:focus-visible .infobulle-texte { opacity: 1; visibility: visible; transform: none; }
/* Menu ouvert, l'infobulle se tait : elle occuperait la place du menu et
   redirait le nom que la personne vient d'aller chercher. */
.side-user[aria-expanded="true"] .infobulle-texte { opacity: 0; visibility: hidden; }
/* La barre du haut est floutée derrière elle, et un flou de fond fait du cadre
   qui le porte le nouveau repère de tout ce qui est « fixé à l'écran » : le menu
   s'en allait à 300 pixels de la photo. Il se pose donc sous la photo elle-même,
   et refuse les coordonnées d'écran que le code commun écrit pour les autres. */
.side-user-wrap .action-dropdown { position: absolute; top: calc(100% + 8px) !important; left: auto !important; right: 0; }
.side-user .avatar { width: 32px; height: 32px; border-radius: 50%; font-size: var(--fs-xs); transition: box-shadow var(--ease); }
.side-user:hover .avatar { box-shadow: 0 0 0 3px var(--line); }
/* Comme sur Kooly.html, c'est toute la bande qui défile : la barre de défilement
   longe son bord droit au lieu de se glisser à l'intérieur, contre les libellés. */
.sidebar-menu { display: flex; flex-direction: column; flex: 1; min-width: 0; padding: 40px 20px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: var(--barre-defilement) transparent; }
.sidebar-menu::-webkit-scrollbar { width: 6px; }
.sidebar-menu::-webkit-scrollbar-track { background: transparent; }
.sidebar-menu::-webkit-scrollbar-thumb { background-color: var(--barre-defilement); border-radius: 10px; }
.sidebar-menu::-webkit-scrollbar-thumb:hover { background-color: var(--barre-defilement-survol); }
/* Un monde d'une seule page n'a pas de bande large : la page prend sa place. */
@media (min-width: 901px) {
  .app-shell.sans-bande { padding-left: 70px; }
  .app-shell.sans-bande .sidebar { width: 70px; }
  .app-shell.sans-bande .sidebar-menu { display: none; }
}
.avatar {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  flex: 0 0 auto;
  border-radius: 12px;
  background: var(--teal);
  color: var(--blanc);
  font-size: var(--fs-sm);
  font-weight: 700;
  position: relative;
  overflow: hidden;
}

/* La photo se superpose aux initiales sans les effacer : si elle ne charge pas,
   le script retire l'image et les initiales sont déjà là, en place. */
.avatar img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

/* Menu principal, en texte seul : les règles de Kooly.html, telles quelles.
   Kooly.html ne fixe pas d'interligne ; l'application en pose un plus haut sur
   tout le corps, qu'on annule ici pour retrouver la même hauteur de ligne. */
.side-nav { display: flex; flex-direction: column; line-height: normal; }
.nav-item { flex-shrink: 0; width: 100%; padding: 12px 16px; margin-bottom: 4px; color: var(--muted); font-size: 14px; font-weight: 500; cursor: pointer; border-radius: 8px; transition: all .2s; white-space: nowrap; border: 0; background: transparent; text-align: left; line-height: inherit; }
.nav-item:hover { background-color: var(--survol); color: var(--ink); }
.nav-item.active { color: var(--teal); font-weight: 600; }
.nav-item:focus-visible, .icon-item:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }
/* Les repères de famille du menu : un mot, pas un bouton. Un compte qui n'ouvre
   aucune page d'une famille ne voit pas son titre : c'est `menuFor()` qui le
   retire, le style n'a rien à cacher. */
.nav-section-title { font-size: 11px; text-transform: uppercase; color: var(--titre-rubrique); letter-spacing: .5px; margin-top: 40px; margin-bottom: 12px; padding-left: 16px; font-weight: 600; }
.side-nav > .nav-section-title:first-child { margin-top: 0; }

/* ---- Barre supérieure ---- */
.topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: var(--topbar-h);
  padding: 26px 50px 26px;
  /* Le gris de la page, un peu transparent : la barre se confond avec le fond comme dans Kooly.html. */
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  backdrop-filter: blur(10px);
}
.topbar-left { display: flex; align-items: center; gap: 14px; min-width: 0; }
.topbar-left > div { min-width: 0; }
/* Le titre de la page, en dernier : un point turquoise puis le texte en gris (la maquette). */
.topbar h1 {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--ink);
  font-size: 0.875rem;
  font-weight: 400;
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.topbar-actions { display: flex; align-items: center; gap: 12px; }
/* L'école et l'année, deux pastilles puis, à la fin, « • Titre de la page » (la maquette) :
   on doit toujours savoir dans quelle école et quelle année on travaille. Le nom de
   l'école se coupe par « … » (nom complet au survol), l'année ne se coupe jamais. */
.topbar-left > div { display: flex; align-items: center; gap: 16px; }
.pastilles-barre { display: flex; align-items: center; gap: 8px; margin: 0; min-width: 0; }
.pastilles-barre:empty { display: none; }
.pastille-ecole, .pastille-annee-barre { display: inline-flex; align-items: center; gap: 8px; padding: 8px 14px; border-radius: 8px; background: var(--pastille-fond); border: 1px solid var(--pastille-trait); color: var(--muted); font-size: 0.875rem; font-weight: 400; white-space: nowrap; }
.pastille-ecole { min-width: 0; max-width: 22rem; }
.pastille-ecole > span { overflow: hidden; text-overflow: ellipsis; }
.pastille-ecole svg { width: 14px; height: 14px; flex: 0 0 auto; }
.pastille-annee-barre { flex: 0 0 auto; }
.menu-annee { position: relative; }
.pastilles-barre button.dropdown-toggle-btn.pastille-annee-barre { padding: 6px 12px; background: var(--pastille-fond); border: 1px solid var(--pastille-trait); cursor: pointer; }
.pastille-annee-barre i { font-size: 10px; color: var(--muted); }
.topbar h1::before { content: ""; flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%; background: var(--teal); box-shadow: 0 0 0 3px var(--pastille-fond); }
.pastille-annee-barre.relue { border-color: var(--amber); color: var(--amber); }
.bandeau-annee { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 16px; padding: 10px 16px; border-radius: 12px; background: color-mix(in srgb, var(--amber) 12%, transparent); border: 1px solid color-mix(in srgb, var(--amber) 35%, transparent); font-weight: 600; }
/* Sous 1100 px la barre est trop étroite : les pastilles passent sous le titre. */
@media (max-width: 1100px) {
  .topbar-left > div { flex-direction: column; align-items: flex-start; gap: 6px; max-width: 100%; }
  .pastilles-barre { max-width: 100%; }
  .pastille-ecole { max-width: none; flex: 0 1 auto; }
}

/* La case de recherche de Kooly.html (.search-bar), valeurs recopiées : pilule
   blanche sans bordure, loupe à droite. */
.global-search {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 40px;
  padding: 0 16px;
  border: 0;
  border-radius: 20px;
  background: var(--card);
  box-shadow: 0 2px 10px rgba(0,0,0,0.03);
  color: var(--muted);
  flex: 1 1 140px;
  min-width: 120px;
  max-width: 200px;
}
.global-search input {
  width: 100%;
  min-width: 0;
  border: 0;
  padding: 0;
  background: transparent;
  color: var(--ink);
  font-size: 14px;
}
.global-search input:focus { outline: none; }
.global-search:focus-within { outline: 2px solid var(--teal); outline-offset: 2px; }

.pastille-menu {
  position: absolute;
  top: -2px;
  right: -2px;
  min-width: 20px;
  height: 20px;
  padding: 0 5px;
  border-radius: var(--pill);
  background: var(--coral);
  color: var(--blanc);
  font-size: var(--fs-xs);
  font-weight: 700;
  line-height: 20px;
  text-align: center;
}

/* ---- Zone de contenu ---- */
.main-content { padding: 0 50px 40px; }

/* ---- Navigation mobile (affichée sous 900px) ----
   Chaque bouton : <span>icône</span><b>libellé</b>. Le menu de l'ordinateur,
   lui, n'a plus d'icônes : seulement le texte. */
.mobile-nav { display: none; }
.mobile-link {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  flex: 1 1 0;
  min-width: 0;
  min-height: var(--mobilenav-h);
  padding: 6px 4px;
  border: 0;
  background: transparent;
  color: var(--muted);
  transition: color var(--ease);
}
.mobile-link > span { display: flex; font-size: var(--fs-md); line-height: 1; }
.mobile-link > b {
  max-width: 100%;
  font-size: var(--fs-xs);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mobile-link.active { color: var(--teal); }
/* Le bouton Menu, au centre et sans nom, comme dans Kooly.html : neuf points. */
.mobile-menu {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 48px;
  height: 48px;
  margin: auto 6px;
  border: 0;
  border-radius: 50%;
  background: var(--teal);
  color: var(--blanc);
}
.mobile-menu .points {
  display: grid;
  grid-template-columns: repeat(3, 4px);
  gap: 4px;
}
.mobile-menu .ico { width: 22px; height: 22px; }
/* Le Menu du téléphone : une page entière au-dessus de la barre du bas.
   Des lignes groupées en blocs, à la manière des réglages d'un téléphone. */
.menu-feuille {
  position: fixed;
  inset: 0 0 calc(var(--mobilenav-h) + env(safe-area-inset-bottom)) 0;
  z-index: 31;
  overflow-y: auto;
  background: var(--bg);
  display: none;
}
.feuille-ouverte .menu-feuille { display: block; }
.feuille-panneau { display: flex; flex-direction: column; padding: 8px 16px 16px; }
/* Sans cela, un menu long écrase ses blocs au lieu de défiler. */
.feuille-panneau > * { flex-shrink: 0; }
.feuille-tete { display: flex; align-items: center; gap: 8px; margin: 0 -4px 12px; }
.feuille-tete h2 { margin: 0; font-size: var(--fs-md); }
.mobile-menu.actif { box-shadow: 0 0 0 4px color-mix(in srgb, var(--teal) 22%, transparent); }
.feuille-personne, .feuille-ligne {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  border: 0;
  background: var(--card);
  color: var(--ink);
  text-align: left;
}
.feuille-personne { padding: 12px 14px; border-radius: 14px; }
.feuille-personne .avatar { flex: none; width: 40px; height: 40px; }
.feuille-personne > span:nth-child(2) { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.feuille-personne strong { font-size: var(--fs-sm); }
.feuille-personne small { color: var(--muted); font-size: var(--fs-xs); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.feuille-groupe { margin: 18px 14px 6px; color: var(--muted); font-size: var(--fs-xs); font-weight: 600; }
.feuille-bloc { overflow: hidden; border-radius: 14px; background: var(--card); }
/* Le dernier bloc n'a pas de titre : l'écart seul le sépare du précédent. */
.feuille-bloc + .feuille-bloc { margin-top: 18px; }
.feuille-ligne { min-height: 48px; padding: 0 14px; font-size: var(--fs-sm); }
.feuille-ligne + .feuille-ligne b, .feuille-ligne + .feuille-ligne .feuille-fleche { box-shadow: 0 -1px 0 var(--line-soft); }
.feuille-ligne b { flex: 1; align-self: stretch; display: flex; align-items: center; font-weight: 500; }
.feuille-icone { display: flex; color: var(--ink-soft); }
.feuille-fleche { flex: none; width: 16px; height: 16px; color: var(--line); }
.feuille-ligne .feuille-fleche { align-self: stretch; height: auto; }
.feuille-ligne.active { color: var(--teal); }
.feuille-ligne.active .feuille-icone { color: var(--teal); }
.feuille-sortie { margin: 18px auto 4px; padding: 10px 16px; border: 0; background: transparent; color: var(--red); font-size: var(--fs-sm); font-weight: 600; }
@media (min-width: 900px) { .menu-feuille { display: none; } }
.mobile-menu .points b { width: 4px; height: 4px; border-radius: 50%; background: currentColor; }

/* ====================== 5. COMPOSANTS DE PAGE ============================ */

.page-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  margin-bottom: 24px;
}
.page-head h2 { margin: 6px 0 4px; font-size: var(--fs-xl); letter-spacing: -.02em; }
.page-head p { color: var(--muted); font-size: var(--fs-sm); }
/* Quand la page s'ouvre sur sa phrase de contexte (le titre est déjà dans la
   barre du haut), la phrase se lit avec le titre : on retire la marge du
   contenu et presque tout le bas de la barre, pour qu'elle remonte sous lui. */
.app-shell:has(> .main-content > .page-head:first-child > div:first-child > p:first-child) > .topbar { padding-bottom: 12px; }
.main-content:has(> .page-head:first-child > div:first-child > p:first-child) { padding-top: 0; }
.page-actions { display: flex; flex-wrap: wrap; gap: 10px; }
/* Une case seule dans un coin ne dit pas ce qu'elle choisit : quand une page
   met un filtre à côté de ses boutons, il garde son intitulé, et les deux
   s'alignent sur leur base. */
.page-actions:has(.filter-field) { align-items: flex-end; }

/* ---- Cartes de statistiques ---- */
.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 16px;
  margin-bottom: 24px;
}
.stat-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 20px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  box-shadow: var(--shadow-sm);
}
/* Un montant n'est pas du texte : on ne le coupe jamais. Coupé au milieu,
   « 223 680 000 MGA » donne « 223 680 0 » puis « 00 MGA », et sur un écran
   d'argent un nombre coupé se lit comme un mauvais nombre. Le chiffre reste
   donc d'un seul tenant ; seule l'unité peut descendre à la ligne suivante,
   ce qui ne trompe personne. Quand la place manque, c'est la taille qui cède
   (voir la règle des écrans intermédiaires). */
.stat-card > div { min-width: 0; }
/* La carte « + » de Kooly.html : elle prend la place du bouton « Nouvel élève ». */
.add-project-card { min-height: 110px; border: 2px dashed var(--line); border-radius: 20px; display: flex; justify-content: center; align-items: center; cursor: pointer; color: var(--muted); font-size: 24px; transition: all 0.2s; background: none; font-family: inherit; padding: 0; }
.add-project-card:hover { border-color: var(--ink); color: var(--ink); }
/* La carte chiffrée de Kooly.html (.stat-card, .stat-card-icon), valeurs recopiées. */
.stat-card.flottante { display: block; position: relative; padding: 24px; border-radius: 20px; border: 1px solid var(--line-soft); box-shadow: 0 10px 30px rgba(0,0,0,0.03); background: var(--card); text-align: left; line-height: normal; }
.stat-card.flottante .stat-icon { position: absolute; top: -20px; right: 40px; width: 44px; height: 44px; border-radius: 14px; display: flex; justify-content: center; align-items: center; color: var(--blanc); font-size: 18px; }
.stat-card.flottante .stat-label { font-size: 14px; color: var(--ink); font-weight: 500; }
.stat-card.flottante .stat-value { font-size: 28px; font-weight: 700; color: var(--ink); margin: 14px 0 6px; letter-spacing: normal; line-height: normal; }
.stat-card.flottante .stat-value.valeur-longue { font-size: 23px; letter-spacing: -.015em; }
.stat-card.flottante .stat-note { font-size: 13px; color: var(--muted); }
/* « Code à remettre » et « Retirer » dans le formulaire Modifier de l'élève :
   des liens discrets, pas des boutons de plus. */
.lien-code { display: block; padding: 0; border: 0; background: none; color: var(--teal); font-size: 13px; font-weight: 500; cursor: pointer; }
.lien-code:hover { text-decoration: underline; }
.compte-parent { display: flex; align-items: center; gap: 12px; color: var(--ink); font-size: 14px; font-weight: 400; }
/* Le rôle d'un compte (.task-icon + .task-role de Kooly.html) : le métier se
   reconnaît à son carré de couleur, le mot reste lisible en encre. */
.role-cell { display: flex; align-items: center; gap: 10px; min-width: 0; color: var(--ink); font-weight: 500; }
.role-icone { display: grid; place-items: center; flex: 0 0 auto; width: 28px; height: 28px; border-radius: 8px; color: var(--blanc); font-size: 12px; }
.role-icone.icon-navy { background: var(--navy); }
.role-icone.icon-violet { background: var(--violet); }
.role-icone.icon-corail { background: var(--corail); }
.role-icone.icon-bleu { background: var(--bleu); }
/* Le tableau de Kooly.html (.tasks-header, .task-row, .badge-status),
   valeurs recopiées, porté sur tous les tableaux des pages (.main-content) et
   sur les listes en fenêtre marquées .liste-cartes : chaque ligne est une carte
   blanche posée sur le fond gris. Les autres fenêtres gardent le tableau
   simple, une carte blanche sur une fenêtre blanche ne se verrait pas. Le
   téléphone garde ses cartes. Le texte est en encre ; le bleu fin de la
   maquette va aux Élèves, où il distingue le matricule, et aux Classes et
   Matières (.liste-bleue), sans les largeurs propres aux Élèves. */
@media (min-width: 641px) {
  :is(.main-content, .liste-cartes) .table-wrap { border: 0; border-radius: 0; background: none; padding: 0 2px 4px; }
  :is(.main-content, .liste-cartes) .data-table { border-collapse: separate; border-spacing: 0 8px; margin-top: -8px; font-size: 14px; }
  :is(.main-content, .liste-cartes) .data-table th { background: none; border: 0; padding: 0 16px 2px; font-size: 12px; font-weight: 500; letter-spacing: normal; text-transform: none; color: var(--muted); }
  :is(.main-content, .liste-cartes) .data-table td { background: var(--card); border: 0; padding: 11px 16px; color: var(--ink); font-weight: 400; }
  :is(.liste-cartes, .liste-bleue) .data-table td { color: var(--bleu); font-weight: 300; }
  /* Dans Kooly.html, l'e-mail est le seul texte bleu de la ligne : c'est ce
     qui le distingue du nom posé juste à côté. */
  .liste-utilisateurs .data-table td.cell-email { color: var(--bleu); font-weight: 300; }
  /* Cinq parts qui font 100 % : le Nom prend le plus, puisque c'est le seul
     texte qui puisse être long, et Statut le moins, puisqu'il n'y tient
     qu'un mot. Une somme différente de 100 % laisserait le navigateur
     répartir le reste lui-même, et les colonnes ne tomberaient pas où on
     les a mises. */
  /* Rôle et Statut sont mesurés en pixels, sur leur mot le plus long
     (« Comptabilité », « INACTIF ») : une part en pourcentage se serait
     rétrécie avec la fenêtre et aurait fini par couper la pastille. Le Nom et
     l'e-mail, eux, prennent le reste : ce sont les seuls qu'on puisse
     raccourcir d'un point de suite sans rien perdre. */
  .liste-utilisateurs .data-table th:nth-child(1) { width: calc((100% - 334px) * .56); }
  .liste-utilisateurs .data-table th:nth-child(2) { width: calc((100% - 334px) * .44); }
  .liste-utilisateurs .data-table th:nth-child(3) { width: 160px; }
  .liste-utilisateurs .data-table th:nth-child(4) { width: 110px; }
  /* Le nom et l'e-mail sont les seuls à se raccourcir ; la pastille de
     statut et le menu à trois points, jamais — rogner leur case cacherait
     le menu dès qu'il s'ouvre. */
  .liste-utilisateurs .data-table td.cell-email { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  :is(.liste-utilisateurs, .liste-roles) .role-cell { overflow: hidden; white-space: nowrap; }
  /* Le niveau et son cycle dessous (.niveau-cell de Kooly.html). */
  .niveau-cell { display: flex; flex-direction: column; line-height: 1.4; }
  .niveau-cycle { color: var(--muted); font-size: 12px; }
  /* Classes et Matières ont des largeurs fixes, reprises des colonnes de
     Kooly.html (1.1fr .9fr 1fr 1.5fr .8fr, puis 2fr 1fr 1fr) : sans elles, trier
     faisait danser les colonnes, comme autrefois sur Élèves. */
  /* Bulletins : colonnes de largeur égale, fixées pour que trier ou changer de
     carte ne fasse plus bouger le tableau. */
  :is(.liste-classes, .liste-matieres, .liste-documents, .liste-devoirs, .liste-bulletins, .liste-tarifs, .liste-retards, .liste-recus, .liste-charges, .liste-recurrentes, .liste-rapports, .liste-utilisateurs, .liste-roles, .liste-audit) .data-table { table-layout: fixed; width: 100%; }
  .liste-roles .data-table th:nth-child(1) { width: 40%; }
  .liste-roles .data-table th:nth-child(2) { width: 18%; }
  .liste-roles .data-table th:nth-child(3) { width: 24%; }
  .liste-audit .data-table th:nth-child(1) { width: 20%; }
  .liste-audit .data-table th:nth-child(2) { width: 30%; }
  .liste-classes .data-table th:nth-child(1) { width: 20%; }
  .liste-classes .data-table th:nth-child(2) { width: 17%; }
  .liste-classes .data-table th:nth-child(3) { width: 18%; }
  .liste-classes .data-table th:nth-child(4) { width: 29%; }
  /* Bulletins, tableau des classes : le nom prend la place, les autres colonnes
     ne portent que des chiffres courts (Herin, 2026-09-19). */
  .liste-bul-classes .data-table th:nth-child(1) { width: 30%; }
  /* Bulletins, une classe ouverte : l'élève (pastille et nom) prend la place. */
  .liste-bul-eleves .data-table th:nth-child(1) { width: 24%; }
  .liste-matieres .data-table th:nth-child(1) { width: 40%; }
  .liste-matieres .data-table th:nth-child(2), .liste-matieres .data-table th:nth-child(3) { width: 20%; }
  /* Documents : 1.8fr 1fr .9fr 1.4fr puis 100px pour la date, comme Kooly.html. */
  .liste-documents .data-table th:nth-child(1) { width: 32%; }
  .liste-documents .data-table th:nth-child(2) { width: 18%; }
  .liste-documents .data-table th:nth-child(3) { width: 16%; }
  .liste-documents .data-table th:nth-child(4) { width: 24%; }
  .liste-documents .data-table th:nth-child(5) { width: 120px; }
  /* Devoirs : 2fr 1fr .8fr 1.3fr .9fr, comme Kooly.html. */
  .liste-devoirs .data-table th:nth-child(1) { width: 33%; }
  .liste-devoirs .data-table th:nth-child(2) { width: 17%; }
  .liste-devoirs .data-table th:nth-child(3) { width: 13%; }
  .liste-devoirs .data-table th:nth-child(4) { width: 22%; }
  .liste-devoirs .data-table th:nth-child(5) { width: 150px; }
  /* Tarifs : le libellé et le périmètre prennent la place, le montant et la
     récurrence tiennent en peu de signes. */
  .liste-tarifs .data-table th:nth-child(1) { width: 30%; }
  .liste-tarifs .data-table th:nth-child(2) { width: 14%; }
  .liste-tarifs .data-table th:nth-child(3) { width: 16%; }
  .liste-tarifs .data-table th:nth-child(4) { width: 24%; }
  /* La pastille Actif/Inactif ne se coupe pas : une colonne serrée la réduisait
     à « ACTIF… ». */
  .liste-tarifs .data-table th:nth-child(5) { width: 120px; }
  /* « Qui doit quoi » : le nom, ce qui reste à régler et le responsable sont les
     colonnes qu'on lit ; le retard et le montant tiennent en peu de signes.
     Ce tableau est le seul de la série dont les cellules s'écrivent sur
     plusieurs lignes (matricule sous le nom, un tarif dû par ligne) : il prend
     les largeurs fixes, mais jamais la coupe à la première ligne.  */
  /* Journal des reçus. Sept colonnes à l'école, six à la famille (« Statut »
     ne lui est pas servi) : la dernière porte alors le bouton, pas un menu. */
  .liste-recus .data-table th:nth-child(1) { width: 128px; }
  .liste-recus .data-table th:nth-child(2) { width: 24%; }
  .liste-recus .data-table th:nth-child(3) { width: 22%; }
  .liste-recus .data-table th:nth-child(4) { width: 15%; }
  .liste-recus .data-table th:nth-child(5) { width: 110px; }
  .liste-recus .data-table th:nth-child(6) { width: 13%; }
  .liste-recus.sans-statut .data-table th:nth-child(6) { width: 96px; }
  .liste-recus .data-table th:nth-child(7) { width: 64px; }

  .liste-retards .data-table th:nth-child(1) { width: 26%; }
  .liste-retards .data-table th:nth-child(2) { width: 9%; }
  .liste-retards .data-table th:nth-child(3) { width: 18%; }
  .liste-retards .data-table th:nth-child(4) { width: 25%; }
  .liste-retards .data-table th:nth-child(5) { width: 100px; }
  .liste-retards .data-table th:nth-child(6) { width: 12%; }
  /* Charges : le libellé porte la catégorie et la référence sous lui, le
     bénéficiaire est un nom entier ; le montant, le rythme et la date tiennent
     en peu de signes. Comme « Qui doit quoi », ce tableau écrit sur deux
     lignes : largeurs fixes, mais pas de coupe à la première ligne. */
  .liste-charges .data-table th:nth-child(1) { width: 22%; }
  .liste-charges .data-table th:nth-child(2) { width: 20%; }
  .liste-charges .data-table th:nth-child(3) { width: 15%; }
  .liste-charges .data-table th:nth-child(4) { width: 12%; }
  .liste-charges .data-table th:nth-child(5) { width: 110px; }
  .liste-charges .data-table th:nth-child(6) { width: 130px; }
  /* Rapports : le nom porte l'année et l'auteur sous lui, le reste tient en
     peu de signes. Comme les charges, ce tableau écrit sur deux lignes. */
  .liste-rapports .data-table th:nth-child(1) { width: 40%; }
  .liste-rapports .data-table th:nth-child(2) { width: 15%; }
  .liste-rapports .data-table th:nth-child(3) { width: 22%; }
  .liste-rapports .data-table th:nth-child(4) { width: 15%; }
  /* La fenêtre des charges qui reviennent : étroite, quatre colonnes lues. */
  .liste-recurrentes .data-table th:nth-child(1) { width: 34%; }
  .liste-recurrentes .data-table th:nth-child(2) { width: 20%; }
  .liste-recurrentes .data-table th:nth-child(3) { width: 16%; }
  .liste-recurrentes .data-table th:nth-child(4) { width: 120px; }
  :is(.liste-classes, .liste-matieres, .liste-documents, .liste-devoirs, .liste-bulletins, .liste-tarifs, .liste-retards, .liste-recus, .liste-charges, .liste-recurrentes, .liste-rapports, .liste-utilisateurs, .liste-roles) .data-table th:last-child:empty { width: 64px; }
  :is(.liste-classes, .liste-matieres, .liste-documents, .liste-devoirs, .liste-bulletins, .liste-tarifs, .liste-recus) .data-table td { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  :is(.liste-classes, .liste-matieres, .liste-documents, .liste-devoirs, .liste-bulletins, .liste-tarifs, .liste-recus, .liste-utilisateurs, .liste-audit) .student-cell { min-width: 0; }
  :is(.liste-classes, .liste-matieres, .liste-documents, .liste-devoirs, .liste-bulletins, .liste-tarifs, .liste-recus, .liste-utilisateurs, .liste-audit) .student-cell .avatar { flex: none; }
  :is(.liste-classes, .liste-matieres, .liste-documents, .liste-devoirs, .liste-bulletins, .liste-tarifs, .liste-recus, .liste-utilisateurs, .liste-audit) .student-cell strong { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
  /* La dernière case porte un bouton ou le menu ⋮ : on ne la rogne jamais (les « … » bleus à droite des lignes). */
  :is(.liste-classes, .liste-matieres, .liste-documents, .liste-devoirs, .liste-bulletins, .liste-tarifs, .liste-recus) .data-table td:last-child { overflow: visible; text-overflow: clip; }
  .liste-recus .data-table th:last-child:empty { width: 120px; }
  .data-table td:has(> .status-badge, > .moyenne-badge) { overflow: visible; text-overflow: clip; }
  .liste-devoirs .niveau-cell > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* Une ligne de devoir se déplie sur son contenu, comme .devoir-expand de
     Kooly.html : la ligne ouverte et son détail forment une seule carte. */
  .liste-devoirs tr.devoir-ligne { cursor: pointer; }
  .devoir-fleche { font-size: 11px; color: var(--muted); margin-left: 6px; transition: transform .2s; }
  .liste-devoirs tr.ouverte .devoir-fleche { transform: rotate(180deg); }
  .liste-devoirs .data-table tr.ouverte td { box-shadow: 0 8px 0 var(--card); }
  .liste-devoirs .data-table tr.ouverte td:first-child { border-radius: 16px 0 0 0; }
  .liste-devoirs .data-table tr.ouverte td:last-child { border-radius: 0 16px 0 0; }
  .liste-devoirs .data-table tr.ligne-detail td { border-radius: 0 0 16px 16px; padding: 2px 24px 20px; white-space: normal; line-height: 1.7; cursor: default; }
  .devoir-piece { display: inline-flex; align-items: center; gap: 10px; margin-top: 16px; padding: 10px 14px; background: var(--bg); border-radius: 10px; font-size: 13px; font-weight: 500; color: var(--ink); text-decoration: none; }
  .devoir-piece .fa-file-pdf { color: var(--red); }
  .devoir-piece-fleche { color: var(--muted); }
  :is(.main-content, .liste-cartes) .data-table th:first-child, :is(.main-content, .liste-cartes) .data-table td:first-child { padding-left: 24px; }
  :is(.main-content, .liste-cartes) .data-table th:last-child, :is(.main-content, .liste-cartes) .data-table td:last-child { padding-right: 24px; }
  :is(.main-content, .liste-cartes) .data-table td:first-child { border-radius: 16px 0 0 16px; }
  :is(.main-content, .liste-cartes) .data-table td:last-child { border-radius: 0 16px 16px 0; }
  /* Une carte qui contient un tableau n'a plus de fond : ses lignes sont
     déjà des cartes, et le titre se pose sur le gris comme dans la maquette. */
  .main-content .panel:has(.table-bloc) { padding: 0; border: 0; background: none; box-shadow: none; }
  /* La ligne monte de 2 px au survol comme dans Kooly.html, mais par « top » et
     non par « transform » : un transform fait descendre le menu à trois points
     (position: fixed) loin sous la ligne. */
  :is(.main-content, .liste-cartes) .data-table tbody tr { position: relative; top: 0; box-shadow: 0 2px 10px rgba(0,0,0,0.02); border-radius: 16px; transition: top 0.2s; }
  :is(.main-content, .liste-cartes) .data-table tbody tr:hover { background: none; top: -2px; box-shadow: 0 4px 15px rgba(0,0,0,0.05); }
  :is(.main-content, .liste-cartes) .data-table td strong { font-size: 14px; font-weight: 500; color: var(--ink); }
  .liste-cartes .data-table td:nth-child(3), .liste-cartes .data-table td:nth-child(4) { color: var(--ink); font-weight: 500; }
  /* Un matricule ne se coupe pas : « ELV- » sur une ligne et « 26003 » sur la suivante ne se recopie plus. */
  .liste-cartes .data-table td:nth-child(2) { white-space: nowrap; }
  /* Le tableau des élèves de l'école (cinq colonnes) a des largeurs fixes :
     sinon le navigateur les recalcule sur les trente lignes affichées, et trier
     ou changer de page faisait danser les colonnes. Un nom trop long finit par
     « … » au lieu de passer à la ligne ; « Petite Section » tient dans Classe.
     Il n'y a plus de colonne Statut : la carte « Archivés » dit ce qu'on
     regarde, et « Bulletin retenu » se pose à côté du nom. */
  .liste-cartes .data-table:has(thead th:nth-child(5):last-child) { table-layout: fixed; width: 100%; }
  .liste-cartes .data-table:has(thead th:nth-child(5):last-child) th:nth-child(1) { width: 34%; }
  .liste-cartes .data-table:has(thead th:nth-child(5):last-child) th:nth-child(2) { width: 15%; }
  .liste-cartes .data-table:has(thead th:nth-child(5):last-child) th:nth-child(3) { width: 17%; }
  .liste-cartes .data-table:has(thead th:nth-child(5):last-child) th:nth-child(4) { width: auto; }
  .liste-cartes .data-table:has(thead th:nth-child(5):last-child) th:nth-child(5) { width: 64px; }
  .liste-cartes .data-table:has(thead th:nth-child(5):last-child) td:nth-child(-n+4) { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
  .liste-cartes .data-table:has(thead th:nth-child(5):last-child) .student-cell { min-width: 0; }
  .liste-cartes .data-table:has(thead th:nth-child(5):last-child) .student-cell .avatar,
  .liste-cartes .data-table:has(thead th:nth-child(5):last-child) .student-cell .status-badge { flex: none; white-space: nowrap; }
  .liste-cartes .data-table:has(thead th:nth-child(5):last-child) .student-cell strong { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
  :is(.main-content, .liste-cartes) .tri-titre { gap: 5px; color: inherit; }
  :is(.main-content, .liste-cartes) .tri-fleche::before { content: '\f0dc'; font-family: 'Font Awesome 6 Free'; font-weight: 900; font-size: 11px; color: var(--fleche-tri); }
  :is(.main-content, .liste-cartes) .tri-titre:hover .tri-fleche::before { color: var(--muted); }
  :is(.main-content, .liste-cartes) th[aria-sort="ascending"] .tri-fleche::before { content: '\f0de'; color: var(--teal); }
  :is(.main-content, .liste-cartes) th[aria-sort="descending"] .tri-fleche::before { content: '\f0dd'; color: var(--teal); }
  .liste-cartes .status-badge, .main-content .data-table .status-badge { text-transform: uppercase; }
}
.stat-icon.icon-pink { background-color: var(--teal); box-shadow: 0 8px 16px rgba(23,195,178,0.3); }
.stat-icon.icon-blue { background-color: var(--bleu); box-shadow: 0 8px 16px rgba(45,156,219,0.3); }
.stat-icon.icon-amber { background-color: var(--amber); box-shadow: 0 8px 16px rgba(227,131,40,0.3); }
/* Recopiées de Kooly.html pour les cartes de l'espace éditeur. */
.stat-icon.icon-purple { background-color: var(--violet); box-shadow: 0 8px 16px rgba(117,89,199,0.3); }
.stat-icon.icon-red { background-color: var(--red); box-shadow: 0 8px 16px rgba(229,72,77,0.3); }
.stat-icon.icon-brown { background-color: var(--brun); box-shadow: 0 8px 16px rgba(139,94,60,0.3); }
.stat-icon.icon-navy { background-color: var(--navy); box-shadow: 0 8px 16px rgba(47,49,64,0.3); }
.stat-icon.icon-corail { background-color: var(--corail); box-shadow: 0 8px 16px rgba(224,122,95,0.3); }

/* La pile de visages d'une carte de groupe : des ronds de 28 px qui se
   chevauchent, cernés de blanc pour se détacher les uns des autres. Les
   pastilles de la pile sont plus petites et rondes, là où celles des listes
   sont carrées à coins arrondis. */
.pile-visages { display: flex; margin-bottom: 24px; }
.pile-visages .avatar, .visages-reste {
  box-sizing: border-box; width: 28px; height: 28px; flex: 0 0 auto;
  border-radius: 50%; border: 2px solid var(--card); margin-left: -8px;
  font-size: 11px; font-weight: 700;
}
.pile-visages > :first-child { margin-left: 0; }
.visages-reste {
  display: grid; place-items: center; line-height: 1;
  /* Sans ce calé, le rond gris se dessine derrière le dernier visage : une
     pastille est calée, lui ne l'était pas, et une pièce calée passe devant. */
  position: relative;
  background: var(--line); color: var(--ink-soft);
}
/* Le nom du groupe est le titre de sa carte, comme dans Kooly.html : grand et
   gras au-dessus de sa ligne de compte, là où les autres cartes mettent un
   chiffre. */
.stat-card.flottante .titre-groupe { display: block; font-size: 24px; font-weight: 700; color: var(--ink); line-height: 1.25; }
/* Les filtres posés au-dessus d'une liste en cartes, sans cadre autour. Case
   et liste déroulante de Kooly.html (.form-control, select.form-control),
   valeurs recopiées : bord gris, coins de 12 px, flèche grise à droite. */
.liste-cartes > .filters { margin-bottom: 16px; gap: 12px; }
.liste-cartes > .filters .filter-control, .emploi-toolbar-row .filter-control, .annonces-barre .filter-control { height: 46px; min-height: 46px; padding: 0 16px; border: 1.5px solid var(--line); border-radius: 12px; background-color: var(--card); box-shadow: none; font-size: 14px; color: var(--ink); outline: none; transition: all 0.2s; }
.liste-cartes > .filters .filter-control:hover, .emploi-toolbar-row .filter-control:hover { border-color: var(--trait-champ); }
/* Liste des écoles : les filtres rangés derrière une icône, à droite. */
.filtres-ecoles { justify-content: flex-end; }
.filtres-choix { display: flex; flex-wrap: wrap; gap: 12px; }
.filtres-choix[hidden] { display: none; }
.bouton-stylo[aria-pressed="true"] { border-color: var(--teal); color: var(--teal); }
.ligne-ouvrable { cursor: pointer; }
.liste-cartes > .filters .filter-control:focus, .emploi-toolbar-row .filter-control:focus { border-color: var(--teal); box-shadow: 0 0 0 4px rgba(23,195,178,0.08); }
/* Listes de l'espace éditeur : le texte en noir. Le bleu de Kooly.html sert au
   matricule des élèves ; posé sur toutes les colonnes, il ne distinguait plus rien. */
@media (min-width: 641px) { .liste-sobre .data-table td { color: var(--ink); font-weight: 400; } }
/* Un nom et, dessous, sa précision en petit, collés à la pastille. */
.cellule-nom { display: flex; flex-direction: column; gap: 2px; min-width: 0; line-height: 1.3; }
/* Payer par mobile money, côté famille : une ligne qui se déplie, fermée au départ. */
.payer-mobile > summary { display: flex; align-items: center; justify-content: space-between; gap: 12px; cursor: pointer; list-style: none;
  font-size: var(--fs-md); font-weight: 700; color: var(--ink); }
.payer-mobile > summary::-webkit-details-marker { display: none; }
.payer-mobile > summary::after { content: ''; width: 8px; height: 8px; border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted);
  transform: rotate(45deg); transition: transform .15s; }
.payer-mobile[open] > summary::after { transform: rotate(-135deg); }
.payer-mobile > p { margin: 12px 0 8px; }
.main-content .panel.payer-mobile { background: var(--card); padding: 14px 18px; border: 1px solid var(--line-soft); box-shadow: 0 10px 30px rgba(0,0,0,.03); }
.main-content .payer-mobile .data-table tbody tr { background: transparent; box-shadow: none; border-bottom: 1px solid var(--line-soft); }
.cellule-nom .muted { font-size: 12px; }
/* Dans une fiche, un téléphone ou une adresse e-mail se lit comme un lien de
   Kooly.html : turquoise, sans soulignement. */
.sheet-line a { color: var(--teal); text-decoration: none; }
.sheet-line a:hover { text-decoration: underline; }
/* À l'écran, une ligne « libellé : valeur » prend le petit libellé gris et le
   texte des rangées du tableau de bord de Kooly.html. Un document (.sheet)
   garde sa propre allure. */
.panel .sheet-line:not(.sheet .sheet-line) { align-items: center; border-bottom: 1px solid var(--line-soft); font-size: 14px; }
.panel .sheet-line:not(.sheet .sheet-line):last-child { border-bottom: 0; }
.panel .sheet-line:not(.sheet .sheet-line) > span { font-size: 12px; color: var(--muted); }
.panel .sheet-line:not(.sheet .sheet-line) > strong { font-weight: 500; color: var(--ink); }
.titre-liste { margin: 32px 0 12px; font-size: 16px; font-weight: 600; color: var(--ink); }
/* La phrase qui expliquait le cadre se range sous son titre, maintenant que le
   cadre n'existe plus. */
.note-liste { margin: -6px 0 16px; font-size: 13px; color: var(--muted); }
/* Les échéances à enregistrer : le trait de séparation d'un cadre blanc ne veut
   plus rien dire sur le fond gris. Chaque ligne devient une carte, aux valeurs
   des lignes du tableau juste en dessous. */
.rappels-charges { gap: 8px; }
.rappels-charges .list-item { padding: 14px 20px; border-bottom: 0; border-radius: 16px; background: var(--card); box-shadow: 0 2px 10px rgba(0,0,0,0.02); }
/* Fiche d'une école en tableau de bord : trois tuiles de front sur un
   ordinateur, une seule colonne quand la place manque. Cadres, boutons et
   étiquettes sont ceux de toute l'application, recopiés de Kooly.html. */
.fiche-tableau { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr)); gap: 24px; margin-top: 24px; }
@media (min-width: 1000px) {
  .fiche-tableau { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.panel.tuile > .form-actions { flex: 0 0 auto; margin-top: 12px; }
.panel.tuile > .form-actions:empty { display: none; }
/* Aucune tuile ne défile chez elle : une barre de défilement dans un cadre
   cache ce qu'il contient. */
.fiche-tableau .panel.tuile > .tuile-corps { overflow: visible; }
.fiche-tableau .panel.tuile .panel-header { min-height: 0; flex-wrap: nowrap; gap: 8px; }
.fiche-tableau .panel-header > button { flex: 0 0 auto; }
.bouton-stylo { width: 34px; height: 34px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; border: 1.5px solid var(--line); border-radius: 10px; background: var(--card); color: var(--muted); cursor: pointer; transition: all .2s; }
.bouton-stylo:hover { border-color: var(--teal); color: var(--teal); }
.fiche-tableau .sheet-line { padding: 5px 0; }
.fiche-tableau .sheet-line > span { flex: 0 0 118px; }
.fiche-tableau .list-item { align-items: flex-start; gap: 12px; padding: 7px 0; border-bottom: 0; }
.fiche-tableau .list-dot { margin-top: 6px; }
/* « Responsable » à côté du nom, pas en bande pleine largeur dessous. */
.fiche-tableau .list-item-content .status-badge { display: inline-block; margin: 0 0 0 6px; vertical-align: middle; }
.fiche-tableau p.muted { font-size: 13px; color: var(--muted); }
.fiche-sous-titre { margin: 14px 0 4px; font-size: 14px; font-weight: 600; color: var(--ink); }
/* Kooly.html laisse 18 px entre la phrase du haut et les cartes ; les icônes
   dépassent dans cet espace, comme sur la maquette. */
.stats-grid:has(.flottante) { gap: 24px; margin-top: -6px; margin-bottom: 28px; }
/* Sans phrase au-dessus (fiche d'une école, liste des écoles), les cartes
   ouvrent la page : la barre du haut, collante, coupait leurs icônes. */
.main-content > .stats-grid:has(.flottante):first-child { margin-top: 28px; }
.stat-label { display: block; font-size: 14px; font-weight: 500; color: var(--ink); }
.stat-value {
  display: block;
  margin: 4px 0 2px;
  font-size: var(--fs-xl);
  line-height: 1.1;
  letter-spacing: -.02em;
  overflow-wrap: normal;
}
.stat-note { display: block; font-size: var(--fs-xs); color: var(--muted); }
.stat-icon {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  flex: 0 0 auto;
  border-radius: 14px;
  background: rgba(23, 195, 178, .16);
  color: var(--teal);
  font-size: var(--fs-lg);
}

/* ---- Un écran qui ne défile pas ----
   Sur ordinateur, le tableau de bord ne doit rien laisser sous le bord de
   l'écran : la coquille prend exactement la hauteur de la fenêtre, la barre du
   haut garde la sienne, et la zone de contenu se plie à ce qui reste. C'est
   `navigate()` qui pose la classe, et seulement sur le tableau de bord : partout
   ailleurs une liste d'élèves doit pouvoir défiler.

   Cette règle remplace un calcul (hauteur de fenêtre moins la barre du haut
   moins les marges) qui supposait une barre du haut toujours de la même
   taille : un titre un peu long la faisait grandir et la page repartait à
   défiler d'un cheveu — juste assez pour faire apparaître l'ascenseur. */
.app-shell.ecran-fixe {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh;
  min-height: 0;
  overflow: hidden;
}
.app-shell.ecran-fixe > .topbar { flex: 0 0 auto; }
.app-shell.ecran-fixe > .main-content { flex: 1 1 auto; min-height: 0; overflow: hidden; }

/* ---- Tableau de bord ----
   Il tient sur un écran. On n'ouvre pas Haitso le matin pour faire défiler trois
   fois la page avant de savoir comment va son école : la hauteur disponible est
   partagée entre le bandeau et les tuiles, et c'est le contenu de chaque tuile
   qui défile à l'intérieur si besoin, jamais la page.

   Le bandeau porte la date, la personne, son établissement — et les quatre
   chiffres du jour. Fond sombre de la charte : l'œil sait tout de suite où
   commence la lecture, et le reste de l'écran reste clair. */
.dash-page {
  display: flex;
  flex-direction: column;
  gap: 18px;
  /* La page occupe exactement la zone que la coquille lui laisse, ni plus ni
     moins : sans hauteur ferme la scène s'étirerait à la taille de son contenu
     et les cadres recommenceraient à pousser la page vers le bas. */
  height: 100%;
  min-height: 0;
}
/* La ligne de bonjour : le salut, la date, l'école. Rien d'autre. Les chiffres
   du jour étaient dans la même boîte sombre, collés au salut — un bonjour et
   quatre nombres ne se lisent pas de la même façon, et les mélanger donnait une
   barre chargée dont on ne savait par où la prendre. */
.dash-salut { display: flex; align-items: baseline; flex-wrap: wrap; gap: 2px 12px; }
.dash-salut h2 { font-size: var(--fs-lg); letter-spacing: -.02em; }
.dash-salut p { font-size: var(--fs-sm); color: var(--muted); }

/* Le ruban des chiffres du jour. Il est posé à même la page, sans carte ni
   cadre : une carte blanche par nombre faisait une bande entière avant le
   premier dessin. Quatre colonnes strictement égales séparées d'un filet, et un
   filet sous l'ensemble : le ruban se lit comme un seul objet, pas comme quatre
   boutons qui flottent. Chacun reste un bouton qui ouvre sa page. */
.dash-bande {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  align-items: stretch;
  border-bottom: 1px solid var(--line-soft);
  padding-bottom: 4px;
}
/* Une couleur par chiffre, dans l'ordre du ruban, et toujours la même : la
   direction finit par reconnaître sa caisse à sa pastille avant d'avoir lu
   l'intitulé. La couleur ne touche que la pastille et le trait de survol — un
   nombre écrit en couleur se lit comme un lien, pas comme un nombre. */
.dash-bande > :nth-child(1) { --accent: var(--teal);   --accent-doux: rgba(23, 195, 178, .14); }
.dash-bande > :nth-child(2) { --accent: var(--violet); --accent-doux: rgba(117, 89, 199, .13); }
/* La troisième n'est pas en ambre : les icônes de Haitso sont un trait de 1,6 px,
   et un trait ambre sur un fond ambre pâle ne se voit plus. Le bleu de nuit de
   la charte le tient, et il pose le rythme du ruban — deux couleurs, une encre,
   une couleur. */
.dash-bande > :nth-child(3) { --accent: var(--navy);   --accent-doux: rgba(11, 23, 57, .09); }
.dash-bande > :nth-child(4) { --accent: var(--green);  --accent-doux: rgba(47, 174, 96, .14); }
.dash-chiffre {
  display: flex;
  align-items: center;
  gap: 14px;
  min-width: 0;
  padding: 6px 18px;
  border: 0;
  border-left: 1px solid var(--line-soft);
  border-radius: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.dash-bande > .dash-chiffre:first-child { border-left: 0; padding-left: 2px; }
/* La pastille prend la teinte du chiffre, à plat : un dégradé identique sur les
   quatre ne distinguait rien et ne décorait que le vide. */
.chiffre-ico {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  flex: 0 0 auto;
  border-radius: 14px;
  background: var(--accent-doux);
  color: var(--accent);
  font-size: var(--fs-md);
  transition: transform var(--ease), box-shadow var(--ease);
}
.chiffre-texte { display: flex; flex-direction: column; align-items: flex-start; min-width: 0; }
/* L'intitulé en petites capitales espacées : il annonce le nombre au lieu de lui
   disputer la place. */
.chiffre-titre {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--muted);
}
/* Le nombre est la seule chose qu'on vient chercher : il est le plus gros de la
   ligne, en encre et non en couleur, et ses chiffres ont tous la même chasse
   pour que deux montants l'un sous l'autre s'alignent. Le trait qui le souligne
   au survol se dessine de la gauche vers la droite — c'est lui qui dit « on peut
   cliquer », sans qu'un rectangle coloré vienne barbouiller la page. */
.dash-chiffre strong {
  align-self: flex-start;
  max-width: 100%;
  margin: 1px 0 1px;
  font-size: clamp(1.25rem, 1.7vw, 1.75rem);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -.025em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  overflow-wrap: anywhere;
  background: linear-gradient(var(--accent), var(--accent)) left bottom / 0 2px no-repeat;
  padding-bottom: 2px;
  transition: background-size var(--ease);
}
.dash-chiffre.chiffre-long strong { font-size: clamp(1rem, 1.2vw, 1.25rem); letter-spacing: -.015em; }
.dash-chiffre:hover strong, .dash-chiffre:focus-visible strong { background-size: 100% 2px; }
.dash-chiffre:hover .chiffre-ico, .dash-chiffre:focus-visible .chiffre-ico {
  transform: translateY(-2px);
  box-shadow: 0 6px 14px var(--accent-doux);
}
.dash-chiffre:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--small-radius); }
.chiffre-note { font-size: var(--fs-xs); color: var(--muted); }
/* Personne n'a demandé à voir la page bouger : sous réglage système, le trait et
   la pastille apparaissent sans transition. */
@media (prefers-reduced-motion: reduce) {
  .chiffre-ico, .dash-chiffre strong { transition: none; }
}

/* La rangée de cartes sous le grand dessin : les chiffres qui n'ont pas tenu
   dans la bande du haut. Trois emplacements, toujours les mêmes : un profil qui
   n'a qu'un chiffre de reste donne une carte de la largeur d'un tiers, pas une
   carte étirée sur toute la ligne avec trois mots perdus dedans. */
.dash-cartes { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px; min-width: 0; }
.dash-cartes > .stat-card { min-width: 0; }
.carte-chiffre {
  text-align: left;
  font: inherit;
  color: inherit;
  cursor: pointer;
  transition: transform var(--ease), box-shadow var(--ease);
}
.carte-chiffre:hover, .carte-chiffre:focus-visible { transform: translateY(-2px); box-shadow: var(--shadow); }
/* Le groupe qu'on regarde reste marqué : sinon on ne sait plus pourquoi la
   liste est courte. */
.carte-chiffre.carte-active { outline: 2px solid var(--teal); outline-offset: 2px; }
.dash-cartes .stat-value { margin: 1px 0 0; font-size: var(--fs-md); overflow-wrap: normal; }
/* Ces cartes portent les chiffres secondaires : elles s'écrivent plus petit que
   la bande du haut, et l'icône y est plus discrète. Sur un écran de portable la
   colonne de gauche descend à 590 px : trois cartes y tiennent, mais seulement si
   elles restent basses — sinon la rangée enfle et c'est le grand dessin qui
   rétrécit. On préfère un intitulé qui passe à la ligne à un intitulé coupé : un
   « Présents aujour… » ne s'explique pas. */
.dash-cartes > .stat-card { padding: 12px 14px; gap: 10px; }
.dash-cartes .stat-icon { width: 36px; height: 36px; font-size: var(--fs-sm); }

/* La scène : une vedette qui prend toute la hauteur, deux cadres empilés à sa
   droite. Une grille de six tuiles égales n'a pas de point d'entrée — l'œil
   fait le tour et ne se pose nulle part. Ici le grand dessin porte la page, les
   deux petits la commentent. La colonne de droite est bornée en largeur : au
   delà elle cesse d'être une marge et devient une seconde page. */
.dash-scene {
  flex: 1 1 auto;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(16rem, 22rem);
  /* Deux rangées à gauche : le dessin prend ce qui reste, la rangée de cartes
     ne prend que sa hauteur. La colonne de droite les traverse toutes les deux —
     sinon elle se couperait au milieu, à la hauteur des cartes. */
  grid-template-rows: minmax(0, 1fr) auto;
  gap: 18px;
  min-height: 0;
}
.dash-scene.seule { grid-template-columns: minmax(0, 1fr); }
.dash-scene.sans-cartes { grid-template-rows: minmax(0, 1fr); }
.dash-vedette { grid-column: 1; grid-row: 1; display: flex; min-width: 0; min-height: 0; }
.dash-cartes { grid-column: 1; grid-row: 2; }
/* Le grand dessin porte la page : son titre se lit de plus loin que celui des
   cadres qui l'accompagnent. */
.dash-vedette .panel-header h2 { font-size: var(--fs-lg); color: var(--teal); }
.dash-vedette > .tuile { flex: 1 1 auto; }
/* Les deux cadres de droite se partagent la hauteur à parts égales : l'un
   au-dessus de l'autre, jamais l'un écrasé par le contenu de l'autre. */
.dash-cote { grid-column: 2; grid-row: 1 / -1; display: grid; grid-auto-rows: minmax(0, 1fr); gap: 18px; min-width: 0; min-height: 0; }
/* La mise en route s'intercale entre le bandeau et la scène. Elle était bridée à
   deux cinquièmes de la hauteur pour laisser la place aux cadres : le premier matin,
   l'école voyait donc trois de ses six étapes et devait faire défiler une boîte dans
   une page qui, elle, ne défile pas. Les cadres ne s'affichent plus tant qu'il reste
   une étape : la liste prend la place et tient entière. Elle accepte encore de
   rétrécir sur un écran très bas, où elle défile alors chez elle. */
.dash-page > .mise-en-route { flex: 0 1 auto; min-height: 0; overflow: auto; margin-bottom: 0; }
.panel.tuile { display: flex; flex-direction: column; min-height: 0; margin-bottom: 0; padding: 24px; }
.panel.tuile .panel-header { flex: 0 0 auto; margin-bottom: 18px; }
/* Le contenu prend ce qui reste et défile chez lui : une école à trente annonces
   ne doit pas repousser ses graphiques hors de l'écran. */
.panel.tuile > .tuile-corps { flex: 1 1 auto; min-height: 0; overflow: hidden; }
.tuile-corps > .graphe-barres,
.tuile-corps > .graphe-colonnes,
.tuile-corps > .graphe-courbe { height: 100%; }
/* Un cadre qui n'a rien à montrer : le message se pose au centre, sinon la
   tuile a l'air cassée plutôt que calme. */
.dash-empty { padding: 6px 0 2px; font-size: 14px; color: var(--muted); }

/* Tableau de bord de la direction (maquette C1, 2026-09-29). Aucune couleur
   écrite à la main : les noms de :root, et color-mix pour les fonds pâles. */
.dir-page { height: 100%; min-height: 0; display: flex; flex-direction: column; }
.dir-cadre { flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 22px; align-items: stretch; }
.dir-gauche { min-height: 0; overflow-x: hidden; display: flex; flex-direction: column; gap: 20px; overflow-y: auto; padding: 4px 4px 4px 0; }
.dir-bandes { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; gap: 20px; }
.dir-chiffres { flex: 0 0 auto; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px; }
.dir-panneau, .dir-carte { background: var(--card); border: 1px solid var(--line); border-radius: 20px; box-shadow: 0 10px 30px color-mix(in srgb, var(--ink) 4%, transparent); }
.dir-panneau { padding: 22px; display: flex; flex-direction: column; min-height: 0; }
.kq { min-width: 0; display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 16px 18px; text-align: left; font: inherit; color: var(--ink); cursor: pointer; width: 100%; }
.kq:disabled { cursor: default; }
.kq .kl { display: block; font-size: 12px; font-weight: 500; color: var(--muted); }
.kq > div { min-width: 0; }
.kq .kb { display: block; font-size: clamp(15px, 1.25vw, 22px); line-height: 1.15; font-weight: 700; font-variant-numeric: tabular-nums; margin: 2px 0; }
.kq .ks { display: block; font-size: 12px; color: var(--muted); }
.kq .ico { width: 46px; height: 46px; border-radius: 14px; display: grid; place-items: center; flex: none; color: var(--blanc); font-size: 19px; }
.t-teal { background: var(--teal); box-shadow: 0 8px 16px color-mix(in srgb, var(--teal) 30%, transparent); }
.t-purple { background: var(--violet); box-shadow: 0 8px 16px color-mix(in srgb, var(--violet) 30%, transparent); }
.t-amber { background: var(--amber); box-shadow: 0 8px 16px color-mix(in srgb, var(--amber) 30%, transparent); }
.evo { display: inline-block; padding: 1px 7px; margin-right: 4px; border-radius: 999px; font-size: 11px; font-weight: 600; }
.evo.bon { background: color-mix(in srgb, var(--green) 14%, transparent); color: var(--green); }
.evo.mal { background: color-mix(in srgb, var(--red) 14%, transparent); color: var(--red); }
.dir-argent { flex: 1 1 0; min-height: 250px; }
.dir-tete { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; margin-bottom: 8px; flex: 0 0 auto; }
.dir-tete h2 { margin: 0; font-size: var(--fs-md); font-weight: 600; color: var(--ink); }
.dir-tete p { margin: 2px 0 0; font-size: var(--fs-xs); color: var(--muted); }
.lien { background: none; border: 0; padding: 0; font: inherit; font-size: 12px; font-weight: 500; color: var(--turquoise-fonce); cursor: pointer; }
.courbe-zone { position: relative; flex: 1 1 0; min-height: 150px; }
.courbe-zone svg { display: block; }
.courbe-zone .grille { stroke: var(--line); stroke-width: 1; stroke-dasharray: 3 4; }
.courbe-zone .echelle { fill: var(--muted); font-size: 12px; }
.courbe-zone .dg-haut { stop-color: var(--teal); stop-opacity: .18; }
.courbe-zone .dg-bas { stop-color: var(--teal); stop-opacity: 0; }
.courbe-zone .tr-entree { stroke: var(--teal); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.courbe-zone .tr-sortie { stroke: var(--violet); stroke-width: 1.5; stroke-dasharray: 1 6; stroke-linecap: round; }
.courbe-zone .bande-mois { fill: var(--teal); opacity: .05; }
.courbe-zone .pt-vedette { fill: var(--card); stroke: var(--teal); stroke-width: 2; }
.bulle-argent { position: absolute; pointer-events: none; padding: 6px 12px; border-radius: 8px; background: var(--card); border: 1px solid var(--line); box-shadow: 0 6px 16px color-mix(in srgb, var(--ink) 10%, transparent); font-size: 11px; color: var(--muted); display: flex; flex-direction: column; gap: 1px; white-space: nowrap; }
.bulle-argent[hidden] { display: none; }
.bulle-argent b { font-size: 13px; font-weight: 700; color: var(--ink); }
.dir-legende { display: flex; gap: 16px; padding-top: 8px; font-size: var(--fs-xs); color: var(--muted); flex: 0 0 auto; }
.dir-legende span { display: inline-flex; align-items: center; gap: 6px; }
.ronde-entree, .ronde-sortie { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
.ronde-entree { background: var(--teal); }
.ronde-sortie { background: var(--violet); }
.dir-classes { flex: 1 1 0; min-height: 0; overflow: hidden; }
.liste-c { width: 100%; border-collapse: separate; border-spacing: 0 8px; }
.liste-c th { padding: 0 16px; text-align: left; font-size: 12px; font-weight: 500; color: var(--muted); }
.liste-c td { background: var(--card); padding: 11px 16px; font-size: 13px; font-weight: 300; color: var(--bleu); }
.liste-c td b { font-weight: 500; color: var(--ink); }
.liste-c td:first-child { border-radius: 16px 0 0 16px; }
.liste-c td:last-child { border-radius: 0 16px 16px 0; }
.liste-c .col-n { text-align: right; font-variant-numeric: tabular-nums; }
.liste-c th.col-n, .liste-c td.col-n { padding-right: 28px; }
.dir-prof { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
.sans-prof { color: var(--amber); font-weight: 600; }
.dir-droite { overflow-y: auto; }
.jauge { width: 100%; max-width: 250px; margin: 14px auto 4px; display: block; }
.jauge-fond { stroke: var(--line); }
.jauge-grand { font-size: 32px; font-weight: 700; fill: var(--ink); }
.jauge-petit { font-size: 11px; fill: var(--muted); }
.seg .lab { opacity: 0; }
.seg:hover .lab { opacity: 1; }
.jauge:has(.seg:hover) .def { opacity: 0; }
.seg .vis { transition: stroke-width 0.15s; }
.seg:hover .vis { stroke-width: 21; }
.dir-afaire { flex: 1 1 0; min-height: 76px; margin-top: 22px; display: flex; flex-direction: column; }
.pile { display: grid; gap: 12px; }
.dir-afaire .pile { flex: 1 1 0; min-height: 0; overflow: hidden; align-content: start; }
@media (max-width: 1180px) { .dir-afaire { flex: 0 0 auto; } .dir-afaire .pile { flex: 0 0 auto; overflow: visible; } }
.tache.vide { align-items: center; border-style: dashed; background: transparent; cursor: default; color: var(--muted); }
.tache.vide b { font-weight: 500; color: var(--muted); }
.tache.vide .ti { background: color-mix(in srgb, var(--muted) 12%, transparent); color: var(--muted); }
.dir-vedette { flex: 1 1 0; min-height: 0; display: flex; }
.dir-vedette > .panel { flex: 1 1 auto; min-width: 0; }
.dir-vedette > .dir-cours { flex: 1 1 auto; min-width: 0; overflow: hidden; }
/* Aucun défilement sur le tableau de bord : ce qui ne tient pas est retiré par ajusteLesListes(), le reste est derrière « Tout voir ». */
.ecran-fixe .dir-gauche, .ecran-fixe .dir-droite { overflow-y: hidden; }
.dir-duo { flex: 0 0 auto; height: 250px; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: minmax(0, 1fr); gap: 20px; }
.dir-duo > .panel { min-width: 0; min-height: 0; }
.dir-vedette > .panel, .dir-duo > .panel { border-radius: 20px; }
/* Les cours du jour prennent la hauteur de leurs lignes ; le reste de la page revient aux deux cadres du dessous. */
.dir-vedette:has(> .dir-cours) { flex: 0 1 auto; max-height: 45%; }
.dir-vedette:has(> .dir-cours) + .dir-duo { flex: 1 1 0; height: auto; min-height: 200px; grid-template-rows: minmax(0, 1fr); }
.dir-duo > .dir-cours { min-height: 0; overflow: hidden; }
.dir-bandes > .dir-cours { flex: 1 1 0; min-height: 160px; overflow: hidden; }
.dir-duo > .panel:only-child { grid-column: 1 / -1; }
.tache { display: flex; gap: 12px; align-items: flex-start; padding: 14px; border-radius: 14px; border: 1px solid var(--line); background: var(--card); text-align: left; font: inherit; color: var(--ink); cursor: pointer; }
.tache b { display: block; font-size: 13px; font-weight: 600; line-height: 1.3; }
.tache span span { font-size: 12px; color: var(--muted); }
.tache .ti { width: 30px; height: 30px; border-radius: 9px; background: var(--turquoise-pale); color: var(--turquoise-fonce); display: grid; place-items: center; font-size: 13px; flex: none; }
.tache.on { background: var(--teal); border-color: var(--teal); box-shadow: 0 10px 20px color-mix(in srgb, var(--teal) 28%, transparent); }
.tache.on b, .tache.on span span { color: var(--blanc); }
.tache.on .ti { background: color-mix(in srgb, var(--blanc) 25%, transparent); color: var(--blanc); }
.pied-bonjour { margin: auto -22px -22px; padding-top: 22px; flex: 0 0 auto; }
.welcome { position: relative; overflow: hidden; background: var(--teal); border-radius: 20px; padding: 30px 28px; color: var(--blanc); min-height: 150px; }
.welcome svg { position: absolute; top: 70%; right: -30px; transform: translateY(-50%); height: 80%; width: auto; opacity: .28; mix-blend-mode: screen; }
.welcome svg path { fill: var(--muted); }
.welcome .wd { position: relative; display: block; font-size: 12px; font-weight: 700; letter-spacing: .5px; text-transform: uppercase; color: color-mix(in srgb, var(--blanc) 75%, transparent); }
.welcome h2 { position: relative; margin: 6px 0 4px; font-size: 24px; font-weight: 700; color: var(--blanc); }
.welcome p { position: relative; margin: 0; font-size: 13px; color: color-mix(in srgb, var(--blanc) 85%, transparent); }
.panel.tuile > .tuile-corps:has(> .dash-empty:only-child) { display: grid; place-items: center; text-align: center; }
.panel.tuile > .tuile-corps > .dash-empty:only-child { max-width: 22rem; padding: 0; }

/* Les graphiques du tableau de bord. Aucun n'a de taille fixe : ils se dessinent
   en pourcentages et remplissent la tuile qu'on leur donne, ce qui les rend
   justes à toutes les tailles sans une ligne de JavaScript de plus.

   Trois règles communes, sans lesquelles quatre dessins côte à côte font quatre
   styles : peu d'encre (des traits fins, jamais d'aplats épais), une échelle
   écrite (un dessin dont on ignore d'où il part ne se lit pas) et la même
   graduation partout — le trait du bas est celui du zéro, les traits du dessus
   sont plus clairs. */

/* Barres horizontales. Le nom à gauche, la piste au milieu, la valeur à droite :
   la colonne de valeurs reste alignée, on compare sans lire. La barre est fine
   — huit barres épaisses remplissent la tuile d'encre et la rendent illisible. */
/* Les lignes se partagent la hauteur de la tuile au lieu d'un écart fixe : à
   huit classes elles se resserrent jusqu'à 20 px et rien ne déborde, à trois
   elles s'écartent sans jamais devenir des pavés. Un graphique qui fait
   apparaître une barre de défilement n'est plus un coup d'œil. */
.graphe-barres { display: flex; flex-direction: column; justify-content: space-evenly; }
.barre-ligne { flex: 1 1 0; min-height: 1.25rem; max-height: 2.25rem; display: grid; grid-template-columns: minmax(0, min(var(--col-nom, 4.5rem), 50%)) minmax(0, 1fr) 3rem; align-items: center; gap: 12px; }
.barre-nom { font-size: var(--fs-sm); color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.barre-piste { height: 8px; border-radius: var(--pill); background: var(--line-soft); overflow: hidden; }
.barre-remplie { display: block; height: 100%; border-radius: var(--pill); transition: width var(--ease); }
.barre-valeur { font-size: var(--fs-sm); font-weight: 600; text-align: right; font-variant-numeric: tabular-nums; }

/* Colonnes appariées, sur une graduation. Sans elle les colonnes flottaient au
   milieu de la tuile : on voyait bien laquelle était la plus haute, mais aucun
   chiffre — un dessin qui ne se chiffre pas ne sert qu'à décorer. La graduation
   se pose par-dessus le dessin, et s'arrête 20 px plus haut que le bas : c'est
   la place du nom du mois, et le trait du zéro doit tomber sur le zéro. */
.graphe-colonnes { display: flex; flex-direction: column; gap: 12px; }
.colonnes-plot { position: relative; flex: 1 1 auto; min-height: 6rem; }
/* La graduation la plus haute est écrite au-dessus de son trait. Sans cette
   réserve en haut, elle sort du cadre et se fait rogner sur un téléphone, où
   la tuile est plus serrée ; la piste des barres descend d'autant, pour que
   le sommet d'une colonne pleine reste sur le trait du haut. */
.colonnes-axe {
  position: absolute;
  top: 0.7em;
  bottom: 20px;
  left: 2.75rem;
  right: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  pointer-events: none;
}
.colonnes-axe span { position: relative; display: block; border-top: 1px solid var(--line-soft); }
.colonnes-axe span:last-child { border-top-color: var(--line); }
.colonnes-axe i { position: absolute; top: -0.7em; right: calc(100% + 10px); font-style: normal; font-size: var(--fs-xs); color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.colonnes-piste { display: flex; align-items: flex-end; gap: 10px; height: 100%; box-sizing: border-box; padding: 0.7em 0 0 2.75rem; }
.colonne-groupe { flex: 1 1 0; display: flex; flex-direction: column; align-items: center; min-width: 0; height: 100%; }
.colonne-barres { flex: 1 1 auto; display: flex; align-items: flex-end; justify-content: center; gap: 4px; width: 100%; }
.colonne { width: 44%; max-width: 26px; border-radius: 5px 5px 0 0; transition: height var(--ease); }
.colonne-nom { flex: 0 0 auto; height: 20px; line-height: 20px; font-size: var(--fs-xs); color: var(--muted); white-space: nowrap; }
.graphe-legende { flex: 0 0 auto; display: flex; flex-wrap: wrap; gap: 16px; font-size: var(--fs-xs); color: var(--ink-soft); }
.graphe-legende span { display: inline-flex; align-items: center; gap: 6px; }
.graphe-legende i { width: 8px; height: 8px; border-radius: 2px; }

/* Courbe de présence. preserveAspectRatio="none" étire le tracé sur toute la
   place disponible ; vector-effect garde le trait à son épaisseur, sinon il
   grossirait avec l'écran. Les deux bornes de l'échelle sont écrites contre les
   traits qu'elles nomment, et non en légende sous le dessin : une pente qu'on
   lit sans savoir d'où elle part ment. */
.graphe-courbe { display: flex; flex-direction: column; gap: 10px; }
.courbe-plot {
  position: relative;
  display: flex;
  flex: 1 1 auto;
  min-height: 5rem;
  padding-right: 2.75rem;
  border-top: 1px solid var(--line-soft);
  border-bottom: 1px solid var(--line);
}
.courbe-plot svg { display: block; width: 100%; height: 100%; }
.courbe-plot b { position: absolute; right: 0; font-size: var(--fs-xs); font-weight: 600; color: var(--muted); font-variant-numeric: tabular-nums; }
.courbe-haut { top: -0.7em; }
.courbe-bas { bottom: -0.7em; }
.courbe-bornes { flex: 0 0 auto; display: flex; justify-content: space-between; gap: 10px; padding-right: 2.75rem; font-size: var(--fs-xs); color: var(--muted); }

/* Mise en route : la liste de ce qui n'est pas encore rempli, le premier mois.
   Elle ne s'affiche que tant qu'il reste une étape, et se retire toute seule. */
.mise-en-route .panel-header span { display: block; margin-top: 2px; font-size: var(--fs-sm); }
.etapes { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; }
.etape { display: flex; align-items: center; gap: 12px; padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--small-radius); }
.etape > div { flex: 1 1 auto; min-width: 0; }
.etape strong { display: block; font-size: var(--fs-base); }
.etape span { font-size: var(--fs-xs); color: var(--muted); }
.etape-marque {
  flex: 0 0 auto;
  width: 22px; height: 22px; border-radius: var(--pill);
  border: 2px solid var(--line);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: var(--fs-xs); font-weight: 800; color: var(--blanc);
}
.etape.faite { border-color: var(--line-soft); background: var(--line-soft); }
.etape.faite .etape-marque { border-color: var(--green); background: var(--green); }
.etape.faite strong { color: var(--ink-soft); }

/* Lien « Tout voir » d'un panneau : discret au repos, souligné par la charte au
   survol. Il garde la hauteur de cible tactile réglementaire. */
.panel-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--teal);
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
}
.panel-link:hover { text-decoration: underline; }
.panel-link .ico { transition: transform var(--ease); }
.panel-link:hover .ico { transform: translateX(3px); }

/* Fil d'annonces : le texte est là, pas seulement son existence. */
.feed { display: flex; flex-direction: column; gap: 12px; }
.feed-item {
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--teal);
  border-radius: var(--small-radius);
  background: var(--line-soft);
}
.feed-item.urgent { border-left-color: var(--red); }
.feed-item header { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.feed-item header strong { font-size: var(--fs-base); }
/* Trois lignes suffisent en résumé ; la page Annonces affiche le texte entier. */
.feed-item p {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin-top: 6px;
  font-size: var(--fs-sm);
  color: var(--ink-soft);
}
/* Sur la page Annonces on vient précisément pour lire : le message s'affiche
   en entier, retours à la ligne compris tels qu'ils ont été saisis. */
.feed-item p.full { -webkit-line-clamp: none; display: block; white-space: pre-wrap; }
.feed-item footer { margin-top: 8px; font-size: var(--fs-xs); color: var(--muted); display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
/* Corriger et retirer passent par le menu à trois points, comme dans les
   tableaux ; il n'apparaît que pour qui a publié, à droite de la priorité. */
.feed-droite { margin-left: auto; display: inline-flex; align-items: center; gap: 8px; }
/* Page Annonces : cartes blanches sur le fond de la page, sans trait de
   couleur, pastilles arrondies sous le message. */
.annonces-barre { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 14px; }
.annonces-nombre { font-size: var(--fs-sm); color: var(--muted); font-weight: 600; }
/* Même marge intérieure, même bordure et même ombre que les cartes de chiffres au-dessus. */
.feed-item.carte-annonce { padding: 24px; border: 1px solid var(--line-soft); border-radius: 20px; background: var(--card); box-shadow: 0 10px 30px rgba(0,0,0,0.03); }
.carte-annonce header { flex-wrap: nowrap; align-items: flex-start; }
.carte-annonce .feed-droite { flex-shrink: 0; }
.carte-annonce .feed-date { font-size: var(--fs-xs); color: var(--muted); white-space: nowrap; }
.carte-annonce .titre-haitso { display: flex; align-items: center; gap: 12px; min-width: 0; }
.logo-annonce-haitso { width: 32px; height: 32px; border-radius: 50%; flex-shrink: 0; }
/* L'icône du filtre est posée dans le champ, devant le texte. */
.champ-filtre { position: relative; display: inline-flex; align-items: center; }
.champ-filtre > i { position: absolute; left: 16px; font-size: 13px; color: var(--muted); pointer-events: none; }
.annonces-barre .champ-filtre select.filter-control { padding-left: 40px; }
.feed-pastilles { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
.feed-pastilles .status-badge { border-radius: 999px; }

/* Journée : l'heure sert de repère à gauche, reliée par un filet vertical. */
.timeline { display: flex; flex-direction: column; gap: 2px; }
.timeline li { display: flex; gap: 14px; padding: 10px 0; }
.timeline li + li { border-top: 1px solid var(--line-soft); }
.timeline-time {
  flex: 0 0 4.25rem;
  padding-left: 12px;
  border-left: 2px solid var(--teal);
  font-size: var(--fs-sm);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.timeline-time b { display: block; font-weight: 500; color: var(--muted); }
/* Le bloc central prend la place restante. */
.timeline li > div { flex: 1; min-width: 0; }
.timeline strong { display: block; font-size: var(--fs-base); }
.timeline span { display: block; margin-top: 2px; font-size: var(--fs-sm); color: var(--muted); }
/* ----- Emploi du temps -----
   La grille de Kooly.html, règles reprises telles quelles, couleurs passées par
   les variables de l'application. Une heure fait 50 px, de 06:00 à 18:00 : la
   colonne d'un jour mesure donc 600 px, et la bande grise de 12 h à 13 h est la
   pause déjeuner. Les cartes sont posées en JavaScript (top et height). */
.emploi-toolbar-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; flex-wrap: wrap; gap: 12px; }
.emploi-toolbar-row .filter-control, .annonces-barre .filter-control { width: auto; min-width: 200px; }
.panel.timetable-card { padding: 0; overflow: hidden; }
.timetable-scroll { overflow-x: auto; }
.timetable-grid { display: flex; min-width: 760px; }
.timetable-hours-col { flex: 0 0 70px; border-right: 1px solid var(--line-soft); }
.timetable-header-cell { height: 48px; display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 600; color: var(--ink); background-color: var(--survol); border-bottom: 1px solid var(--line-soft); }
.timetable-hour-label { height: 50px; display: flex; align-items: flex-start; justify-content: center; padding-top: 5px; font-size: 12px; color: var(--muted); border-top: 1px solid var(--survol); }

.timetable-day-col { flex: 1; position: relative; border-right: 1px solid var(--line-soft); min-width: 130px; }
.timetable-day-col:last-child { border-right: none; }
.timetable-day-body { position: relative; height: 600px; background-image: repeating-linear-gradient(to bottom, var(--survol) 0, var(--survol) 1px, transparent 1px, transparent 50px); }
.timetable-lunch-band { position: absolute; left: 0; right: 0; top: 300px; height: 50px; background-color: var(--survol); z-index: 1; pointer-events: none; }
.timetable-day-col.drag-over .timetable-day-body { background-color: rgba(23, 195, 178, .05); }
.cours-card { position: absolute; left: 6px; right: 6px; border-left: 4px solid var(--accent); background-color: var(--accent-fond); border-radius: 10px; padding: 10px 12px; cursor: grab; z-index: 2; box-shadow: 0 2px 6px rgba(0,0,0,.04); transition: box-shadow .15s ease, transform .15s ease, height .15s ease; user-select: none; touch-action: none; }
/* Le menu à trois points est en position fixe : un transform sur la carte le
   ferait dériver. La carte ne se soulève donc pas tant que son menu est ouvert,
   et passe devant ses voisines. */
.cours-card:hover:not(:has(.action-dropdown.active)) { transform: translateY(-2px); }
.cours-card:hover { box-shadow: 0 8px 18px rgba(0,0,0,.10); }
.cours-card:has(.action-dropdown.active) { z-index: 30; }
/* Une famille ou un professeur lit la grille : rien à déplacer. */
.cours-card.fixe { cursor: default; touch-action: auto; }
.cours-card.compact { padding: 6px 12px; }
.cours-card.compact .cours-titre, .cours-card.compact .cours-horaire { margin-bottom: 0; }
.cours-card.compact .cours-prof { display: none; }
.cours-card.compact:hover { height: 100px !important; padding: 10px 12px; z-index: 20; }
.cours-card.compact:hover .cours-titre { margin-bottom: 2px; }
.cours-card.compact:hover .cours-horaire { margin-bottom: 6px; }
.cours-card.compact:hover .cours-prof { display: flex; }
.cours-card.dragging { cursor: grabbing; box-shadow: 0 16px 28px rgba(0,0,0,.18); transform: scale(1.03); z-index: 1000; }
.cours-card .cours-titre { font-size: 13px; font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-bottom: 2px; padding-right: 16px; }
.cours-card .cours-horaire { font-size: 11px; color: var(--muted); margin-bottom: 6px; }
.cours-prof { display: flex; align-items: center; gap: 6px; min-width: 0; }
.cours-card .cours-avatar { width: 20px; height: 20px; border-radius: 50%; flex-shrink: 0; font-size: 9px; }
.cours-card .cours-detail { font-size: 11px; color: var(--muted); line-height: 1.5; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cours-actions { position: absolute; top: 4px; right: 4px; z-index: 3; }
.cours-actions .dropdown-toggle-btn { font-size: 11px; color: var(--muted); padding: 4px; cursor: pointer; }
.cours-malagasy { --accent: var(--cours-malagasy); --accent-fond: var(--cours-malagasy-fond); }
.cours-francais { --accent: var(--cours-francais); --accent-fond: var(--cours-francais-fond); }
.cours-anglais { --accent: var(--cours-anglais); --accent-fond: var(--cours-anglais-fond); }
.cours-maths { --accent: var(--cours-maths); --accent-fond: var(--cours-maths-fond); }
.cours-svt { --accent: var(--cours-svt); --accent-fond: var(--cours-svt-fond); }
.cours-physique { --accent: var(--cours-physique); --accent-fond: var(--cours-physique-fond); }
.cours-histoire { --accent: var(--cours-histoire); --accent-fond: var(--cours-histoire-fond); }
.cours-ecm { --accent: var(--cours-ecm); --accent-fond: var(--cours-ecm-fond); }
.cours-eps { --accent: var(--cours-eps); --accent-fond: var(--cours-eps-fond); }
.cours-info { --accent: var(--cours-info); --accent-fond: var(--cours-info-fond); }
.cours-artistique { --accent: var(--cours-artistique); --accent-fond: var(--cours-artistique-fond); }
.cours-philo { --accent: var(--cours-philo); --accent-fond: var(--cours-philo-fond); }
.cours-economie { --accent: var(--cours-economie); --accent-fond: var(--cours-economie-fond); }

/* Pastilles d'échéance et de note : même gabarit, deux lectures différentes. */
.due-pill, .grade-pill {
  display: grid;
  place-items: center;
  min-width: 3.6rem;
  padding: 8px 10px;
  flex: 0 0 auto;
  border-radius: var(--small-radius);
  font-size: var(--fs-xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-align: center;
}
.due-pill { background: rgba(227, 131, 40, .18); color: var(--brun); }
.grade-pill { background: rgba(23, 195, 178, .14); color: var(--turquoise-fonce); font-size: var(--fs-md); line-height: 1.1; }
.grade-pill b { font-size: var(--fs-xs); font-weight: 600; opacity: .7; }
/* Herin, le 2026-09-19 : la note et son « /20 » sur une seule ligne, partout —
   empilés, ils faisaient une pastille de deux lignes au milieu du tableau. */
.grade-pill { grid-auto-flow: column; align-items: baseline; justify-content: center; gap: 1px; white-space: nowrap; }
/* Une échéance dépassée et une moyenne sous la barre ne sont pas des états
   normaux : la couleur le dit avant qu'on ait lu le chiffre. */
/* Posée seule dans une cellule de tableau ou dans une phrase, la pastille doit
   rester à la largeur de son contenu — en grille bloc elle prendrait toute la
   cellule. Dans une liste elle reste en `flex: 0 0 auto`, donc inchangée. */
td > .grade-pill, .recap .grade-pill, .sheet-line .grade-pill { display: inline-grid; }
.due-pill.overdue { background: rgba(229, 72, 77, .12); color: var(--red); }
.grade-pill.low { background: rgba(229, 72, 77, .12); color: var(--red); }

/* ---- Barres d'outils et récapitulatifs de page ------------------------------
   Deux petites briques réutilisées partout : une ligne d'actions au-dessus
   d'une liste (« Tout marquer présent »), et un rappel chiffré au-dessus d'un
   tableau, pour ne pas avoir à compter les lignes à l'œil. */
.toolbar {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}
.toolbar .muted { font-size: var(--fs-sm); }
.recap {
  display: flex;
  align-items: center;
  gap: 8px 18px;
  flex-wrap: wrap;
  margin-bottom: 14px;
  font-size: var(--fs-sm);
  color: var(--ink-soft);
}
.recap span { display: inline-flex; align-items: center; gap: 8px; }
/* Quand une famille a plusieurs enfants, le résumé se dédouble : une ligne par
   enfant, son prénom en tête. Les lignes se suivent de près — c'est un seul
   bloc de lecture, pas deux résumés séparés. */
.recap + .recap { margin-top: -8px; }
.recap-qui { font-weight: 600; color: var(--ink); }

/* ---- Panneaux ---- */
.panel {
  display: block;
  margin-bottom: 24px;
  padding: 24px;
  border: 1px solid var(--line-soft);
  border-radius: 20px;
  background: var(--card);
  box-shadow: 0 10px 30px rgba(0, 0, 0, .03);
}
.panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}
/* Le titre d une carte est un h2 : la barre du haut porte le h1 de la page, et
   sauter directement au h3 faisait croire a un niveau manquant - un lecteur
   d ecran annoncait un titre de niveau 3 sans niveau 2 au-dessus. */
.panel-header h2 { font-size: 16px; font-weight: 600; color: var(--ink); }
.panel-header p { margin-top: 2px; font-size: var(--fs-sm); color: var(--muted); }

.panel-grid { display: grid; gap: 20px; margin-bottom: 24px; }
/* Deux cartes côte à côte seulement quand chacune a vraiment la place. À 20rem,
   une tablette de 768 px les mettait quand même sur deux colonnes de 350 px, et
   un tableau de six colonnes s'y retrouvait enfermé dans une fenêtre de la
   largeur d'un pouce, à faire glisser latéralement pour lire une ligne. Sous ce
   seuil, mieux vaut une carte pleine largeur au-dessus de l'autre. */
/* Le « min(28rem, 100%) » est la moitié qui manquait : sans lui, la colonne
   gardait ses 28 rem même sur un écran qui n'en fait que 20. La carte
   dépassait alors la page, et le téléphone se mettait à glisser de côté —
   les emplois du temps et les bilans se lisaient en poussant l'écran vers la
   droite. Une carte ne descend jamais sous la largeur qu'on lui laisse. */
.panel-grid.equal { grid-template-columns: repeat(auto-fit, minmax(min(28rem, 100%), 1fr)); }
.panel-grid .panel { margin-bottom: 0; }

/* ---- Actions rapides ---- */
.quick-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: 14px;
}
.quick-action {
  display: block;
  padding: 18px;
  border: 1px solid var(--line);
  border-radius: var(--small-radius);
  background: var(--card);
  color: inherit;
  text-decoration: none;
  transition: border-color var(--ease), transform var(--ease), box-shadow var(--ease);
}
.quick-action:hover { border-color: var(--teal); transform: translateY(-2px); box-shadow: var(--shadow); }
.quick-action b {
  display: inline-block;
  margin-bottom: 10px;
  padding: 3px 10px;
  border-radius: var(--pill);
  background: rgba(23, 195, 178, .12);
  color: var(--teal);
  font-size: var(--fs-xs);
  letter-spacing: .06em;
}
.quick-action strong { display: block; font-size: var(--fs-base); }
.quick-action span { display: block; margin-top: 2px; font-size: var(--fs-sm); color: var(--muted); }

/* ---- Listes ---- */
.list-stack { display: flex; flex-direction: column; }
.list-item {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 4px;
  border-bottom: 1px solid var(--line-soft);
}
.list-item:last-child { border-bottom: 0; }
.list-dot {
  width: 8px;
  height: 8px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--corail);
}
.list-item-content { flex: 1; min-width: 0; }
.list-item-content strong {
  display: block;
  font-size: 14px;
  font-weight: 500;
  color: var(--ink);
  overflow-wrap: anywhere;
}
.list-item-content span {
  display: block;
  margin-top: 2px;
  font-size: 12px;
  color: var(--muted);
  overflow-wrap: anywhere;
}

/* ---- Tableaux ----
   table() produit <div class="table-wrap"><table class="data-table">.
   Ici, en grand écran, c'est un vrai tableau et le conteneur porte le
   défilement horizontal résiduel. Sous 640px la section 7 le convertit en
   liste de cartes : plus de tableau, donc plus de défilement du tout. */
.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: var(--small-radius);
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  /* Quand le tableau est plus large que la place, la dernière colonne — celle
     des boutons — se retrouve hors de vue, et rien ne le disait : l'en-tête
     « ACTIONS » finissait coupé en « ACTIOI » et on croyait les boutons
     absents. Une ombre apparaît maintenant sur le bord qui cache quelque
     chose, et s'efface dès qu'on y est arrivé. Les deux premiers calques
     voyagent avec le contenu (« local ») et masquent l'ombre au bout de la
     course ; les deux autres restent collés au cadre (« scroll »). */
  background:
    linear-gradient(to right, var(--card), rgba(255, 255, 255, 0)) 0 0 / 22px 100% no-repeat local,
    linear-gradient(to left, var(--card), rgba(255, 255, 255, 0)) 100% 0 / 22px 100% no-repeat local,
    linear-gradient(to right, rgba(11, 23, 57, .14), rgba(11, 23, 57, 0)) 0 0 / 16px 100% no-repeat scroll,
    linear-gradient(to left, rgba(11, 23, 57, .14), rgba(11, 23, 57, 0)) 100% 0 / 16px 100% no-repeat scroll;
}
.data-table {
  width: 100%;
  min-width: 40rem;
  border-collapse: collapse;
  font-size: var(--fs-sm);
}
.data-table th, .data-table td {
  padding: 12px 16px;
  text-align: left;
  vertical-align: middle;
  border-bottom: 1px solid var(--line-soft);
}
.data-table th {
  background: var(--line-soft);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-soft);
  white-space: nowrap;
}
/* Un filtre d'écran masque des lignes. La pagination, elle, se sert de
   style.display : les deux ne doivent pas se marcher dessus. */
.data-table tbody tr[hidden] { display: none; }
.data-table tbody tr:last-child td { border-bottom: 0; }
.data-table tbody tr:hover { background: rgba(23, 195, 178, .04); }
.data-table td .muted { font-size: var(--fs-xs); }

.table-actions { display: flex; flex-wrap: wrap; gap: 6px; }
/* 34 px, c'était deux boutons collés dans la même cellule qu'on visait de
   travers : on ouvrait « Modifier » en voulant « Fiche ». */
.table-button {
  min-height: 38px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: var(--pill);
  background: var(--card);
  color: var(--ink-soft);
  font-size: var(--fs-xs);
  font-weight: 600;
  white-space: nowrap;
  transition: border-color var(--ease), color var(--ease), background var(--ease);
}
.table-button:hover { border-color: var(--teal); color: var(--teal); background: var(--line-soft); }

/* Le menu à trois points, valeurs de Kooly.html. Deux écarts voulus : la
   position fixe (placée par app.js, pour ne pas être coupée par le cadre du
   tableau) et une largeur qui suit le plus long libellé. */
.task-more { color: var(--muted); cursor: pointer; text-align: right; position: relative; }
button.dropdown-toggle-btn:not(.menu-choix-bouton) { display: inline-flex; align-items: center; justify-content: center; background: none; border: none; margin: 0; padding: 8px; font: inherit; color: inherit; cursor: pointer; }
.action-dropdown { position: fixed; background-color: var(--card); border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.12); border: 1px solid var(--line); min-width: 140px; display: none; z-index: 50; overflow: hidden; text-align: left; padding: 6px 0; }
.action-dropdown.active { display: block; }
.action-dropdown-item { display: flex; align-items: center; gap: 10px; width: 100%; padding: 10px 16px; font-size: 13px; color: var(--ink); cursor: pointer; transition: background 0.2s; font-weight: 500; background: none; border: none; font-family: inherit; text-align: left; white-space: nowrap; }
a.action-dropdown-item { text-decoration: none; }
/* L'image garde toujours la meme largeur : les mots restent alignes entre eux,
   meme quand une image est plus etroite qu'une autre. Elle prend la couleur de
   sa ligne, donc le rouge sur « Supprimer ». */
.action-dropdown-item > i { width: 15px; flex: none; font-size: 13px; text-align: center; opacity: 0.8; }
.action-dropdown-item:hover { background-color: var(--survol); }
.action-dropdown-item.delete { color: #FF3366; }
/* Le menu de choix : meme cadre et memes lignes que le menu a trois points,
   mais le bouton qui l'ouvre doit ressembler a la case qu'il remplace. Il porte
   donc .filter-control, et ces trois lignes rattrapent ce que la regle du menu
   a trois points lui enleverait au passage (bordure, remplissage, centrage).
   Le selecteur est volontairement long : sans lui, button.dropdown-toggle-btn
   l'emporte sur .filter-control et le bouton redevient une puce sans bord. */
.menu-choix { position: relative; width: 100%; }
button.filter-control.menu-choix-bouton { font-family: inherit; font-weight: inherit; margin: 0; display: flex; align-items: center; justify-content: space-between; gap: 8px; text-align: left; cursor: pointer; }
.menu-choix-valeur { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.menu-choix-fleche { flex: none; font-size: 12px; color: var(--muted); }
/* La ligne choisie se reconnait a son encre, pas a un fond : un fond la ferait
   passer pour la ligne survolee. La ligne fermee ne repond pas au survol —
   un fond qui s'allume sous le doigt promet un clic qui n'arrivera jamais. */
.menu-choix-item[aria-selected="true"] { color: var(--teal); font-weight: 600; }
.menu-choix-item[aria-disabled="true"] { color: var(--muted); cursor: default; }
.menu-choix-item[aria-disabled="true"]:hover { background-color: transparent; }
.student-cell { display: flex; align-items: center; gap: 10px; min-width: 0; }
/* La ligne du dessous grise et empile tous les <span> de la cellule — c'est ce
   qui met le nom au-dessus de sa classe. La pastille en est un aussi : sans ce
   rappel, ses initiales devenaient grises sur fond vert, donc illisibles, et se
   collaient dans le coin au lieu de rester au centre. */
.student-cell .avatar { display: grid; width: 28px; height: 28px; border-radius: 50%; font-size: 12px; font-weight: 600; color: var(--blanc); }
.student-cell strong { display: block; font-size: var(--fs-sm); }
.student-cell span { display: block; font-size: var(--fs-xs); color: var(--muted); }

.grade-input { width: 5.5rem; min-height: 36px; padding: 6px 10px; }
.comment-input { min-width: 12rem; min-height: 36px; padding: 6px 10px; }
/* La ligne d'une publication de bulletins : le nom, la moyenne que l'élève a
   réellement obtenue, puis l'appréciation qu'on lui écrit. Sur téléphone le
   champ passe seul à la ligne : à trois de front il ne restait plus de place
   pour écrire, et c'est pourtant là qu'on écrit. */
.ligne-publication { display: flex; align-items: center; gap: 10px; padding: 7px 0; border-bottom: 1px dotted var(--line-soft); }
.ligne-publication .nom { flex: 1 1 9rem; min-width: 0; overflow-wrap: anywhere; font-size: var(--fs-sm); }
.ligne-publication .comment-input { flex: 2 1 12rem; }
@media (max-width: 640px) {
  .ligne-publication { flex-wrap: wrap; }
  .ligne-publication .comment-input { flex: 1 1 100%; }
}
/* La note qu'on n'a pas su lire se montre, sinon le message nomme un élève que
   le professeur doit ensuite chercher lui-même dans trente lignes. */
/* Les trois teintes de Kooly.html : la note se lit bonne ou mauvaise à l'œil. */
.grade-input.note-faible { background-color: rgba(229, 72, 77, .08); border-color: rgba(229, 72, 77, .35); color: var(--red); font-weight: 600; }
.grade-input.note-moyenne { background-color: rgba(227, 131, 40, .08); border-color: rgba(227, 131, 40, .35); color: var(--amber); font-weight: 600; }
.grade-input.note-bonne { background-color: rgba(52, 168, 83, .08); border-color: rgba(52, 168, 83, .35); color: var(--vert-note); font-weight: 600; }
.grade-input.note-fausse { border-color: var(--red); background: rgba(229, 72, 77, .06); }

/* ---- Badges ---- */
.status-badge {
  display: inline-flex;
  align-items: center;
  padding: 6px 12px;
  border-radius: 6px;
  background: var(--turquoise-pale);
  color: var(--teal);
  font-size: 11px;
  font-weight: 600;
  line-height: normal;
  white-space: nowrap;
}
.status-badge.danger { background: rgba(229, 72, 77, .12); color: var(--red); }
.status-badge.warn { background: rgba(227, 131, 40, .12); color: var(--amber); }
/* Éteint : un compte fermé, un tarif retiré, une charge suspendue. Ce n'est pas
   un problème, c'est une décision prise — donc gris, comme dans Kooly.html, et
   le rouge reste réservé à ce qui réclame un geste. */
.status-badge.eteint { background: var(--line); color: var(--ink-soft); }
/* L'audience d'un document prend la couleur du rôle visé ; « Tous » garde le
   turquoise de Haitso. */
.status-badge.cible-parents { background: color-mix(in srgb, var(--corail) 14%, transparent); color: var(--corail); }
.status-badge.cible-teachers { background: color-mix(in srgb, var(--violet) 14%, transparent); color: var(--violet); }
.status-badge.cible-students { background: color-mix(in srgb, var(--bleu) 14%, transparent); color: var(--bleu); }
/* Nouveau document : la liste des catégories et, sur « Autre », le champ libre
   à côté, comme dans Kooly.html. */
.documents-categorie-row { display: flex; gap: 12px; }
.documents-categorie-row > * { flex: 1; min-width: 0; }

/* ---- État vide ---- */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 40px 20px;
  color: var(--muted);
  text-align: center;
}
.empty-state b { display: flex; opacity: .4; }
.empty-state .ico { width: 2rem; height: 2rem; stroke-width: 1.4; }
.empty-state strong { font-size: var(--fs-base); font-weight: 600; color: var(--ink); }
.data-table .empty-state { padding: 28px 20px; }

/* Le pied du tableau : le nombre de lignes par page à gauche, ce qu'on est en
   train de regarder au milieu, les pages à droite. Trois informations dans
   l'ordre où on les cherche. Valeurs recopiées de .table-pagination dans
   Kooly.html, sur tous les tableaux de l'application. */
.table-pied {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 4px;
  padding: 14px 20px;
}
.table-pied[hidden] { display: none; }
.pied-choix { display: flex; align-items: center; gap: 10px; font-size: 13px; font-weight: 500; color: var(--muted); }
.pied-choix select {
  height: 34px;
  min-height: 0;
  padding: 0 34px 0 12px;
  border: 1.5px solid var(--line);
  border-radius: 10px;
  background-color: var(--card);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%2386909C'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 14px;
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  appearance: none;
  outline: none;
  transition: border-color 0.15s;
}
.pied-choix select:hover, .pied-choix select:focus { border-color: var(--teal); }
.pied-compte { margin: 0; font-size: 13px; font-weight: 500; color: var(--muted); }
.pied-pages { display: flex; align-items: center; gap: 8px; }
.page-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  height: 32px;
  padding: 0 6px;
  border: 1.5px solid var(--line);
  border-radius: 10px;
  background-color: var(--card);
  color: var(--ink);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
  transition: all 0.15s;
}
.page-btn:hover:not(:disabled) { background-color: var(--teal); border-color: var(--teal); color: var(--blanc); }
.page-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.page-active, .page-active:hover:not(:disabled) { background-color: var(--turquoise-pale); border-color: var(--teal); color: var(--teal); font-weight: 700; }
.page-saut { padding: 0 2px; color: var(--muted); }
/* Le tri se prend par le titre de la colonne : c'est là qu'on le cherche. */
.tri-titre {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
}
.tri-titre:hover { color: var(--ink); }
/* La flèche dit dans quel sens on a trié, et reste en gris tant qu'on n'a rien
   demandé — pour qu'on sache que la colonne est triable avant de cliquer. */
.tri-fleche::before { content: '↕'; color: var(--muted); }
th[aria-sort="ascending"] .tri-fleche::before { content: '↑'; color: var(--teal); }
th[aria-sort="descending"] .tri-fleche::before { content: '↓'; color: var(--teal); }
th[aria-sort="ascending"], th[aria-sort="descending"] { color: var(--ink); }
/* Sur grand écran les titres suffisent : la liste déroulante de tri ne sert
   qu'au téléphone, où le tableau devient des cartes sans en-tête. */
.tri-au-pouce { display: none; }

/* Le choix du mois, posé juste au-dessus de la liste qu'il commande. */

/* ---- Appel / présences ----
   La page de Kooly.html : trois cartes chiffrées, le cadre Sélection, « Tout
   marquer présent » à droite, puis le tableau commun (Élève, Matricule, Statut)
   aux largeurs de ses colonnes (1.8fr 1fr 2.2fr). */
.presences-actions { display: flex; justify-content: flex-end; margin: 4px 0 16px; }
@media (min-width: 641px) {
  .liste-appel .data-table { table-layout: fixed; width: 100%; }
  .liste-appel .data-table th:nth-child(1) { width: 36%; }
  .liste-appel .data-table th:nth-child(2) { width: 20%; }
}
/* Trois boutons plutôt qu'une liste déroulante. Marquer un absent demandait
   d'ouvrir la liste, de viser la bonne ligne et de refermer — trois gestes par
   élève, trente fois par matin. Ici l'état se lit sans rien ouvrir et se change
   d'une touche. Ce sont de vrais boutons radio : le clavier et le lecteur
   d'écran les parcourent comme ils parcouraient la liste. Valeurs recopiées de
   .attendance-btn dans Kooly.html. */
.att-choix { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.att-bouton {
  display: grid;
  place-items: center;
  padding: 7px 16px;
  border: 1.5px solid var(--line);
  border-radius: 20px;
  background: var(--card);
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: all 0.15s;
}
/* La case reste là — c'est elle que le clavier suit — mais on ne la dessine
   pas : c'est le bouton entier qui se vise. */
.att-bouton input { position: absolute; width: 1px; height: 1px; opacity: 0; }
.att-bouton:hover { border-color: var(--trait-champ); }
.att-bouton:has(input:focus-visible) { outline: 2px solid var(--teal); outline-offset: 2px; }
/* L'état retenu prend la couleur de sa carte en haut de la page : turquoise
   présent, rouge absent, brun en retard. */
.att-bouton:has(input:checked) { border-color: transparent; background: var(--turquoise-pale); color: var(--teal); }
.att-absent:has(input:checked) { background: rgba(229, 72, 77, .12); color: var(--red); }
.att-late:has(input:checked) { background: rgba(139, 94, 60, .12); color: var(--brun); }

/* ---- Modules & permissions ---- */
.module-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 14px;
  margin-top: 18px;
}
.module-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px;
  border: 1px solid var(--line);
  border-radius: var(--small-radius);
  background: var(--card);
}
.module-card > div { min-width: 0; }
.module-card strong {
  display: block;
  font-size: var(--fs-base);
  font-weight: 600;
  overflow-wrap: anywhere;
}
.module-card span {
  display: block;
  margin-top: 2px;
  font-size: var(--fs-sm);
  color: var(--muted);
}
/* Quarante droits l'un sous l'autre faisaient une page qu'on parcourait
   longtemps : ils tiennent désormais sur deux colonnes, qui se remettent
   d'elles-mêmes l'une sous l'autre quand la fenêtre se rétrécit. */
.permission-matrix {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr));
  align-items: start;
  gap: 20px;
}
/* Chaque colonne est un seul cadre, au dessin commun à toute l'application :
   les rubriques s'y séparent d'un filet, au lieu d'encadrer chacune des
   quarante lignes. */
.permission-colonne {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0 24px;
}
.permission-group {
  display: flex;
  flex-direction: column;
  padding: 18px 0;
  border-bottom: 1px solid var(--line-soft);
}
.permission-group:last-child { border-bottom: 0; }
/* Les libellés ne sont plus des codes : ils se lisent comme des phrases, dans la
   même police que le reste de l'application. */
.permission-group h4 {
  margin: 0 0 6px;
  font-size: var(--fs-base);
  font-weight: 700;
  color: var(--ink);
}
.permission-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 9px 0;
}
.permission-row > div { min-width: 0; }
.permission-row strong {
  display: block;
  font-size: var(--fs-base);
  font-weight: 400;
  overflow-wrap: anywhere;
}
.permission-row span {
  display: block;
  margin-top: 2px;
  font-size: var(--fs-sm);
  color: var(--muted);
}

/* Interrupteur */
.switch {
  position: relative;
  width: 52px;
  height: 30px;
  flex: 0 0 auto;
  padding: 0;
  border: 0;
  border-radius: var(--pill);
  background: var(--line);
  transition: background var(--ease);
}
.switch i {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--blanc);
  box-shadow: var(--shadow-sm);
  transition: transform var(--ease);
}
.switch.on { background: var(--teal); }
.switch.on i { transform: translateX(22px); }

/* ---- Onglets de paramètres ---- */
.settings-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 22px;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--line);
}
.settings-tab {
  min-height: var(--tap);
  padding: 0 18px;
  border: 0;
  border-radius: var(--pill);
  background: transparent;
  color: var(--muted);
  font-size: var(--fs-sm);
  font-weight: 600;
  transition: background var(--ease), color var(--ease);
}
.settings-tab:hover { background: var(--line-soft); color: var(--ink); }
.settings-tab.active { background: var(--teal); color: var(--blanc); }
/* Sur ordinateur, ces rubriques sont dans la barre latérale, comme sur
   Kooly.html. Les onglets ne restent que là où la barre est rangée. */
@media (min-width: 901px) { .settings-tabs { display: none; } }

/* Le chemin de retour, au-dessus du tableau d'une classe ouverte. */
.fil-classe { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin-bottom: 16px; }
.fil-classe strong { font-size: var(--fs-md); }
.fil-classe > span:not(.status-badge) { font-size: var(--fs-sm); color: var(--muted); }
/* Les onglets chiffrés des Bulletins : un nombre, un mot. Ce qui est fait, ce
   qui reste, ce qui est à refaire — lisible sans phrase d'explication, et sans
   avoir à redemander à chaque ouverture comment la page s'utilise. */
/* Sur Paiements, ces deux boutons arrivent juste sous les cartes de chiffres de
   la page, et flottaient dans le vide comme des cartes ratées. Le filet qui
   court sous eux dit ce qu'ils sont : une barre d'onglets qui commande le
   tableau du dessous, et non des chiffres de plus à lire. */
.onglets-chiffres {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 22px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--line-soft);
}
/* Ils sont posés là où l'écran affichait avant trois cartes de statistiques, qui
   ne se cliquaient pas. Blancs sur un panneau blanc, ils auraient enseigné le
   contraire de ce qu'ils font : le fond plein et la bordure les désignent comme
   des boutons, pas comme un chiffre à lire. */
.onglet-chiffre {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  min-width: 6.5rem;
  padding: 9px 16px;
  border: 1px solid var(--line);
  border-radius: var(--small-radius);
  background: var(--line-soft);
  color: var(--muted);
  text-align: left;
  cursor: pointer;
  transition: border-color var(--ease), background var(--ease), color var(--ease);
}
.onglet-chiffre b { font-size: var(--fs-lg); font-weight: 700; line-height: 1.2; color: var(--ink); }
.onglet-chiffre span { font-size: var(--fs-sm); font-weight: 600; }
.onglet-chiffre:hover { border-color: var(--teal); background: var(--card); }
.onglet-chiffre.active { background: var(--teal); border-color: transparent; color: var(--blanc); }
.onglet-chiffre.active b { color: var(--blanc); }
/* Un compte à rééditer qui n'est pas nul se voit sans être ouvert : c'est le
   seul des trois onglets qui annonce un travail à refaire. */
.onglet-chiffre.alerte:not(.active) { border-color: var(--amber); background: rgba(227, 131, 40, .16); }
.onglet-chiffre.alerte:not(.active) b { color: var(--amber); }
/* Un avertissement qui reste à l'écran, à ne pas confondre avec le message
   passager du bas de page : celui-ci ne part pas tant que rien n'est fait. */
.rappel {
  margin: 0 0 16px;
  padding: 12px 14px;
  border-left: 3px solid var(--amber);
  border-radius: var(--small-radius);
  background: rgba(227, 131, 40, .16);
  font-size: var(--fs-sm);
  line-height: 1.6;
}

.profile-summary {
  margin-bottom: 20px;
  padding: 16px;
  border-radius: var(--small-radius);
  background: var(--line-soft);
  line-height: 1.7;
  color: var(--ink-soft);
}
.profile-summary strong { color: var(--ink); font-size: var(--fs-md); }
/* La clé se recopie caractère par caractère sur un clavier de téléphone : une
   police à chasse fixe et de l'espace entre les lettres évitent de confondre
   un 0 avec un O. Elle doit aussi pouvoir être sélectionnée pour être copiée. */
.twofa-secret {
  margin: 10px 0;
  padding: 10px 12px;
  border: 1px dashed var(--line);
  border-radius: var(--small-radius);
  background: var(--bg);
  color: var(--ink);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--fs-md);
  letter-spacing: .08em;
  overflow-wrap: anywhere;
  user-select: all;
}

/* ---- Journal des changements ----
   Une frise : un fil vertical, un repère par version, et la version installée
   marquée. Haitso reste clair ici comme partout — c'est un écran qui se lit,
   souvent longuement, et du texte long sur fond sombre fatigue. */
.journal { max-width: 860px; }

.journal-version {
  position: relative;
  padding: 0 0 26px 30px;
}
/* Le fil court d'un repère à l'autre ; la dernière version ne le prolonge pas
   dans le vide. */
.journal-version::before {
  content: "";
  position: absolute;
  top: 18px;
  bottom: 0;
  left: 5px;
  width: 2px;
  background: var(--line);
}
.journal-version:last-child::before { display: none; }
.journal-version::after {
  content: "";
  position: absolute;
  top: 7px;
  left: 0;
  width: 12px;
  height: 12px;
  border: 2px solid var(--line);
  border-radius: 50%;
  background: var(--card);
}
.journal-version.installee::after {
  border-color: var(--teal);
  background: var(--teal);
  box-shadow: 0 0 0 4px rgba(23, 195, 178, .16);
}

.journal-entete {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
}
.journal-entete h3 { font-size: var(--fs-lg); letter-spacing: -.01em; }
.journal-entete p {
  flex-basis: 100%;
  color: var(--muted);
  font-size: var(--fs-sm);
}
.journal-chip {
  padding: 3px 10px;
  border-radius: var(--pill);
  background: rgba(23, 195, 178, .12);
  color: var(--turquoise-fonce);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
}

.journal-carte {
  padding: 6px 20px 18px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  box-shadow: var(--shadow-sm);
}
.journal-theme + .journal-theme { margin-top: 6px; }
/* L'intitulé du thème est encadré, et le filet file jusqu'au bord : c'est lui
   qui découpe la carte en tranches lisibles. */
.journal-theme h4 {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 18px 0 10px;
  font-size: var(--fs-sm);
}
.journal-theme h4 span {
  padding: 4px 10px;
  border: 1px solid rgba(23, 195, 178, .3);
  border-radius: 8px;
  background: rgba(23, 195, 178, .07);
  color: var(--teal);
  font-weight: 700;
}
.journal-theme h4::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}
.journal-theme ul { margin: 0; padding: 0; list-style: none; }
.journal-theme li {
  position: relative;
  padding-left: 18px;
  color: var(--ink-soft);
}
.journal-theme li + li { margin-top: 10px; }
.journal-theme li::before {
  content: "";
  position: absolute;
  top: .6em;
  left: 4px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--teal);
}
.journal-theme li strong { color: var(--ink); }
.journal-theme li code {
  padding: 1px 5px;
  border-radius: 6px;
  background: var(--line-soft);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--fs-sm);
}

.journal-reglage {
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: 860px;
  margin-top: 6px;
  padding: 14px 20px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--ink-soft);
  font-size: var(--fs-sm);
  cursor: pointer;
}
.journal-reglage input { width: 18px; height: 18px; accent-color: var(--teal); }

/* ============================ 6. OVERLAYS ================================ */

.drawer-backdrop, .modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(11, 23, 57, .55);
  backdrop-filter: blur(3px);
}
.drawer-backdrop { z-index: 35; }

.modal-backdrop {
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.modal {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 620px;
  max-height: calc(100vh - 48px);
  max-height: calc(100dvh - 48px);
  border: 1px solid var(--line-soft);
  border-radius: 24px;
  background: var(--card);
  box-shadow: 0 25px 50px -12px rgba(0,0,0,0.15);
  overflow: hidden;
}
/* Une fenêtre qui contient un énoncé à écrire, ou trente élèves à commenter,
   prend toute la largeur qu'on peut lui donner : on n'y tape pas deux mots. */
.modal.large { max-width: 1040px; }
/* Une fenêtre de saisie à dix cases : plus large et sur trois colonnes, elle
   tient entière sous les yeux au lieu de se lire en défilant. Sur un écran
   moins large, elle reprend d'elle-même la disposition ordinaire. */
.modal.trois-colonnes { max-width: 860px; }
@media (min-width: 900px) {
  .modal.trois-colonnes .form-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  /* La place gagnée en largeur sert à poser les deux boutons de la photo à
     côté de son explication, et non dessous : c'est ce qui fait tenir la
     fenêtre entière à l'écran. */
  .modal.trois-colonnes .logo-copy { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; column-gap: 18px; }
  .modal.trois-colonnes .logo-actions { grid-column: 2; grid-row: 1 / 3; }
}

/* --- La fenêtre « Êtes-vous sûr ? » (voir demandeConfirmation) ---
   Elle passe au-dessus de tout, y compris d'une fenêtre déjà ouverte, parce
   qu'on confirme presque toujours depuis un formulaire qu'il ne faut pas
   perdre. Elle est volontairement étroite : une question, deux boutons, rien
   d'autre à lire. */
.confirm-voile {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.confirm-boite {
  width: 100%;
  max-width: 30rem;
  padding: 28px 32px 32px;
  border: 1px solid var(--line-soft);
  border-radius: 24px;
  background: var(--card);
  box-shadow: 0 25px 50px -12px rgba(0,0,0,0.15);
}
/* La tête reprend le dessin du message surgissant : une icône de la couleur de
   ce qui va se passer, posée sur un rond de la même teinte, et le titre à côté.
   Le fond de la fenêtre reste blanc : à cette taille, un aplat coloré crierait. */
.confirm-boite { --ton: var(--red); }
.confirm-boite.douce { --ton: var(--teal); }
.confirm-tete { display: flex; align-items: flex-start; gap: 14px; }
.confirm-tete > div { min-width: 0; }
.confirm-pastille {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--ton) 30%, transparent);
  background: color-mix(in srgb, var(--ton) 12%, var(--card));
  color: var(--ton);
  font-size: 16px;
}
.confirm-boite h2 { margin: 0; font-size: 22px; font-weight: 700; color: var(--ink); overflow-wrap: anywhere; }
.confirm-boite p { margin: 10px 0 0; color: var(--muted); line-height: 1.6; white-space: pre-line; }
.confirm-boite .form-field { margin-top: 16px; }
.confirm-actions { display: flex; justify-content: flex-end; }
@media (max-width: 480px) {
  .confirm-actions { flex-direction: column-reverse; }
  .confirm-actions button { width: 100%; }
}
.modal > header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 28px 32px 20px;
}
.modal > header > div { min-width: 0; }
.modal h2 { margin: 0; font-size: 22px; font-weight: 700; color: var(--ink); overflow-wrap: anywhere; }
.modal-body {
  flex: 1;
  padding: 0 32px 32px;
  overflow-y: auto;
  /* La bulle d'explication déborde du cadre alors qu'elle est cachée : sans
     ce rognage, toute fenêtre qui en contient une gagne une barre de
     défilement horizontale. Au survol, placeInfobulle la ramène déjà dans
     le cadre, elle n'est donc jamais coupée. */
  overflow-x: clip;
  -webkit-overflow-scrolling: touch;
}

/* ----- La feuille -----
   Elle ne se dimensionne pas sur son contenu mais sur le papier : la A4 est
   dessinée en grand, puis réduite en bloc. Rien ne défile, la page se juge
   entière — c'est la seule façon de voir ce qui sortira de l'imprimante. */
.feuille-a4 { position: relative; margin: 0 auto; overflow: hidden; border-radius: 4px; background: var(--blanc); box-shadow: var(--shadow); }
.feuille-a4-page {
  position: absolute;
  top: 0; left: 0;
  width: 794px; height: 1123px;
  transform: scale(var(--a4-k, 1));
  transform-origin: top left;
  /* La marge du papier, pas celle d'une carte à l'écran : 14 mm, la même
     mesure que `#printRoot` applique à l'impression — deux chiffres
     différents feraient d'un aperçu fidèle un aperçu ressemblant. */
  padding: 14mm;
}
/* Dans la feuille, la feuille : ni coin arrondi ni cadre, et aucune marge à
   elle, celle de la page suffit. */
.feuille-a4-page .sheet { height: 100%; border: 0; border-radius: 0; padding: 0; }

/* Les fenêtres prennent le dessin de Kooly.html (.modal-overlay,
   .modal-container, .modal-header, .close-modal-btn, .form-control,
   .btn-cancel, .btn-primary), valeurs recopiées ; celles du cadre, de l'en-tête,
   du titre et du corps sont posées plus haut. Tout est limité à .modal et à
   la confirmation : les formulaires hors fenêtre ne bougent pas. Au téléphone,
   la feuille qui monte du bas reste la même (règle plus bas). */
.modal-backdrop, .confirm-voile { background: var(--voile); backdrop-filter: blur(4px); }
.modal #modalClose { flex: none; width: 36px; height: 36px; min-width: 0; min-height: 0; padding: 0; border: 0; border-radius: 50%; background: var(--survol); color: var(--muted); transition: all 0.2s; }
.modal #modalClose:hover { background: var(--line); color: var(--ink); }
.modal .form-grid { gap: 20px; }
.modal .form-field { gap: 8px; font-size: 13px; font-weight: 600; color: var(--ink-soft); }
.modal .form-field input:not([type="checkbox"]):not([type="radio"]):not([type="file"]),
.modal .form-field select { min-height: 46px; padding: 0 16px; border: 1.5px solid var(--line); border-radius: 12px; font-size: 14px; font-weight: 400; color: var(--ink); transition: all 0.2s; }
.modal .form-field textarea { padding: 12px 16px; border: 1.5px solid var(--line); border-radius: 12px; font-size: 14px; font-weight: 400; color: var(--ink); }
.modal .form-field input:hover, .modal .form-field select:hover, .modal .form-field textarea:hover { border-color: var(--trait-champ); }
.modal .form-field input:focus, .modal .form-field select:focus, .modal .form-field textarea:focus { border-color: var(--teal); box-shadow: 0 0 0 4px rgba(23,195,178,0.08); }
/* Toutes les listes déroulantes, dans les fenêtres comme dans les filtres : la
   flèche grise de Kooly.html (select.form-control) à la place de celle du
   navigateur. */
.form-field select, select.filter-control,
.modal .form-field select, .liste-cartes > .filters select.filter-control, .annonces-barre select.filter-control { appearance: none; -webkit-appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%2386909C'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 16px center; background-size: 16px; padding-right: 40px; cursor: pointer; }
.modal .form-field input[readonly], .modal .form-field input:disabled { background: var(--fond-doux); color: var(--muted); }
/* Téléphone : une seule case, comme les autres champs. À gauche, le pays
   (« MG +261 ») sur un fond gris, séparé du numéro par un trait ; c'est la case
   entière qui prend le liseré quand on tape. Les deux lettres servent à se
   relire d'un coup d'œil : on reconnaît « MG » plus vite qu'on ne compte
   « 261 ». Les deux cases intérieures perdent leur cadre — la règle des champs
   de fenêtre leur en redonnait un, et l'indicatif ne montrait plus que « + ». */
.champ-tel { display: flex; align-items: stretch; min-height: var(--tap); overflow: hidden; border: 1px solid var(--line); border-radius: var(--small-radius); background: var(--card); transition: all 0.2s; }
/* Dans une fenêtre, les champs sont un cran plus hauts : la case suit. */
.modal .champ-tel { min-height: 46px; border: 1.5px solid var(--line); border-radius: 12px; background: var(--card); }
.modal .champ-tel .tel-indicatif { border-right-width: 1.5px; }
.champ-tel:hover { border-color: var(--trait-champ); }
.champ-tel:focus-within { border-color: var(--teal); box-shadow: 0 0 0 4px rgba(23,195,178,0.08); }
.champ-tel .tel-indicatif { display: flex; flex: none; align-items: center; gap: 6px; padding: 0 12px 0 16px; border-right: 1px solid var(--line); background: var(--survol); }
.champ-tel .tel-indicatif b { color: var(--muted); font-size: 12px; font-weight: 600; }
.form-field .champ-tel input:not([type="checkbox"]):not([type="radio"]):not([type="file"]) { min-height: 0; padding: 0 16px; border: 0; border-radius: 0; background: none; box-shadow: none; font-size: 14px; color: var(--ink); }
.form-field .champ-tel .tel-indicatif input:not([type="checkbox"]):not([type="radio"]):not([type="file"]) { width: 4.5ch; padding: 0; font-weight: 600; }
.champ-tel > input { flex: 1; min-width: 0; }
.modal .form-actions, .confirm-actions { gap: 12px; margin-top: 24px; }
.modal .form-actions .secondary-btn, .confirm-boite .secondary-btn { padding: 12px 24px; border: 0; border-radius: 20px; background: transparent; color: var(--muted); font-size: 14px; font-weight: 600; box-shadow: none; }
.modal .form-actions .secondary-btn:hover, .confirm-boite .secondary-btn:hover { background: var(--survol); color: var(--ink); }
.modal .form-actions .primary-btn, .confirm-boite .primary-btn { padding: 12px 28px; border: 0; border-radius: 20px; background: var(--teal); color: var(--blanc); font-size: 14px; font-weight: 600; box-shadow: 0 4px 12px rgba(23,195,178,0.25); transition: all 0.2s; }
.modal .form-actions .primary-btn:hover, .confirm-boite .primary-btn:hover { transform: translateY(-1px); box-shadow: 0 6px 16px rgba(23,195,178,0.35); }
/* Rouge pour ce qui efface : le bouton porte le nom du geste, sa couleur en
   porte la conséquence. Un aplat plein, sans l'ombre des boutons ordinaires. */
.confirm-oui.rouge, .confirm-oui.rouge:hover { background: var(--red); box-shadow: none; }

.toast-stack {
  position: fixed;
  right: 20px;
  /* En haut à droite, juste sous la barre du haut : c'est là que le regard
     revient après un clic, et la recherche comme l'avatar restent visibles. */
  top: calc(var(--topbar-h) + 12px);
  /* Au-dessus de tout, fenetre de confirmation comprise : le message qui dit ce
     qui vient de se passer ne doit jamais passer derriere ce qui l a declenche. */
  z-index: 95;
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: min(24rem, calc(100vw - 40px));
  pointer-events: none;
}
/* Le message : un bandeau posé sur la page. Fond très pâle de la couleur de
   l'état, filet de la même teinte, icône à gauche, croix à droite. La teinte se
   déclare une seule fois dans --ton : le filet, le fond et l'icône en découlent,
   donc aucune couleur n'est écrite deux fois. */
.toast {
  --ton: var(--teal);
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 13px 42px 13px 16px;
  border-radius: var(--small-radius);
  border: 1px solid color-mix(in srgb, var(--ton) 30%, transparent);
  background: color-mix(in srgb, var(--ton) 10%, var(--card));
  color: var(--ink);
  font-size: var(--fs-sm);
  line-height: 1.45;
  box-shadow: var(--shadow-lg);
  animation: toast-in var(--ease);
  /* La croix doit être cliquable, alors que la pile laisse passer les clics. */
  pointer-events: auto;
}
/* Une couleur par nature, exactement celles des pastilles d'état : le turquoise
   de Haitso pour ce qui a réussi, rouge pour ce qui a échoué, ambre pour ce qui
   demande une vérification, gris pour une simple information. Ni vert ni bleu :
   le bleu est la couleur des Élèves, il ne peut pas dire autre chose. */
.toast.error { --ton: var(--red); }
.toast.warn { --ton: var(--amber); }
.toast.info { --ton: var(--ink-soft); }
.toast > i {
  flex: 0 0 auto;
  margin-top: 1px;
  font-size: 15px;
  color: var(--ton);
}
.toast-fermer {
  position: absolute;
  top: 7px;
  right: 7px;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--muted);
  font-size: 13px;
  cursor: pointer;
  transition: background var(--ease), color var(--ease);
}
.toast-fermer:hover { background: color-mix(in srgb, var(--ton) 16%, transparent); color: var(--ink); }
@keyframes toast-in {
  from { opacity: 0; transform: translateY(-10px); }
  to { opacity: 1; transform: none; }
}

/* L'encart qui propose de poser Haitso sur l'écran d'accueil. Il ne s'affiche
   que sur un téléphone, une seule fois, et se ferme définitivement — donc pas
   de média query ici, c'est le JavaScript qui décide s'il existe. Il se pose
   au-dessus de la barre du bas et respecte l'encoche des iPhone. */
.encart-install {
  position: fixed;
  left: 12px;
  right: 12px;
  bottom: calc(12px + env(safe-area-inset-bottom, 0px));
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border-radius: var(--radius);
  background: var(--card);
  border: 1px solid var(--line);
  box-shadow: 0 18px 40px rgba(11, 23, 57, .22);
}
.encart-install img { border-radius: 12px; flex: 0 0 auto; }
.encart-install-texte { flex: 1 1 auto; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.encart-install-texte strong { font-size: var(--fs-sm); color: var(--ink); }
.encart-install-texte span { font-size: var(--fs-xs); color: var(--muted); }
.encart-install-texte .primary-btn { margin-top: 8px; align-self: flex-start; }
.encart-install-fermer {
  flex: 0 0 auto;
  align-self: flex-start;
  border: 0;
  background: none;
  cursor: pointer;
  font-size: 20px;
  line-height: 1;
  color: var(--muted);
  padding: 2px 4px;
}

/* ----- La fenêtre du premier matin -----
   Une image, une phrase, et de quoi avancer. Le dessin occupe la moitié haute
   parce que c'est lui qui fait comprendre avant qu'on ait lu ; les six points
   disent combien il en reste ; le pied ne porte que trois gestes. */
.bienvenue-voile {
  position: fixed;
  inset: 0;
  z-index: 95;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(11, 23, 57, .6);
  backdrop-filter: blur(3px);
  animation: bienvenue-voile-entre .2s ease-out;
}
@keyframes bienvenue-voile-entre { from { opacity: 0 } }
.bienvenue {
  width: 100%;
  max-width: 34rem;
  padding: 16px 16px 18px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  box-shadow: var(--shadow-lg);
  animation: bienvenue-entre .22s ease-out;
}
@keyframes bienvenue-entre { from { opacity: 0; transform: scale(.96) } }
/* La piste coupe ce qui dépasse : les six dessins sont côte à côte sur une
   seule ligne, et c'est elle qui glisse d'un cran quand on avance. */
.bienvenue-piste { overflow: hidden; border-radius: var(--small-radius); }
.bienvenue-vues { display: flex; transition: transform .35s ease; }
.bienvenue-vue { flex: 0 0 100%; margin: 0; }
.bienvenue-vue img { display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover; }
.bienvenue-points { display: flex; justify-content: center; gap: 6px; margin: 16px 0 4px; }
.bienvenue-point {
  width: 16px; height: 6px;
  padding: 0;
  border: 0; border-radius: var(--pill);
  background: var(--line);
  opacity: .6;
  cursor: pointer;
  transition: width .22s ease-out, opacity .22s ease-out, background .22s ease-out;
}
.bienvenue-point.ici { width: 24px; opacity: 1; background: var(--ink); }
/* Les six textes sont empilés au même endroit : celui de l'étape ouverte
   apparaît, les autres restent là sans se voir. Sans cet empilement, la
   fenêtre changerait de hauteur à chaque phrase et sauterait sous la souris. */
.bienvenue-textes { display: grid; margin-top: 12px; }
.bienvenue-texte {
  grid-area: 1 / 1;
  opacity: 0;
  visibility: hidden;
  transition: opacity .24s ease-out;
}
.bienvenue-texte.ici { opacity: 1; visibility: visible; }
.bienvenue-texte h2 { margin: 0; font-size: var(--fs-md); }
.bienvenue-texte p { margin: 6px 0 0; color: var(--muted); line-height: 1.6; font-size: var(--fs-sm); }
.bienvenue-pied { display: flex; align-items: center; gap: 10px; margin-top: 20px; }
/* Le compteur pousse « Passer » et « Suivant » à droite : le geste qui avance
   est toujours au même endroit, celui qui recule reste à gauche. */
.bienvenue-compte { flex: 1 1 auto; font-size: var(--fs-xs); color: var(--muted); text-align: center; }
.bienvenue-retour[hidden] { display: none; }
.mise-en-route .panel-header { align-items: flex-start; gap: 12px; }
@media (prefers-reduced-motion: reduce) {
  .bienvenue, .bienvenue-voile { animation: none; }
  .bienvenue-vues, .bienvenue-point, .bienvenue-texte { transition: none; }
}

/* ----- L'onglet Identité -----
   Cinq cartes, rangées par qui voit quoi : les deux images, l'école à l'écran,
   la papeterie imprimée, ce qui arrive chez les familles, et le sort de
   l'année finie. Chacune se replie : l'onglet faisait une seule page longue où
   l'œil devait retrouver seul où commençait la papeterie. */
.identite { display: flex; flex-direction: column; gap: 18px; }
/* L'onglet « L'école » pose ses propres cartes : le grand cadre commun de la
   page ferait un cadre dans un cadre. */
.panel-nu { padding: 0; border: 0; background: none; box-shadow: none; }
.carte-reglage {
  padding: 24px;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  background: var(--card);
  box-shadow: 0 10px 30px rgba(0, 0, 0, .03);
}
.carte-reglage-tete { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.carte-reglage-tete h3 { margin: 0; font-size: var(--fs-md); }
.carte-reglage-tete p { margin: 4px 0 0; max-width: 46rem; color: var(--muted); font-size: var(--fs-sm); line-height: 1.6; }
.carte-reglage-plier {
  display: grid;
  place-items: center;
  width: 34px; height: 34px;
  flex: 0 0 auto;
  border: 1.5px solid var(--line);
  border-radius: 10px;
  background: var(--card);
  color: var(--muted);
  cursor: pointer;
  transition: background var(--ease), color var(--ease);
}
.carte-reglage-plier:hover { background: var(--line-soft); color: var(--ink); }
.carte-reglage-plier i { transition: transform var(--ease); }
.carte-reglage.repliee .carte-reglage-plier i { transform: rotate(180deg); }
.carte-reglage.repliee .carte-reglage-corps { display: none; }
.carte-reglage-corps { display: flex; flex-direction: column; gap: 18px; margin-top: 18px; }
/* Le logo et le cachet se déposent côte à côte : ce sont les deux mêmes gestes. */
.ident-deux-images { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; align-items: start; }
.ident-remarque { margin: 0; color: var(--muted); font-size: var(--fs-sm); line-height: 1.6; font-weight: 400; }
/* Le dépôt d'image est ici au milieu de champs : sans fond propre il se
   confondrait avec la bande des faits, juste en dessous. */
.identite .logo-field { margin-bottom: 0; background: var(--card); }
/* ----- Ce que l'école ne décide pas -----
   Son code, son année, ses cycles : quatre faits posés avec l'abonnement. Ils
   étaient dessinés comme des champs de saisie grisés — donc comme des champs
   cassés. Ce sont des lignes qui se lisent, elles en ont maintenant la forme. */
.ident-pose { padding: 16px 18px; border: 1px solid var(--line); border-radius: var(--small-radius); background: var(--line-soft); }
.ident-pose h4 { margin: 0; font-size: var(--fs-sm); font-weight: 700; color: var(--ink); }
.ident-faits { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px 24px; margin: 14px 0; }
.ident-fait dt { margin: 0; color: var(--muted); font-size: var(--fs-xs); font-weight: 600; }
.ident-fait dd { margin: 2px 0 0; color: var(--ink); font-size: var(--fs-base); font-weight: 600; }

/* ----- L'atelier des documents -----
   La disposition est celle dessinée par Herin : un panneau à gauche où les
   choix se suivent une ligne à la fois, séparés par de simples traits, et
   la feuille seule à droite, en grand. Elle reste en place pendant qu'on
   descend dans les réglages : c'est elle qu'on regarde en essayant une
   couleur, pas la case où l'on tape. */
.doc-atelier { display: grid; grid-template-columns: 20rem minmax(0, 1fr); gap: 28px; align-items: start; }
.doc-panneau {
  display: flex; flex-direction: column; min-width: 0;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--card); box-shadow: var(--shadow-sm);
}
/* Le panneau porte son propre intitulé : à sa place, le titre de la page
   répétait « Paramètres » au-dessus d'un écran qui ne parle que d'une chose. */
.doc-panneau-titre {
  flex: 0 0 auto; margin: 0; padding: 18px 20px; font-size: var(--fs-base);
  border-bottom: 1px solid var(--line-soft);
}
/* Seuls les réglages défilent : le titre reste en haut, les deux boutons en bas. */
.doc-reglages { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; gap: 20px; padding: 18px 20px; }
.doc-bloc { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.doc-bloc h3 { margin: 0; font-size: var(--fs-xs); font-weight: 600; color: var(--muted); }
.doc-trait { width: 100%; margin: 2px 0; border: 0; border-top: 1px solid var(--line-soft); }
.doc-aide { margin: 0; font-size: var(--fs-xs); color: var(--muted); line-height: 1.5; }
.doc-lien {
  align-self: flex-start; min-height: 0; padding: 2px 0; border: 0; background: none;
  color: var(--teal); font: inherit; font-size: var(--fs-sm); font-weight: 600;
  cursor: pointer; text-decoration: underline;
}

/* Un modèle par ligne : la vignette dit la forme, le nom et la phrase disent
   le reste. À trois de front dans un panneau étroit, on ne lisait plus rien. */
.doc-modeles { display: flex; flex-direction: column; gap: 8px; }
.doc-modele {
  /* `position: relative` n'est pas decoratif : le bouton rond du choix est
     cache en position absolue dedans. Sans repere, il se cale sur la page
     entiere et l'elargit. */
  position: relative; display: flex; align-items: center; gap: 12px; padding: 10px 12px; cursor: pointer;
  border: 1.5px solid var(--line); border-radius: var(--small-radius); background: var(--line-soft);
  transition: border-color var(--ease), background var(--ease);
}
.doc-modele:hover { border-color: var(--teal); }
.doc-modele:has(input:checked) { border-color: var(--teal); background: var(--turquoise-pale); }
.doc-modele input { position: absolute; opacity: 0; pointer-events: none; }
.doc-modele-vignette {
  display: block; flex: 0 0 auto; width: 42px; height: 32px; padding: 5px; overflow: hidden;
  border: 1px solid var(--line); border-top: 3px solid var(--doc-primaire);
  border-radius: 5px; background: var(--blanc);
}
.doc-modele-vignette i { display: block; height: 4px; width: 55%; background: var(--papier-encre); margin-bottom: 4px; }
.doc-modele-vignette b { display: block; height: 2px; background: var(--papier-grise); margin-bottom: 3px; }
.doc-modele-vignette.classique { border: 1.5px solid var(--papier-encre); border-top: 3px solid var(--doc-primaire); }
.doc-modele-vignette.classique i { margin: 0 auto 4px; }
.doc-modele-vignette.compact { padding: 4px 5px; }
.doc-modele-vignette.compact b { height: 1.5px; margin-bottom: 2px; }
.doc-modele-mots { min-width: 0; }
.doc-modele-mots strong { display: block; font-size: var(--fs-sm); }
.doc-modele-mots span { display: block; margin-top: 1px; font-size: var(--fs-xs); color: var(--muted); line-height: 1.4; }

/* Le logo et le cachet se déposent l'un sous l'autre, sur une ligne chacun :
   l'image à gauche, ce qu'elle est et le bouton à droite. */
.doc-bloc .logo-field { margin-bottom: 0; gap: 14px; padding: 0; flex-wrap: nowrap; border: 0; background: none; }
.doc-bloc .logo-preview { width: 56px; height: 56px; border-radius: 10px; font-size: var(--fs-sm); }
/* Tant qu'aucune image n'est déposée, le cadre est en pointillé : il dit
   qu'il attend quelque chose, sans avoir à l'écrire. */
.doc-bloc .logo-field:not(:has(img)) .logo-preview { border-style: dashed; background: none; color: var(--muted); }
.doc-bloc .logo-copy { flex: 1 1 auto; min-width: 0; }
.doc-bloc .logo-copy strong { font-size: var(--fs-sm); }
.doc-bloc .logo-actions { margin-top: 8px; gap: 8px; }
/* Dans l'atelier, le bouton se resserre et perd sa forme de pilule : il est
   posé contre l'image, pas au milieu d'une barre d'actions. */
.doc-bloc .logo-actions .secondary-btn { min-height: 0; padding: 7px 14px; border-radius: var(--small-radius); }
/* « Retirer » ne se met pas sur le même pied qu'« Importer » : c'est le geste
   rare, et l'écran ne doit pas inviter à effacer le logo de l'école. */
.doc-bloc .logo-actions .retirer-image { border-color: transparent; background: none; color: var(--muted); }
.doc-bloc .logo-actions .retirer-image:hover { color: var(--ink); }

/* Fermé, une couleur tient sur une ligne : son intitulé, la teinte du
   moment, son code. Le nuancier s'ouvre à côté, au clic. */
.doc-couleurs { display: flex; flex-direction: column; gap: 10px; }
.doc-couleur-bouton {
  display: flex; align-items: center; gap: 10px; width: 100%; min-height: 0;
  padding: 8px 10px; cursor: pointer; font: inherit; text-align: left;
  border: 1.5px solid var(--line); border-radius: var(--small-radius);
  background: var(--line-soft); color: var(--ink);
}
.doc-couleur-bouton:hover, .doc-couleur-bouton[aria-expanded="true"] { border-color: var(--teal); }
.doc-couleur-mot { flex: 0 0 5rem; color: var(--muted); font-size: var(--fs-xs); }
.doc-couleur-temoin { width: 24px; height: 24px; flex: 0 0 auto; border: 1px solid rgba(0, 0, 0, .14); border-radius: 6px; }
.doc-couleur-bouton b { flex: 1 1 auto; font-size: var(--fs-sm); font-weight: 600; letter-spacing: .04em; }
.doc-couleur-bouton i { flex: 0 0 auto; color: var(--muted); font-size: 11px; transition: transform var(--ease); }
.doc-couleur-bouton[aria-expanded="true"] i { transform: rotate(180deg); }

/* La scène : rien que la feuille, aussi grande que l'écran le permet. */
.doc-scene { position: sticky; top: 16px; display: flex; flex-direction: column; align-items: center; gap: 12px; min-width: 0; }
.doc-scene-barre { display: flex; align-items: center; justify-content: space-between; gap: 12px; width: 100%; }
.doc-scene-titre { display: flex; align-items: center; gap: 8px; margin: 0; color: var(--muted); font-size: var(--fs-xs); }
/* La boîte ne grandit pas avec la feuille : passé la page entière, c'est elle
   qui défile, et la feuille se déplace à la souris. Les marges automatiques
   centrent la feuille tant qu'elle tient — un centrage par la grille couperait
   son bord haut dès qu'elle dépasse. */
.doc-scene-boite { display: flex; flex: 1 1 auto; min-width: 0; min-height: 0; width: 100%; overflow: auto; }
.doc-scene-boite .feuille-a4 { margin: auto; }
.doc-scene-boite.agrandi { cursor: grab; }
.doc-scene-boite.agrandi.tire { cursor: grabbing; }
/* La loupe, en haut à droite de la feuille. Le taux au milieu est un bouton :
   il ramène à la page entière, qui est le réglage de départ. */
.doc-loupe { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; padding: 2px; border: 1px solid var(--line); border-radius: var(--small-radius); background: var(--card); }
.doc-loupe-bouton, .doc-loupe-taux { border: 0; border-radius: 8px; background: none; color: var(--ink-soft); cursor: pointer; transition: background var(--ease), color var(--ease); }
.doc-loupe-bouton { width: 28px; height: 28px; font-size: 12px; }
.doc-loupe-taux { min-width: 56px; height: 28px; font-size: var(--fs-xs); font-weight: 600; font-variant-numeric: tabular-nums; }
.doc-loupe-bouton:hover:not(:disabled), .doc-loupe-taux:hover { background: var(--line-soft); color: var(--ink); }
.doc-loupe-bouton:disabled { opacity: .35; cursor: default; }
.doc-point { width: 6px; height: 6px; border-radius: 50%; background: var(--green); }
/* La barre « pas encore enregistré » est posée à la fin du formulaire : dans
   cette grille à deux colonnes, elle doit tenir toute la largeur. */
.doc-atelier .barre-enregistrer { grid-column: 1 / -1; }
/* Au bas du panneau, les deux boutons ne sont plus une alerte posée sur la
   page : ils tiennent dans la colonne, séparés du dernier réglage par un
   trait, et perdent leur forme de pilule pour rejoindre celle des cases
   qu'ils enregistrent. */
.doc-pied { flex: 0 0 auto; padding: 16px 20px; border-top: 1px solid var(--line-soft); }
.barre-enregistrer.nue {
  position: static; margin: 0; padding: 0; gap: 10px; flex-wrap: nowrap;
  border: 0; border-radius: 0; background: none; box-shadow: none; animation: none;
}
.barre-enregistrer.nue .secondary-btn,
.barre-enregistrer.nue .primary-btn { flex: 1 1 0; min-height: 0; padding: 11px 16px; border-radius: var(--small-radius); }
.barre-enregistrer.nue .primary-btn { box-shadow: none; }

/* Dans l'atelier plein écran, le panneau se colle au bord gauche du contenu et
   monte jusqu'en haut : il n'a plus de cadre flottant, seulement un trait qui
   le sépare de la feuille. Celle-ci, elle, ne bouge plus jamais. */
/* La bande du haut s'efface aussi : elle répétait « Paramètres » au-dessus
   d'un écran qui ne parle que d'une chose, et la feuille a besoin de la
   hauteur. On la retrouve en sortant de l'atelier. */
.atelier-documents > .topbar { display: none; }
.atelier-documents > .main-content { padding: 0; display: flex; }
.atelier-documents .page-head, .atelier-documents .settings-tabs { display: none; }
.atelier-documents #settingsPanel,
.atelier-documents #settingsArea { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.atelier-documents #settingsPanel { margin: 0; }
.atelier-documents .doc-atelier { flex: 1 1 auto; min-height: 0; }
.atelier-documents .doc-atelier { gap: 0; align-items: stretch; grid-template-rows: minmax(0, 1fr); }
.atelier-documents .doc-panneau {
  height: 100%; min-height: 0; border: 0; border-right: 1px solid var(--line);
  border-radius: 0; box-shadow: none;
}
.atelier-documents .doc-reglages { overflow-y: auto; }
.atelier-documents .doc-scene { position: static; height: 100%; justify-content: center; padding: 16px; }

/* ----- Le nuancier -----
   Il flotte à côté du réglage ouvert, jamais dedans : posé dans le panneau,
   il repousserait tout ce qui suit à chaque ouverture. */
.nuancier {
  position: fixed; z-index: 60; width: 276px;
  display: flex; flex-direction: column; gap: 12px;
  padding: 16px; border: 1px solid var(--line); border-radius: var(--small-radius);
  background: var(--card); box-shadow: var(--shadow-lg);
}
.nuancier[hidden] { display: none; }
.nuancier-titre { margin: 0; font-size: var(--fs-xs); font-weight: 600; color: var(--muted); }
.nuancier-carre {
  position: relative; width: 100%; height: 150px; border-radius: 8px; cursor: crosshair;
  border: 1px solid var(--line); touch-action: none;
  background-image: linear-gradient(to top, black, rgba(0, 0, 0, 0)), linear-gradient(to right, white, rgba(255, 255, 255, 0));
}
.nuancier-viseur {
  position: absolute; width: 14px; height: 14px; border-radius: 50%; border: 2px solid white;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .25); transform: translate(-50%, -50%); pointer-events: none;
}
.nuancier-bande {
  position: relative; height: 12px; border-radius: 6px; cursor: pointer; touch-action: none;
  background: linear-gradient(to right, hsl(0 100% 50%), hsl(60 100% 50%), hsl(120 100% 50%), hsl(180 100% 50%), hsl(240 100% 50%), hsl(300 100% 50%), hsl(360 100% 50%));
}
.nuancier-poignee {
  position: absolute; top: 50%; width: 14px; height: 14px; border-radius: 50%; background: white;
  border: 1.5px solid var(--line); box-shadow: 0 1px 3px rgba(0, 0, 0, .25);
  transform: translate(-50%, -50%); pointer-events: none;
}
.nuancier-ligne { display: flex; align-items: center; gap: 8px; }
.nuancier-pipette {
  width: 34px; height: 34px; min-height: 0; flex: 0 0 auto; padding: 0; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); border-radius: 8px; background: var(--line-soft); color: var(--muted);
}
.nuancier-pipette:hover { border-color: var(--teal); color: var(--teal); }
.nuancier-hex {
  display: flex; align-items: center; gap: 6px; flex: 1 1 auto; min-width: 0; height: 34px;
  padding: 0 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--line-soft);
}
.nuancier-hex:focus-within { border-color: var(--teal); }
.nuancier-hex input {
  width: 100%; min-width: 0; padding: 0; border: 0; background: none; color: var(--ink);
  font: inherit; font-size: var(--fs-sm); font-weight: 600; letter-spacing: .04em;
}
.nuancier-hex input:focus { outline: none; }
.nuancier-hex b { flex: 0 0 auto; font-size: 11px; color: var(--muted); }
.nuancier-aide { margin: 0; font-size: var(--fs-xs); color: var(--muted); }
.nuancier-pastilles { display: flex; flex-wrap: wrap; gap: 8px; }
.pastille {
  width: 26px; height: 26px; min-height: 0; padding: 0; flex: 0 0 auto;
  border: 1px solid rgba(0, 0, 0, .14); border-radius: 50%; cursor: pointer;
}
.pastille[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--card), 0 0 0 4px var(--ink); }
.pastille:focus-visible { outline: 2px solid var(--teal); outline-offset: 3px; }

/* Un opérateur par ligne : le numéro et le nom du compte vont ensemble, et
   trois paires mélangées dans une grille se lisaient de travers. */
.ident-operateur { display: grid; grid-template-columns: 8rem minmax(0, 1fr); gap: 16px; align-items: center; }
.ident-operateur-nom { font-size: var(--fs-sm); font-weight: 700; color: var(--ink); }

/* ----- « Ce n'est pas encore enregistré » -----
   Elle colle au bas de l'écran tant qu'il reste quelque chose à enregistrer, et
   disparaît dès qu'il n'y a plus rien. Le liseré ambre à sa gauche dit l'état
   sans mot : c'est la seule chose de l'écran qui porte cette couleur.
   Elle est dans le flux, à la fin du formulaire : elle ne recouvre donc jamais
   le dernier champ, elle remonte seulement quand on ne le voit plus. */
.barre-enregistrer {
  position: sticky;
  bottom: 16px;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin-top: 24px;
  padding: 14px 18px;
  border: 1px solid var(--line);
  border-left: 4px solid var(--amber);
  border-radius: var(--small-radius);
  background: var(--card);
  box-shadow: var(--shadow-lg);
  animation: barre-entre .2s ease-out;
}
@keyframes barre-entre { from { opacity: 0; transform: translateY(8px) } }
.barre-mot { flex: 1 1 14rem; min-width: 0; }
.barre-mot strong { display: block; font-size: var(--fs-base); }
.barre-detail { display: block; margin-top: 2px; color: var(--muted); font-size: var(--fs-sm); line-height: 1.5; }
@media (prefers-reduced-motion: reduce) { .barre-enregistrer { animation: none; } }

/* Ce que « laissé vide » veut dire, écrit sous la ligne plutôt qu'en faux
   numéro dans la case : un exemple gris se lit comme une vraie donnée. */
.ident-vide { margin: 6px 0 0; color: var(--muted); font-size: var(--fs-xs); line-height: 1.5; }
.voir-modele { margin-top: 14px; }
/* La feuille d'exemple est montrée, pas imprimée : la fenêtre la laisse
   déborder plutôt que de la comprimer à une largeur qui n'est celle d'aucun
   papier. */
#modalBody .sheet-bulletin { margin-top: 12px; }

/* ----- Le reçu de paiement -----
   Le seul document que la famille garde des années, et souvent le seul qu'elle
   ait de l'école. Il est donc dessiné pour la photocopie : pas de bandeau de
   couleur, pas de logo, du noir sur du blanc. La couleur de l'école ne tient
   que deux places — le mot « Reçu » et le filet du total — pour qu'on
   reconnaisse l'établissement sans rien perdre à la photocopieuse.
   Les tailles sont en pixels et non à l'échelle de l'écran : une feuille A4
   fait 794 pixels de large, et ce sont les tailles du papier qu'on pose ici. */
.sheet.recu { border: 1px solid var(--line-soft); border-top: 1px solid var(--line-soft); border-radius: 0; padding: 34px 40px 30px; }
/* Le reçu entier est dans un tableau d'une seule case. C'est le seul moyen
   qu'ait une page web de répéter un bandeau en haut de chaque feuille : le
   navigateur réimprime l'en-tête d'un tableau à chaque page. Le rappel sert
   quand un reçu de dix mois demande deux feuilles — séparées, chacune dit
   encore de quelle école, de quel reçu et de quel élève elle vient. */
.recu-feuille { width: 100%; border-collapse: collapse; }
.recu-feuille > thead > tr > td, .recu-feuille > tbody > tr > td { padding: 0; }
.recu-rappel { display: flex; flex-wrap: wrap; gap: 4px 18px; margin-bottom: 20px; padding-bottom: 9px; border-bottom: 1px solid var(--papier-fond); font-size: 8.5px; text-transform: uppercase; letter-spacing: .06em; color: var(--papier-mention); }
.recu-rappel strong { font-weight: 700; color: var(--papier-encre); }
.recu-annule { margin: 0 0 18px; padding: 10px 14px; border-left: 4px solid var(--papier-alerte); background: var(--papier-alerte-fond); color: var(--papier-alerte); font-weight: 700; font-size: var(--fs-sm); }
.recu-tete { display: flex; align-items: flex-start; justify-content: space-between; gap: 24px; }
.recu-titre { margin: 0; font-size: 30px; line-height: 1; text-transform: uppercase; letter-spacing: .01em; color: var(--doc-primaire); }
.recu-ecole { display: flex; flex-direction: column; gap: 4px; text-align: right; min-width: 0; }
.recu-ecole strong { font-size: 16px; }
.recu-ecole em { font-size: 8px; font-style: normal; text-transform: uppercase; letter-spacing: .06em; color: var(--papier-mention); }
/* Le nom sur la ligne « Remis à » est celui de l'élève, pas celui du payeur :
   c'est l'élève dont les mois sont réglés, et c'est sur son dossier qu'on
   viendra vérifier. Le responsable est nommé juste dessous. */
.recu-parties { display: flex; justify-content: space-between; gap: 30px; margin-top: 34px; }
.recu-parties > div { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.recu-adresse { text-align: right; }
.recu-parties span { font-size: 10px; color: var(--papier-texte); overflow-wrap: anywhere; }
.recu-etiquette { display: block; margin-bottom: 8px; font-size: 8.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; color: var(--papier-mention); }
.recu-nom { font-size: 14px; margin-bottom: 3px; }
.recu-bandeau { display: flex; align-items: flex-end; justify-content: space-between; gap: 30px; margin-top: 58px; }
.recu-objet { margin: 0; font-size: 13px; color: var(--papier-texte); }
.recu-objet strong { color: var(--papier-encre); }
.recu-refs { display: flex; flex-direction: column; gap: 6px; flex: 0 0 auto; min-width: 200px; }
.recu-info { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
.recu-info span { font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; }
.recu-refs .recu-info span { color: var(--papier-encre); }
.recu-refs .recu-info strong { font-size: 10px; font-weight: 400; color: var(--papier-texte); }
.recu-recap .recu-info span { color: var(--papier-mention); }
.recu-recap .recu-info strong { font-size: 10.5px; color: var(--papier-encre); }
/* Un mois par ligne : la famille lit octobre et novembre nommés, et sait donc
   aussi ce que ce papier ne couvre pas. Une tranche « octobre à novembre »
   laissait la question ouverte au moment où elle compte. */
.recu-table { width: 100%; border-collapse: collapse; margin-top: 18px; }
.recu-table th { padding: 10px; background: var(--papier-fond); font-size: 8.5px; font-weight: 700; text-align: left; text-transform: uppercase; letter-spacing: .06em; color: var(--papier-encre); }
.recu-table td { padding: 13px 10px; border-bottom: 1px solid var(--papier-fond); font-size: 11px; color: var(--papier-texte); vertical-align: top; }
.recu-table th:nth-child(n+3), .recu-table td:nth-child(n+3) { text-align: right; }
.recu-table td:first-child { width: 42px; color: var(--papier-mention); }
.recu-table td strong { color: var(--papier-encre); }
.recu-table td span { display: block; margin-top: 3px; font-size: 9px; color: var(--papier-mention); }
.recu-bilan { display: flex; align-items: flex-start; justify-content: space-between; gap: 34px; margin-top: 28px; }
.recu-somme { display: inline-block; margin-bottom: 11px; padding: 10px 18px; background: var(--papier-fond-somme); font-size: 25px; font-weight: 700; color: var(--papier-encre); }
.recu-lettres { display: block; font-size: 10px; color: var(--papier-mention); }
.recu-recap { display: flex; flex-direction: column; gap: 9px; flex: 0 1 320px; }
/* Le total est écrit deux fois : dans le pavé gris, et ici en couleur sous le
   filet. Ce n'est pas une répétition — l'œil qui suit la colonne de droite
   n'est jamais passé par le pavé. */
.recu-info-total { margin-top: 6px; padding-top: 11px; border-top: 1px solid var(--doc-secondaire); }
.recu-info-total span { color: var(--doc-secondaire); }
.recu-info-total strong { font-size: 13px; color: var(--doc-secondaire); }
.recu-signature { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; margin-top: 46px; text-align: right; }
.recu-signature .recu-etiquette { margin-bottom: 2px; }
/* Le cercle en pointillés n'est pas un décor : il dit où poser le cachet aux
   écoles qui n'ont pas encore déposé le leur. */
.recu-signature .sheet-stamp { display: grid; place-items: center; width: 92px; height: 92px; border: 1px dashed var(--papier-trait); border-radius: 50%; }
/* Un tampon déposé n'est pas toujours rond : le repère s'efface et l'image
   s'affiche entière, sans rien perdre de ses bords. */
.recu-signature .sheet-stamp:has(img) { width: auto; height: auto; max-width: 100%; border: 0; border-radius: 0; }
.recu-signature .sheet-stamp img { max-width: 170px; max-height: 92px; object-fit: contain; }
.recu-signature strong { font-size: 11.5px; }
.recu-pied { display: grid; grid-template-columns: repeat(3, 1fr); gap: 26px; margin-top: 46px; padding: 22px 24px; background: var(--papier-fond); font-size: 10px; color: var(--papier-texte); }
.recu-pied strong { display: block; margin-bottom: 7px; color: var(--papier-encre); }
.recu-pied span { display: block; overflow-wrap: anywhere; }

/* =========================== 7. RESPONSIVE ===============================
   Ces règles closent le fichier : rien ne doit les surcharger ensuite.
   -------------------------------------------------------------------- */

/* Écrans intermédiaires : marges resserrées (la barre latérale garde ses 280px). */
@media (max-width: 1100px) {
  /* Les cartes se resserrent, et un montant à huit chiffres ne tient plus en
     28 pixels. Puisqu'un nombre ne se coupe jamais, c'est la taille qui cède. */
  .stat-value { font-size: var(--fs-lg); }
  /* Les cellules se resserrent aussi : sur un 1024, ces quelques pixels par
     colonne suffisent à faire rentrer la plupart des tableaux en entier,
     boutons compris, sans rien avoir à faire glisser. */
  .data-table th, .data-table td { padding: 10px 10px; }
  .topbar { padding: 12px 20px; }
  .main-content { padding: 22px 20px; }
  /* La colonne de droite se resserre avant la vedette : c'est elle qui porte le
     dessin, et un graphique rétréci ne se lit plus. */
  .dash-scene { grid-template-columns: minmax(0, 1fr) minmax(15rem, 19rem); }

  /* Sous cette largeur, les réglages et la feuille ne tiennent plus côte à
     côte : la feuille passe au-dessous et cesse de suivre le défilement. */
  .doc-atelier { grid-template-columns: minmax(0, 1fr); }
  .doc-scene { position: static; }
  .doc-reglages { padding: 18px 16px; }
  /* Sur un écran étroit, la feuille passe dessous : la page redevient une page
     qui défile, sinon les réglages seraient enfermés dans une lucarne. */
  .app-shell.ecran-fixe.atelier-documents { height: auto; overflow: visible; }
  .app-shell.ecran-fixe.atelier-documents > .main-content { overflow: visible; padding: 0 50px 40px; }
  .atelier-documents > .main-content { display: block; }
  .atelier-documents .doc-panneau { height: auto; border: 1px solid var(--line); border-radius: var(--radius); }
  .atelier-documents .doc-scene { height: auto; padding: 0; }
}

/* Tablette et mobile : la barre latérale devient un tiroir,
   la navigation principale passe en bas de l'écran. */
/* Tableau de bord de la direction, écrans moyens : les trois cartes n'ont plus la place
   de mettre l'icône à côté du texte, elle passe au-dessus, et le panneau de droite s'amincit. */
@media (min-width: 901px) and (max-width: 1280px) {
  .dir-cadre { grid-template-columns: minmax(0, 1fr) 300px; gap: 18px; }
  .dir-chiffres { gap: 14px; }
  .kq { flex-direction: column-reverse; align-items: flex-start; justify-content: flex-end; gap: 8px; }
}

@media (max-width: 900px) {
  .app-shell { padding-left: 0; padding-bottom: var(--mobilenav-h); }

  /* Les explications qui s'ouvrent au survol du petit point d'interrogation
     attendent, invisibles, à côté de leur icône. Invisibles mais mesurées :
     sur les Paramètres, elles poussaient la page 116 px plus loin que l'écran
     et le téléphone glissait de côté au moindre doigt. On arrête la page au
     bord de l'écran ; les bulles, elles, se replacent d'elles-mêmes vers
     l'intérieur au moment où on les ouvre (voir placeInfobulle). */
  .main-content { overflow-x: clip; }

  .mobile-only { display: inline-flex; }

  .sidebar {
    width: min(300px, 86vw);
    transform: translateX(-100%);
    box-shadow: var(--shadow-lg);
  }
  .sidebar.open { transform: none; }

  .mobile-nav {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 32;
    display: flex;
    align-items: center;
    justify-content: space-between;
    height: calc(74px + env(safe-area-inset-bottom));
    padding: 0 24px env(safe-area-inset-bottom);
    background: var(--card);
    border-radius: 28px 28px 0 0;
    box-shadow: 0 -8px 24px rgba(0, 0, 0, .08);
  }
  /* Barre du bas de Kooly.html : des ronds de 40 px sans texte, le bouton actif
     grossit, passe en turquoise et monte hors de la barre, dans une encoche. */
  .mobile-link { flex: 0 0 auto; width: 40px; height: 40px; min-height: 0; padding: 0; border-radius: 50%; position: relative; z-index: 2; transition: transform .35s cubic-bezier(.34,1.56,.64,1), background-color .35s, color .35s; }
  .mobile-link > b { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .mobile-link > span { font-size: 16px; }
  .mobile-link.active { width: 56px; height: 56px; background: var(--teal); color: var(--blanc); transform: translateY(-21px); animation: barreBasPop .45s cubic-bezier(.34,1.56,.64,1); }
  .mobile-link.active > span { font-size: 20px; }
  /* Feuille du Menu ouverte : un seul bouton relevé à la fois. */
  .feuille-ouverte .mobile-link.active { width: 40px; height: 40px; background: transparent; color: var(--muted); transform: none; animation: none; }
  .feuille-ouverte .mobile-link.active > span { font-size: 16px; }
  .mobile-menu { position: relative; z-index: 2; width: 40px; height: 40px; margin: 0; background: transparent; color: var(--muted); }
  .mobile-menu.actif { width: 56px; height: 56px; background: var(--teal); color: var(--blanc); box-shadow: none; transform: translateY(-21px); }
  .encoche { position: absolute; top: 0; left: 0; z-index: 1; width: 136px; height: 54px; transform: translateX(-50%); pointer-events: none; transition: left .35s cubic-bezier(.34,1.56,.64,1); }
  /* Le Menu défile chez lui : la page derrière ne doit pas avoir son propre ascenseur. */
  html:has(body.feuille-ouverte), body.feuille-ouverte { overflow: hidden; }
  .encoche svg { display: block; width: 100%; height: 100%; fill: var(--bg); }
  @keyframes barreBasPop { 0% { transform: translateY(0) scale(.8); } 55% { transform: translateY(-27px) scale(1.1); } 100% { transform: translateY(-21px) scale(1); } }

  .login-screen { grid-template-columns: 1fr; }
  .login-showcase { display: none; }
  .login-panel { padding: 24px 20px; }
  .mobile-brand { display: flex; }

  /* Ici la barre latérale est passée en bas : on est sur une tablette ou un
     téléphone. L'écran sans ascenseur est fait pour un ordinateur : ici il ne
     resterait pas de quoi dessiner quoi que ce soit, donc la page redéfile
     comme les autres. */
  .app-shell.ecran-fixe { display: block; height: auto; overflow: visible; }
  .app-shell.ecran-fixe > .main-content { overflow: visible; }
  /* La vedette et sa colonne côte à côte laisseraient deux bandes
     trop étroites pour un dessin : les trois cadres se remettent l'un sous
     l'autre, et le tableau de bord se lit en descendant comme le reste. */
  .dash-page { height: auto; min-height: 0; }
  .dash-scene, .dash-cote, .dash-cartes { display: flex; flex-direction: column; }
  .dash-scene > * { min-height: 0; }
  .dash-page .panel.tuile { min-height: 14rem; }
  .dir-page { height: auto; }
  .dir-cadre { display: flex; flex-direction: column; }
  .dir-gauche { overflow: visible; }
  .dir-argent { flex: 0 0 auto; }
  .courbe-zone { flex: 0 0 auto; height: 220px; }
  .dir-chiffres { grid-template-columns: 1fr; }
}

/* La recherche globale et le profil détaillé n'ont plus la place. */
@media (max-width: 640px) {

  /* L'emploi du temps sur un téléphone, comme dans Kooly.html : plus de grille
     horaire, chaque jour devient une liste de cartes les unes sous les autres. */
  .timetable-scroll { overflow-x: visible; }
  .timetable-grid { display: block; min-width: 0; }
  .timetable-hours-col, .timetable-lunch-band { display: none; }
  .timetable-day-col { width: 100%; min-width: 0; border-right: none; border-bottom: 1px solid var(--line-soft); }
  .timetable-day-col:last-child { border-bottom: none; }
  .timetable-day-body { height: auto !important; background-image: none; padding: 12px; display: flex; flex-direction: column; gap: 10px; }
  .cours-card { position: relative !important; left: auto !important; right: auto !important; top: auto !important; height: auto !important; width: auto !important; }
  .cours-card.compact { padding: 10px 12px; }
  .cours-card.compact .cours-titre { margin-bottom: 2px; }
  .cours-card.compact .cours-horaire { margin-bottom: 6px; }
  .cours-card.compact .cours-prof { display: flex; }

  /* Sur un téléphone, le reçu se lit avant de s'imprimer : les deux colonnes
     s'empilent et tout revient à gauche. Le papier, lui, ne change pas. */
  .sheet.recu { padding: 18px 16px; }
  .recu-tete, .recu-parties, .recu-bandeau, .recu-bilan { flex-direction: column; align-items: flex-start; gap: 16px; }
  .recu-ecole, .recu-adresse, .recu-signature { text-align: left; align-items: flex-start; }
  .recu-parties, .recu-bandeau { margin-top: 22px; }
  .recu-recap { flex: 1 1 auto; align-self: stretch; }
  .recu-pied { grid-template-columns: 1fr; gap: 16px; margin-top: 28px; }
  /* Au pouce le tableau garde ses cinq colonnes : c’est le détail des mois,
     et le retirer rendrait la lecture téléphone différente de la lecture
     papier. On resserre, et les sommes ne se coupent jamais en deux. */
  .recu-table th, .recu-table td { padding: 9px 5px; }
  .recu-table td { font-size: 10px; }
  .recu-table td:nth-child(n+3) { white-space: nowrap; }
  .recu-table td:first-child { width: 26px; }

  /* La barre de menu du bas est fixe : sans ce décalage, « Enregistrer »
     passerait dessous et deviendrait impossible à cliquer. */
  .barre-enregistrer { bottom: calc(var(--mobilenav-h) + 8px); padding: 12px 14px; }
  .barre-enregistrer .secondary-btn,
  .barre-enregistrer .primary-btn { flex: 1 1 8rem; justify-content: center; }

  /* Sur un téléphone, tout passe sur une colonne : l'aperçu se met au-dessus
     des champs qu'il montre, et le nom de l'opérateur au-dessus de ses cases. */
  .ident-faits { grid-template-columns: 1fr; }
  .ident-operateur { grid-template-columns: 1fr; gap: 8px; }

  /* Sur un téléphone la fenêtre prend presque tout l'écran, et le pied passe
     sur deux lignes : trois boutons côte à côte y deviendraient illisibles. */
  .bienvenue-voile { padding: 12px; }
  .bienvenue-pied { flex-wrap: wrap; }
  .bienvenue-compte { order: -1; flex: 1 0 100%; text-align: left; }
  .bienvenue-suivant { margin-left: auto; }

  :root { --fs-xl: 1.5rem; }

  /* Sur un téléphone, deux boutons à droite réduisent l'énoncé du devoir à une
     colonne de trois mots. Ils passent donc sous la ligne, en pleine largeur. */
  .list-item { flex-wrap: wrap; }

  .topbar { padding: 10px 16px; }
  .topbar h1 { font-size: var(--fs-lg); }
  .main-content { padding: 18px 16px; }

  /* La case de recherche ne tient plus dans la barre : elle était simplement
     masquée, et une secrétaire sur téléphone n'avait plus aucun moyen de
     retrouver un élève dans une liste de cent vingt. La loupe reste ; on la
     touche, et la case s'ouvre à la place du titre, sur toute la barre.
     La photo, elle, part vraiment : le téléphone a son Menu. */
  .topbar-actions .side-user-wrap { display: none; }
  .recherche-mobile { display: inline-flex; }
  .global-search { display: none; }
  .topbar.cherche .topbar-left { display: none; }
  .topbar.cherche .topbar-actions { flex: 1; min-width: 0; }
  .topbar.cherche .global-search { display: flex; flex: 1; min-width: 0; max-width: none; }
  .topbar.cherche .global-search input { width: 100%; min-width: 0; }

  .page-head { align-items: flex-start; gap: 14px; }
  .page-actions { width: 100%; }
  .page-actions .primary-btn, .page-actions .secondary-btn { flex: 1; }

  .form-grid { grid-template-columns: 1fr; }
  /* La case vide n'a plus de rôle sur une seule colonne : elle y ferait un trou. */
  .form-grid .cale { display: none; }
  .form-actions .primary-btn, .form-actions .secondary-btn { flex: 1; }

  .filter-control { flex: 1 1 11rem; min-width: 0; }
  /* Au pouce, c'est le champ étiqueté qui s'étire, pas le contrôle : dans une colonne,
     `flex` jouerait sur la hauteur et étirerait la liste au lieu de sa largeur. */
  .filter-field { flex: 1 1 11rem; min-width: 0; }
  .filter-field .filter-control { flex: 0 0 auto; width: 100%; }

  .panel { padding: 18px; border-radius: var(--small-radius); }
  .stat-card { padding: 16px; }
  /* Le ruban de chiffres (`statsRow(..., true)`). Le chiffre qu'on vient vérifier
     — ce qui est entré en caisse — garde sa carte et sa taille ; les autres se
     rangent à deux par ligne, et la liste des reçus commence dans le premier
     écran. Aucun montant n'est raccourci : « 223,6 M » ne se vérifie pas.
     La classe est doublée (`.stats-grid.ruban-tel`) pour passer devant la règle
     à une colonne des petits téléphones, plus bas dans la feuille. */
  .stats-grid.ruban-tel { grid-template-columns: 1fr 1fr; gap: 10px; }
  .stats-grid.ruban-tel > :first-child { grid-column: 1 / -1; }
  .stats-grid.ruban-tel > :not(:first-child) { padding: 12px 14px; }
  .stats-grid.ruban-tel > :not(:first-child) .stat-icon,
  .stats-grid.ruban-tel > :not(:first-child) .stat-note { display: none; }
  .stats-grid.ruban-tel > :not(:first-child) .stat-value { font-size: var(--fs-lg); }

  /* Tableau de bord au pouce : les chiffres passent en deux colonnes et la
     pastille d'icône rétrécit — à cette largeur, c'est le nombre qu'on lit. Les
     filets de séparation tombent : sur deux colonnes ils ne sépareraient plus des
     colonnes mais couperaient des lignes au hasard. */
  .dash-bande { grid-template-columns: 1fr 1fr; gap: 8px 4px; padding-bottom: 8px; }
  .dash-chiffre, .dash-bande > .dash-chiffre:first-child { gap: 10px; padding: 6px; border-left: 0; }
  .chiffre-ico { width: 34px; height: 34px; border-radius: 11px; font-size: var(--fs-sm); }
  .chiffre-titre { letter-spacing: .05em; }
  .timeline-time { flex-basis: 3.75rem; }
  .feed-item { padding: 12px 14px; }

  /* ---- Tableaux en cartes ----
     Un tableau de 8 colonnes ne rentrera jamais dans 375px : le faire défiler
     latéralement cache la moitié de l'information et casse le défilement
     vertical de la page. On sort donc de la grille tabulaire — chaque ligne
     devient une carte, chaque cellule une paire intitulé / valeur. L'intitulé
     vient de data-label, posé par table() dans app.js.
     Rien n'est tronqué, rien ne déborde, et la lecture se fait au pouce. */
  .table-wrap {
    overflow: visible;
    border: 0;
    border-radius: 0;
    background: transparent;
  }
  .data-table {
    display: block;
    min-width: 0;
    font-size: var(--fs-base);
  }
  /* En-têtes retirés de l'affichage mais laissés aux lecteurs d'écran. */
  .data-table thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .data-table tbody { display: flex; flex-direction: column; gap: 10px; }
  .tri-au-pouce { display: flex; }
  .table-pied { justify-content: flex-start; }
  /* Sur une largeur de pouce, « 1–30 sur 262 » prend sa propre ligne : collé au
     tri, on lisait les deux comme une seule phrase. */
  .pied-compte { flex-basis: 100%; }
  .pied-pages { margin: 0 auto; }
  /* Sur téléphone, un numéro de page se vise au doigt : même allure, surface plus grande. */
  .page-btn { min-width: var(--tap); height: var(--tap); }
  .pied-choix select { height: var(--tap); }
  .data-table tr {
    display: block;
    padding: 2px 16px 12px;
    border: 1px solid var(--line);
    border-radius: var(--small-radius);
    background: var(--card);
    box-shadow: var(--shadow-sm);
  }
  .data-table tbody tr:hover { background: var(--card); }

  .data-table td {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
    padding: 9px 0;
    border: 0;
    border-top: 1px solid var(--line-soft);
    text-align: right;
  }
  .data-table td::before {
    content: attr(data-label);
    flex: 0 0 auto;
    max-width: 45%;
    color: var(--muted);
    font-size: var(--fs-sm);
    font-weight: 600;
    text-align: left;
  }

  /* La première colonne titre la carte : pas d'intitulé, pleine largeur. */
  .data-table td:first-child {
    display: block;
    padding: 14px 0 10px;
    border-top: 0;
    font-size: var(--fs-md);
    font-weight: 700;
    text-align: left;
  }
  .data-table td:first-child::before { display: none; }
  .data-table td:first-child .muted { font-size: var(--fs-sm); font-weight: 400; }

  /* Ligne d'état vide : c'est un message, pas une paire intitulé / valeur. */
  .data-table td[colspan] { display: block; padding: 0; text-align: center; }
  .data-table td[colspan]::before { display: none; }
  .data-table tr:has(td[colspan]) { padding: 0; }

  /* Boutons d'action : alignés à droite sous leur intitulé, jamais coupés. */
  .data-table td .table-actions { justify-content: flex-end; }
  .data-table td { flex-wrap: wrap; }
  .data-table .table-button { min-height: var(--tap); }

  /* Les cellules de saisie (notes) occupent la largeur restante. */
  .data-table td .grade-input, .data-table td .comment-input {
    flex: 1 1 auto;
    width: auto;
    min-width: 0;
    max-width: 60%;
    text-align: left;
  }


  /* Modale en feuille glissante, plus confortable au pouce. */
  .modal-backdrop { padding: 0; align-items: flex-end; }
  .modal, .modal.large {
    max-width: none;
    max-height: 92dvh;
    border-radius: var(--radius) var(--radius) 0 0;
  }
  .modal > header { padding: 20px 20px 16px; }
  .modal-body { padding: 0 20px 24px; }

  .toast-stack {
    right: 12px;
    left: 12px;
    max-width: none;
  }
}

/* Petits téléphones. */
@media (max-width: 480px) {
  .login-panel { padding: 20px 16px; }
  .login-card { padding: 24px 20px; background: transparent; box-shadow: none; }
  .demo-roles { grid-template-columns: 1fr 1fr; }

  .stats-grid { grid-template-columns: 1fr; gap: 12px; }
  .add-project-card { min-height: 60px; height: 60px; }
  .quick-grid, .module-grid { grid-template-columns: 1fr; }

  .topbar h1 { font-size: var(--fs-md); }
  /* Les tableaux sont déjà en cartes ici : on resserre la carte, pas la cellule
     (remettre un padding horizontal la décalerait de son bord). */
  .data-table tr { padding: 2px 14px 12px; }

  /* Au pouce, les trois boutons prennent toute la largeur sous le nom et se
     partagent la ligne à parts égales : on vise sans regarder. */
  .att-choix { width: 100%; gap: 6px; }
  .att-bouton { flex: 1; min-height: var(--tap); padding: 0 6px; }
}

/* ---- Liste des élèves au pouce (modèle des autres listes) ----
   Une carte = une porte d'entrée : toute la carte ouvre la fiche, le menu ⋮ ne
   garde que les gestes secondaires. Deux lignes suffisent — le nom, puis la
   classe et le matricule ; le responsable se lit dans la fiche. Le tri passe
   au-dessus de la liste et « Afficher la suite » remplace les numéros de page. */
@media (max-width: 640px) {
  .liste-eleves .data-table tbody { gap: 8px; }
  .liste-eleves .data-table tr.ligne-ouvrable { display: flex; flex-wrap: wrap; align-items: center; padding: 0 4px 0 14px; min-height: 64px; cursor: pointer; transition: background 0.15s; }
  .liste-eleves .data-table tr.ligne-ouvrable:active { background: color-mix(in srgb, var(--teal) 8%, var(--card)); }
  .liste-eleves .data-table td { display: block; padding: 0; border: 0; background: none; text-align: left; font-size: var(--fs-sm); font-weight: 400; color: var(--muted); }
  .liste-eleves .data-table td::before { display: none; }
  .liste-eleves .data-table td:nth-child(1) { order: 0; flex: 1 1 calc(100% - 64px); min-width: 0; padding: 10px 0 0; }
  .liste-eleves .data-table td:nth-child(1) strong { font-size: var(--fs-md); font-weight: 600; color: var(--ink); white-space: normal; }
  .liste-eleves .student-cell { gap: 10px; }
  .liste-eleves .student-cell .avatar { width: 36px; height: 36px; font-size: 13px; }
  .liste-eleves .data-table td:last-child { order: 1; flex: 0 0 56px; align-self: stretch; margin: 0; padding: 0; }
  .liste-eleves .data-table td:last-child .task-more { display: flex; justify-content: center; height: 100%; }
  .liste-eleves .data-table td:last-child .dropdown-toggle-btn:not(.menu-choix-bouton) { width: 56px; height: 100%; min-height: 64px; padding: 0; border-radius: 0 var(--radius-md, 14px) var(--radius-md, 14px) 0; font-size: 22px; }
  .liste-eleves .data-table td:last-child .dropdown-toggle-btn:not(.menu-choix-bouton):active { background: color-mix(in srgb, var(--teal) 12%, var(--card)); }
  .liste-eleves .data-table td:nth-child(3) { order: 2; padding: 0 0 12px 46px; color: var(--ink); font-weight: 500; }
  .liste-eleves .data-table td:nth-child(2) { order: 3; padding: 0 0 12px; }
  .liste-eleves .data-table td:nth-child(2)::before { display: inline; content: '·'; margin: 0 8px; flex: none; }
  .liste-eleves .data-table td:nth-child(4):not(:last-child) { display: none; }
  /* ---- Les autres listes au pouce : le même geste que les élèves ----
     Le ⋮ quitte sa ligne (qui restait vide) pour le coin de la carte, en zone de
     56 px ; le titre lui laisse la place. Une carte qui a un geste de lecture
     (Fiche, Voir, Reçu…) s'ouvre en entier. */
  section:not(.liste-eleves) .data-table tr:has(> td:last-child .task-more) { position: relative; }
  section:not(.liste-eleves) .data-table tr:has(> td:last-child .task-more) > td:first-child { padding-right: 52px; }
  section:not(.liste-eleves) .data-table td:last-child:has(.task-more) { position: absolute; top: 0; right: 0; width: 56px; height: 56px; display: block; padding: 0; border: 0; }
  section:not(.liste-eleves) .data-table td:last-child:has(.task-more)::before { display: none; }
  section:not(.liste-eleves) .data-table td:last-child .task-more { display: flex; justify-content: center; height: 100%; }
  section:not(.liste-eleves) .data-table td:last-child .dropdown-toggle-btn:not(.menu-choix-bouton) { width: 56px; height: 56px; padding: 0; font-size: 22px; border-radius: 0 var(--small-radius) 0 var(--small-radius); }
  section:not(.liste-eleves) .data-table td:last-child .dropdown-toggle-btn:not(.menu-choix-bouton):active { background: color-mix(in srgb, var(--teal) 12%, var(--card)); }
  .app-shell.ecran-fixe.atelier-documents > .main-content { padding-inline: 16px; }
  .dir-cours table { table-layout: fixed; width: 100%; }
  .dir-cours th, .dir-cours td { overflow-wrap: anywhere; }
  .dir-tete .lien { min-height: var(--tap); display: inline-flex; align-items: center; }
  button.msg-conv-add-btn { width: var(--tap); height: var(--tap); font-size: 14px; }
  .cours-actions .dropdown-toggle-btn:not(.menu-choix-bouton) { min-width: var(--tap); min-height: var(--tap); font-size: 18px; }
  .data-table tr.carte-tactile { cursor: pointer; transition: background 0.15s; }
  .data-table tr.carte-tactile:active { background: color-mix(in srgb, var(--teal) 8%, var(--card)); }
  .barre-tri { display: flex; justify-content: flex-end; margin-bottom: 10px; }
  .barre-tri .pied-choix { gap: 8px; }
  .pied-suite { flex-direction: column; align-items: stretch; gap: 10px; text-align: center; }
  .pied-suite .pied-compte { flex-basis: auto; margin: 0; }
  .pied-suite .suite-btn { width: 100%; min-height: var(--tap); }
  .pied-suite .suite-btn[hidden] { display: none; }
}

/* ---- Lot 2 au pouce : feuille du bas, pastilles, formulaires plein écran ----
   Le menu ⋮ et le choix du tri montent du bas de l'écran : on les vise au pouce,
   sans viser un petit menu collé au bord. Dans une fenêtre modale, la liste garde
   sa place ordinaire. Les filtres deviennent une rangée de pastilles qui défile de
   côté. Un formulaire prend tout l'écran, son bouton d'enregistrement reste en bas. */
@media (max-width: 640px) {
  body.actions-ouvertes::after { content: ''; position: fixed; inset: 0; background: var(--voile); z-index: 79; }
  .action-dropdown.feuille-actions, .side-user-wrap .action-dropdown.feuille-actions {
    position: fixed !important; top: auto !important; left: 0 !important; right: 0 !important; bottom: 0 !important;
    z-index: 80; min-width: 0; max-height: 70dvh; overflow-y: auto; border: 0;
    border-radius: 20px 20px 0 0; padding: 22px 8px calc(12px + env(safe-area-inset-bottom));
    box-shadow: 0 -12px 40px rgba(0,0,0,0.18); animation: feuille-monte 0.2s ease-out;
  }
  .action-dropdown.feuille-actions::before { content: ''; position: absolute; top: 8px; left: 50%; width: 40px; height: 4px; margin-left: -20px; border-radius: 2px; background: var(--line); }
  .action-dropdown.feuille-actions .action-dropdown-item { min-height: 52px; padding: 0 16px; border-radius: 12px; font-size: 16px; white-space: normal; }
  .action-dropdown.feuille-actions .action-dropdown-item:active { background: var(--survol); }
  .action-dropdown.feuille-actions .action-dropdown-item[aria-selected="true"] { color: var(--teal); font-weight: 700; }
  @keyframes feuille-monte { from { transform: translateY(100%); } to { transform: none; } }
  .barre-tri .tri-au-pouce { align-items: center; white-space: nowrap; }
  .barre-tri .menu-choix { min-width: 12rem; }
  .barre-tri button.menu-choix-bouton { height: var(--tap); border-radius: 999px; }

  /* Pastilles de filtre : une rangée qui défile de côté, jamais trois lignes de listes. */
  .filters:not(:has(.filter-field, .primary-btn)) { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; gap: 8px; padding-bottom: 4px; }
  .filters:not(:has(.filter-field, .primary-btn))::-webkit-scrollbar { display: none; }
  .filters.filters:not(:has(.filter-field, .primary-btn)) select.filter-control, .filters.filters:not(:has(.filter-field, .primary-btn)) button.filter-control:not(.menu-choix-bouton), .annonces-barre select.filter-control { flex: 0 0 auto; width: auto; min-width: 0; height: 40px; min-height: 40px; padding: 0 16px; border-radius: 999px; font-size: 14px; font-weight: 500; }
  .filters.filters:not(:has(.filter-field, .primary-btn)) select.filter-control, .annonces-barre select.filter-control { padding-right: 32px; }

  /* Formulaires en plein écran : le titre en haut, les champs qui défilent, les
     boutons qui restent sous le pouce. Les fenêtres sans formulaire restent une
     feuille du bas. */
  .modal:has(form), .modal.large:has(form) { height: 100dvh; max-height: 100dvh; border-radius: 0; border: 0; }
  .modal:has(form) .form-actions { position: sticky; bottom: 0; margin: 16px -20px -24px; padding: 12px 20px calc(12px + env(safe-area-inset-bottom)); background: var(--card); border-top: 1px solid var(--line-soft); }
  .modal:has(form) .form-field input:not([type="checkbox"]):not([type="radio"]):not([type="file"]), .modal:has(form) .form-field select { font-size: 16px; }
}

/* ----- Lot 3 : chargement en blocs, tirer pour actualiser ----- */
.squelette { display: flex; flex-direction: column; gap: 12px; padding: 8px 0; }
.squelette i { display: block; border-radius: 16px; background: linear-gradient(90deg, color-mix(in srgb, var(--muted) 12%, transparent) 25%, color-mix(in srgb, var(--muted) 22%, transparent) 50%, color-mix(in srgb, var(--muted) 12%, transparent) 75%); background-size: 200% 100%; animation: squelette-passe 1.4s linear infinite; }
.squelette .sq-titre { height: 28px; width: 45%; border-radius: 10px; }
.squelette .sq-carte { height: 120px; }
.squelette .sq-ligne { height: 56px; }
@keyframes squelette-passe { from { background-position: 200% 0; } to { background-position: -200% 0; } }
@media (prefers-reduced-motion: reduce) { .squelette i { animation: none; } }
.tirer-actualiser { display: none; }
@media (max-width: 640px) {
  body { overscroll-behavior-y: contain; }
  .tirer-actualiser { display: flex; position: fixed; top: 8px; left: 50%; z-index: 70; width: 40px; height: 40px; align-items: center; justify-content: center; border-radius: 50%; background: var(--card); box-shadow: 0 4px 14px rgba(0,0,0,.15); opacity: .6; pointer-events: none; transform: translate(-50%, -80px); }
  .tirer-actualiser.pret { opacity: 1; color: var(--teal); }
  .tirer-actualiser .ico { width: 20px; height: 20px; }
}

/* ----- Fiches imprimables -----
   La même mise en page sert à l'écran et au papier. #printRoot reçoit une copie
   de la fiche au moment d'imprimer : à l'impression on masque tout le reste de
   la page, ce qui évite d'avoir à démonter la barre latérale et la fenêtre. */
#printRoot { display: none; }
/* Deux écoles ne doivent pas rendre la même feuille. Le nom et le logo n'y
   suffisent pas : sur une photocopie posée parmi d'autres, c'est la couleur qui
   dit d'un coup d'œil de quel établissement vient le document.
   Règle tenue partout ici : la couleur de l'école ne porte jamais de texte —
   bandeau, filets et soulignements seulement. Une école reste donc libre de
   choisir son bleu marine comme son jaune pâle sans pouvoir rendre son propre
   bulletin illisible, et le contenu reste noir sur blanc à la photocopie. */
.sheet {
  border: 1px solid var(--line-soft);
  border-top: 6px solid var(--doc-primaire);
  border-radius: 16px;
  padding: 22px;
  background: var(--blanc);
  color: var(--papier-encre);
}
/* Sur téléphone les trois colonnes de l'en-tête ne tiennent pas côte à côte :
   plutôt que de les écraser, on laisse la nature de la fiche passer à la ligne. */
.sheet-head { display: flex; align-items: flex-start; flex-wrap: wrap; gap: 14px; border-bottom: 2px solid var(--doc-primaire); padding-bottom: 14px; }
/* La ligne officielle se place au-dessus du nom, en petites capitales : c'est la
   convention des documents administratifs malgaches, et elle doit rester lisible
   même quand l'école n'en saisit aucune (elle disparaît alors complètement). */
.sheet-official { display: block; font-size: var(--fs-xs); font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--papier-encre); }
.sheet-mark {
  display: grid;
  place-items: center;
  width: 68px;
  height: 68px;
  flex: 0 0 auto;
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  font-weight: 700;
  overflow: hidden;
  border-color: var(--doc-primaire);
}
.sheet-mark:has(img) { display: block; width: auto; max-width: 180px; height: 68px; border: 0; border-radius: 0; background: none; overflow: visible; }
.sheet-mark img { width: auto; max-width: 180px; height: 100%; object-fit: contain; }
.sheet-org { display: flex; flex-direction: column; gap: 2px; flex: 1 1 180px; min-width: 0; }
.sheet-org strong { font-size: var(--fs-md); }
.sheet-org span { font-size: var(--fs-xs); color: var(--papier-texte); }
.sheet-kind { font-size: var(--fs-xs); font-weight: 700; text-transform: uppercase; letter-spacing: .06em; white-space: nowrap; margin-left: auto; }
.sheet-body { display: flex; gap: 20px; padding: 18px 0; flex-wrap: wrap; }
.sheet-photo {
  display: grid;
  place-items: center;
  width: 108px;
  height: 132px;
  flex: 0 0 auto;
  border: 1px solid var(--line-soft);
  border-radius: 10px;
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--papier-texte);
  overflow: hidden;
}
.sheet-photo img { width: 100%; height: 100%; object-fit: cover; }
.sheet-lines { flex: 1 1 260px; min-width: 0; }
.sheet-lines h3 { margin: 0 0 10px; font-size: var(--fs-lg); }
/* Une phrase d'explication en tete d'un bloc : tant que les lignes portaient
   leur propre bordure, elles apportaient cet air ; un tableau, non. */
.sheet-lines p { margin: 0 0 16px; }
.sheet-line { display: flex; gap: 12px; padding: 7px 0; border-bottom: 1px dotted var(--line-soft); font-size: var(--fs-sm); }
.sheet-line span { flex: 0 0 132px; color: var(--papier-texte); }
.sheet-line strong { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
/* Une note, une cellule ou une ligne de feuille qui porte plusieurs réponses
   — un tarif par ligne. Côte à côte elles se lisaient comme une seule phrase. */
.note-champ .ligne-tarif, td .ligne-tarif, .sheet-line .ligne-tarif { display: block; }
/* « Qui doit quoi » : le bouton d'impression au-dessus de la liste, et le
   total juste avant le tableau. C'est le chiffre qu'on cherche en premier en
   ouvrant l'écran — il ne doit pas se trouver au bas d'une page qu'on déroule. */
.qdq-barre { justify-content: space-between; }
.qdq-actions { display: flex; gap: 12px; }
.qdq-total { margin: 0; font-size: var(--fs-sm); color: var(--muted); }
.qdq-total strong { color: var(--ink); font-size: var(--fs-md); }
/* Le code se recopie à la main sur un carnet, parfois sous une ampoule faible :
   il est gros, espacé, et dans une police à chasse fixe pour qu'aucun caractère
   n'en imite un autre. */
.code-acces { margin: 14px 0; font-family: ui-monospace, "Courier New", monospace; font-size: 30px; font-weight: 700; letter-spacing: .16em; text-align: center; color: var(--doc-principale); }
.sheet-foot { border-top: 2px solid var(--doc-secondaire); padding-top: 10px; font-size: var(--fs-xs); color: var(--papier-texte); }
/* Le cachet et la signature vont à droite, comme sur un bulletin remis en main
   propre. Le bloc garde sa hauteur même sans image déposée : la direction signe
   alors dans le vide laissé, ce qui reste la pratique la plus répandue. */
/* Vérification à gauche, signature à droite : les deux disent la même chose —
   ce papier vient bien d'ici — l'une à la main, l'autre par le code. Elles se
   lisent donc sur la même ligne, et ne se coupent jamais entre deux pages. */
.sheet-verify-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; flex-wrap: wrap; break-inside: avoid; }
.sheet-verify-row .sheet-sign { padding-top: 0; }
.sheet-verify { flex: 1 1 210px; min-width: 0; padding: 14px 0 4px; font-size: var(--fs-xs); color: var(--papier-texte); }
.sheet-verify strong { display: block; font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .05em; color: var(--papier-encre); }
.sheet-verify span { display: block; margin-top: 4px; overflow-wrap: anywhere; }
/* Douze caractères à recopier à la main : espacés et en chasse fixe, sinon on
   confond deux lettres et on accuse l'école d'avoir délivré un faux. */
.sheet-verify code { display: inline-block; margin-top: 4px; font-family: ui-monospace, "SFMono-Regular", monospace; font-size: var(--fs-sm); font-weight: 700; letter-spacing: .12em; color: var(--papier-encre); }
.sheet-verify em { display: block; margin-top: 4px; font-style: normal; font-size: var(--fs-xs); }
.sheet-sign { display: flex; justify-content: flex-end; gap: 18px; padding: 14px 0 4px; break-inside: avoid; }
/* Deux signatures se font face : celui qui reçoit à gauche, l'école à droite.
   Serrées du même côté, on ne sait plus laquelle est laquelle. */
.sheet-sign.deux { justify-content: space-between; }
/* Ce qu'un professeur porte tient dans une carte : ce qu'il est en gros, ce
   que ça pèse en petit, et — au collège seulement — une ligne par classe. La
   première version dépliait son emploi du temps entier, vingt lignes que
   personne ne lisait et qui donnaient à un départ l'air d'un formulaire. */
.remplacement-carte { margin: 0 0 18px; padding: 16px 18px; border: 1px solid var(--line);
  border-left: 3px solid var(--teal); border-radius: var(--small-radius); background: var(--line-soft); }
.remplacement-titre { margin: 0; font-size: var(--fs-md); font-weight: 650; color: var(--ink); }
.remplacement-detail { margin: 3px 0 0; font-size: var(--fs-sm); color: var(--muted); }
.remplacement-classes { list-style: none; display: grid; gap: 9px;
  margin: 14px 0 0; padding: 13px 0 0; border-top: 1px solid var(--line); }
.remplacement-classes li { display: grid; grid-template-columns: 5.5rem 1fr auto; gap: 14px;
  align-items: baseline; font-size: var(--fs-sm); }
.remplacement-classes b { font-weight: 650; color: var(--ink); }
.remplacement-classes span { color: var(--ink-soft); }
/* Les heures alignées à droite, chiffres de même largeur : la colonne se lit
   d'un coup, sans comparer classe par classe. */
.remplacement-classes em { font-style: normal; color: var(--muted); white-space: nowrap;
  font-variant-numeric: tabular-nums; }
@media (max-width: 640px) {
  .remplacement-classes li { grid-template-columns: 1fr auto; }
  .remplacement-classes b { grid-column: 1; }
  .remplacement-classes span { grid-column: 1 / -1; }
}
.sheet-engagement { margin: 14px 0 0; font-size: var(--fs-sm); line-height: 1.5; }
.sheet-sign > div { text-align: center; min-width: 190px; }
.sheet-sign strong { display: block; font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .05em; }
.sheet-sign .sheet-stamp { display: grid; place-items: center; height: 96px; margin: 4px 0; }
.sheet-sign .sheet-stamp img { max-height: 96px; max-width: 100%; object-fit: contain; }
.sheet-sign span { display: block; border-top: 1px dotted var(--papier-grise); padding-top: 6px; font-size: var(--fs-xs); color: var(--papier-texte); }
/* Un bulletin remplacé qui circule sur papier est le pire cas : l'avertissement
   doit rester lisible même sur une imprimante noir et blanc, donc un cadre épais
   et du gras, pas seulement de la couleur. */
.sheet-warn { border: 2px solid var(--papier-encre); padding: 8px 10px; margin: 0 0 12px; font-size: var(--fs-sm); font-weight: 700; }
/* Le tableau des matières est dans la fiche : il s'imprime en noir sur blanc,
   sans le fond ni les coins arrondis de l'écran. */
.sheet .data-table { width: 100%; margin: 0 0 14px; }
.sheet .data-table th { border-bottom: 2px solid var(--violet); }

/* ----- L'en-tête des documents et le bulletin -----
   Il est bâti comme celui d'un établissement, pas comme un écran : le mot
   BULLETIN en grand, une bande d'identité, le tableau des matières, le bilan en
   gros chiffres, le mot du conseil, la signature. Chaque bloc est dessiné pour
   tenir sur une seule page A4, y compris avec quinze matières — un bulletin qui
   part sur trois feuilles n'est plus un bulletin, c'est un listing.
   Les tailles sont en pixels, comme sur le reçu : une feuille A4 fait 794 pixels
   de large, et ce sont les tailles du papier qu'on pose ici. */
.sheet.sheet-bulletin { border-top: 1px solid var(--line-soft); border-radius: 0; padding: 34px 40px 30px; }
.doc-tete { display: flex; align-items: flex-start; gap: 20px; margin-bottom: 30px; }
.doc-titre { flex: 1 1 auto; margin: 0; font-size: 26px; line-height: 1; text-transform: uppercase; letter-spacing: .01em; color: var(--doc-primaire); }
/* Les lettres du logo, dans un carré à la couleur de l'école : le bulletin est
   un document de l'établissement, pas un formulaire de Haitso. Le carré reste
   vide si l'école n'a rien mis — il ne va pas inventer des initiales. */
.doc-marque { display: grid; place-items: center; flex: 0 0 auto; width: 64px; height: 64px; border-radius: 6px; background: var(--doc-primaire); color: var(--blanc); font-size: 18px; font-weight: 700; letter-spacing: .02em; overflow: hidden; }
/* Dès que l'école a déposé un logo, il se pose seul : ni carré coloré ni
   cadre derrière lui, et un emplacement en rectangle plutôt qu'en carré.
   Un logo large — un nom écrit en long, un blason couché — y tient en entier
   à sa pleine hauteur, au lieu d'être réduit à la taille d'un carré. Le carré
   à la couleur de l'école ne sert plus qu'aux initiales, faute de logo. */
.doc-marque:has(img) { display: block; width: auto; max-width: 180px; height: 64px; border-radius: 0; background: none; overflow: visible; }
.doc-marque img { width: auto; max-width: 180px; height: 100%; object-fit: contain; }
.doc-ecole { display: flex; flex-direction: column; gap: 3px; flex: 0 1 auto; text-align: right; min-width: 0; }
.doc-ecole strong { font-size: 16px; }
.doc-ecole em { font-size: 10px; font-style: normal; color: var(--papier-mention); }
.doc-ecole span { font-size: 10px; color: var(--papier-texte); overflow-wrap: anywhere; }
/* La bande d'identité répond en un coup d'œil aux cinq questions qu'on pose à un
   bulletin qu'on ramasse sur une table : de qui, quel numéro, quelle classe,
   quelle période, et quelle référence si on doit le retrouver. */
.bul-ident { display: flex; flex-wrap: wrap; gap: 14px 34px; padding: 16px 20px; border-radius: 6px; background: var(--papier-fond); margin-bottom: 24px; }
.bul-ident > div { min-width: 0; }
.bul-ident span { display: block; margin-bottom: 6px; font-size: 8px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; color: var(--papier-mention); }
.bul-ident strong { display: block; font-size: 11px; overflow-wrap: anywhere; }
.bul-notes { width: 100%; border-collapse: separate; border-spacing: 0; margin-bottom: 22px; }
.bul-notes th { padding: 10px; background: var(--doc-secondaire); font-size: 8.5px; font-weight: 700; text-align: left; text-transform: uppercase; letter-spacing: .06em; color: var(--blanc); white-space: nowrap; }
.bul-notes th:first-child { border-radius: 6px 0 0 6px; }
.bul-notes th:last-child { border-radius: 0 6px 6px 0; }
.bul-notes td { padding: 11px 10px; font-size: 11px; color: var(--papier-texte); vertical-align: top; }
/* Une ligne sur deux est grisée : c'est ce qui empêche l'œil de sauter d'une
   matière à la moyenne de la suivante quand on lit une colonne de quinze. */
.bul-notes tbody tr:nth-child(even) td { background: var(--papier-fond); }
.bul-notes tbody tr:nth-child(even) td:first-child { border-radius: 6px 0 0 6px; }
.bul-notes tbody tr:nth-child(even) td:last-child { border-radius: 0 6px 6px 0; }
/* Coefficient et moyennes sont des nombres qu'on compare de haut en bas : ils
   sont alignés en colonne et n'ont pas à s'étaler. */
.bul-notes .col-nombre { text-align: right; white-space: nowrap; width: 1%; }

/* ----- Une liste qui s'imprime sur plusieurs A4 -----
   Un bulletin tient sur une page et se lit de près ; une liste de recouvrement
   se parcourt en réunion, et chaque ligne gagnée est une famille de plus sur la
   feuille. Ce sont les blancs qui descendent, pas le texte : le tableau est
   déjà écrit dans la taille d'un document de bureau, et ce qui mangeait la
   page, c'étaient les marges, les rembourrages et le chiffre du bilan, dessiné
   pour la moyenne générale d'un bulletin. */
.sheet-longue .sheet-lines h3 { font-size: var(--fs-base); }
.sheet-longue .sheet-lines p { font-size: var(--fs-sm); margin-bottom: 10px; }
.sheet-longue .bul-bilan { gap: 10px 40px; margin-bottom: 14px; }
.sheet-longue .bul-bilan strong { font-size: var(--fs-md); }
.sheet-longue .bul-notes { margin-bottom: 14px; }
.sheet-longue .bul-notes th { padding: 7px 9px; }
.sheet-longue .bul-notes td { padding: 7px 9px; }

/* Sept colonnes sur une A4 : sans largeurs posées, le navigateur donne presque
   tout à « Ce qui reste à régler » et coupe les noms en deux. Les largeurs sont
   fixes, comme sur tout tableau de Haitso, sinon elles changeraient d'une
   impression à l'autre selon les familles listées. */
.sheet-retards .bul-notes { table-layout: fixed; }
.sheet-retards .bul-notes :is(th, td):nth-child(1) { width: 17%; }
.sheet-retards .bul-notes :is(th, td):nth-child(2) { width: 9%; }
.sheet-retards .bul-notes :is(th, td):nth-child(3) { width: 15%; }
.sheet-retards .bul-notes :is(th, td):nth-child(4) { width: 12%; }
.sheet-retards .bul-notes :is(th, td):nth-child(5) { width: 26%; }
.sheet-retards .bul-notes :is(th, td):nth-child(6) { width: 9%; }
.sheet-retards .bul-notes :is(th, td):nth-child(7) { width: 12%; }
.sheet-retards .bul-notes td { overflow-wrap: anywhere; }

/* Sur un bulletin, les colonnes 2 à 4 sont toujours des nombres, et la règle
   plus haut les aligne à droite par leur rang. Une feuille-liste n'a pas ce
   rang fixe : « Qui doit quoi » met la classe, le responsable et son numéro
   à ces places-là. L'intitulé partait donc à droite pendant que la colonne
   s'écrivait à gauche. Ici c'est `col-nombre` qui dit ce qui est un nombre. */
.sheet-longue .bul-notes th:not(.col-nombre) { text-align: left; }
.bul-notes th:nth-child(2), .bul-notes th:nth-child(3), .bul-notes th:nth-child(4) { text-align: right; }
.bul-notes td:first-child { color: var(--papier-encre); }
/* Pas de pastille de couleur ici : un bulletin se lit en noir sur blanc, et la
   pastille imposait deux lignes de haut par matière. Sous la moyenne, le chiffre
   reste signalé — en gras et en rouge, ce qui se voit aussi sur une photocopie. */
.bul-note { font-weight: 700; color: var(--papier-encre); }
.bul-note.faible { color: var(--papier-alerte); }
/* Le bilan n'est plus dans des cadres : trois chiffres en grand, posés sur la
   feuille. C'est la ligne qu'un parent lit en premier, elle n'a pas besoin
   qu'on l'encadre pour se faire voir. */
.bul-bilan { display: flex; flex-wrap: wrap; gap: 16px 60px; margin-bottom: 26px; }
.bul-bilan span { display: block; margin-bottom: 8px; font-size: 8.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; color: var(--papier-mention); }
.bul-bilan strong { font-size: 22px; line-height: 1; }
.bul-decision strong { color: var(--doc-secondaire); }
/* Le mot du conseil garde sa place même vide : beaucoup d'écoles l'écrivent à la
   main au moment de remettre le bulletin. */
.bul-mot { padding-top: 18px; border-top: 1px solid var(--papier-fond); margin-bottom: 22px; }
.bul-mot span { display: block; margin-bottom: 8px; font-size: 8.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; color: var(--papier-mention); }
.bul-mot p { margin: 0; min-height: 2.6em; font-size: 11px; line-height: 1.6; color: var(--papier-encre); }
/* Le code de vérification et la signature se font face en bas de page : la
   preuve que le document vient bien de l'école à gauche, la main qui l'a signé
   à droite. Le code est écrit dans la couleur de l'école — c'est la seule chose
   du bas de page qu'un parent aura à recopier. */
.sheet-bulletin .sheet-verify-row { margin-top: 30px; }
.sheet-bulletin .sheet-verify { padding: 0; font-size: 9px; }
.sheet-bulletin .sheet-verify strong { font-size: 10px; text-transform: none; letter-spacing: 0; }
.sheet-bulletin .sheet-verify span { font-size: 9px; }
.sheet-bulletin .sheet-verify code { font-family: inherit; font-size: 13px; letter-spacing: .04em; color: var(--doc-primaire); }
.sheet-bulletin .sheet-verify em { font-size: 8.5px; color: var(--papier-mention); }
/* Sans code de vérification — un exemple, un aperçu — la signature restait
   collée à gauche. Elle garde sa place à droite quoi qu il arrive, mais rentre
   un peu dans la page : on signe à la main dans cette zone, et le bord de la
   feuille ne laissait pas de quoi poser le stylo. */
.sheet-bulletin .sheet-sign { padding: 0; margin-left: auto; margin-right: 40px; }
.sheet-bulletin .sheet-sign > div { min-width: 0; text-align: right; }
.sheet-bulletin .sheet-sign strong { font-size: 9px; color: var(--papier-mention); }
/* Le cercle en pointillés dit où poser le cachet aux écoles qui n'ont pas encore
   déposé le leur : c'est un repère, pas un cadre. */
.sheet-bulletin .sheet-sign .sheet-stamp { display: grid; place-items: center; width: 92px; height: 92px; margin: 8px auto; border: 1px dashed var(--papier-trait); border-radius: 50%; }
/* Un tampon déposé n'est pas toujours rond, et rien ne doit en être rogné : le
   repère s'efface, l'image s'affiche entière et se centre sous la fonction. */
.sheet-bulletin .sheet-sign .sheet-stamp:has(img) { width: auto; height: auto; max-width: 100%; border: 0; border-radius: 0; }
.sheet-bulletin .sheet-sign .sheet-stamp img { max-width: 170px; max-height: 92px; object-fit: contain; }
.sheet-bulletin .sheet-sign span { border: 0; padding: 0; font-size: 11.5px; font-weight: 700; color: var(--papier-encre); }
.sheet-bulletin .sheet-foot { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 8px 24px; border-top: 1px solid var(--papier-fond); padding-top: 16px; margin-top: 28px; font-size: 9px; color: var(--papier-mention); }

/* ----- Le bulletin tel qu'il sortira de l'imprimante -----
   Ces règles ne décrivent pas un écran : elles décrivent une feuille de papier.
   Elles étaient enfermées dans `@media print`, et l'aperçu de Paramètres >
   Documents — dont c'est pourtant le seul métier — ne les voyait pas. Une
   école réglait donc son bulletin sur une feuille qui n'était pas la sienne :
   marges plus étroites, tableau plus aéré, et surtout la signature restée au
   milieu de la page au lieu de descendre en bas. Elles valent maintenant pour
   les deux, écrites une seule fois : `#printRoot` est la copie qui part à
   l'imprimante, `.feuille-a4-page` est la A4 de l'aperçu. Il n'y a plus de
   version de l'aperçu à tenir à jour à côté de celle du papier.

   Le bulletin tient sur une page, quel que soit le nombre de matières. Un
   document remis à une famille se lit d'un seul tenant : deux feuilles se
   séparent, et c'est toujours celle qui porte la signature qui se perd.
   L'échelle typographique est resserrée ici seulement — le bulletin consulté
   dans une fenêtre garde la sienne, où il y a la place.
   Deux classes dans le sélecteur, pas une : « compact » et « classique »
   posent leur propre échelle sur `.sheet.compact`, qui l'emporterait sinon —
   et le bulletin repartirait sur deux pages avec l'un de ces deux modèles. */
:is(#printRoot, .feuille-a4-page) .sheet.sheet-bulletin { border-top: 0; padding: 0; display: flex; flex-direction: column; }
:is(#printRoot, .feuille-a4-page) .sheet-bulletin .doc-tete { margin-bottom: 22px; }
:is(#printRoot, .feuille-a4-page) .sheet-bulletin .bul-ident { margin-bottom: 18px; }
/* L'interligne du tableau est resserré : c'est lui qui décide de la page quand
   une école commente chacune des quinze matières d'une terminale. */
:is(#printRoot, .feuille-a4-page) .sheet-bulletin .bul-notes { line-height: 1.35; margin-bottom: 16px; }
:is(#printRoot, .feuille-a4-page) .sheet-bulletin .bul-notes th { padding: 8px; }
:is(#printRoot, .feuille-a4-page) .sheet-bulletin .bul-notes td { padding: 8px; }
:is(#printRoot, .feuille-a4-page) .sheet-bulletin .bul-notes tr,
:is(#printRoot, .feuille-a4-page) .sheet-bulletin .bul-ident,
:is(#printRoot, .feuille-a4-page) .sheet-bulletin .bul-bilan,
:is(#printRoot, .feuille-a4-page) .sheet-bulletin .bul-mot { break-inside: avoid; }
:is(#printRoot, .feuille-a4-page) .sheet-bulletin .bul-bilan { margin-bottom: 18px; }
:is(#printRoot, .feuille-a4-page) .sheet-bulletin .bul-mot { padding-top: 13px; margin-bottom: 16px; }
:is(#printRoot, .feuille-a4-page) .sheet-bulletin .bul-mot p { min-height: 1.8em; }
/* La signature et le code de vérification descendent au bas de la feuille.
   Avec peu de matières, ils remontaient se coller sous l'appréciation et
   laissaient un tiers de page blanc en dessous : un parent ne sait plus si
   le document est complet, ni si quelqu'un a coupé le bas. Un bulletin remis
   en main propre se signe en bas de page. La colonne s'étire sur la hauteur
   de la feuille, et la marge du haut, laissée au calcul, avale le vide.
   Quand les matières remplissent la page, cette marge vaut zéro et rien ne
   bouge. */
:is(#printRoot, .feuille-a4-page) .sheet-bulletin .sheet-verify-row { margin-top: auto; }
:is(#printRoot, .feuille-a4-page) .sheet-bulletin .sheet-verify,
:is(#printRoot, .feuille-a4-page) .sheet-bulletin .sheet-sign { padding: 6px 0 0; }
/* Sur la feuille imprimée, le repère vide se réduit : il ne sert qu'à garder la
   place de signer. Largeur et hauteur descendent ensemble, sinon le rond
   s'écrase en ovale. Un tampon déposé, lui, garde ses proportions. */
:is(#printRoot, .feuille-a4-page) .sheet-bulletin .sheet-sign .sheet-stamp { width: 64px; height: 64px; }
:is(#printRoot, .feuille-a4-page) .sheet-bulletin .sheet-sign .sheet-stamp:has(img) { width: auto; height: auto; }
:is(#printRoot, .feuille-a4-page) .sheet-bulletin .sheet-sign .sheet-stamp img { max-width: 130px; max-height: 64px; }
:is(#printRoot, .feuille-a4-page) .sheet-bulletin .sheet-foot { margin-top: 18px; padding-top: 6px; }
/* Un tableau de cinq colonnes ne tient pas dans un téléphone : plutôt que de
   l'écraser ou de cacher des colonnes, on le laisse défiler latéralement. Le
   parent voit alors les mêmes colonnes que sur le papier. */
.bul-tableau { overflow-x: auto; }
@media (max-width: 640px) {
  /* Sur un téléphone le bulletin se lit avant de s'imprimer : l'en-tête s'empile
     et tout revient à gauche. Le papier, lui, ne change pas. */
  .sheet.sheet-bulletin { padding: 18px 16px; }
  .doc-tete { flex-wrap: wrap; gap: 12px; margin-bottom: 20px; }
  .doc-titre { flex: 1 1 100%; order: 2; }
  .doc-marque { order: 1; }
  .doc-ecole { order: 3; flex: 1 1 100%; text-align: left; }
  .bul-ident { gap: 12px 22px; padding: 14px 16px; }
  .bul-notes th, .bul-notes td { padding: 8px 5px; }
  .bul-notes td { font-size: 10px; }
  .bul-bilan { gap: 14px 30px; }
}

/* ----- La fiche élève -----
   La feuille qu'un secrétariat sort quand une famille se présente au guichet.
   Elle répond aux deux questions qu'on lui pose — qui est cet enfant, et où il
   en est de ses mois — sans qu'on ait à ouvrir un second écran.
   Les tailles sont en pixels, comme sur le reçu et le bulletin : une feuille A4
   fait 794 pixels de large, et ce sont les tailles du papier qu'on pose ici. */
.sheet.sheet-fiche { border-top: 1px solid var(--papier-trait-pale); border-radius: 0; padding: 34px 40px 30px; }
.fic-haut { display: flex; gap: 32px; margin: 30px 0 26px; }
/* La photo garde sa place même quand il n'y en a pas : une fiche sans portrait
   ne doit pas changer de mise en page, sinon deux fiches côte à côte ne se
   lisent plus de la même façon. Le mot « Photo » dit alors ce qui manque. */
.fic-photo {
  display: grid;
  place-items: center;
  align-content: center;
  gap: 6px;
  flex: 0 0 auto;
  width: 136px;
  height: 170px;
  border-radius: 6px;
  background: var(--papier-fond);
  font-size: 34px;
  font-weight: 700;
  color: var(--papier-grise);
  overflow: hidden;
}
.fic-photo img { width: 100%; height: 100%; object-fit: cover; }
.fic-photo em { font-size: 9px; font-style: normal; font-weight: 400; letter-spacing: .04em; }
.fic-ident { flex: 1 1 auto; min-width: 0; }
.fic-ident h3 { margin: 0 0 18px; font-size: 22px; line-height: 1.1; }
.fic-faits { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px 30px; }
.fic-faits span { display: block; margin-bottom: 6px; font-size: 8.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; color: var(--papier-mention); }
.fic-faits strong { display: block; font-size: 11px; line-height: 1.4; overflow-wrap: anywhere; }
/* Ce qu'un mois coûte, écrit au-dessus du tableau et à droite : la famille lit
   la colonne des sommes en sachant déjà à quoi elle doit s'attendre. */
.fic-tarif { margin: 0 0 10px; text-align: right; font-size: 10px; color: var(--papier-mention); }
.fic-tableau { overflow-x: auto; }
.fic-table { width: 100%; border-collapse: separate; border-spacing: 0; margin-bottom: 22px; }
.fic-table th { padding: 10px; background: var(--doc-secondaire); font-size: 8.5px; font-weight: 700; text-align: left; text-transform: uppercase; letter-spacing: .06em; color: var(--blanc); white-space: nowrap; }
.fic-table th:first-child { border-radius: 6px 0 0 6px; }
.fic-table th:last-child { border-radius: 0 6px 6px 0; }
.fic-table th:nth-child(4) { text-align: right; }
.fic-table td { padding: 11px 10px; font-size: 11px; color: var(--papier-mention); white-space: nowrap; }
.fic-table tbody tr:nth-child(even) td { background: var(--papier-fond); }
.fic-table tbody tr:nth-child(even) td:first-child { border-radius: 6px 0 0 6px; }
.fic-table tbody tr:nth-child(even) td:last-child { border-radius: 0 6px 6px 0; }
.fic-table .col-nombre { text-align: right; font-weight: 700; color: var(--papier-encre); }
.fic-table .fic-etat { font-weight: 700; }
.fic-table tr.regle .fic-etat { color: var(--doc-secondaire); }
.fic-table tr.ouvert .fic-etat { color: var(--papier-alerte); }
/* Un mois qui n'est pas encore arrivé n'est pas une dette : toute sa ligne
   passe en gris clair, et il ne compte pas dans les mois ouverts. */
.fic-table tr.venir td, .fic-table tr.venir .col-nombre, .fic-table tr.venir .fic-etat { color: var(--papier-grise); font-weight: 400; }
.fic-autres { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 18px; margin: 0 0 22px; font-size: 11px; color: var(--papier-encre); }
.fic-autres span { font-size: 8.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; color: var(--papier-mention); }
.fic-autres em { font-style: normal; }
.fic-vide { margin: 0 0 22px; font-size: 11px; color: var(--papier-mention); }
.fic-bilan { display: flex; flex-wrap: wrap; gap: 16px 60px; }
.fic-bilan span { display: block; margin-bottom: 8px; font-size: 8.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; color: var(--papier-mention); }
.fic-bilan strong { font-size: 22px; line-height: 1; }
.fic-ouverts strong { color: var(--doc-secondaire); }
.fic-presences { margin-top: 26px; }
/* Les années passées, rangées sous le bilan de l'année en cours. */
.fic-presences .fic-bilan + .fic-tableau { margin-top: 26px; }
.sheet-fiche .sheet-foot { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 8px 24px; border-top: 1px solid var(--papier-fond); padding-top: 16px; margin-top: 28px; font-size: 9px; color: var(--papier-mention); }
@media (max-width: 640px) {
  /* Au téléphone la photo passe au-dessus, les faits reviennent sur une colonne
     et le tableau défile de côté : aucune colonne n'est cachée, le parent voit
     les mêmes que sur le papier. */
  .sheet.sheet-fiche { padding: 18px 16px; }
  .fic-haut { flex-direction: column; gap: 18px; margin: 20px 0; }
  .fic-photo { width: 108px; height: 134px; }
  .fic-faits { grid-template-columns: minmax(0, 1fr); gap: 12px; }
  .fic-table th, .fic-table td { padding: 8px 6px; }
  .fic-table td { font-size: 10px; }
  .fic-bilan { gap: 14px 30px; }
}

/* ----- Modèles de mise en page -----
   Trois gabarits, choisis dans les paramètres et posés en classe sur la feuille.
   « moderne » est la base écrite ci-dessus : il n'a rien à redéfinir, c'est ce
   que toutes les écoles imprimaient avant. Les deux autres se contentent de
   surcharger — aucune règle de base n'est déplacée, donc changer de modèle ne
   peut pas casser la feuille d'une école qui n'y touche pas.
   La règle des couleurs tient dans les trois : la couleur de l'école ne porte
   jamais de texte, seulement des filets. */

/* Classique : ce à quoi ressemble un vrai bulletin d'école malgache. Empattements,
   en-tête centré, tableau entièrement quadrillé, filets pleins. C'est le modèle
   à prendre quand le document doit être visé ou tamponné par une tutelle. */
.sheet.classique { font-family: Georgia, "Times New Roman", serif; border: 2px solid var(--papier-encre); border-top: 6px solid var(--doc-primaire); border-radius: 4px; }
.sheet.classique .sheet-head { flex-direction: column; align-items: center; text-align: center; border-bottom-width: 3px; }
.sheet.classique .sheet-org { align-items: center; flex: 0 0 auto; }
.sheet.classique .sheet-kind { margin: 6px auto 0; border: 1px solid var(--papier-encre); padding: 3px 14px; }
.sheet.classique .sheet-mark { border-radius: 0; }
.sheet.classique .sheet-line { border-bottom: 1px solid var(--papier-trait-pale); }
.sheet.classique .sheet-lines h3 { text-align: center; text-transform: uppercase; letter-spacing: .04em; }
/* Quadrillage complet : sur un document visé à la main, chaque note doit être
   dans une case fermée — c'est ce qui empêche d'en ajouter une après coup. */
.sheet.classique .data-table { border: 1.5px solid var(--papier-encre); }
.sheet.classique .data-table th,
.sheet.classique .data-table td { border: 1px solid var(--papier-grise); }
.sheet.classique .data-table th { border-bottom: 2px solid var(--violet); }
.sheet.classique .sheet-sign span { border-top-style: solid; }
/* Le bulletin visé à la main suit la même règle que les autres tableaux : chaque
   note dans une case fermée, pour qu'on ne puisse pas en ajouter une après coup. */
.sheet.classique .bul-notes { border: 1.5px solid var(--papier-encre); }
.sheet.classique .bul-notes th,
.sheet.classique .bul-notes td { border: 1px solid var(--papier-grise); }
/* La barre du tableau est déjà pleine de la couleur de l'école : lui ajouter un
   filet violet dessous ne dirait rien de plus, et jurerait. */
.sheet.classique .bul-notes th { border: 0; }
.sheet.classique .bul-ident { border: 1px solid var(--papier-grise); padding: 8px 10px; }
.sheet.classique.sheet-fiche { padding: 30px 32px; }
.sheet.classique .fic-photo { border-radius: 0; border: 1px solid var(--papier-grise); }
.sheet.classique .fic-table th { border: 0; }
.sheet.classique .fic-table td { border: 1px solid var(--papier-grise); }
.sheet.classique .fic-ident h3 { text-transform: uppercase; letter-spacing: .03em; }

/* Compact : même contenu, moins de place. Un bulletin de terminale à douze
   matières débordait sur une deuxième page à moitié vide ; ici il tient sur une.
   On resserre l'échelle typographique de la feuille seulement — le reste de
   l'application garde la sienne. */
.sheet.compact { --fs-lg: 1.05rem; --fs-md: 0.875rem; --fs-sm: 0.75rem; --fs-xs: 0.6875rem; padding: 16px; }
.sheet.compact .sheet-head { gap: 10px; padding-bottom: 9px; }
.sheet.compact .sheet-mark { width: 56px; height: 56px; }
.sheet.compact .sheet-mark:has(img) { width: auto; height: 56px; }
.sheet.compact .sheet-body { gap: 14px; padding: 11px 0; }
.sheet.compact .sheet-photo { width: 76px; height: 96px; }
.sheet.compact .sheet-line { padding: 3px 0; }
.sheet.compact .sheet-line span { flex-basis: 112px; }
.sheet.compact .sheet-lines h3 { margin-bottom: 6px; }
.sheet.compact .data-table th,
.sheet.compact .data-table td { padding: 4px 8px; }
/* La pastille de note tient sur deux lignes ailleurs, parce qu'elle rappelle
   l'échelle sous le chiffre. Dans un tableau de douze matières, ce rappel est
   déjà en tête de colonne (« Moyenne /20 ») et coûtait à lui seul la moitié de
   la hauteur des lignes : ici on garde le chiffre et on retire le doublon. */
.sheet.compact .grade-pill { padding: 2px 6px; min-width: 2.6rem; font-size: var(--fs-sm); }
.sheet.compact .grade-pill b { display: none; }
.sheet.compact.sheet-bulletin { padding: 20px 24px 18px; }
.sheet.compact .doc-tete { margin-bottom: 18px; }
.sheet.compact .doc-titre { font-size: 20px; }
.sheet.compact .bul-ident { padding: 11px 14px; margin-bottom: 16px; gap: 10px 24px; }
.sheet.compact .bul-notes { margin-bottom: 15px; }
.sheet.compact .bul-notes th { padding: 7px 7px; }
.sheet.compact .bul-notes td { padding: 7px; font-size: 10px; }
.sheet.compact .bul-bilan { gap: 12px 40px; margin-bottom: 17px; }
.sheet.compact .bul-bilan strong { font-size: 18px; }
.sheet.compact .bul-mot { padding-top: 12px; margin-bottom: 15px; }
.sheet.compact .bul-mot p { min-height: 1.9em; }
.sheet.compact.sheet-fiche { padding: 20px 24px 18px; }
.sheet.compact .fic-haut { gap: 22px; margin: 18px 0 16px; }
.sheet.compact .fic-photo { width: 104px; height: 130px; font-size: 26px; }
.sheet.compact .fic-ident h3 { margin-bottom: 12px; font-size: 18px; }
.sheet.compact .fic-faits { gap: 10px 24px; }
.sheet.compact .fic-table { margin-bottom: 15px; }
.sheet.compact .fic-table th { padding: 7px; }
.sheet.compact .fic-table td { padding: 7px; font-size: 10px; }
.sheet.compact .fic-bilan { gap: 12px 40px; }
.sheet.compact .fic-bilan strong { font-size: 18px; }
.sheet.compact .sheet-sign,
.sheet.compact .sheet-verify { padding: 8px 0 2px; }
/* Le bulletin est mis à part : son cachet est un rond, et ne lui rabaisser que
   la hauteur l'écrasait en ovale. Il garde le sien, carré, réglé plus haut. */
.sheet.compact:not(.sheet-bulletin) .sheet-sign .sheet-stamp { height: 62px; }
.sheet.compact:not(.sheet-bulletin) .sheet-sign .sheet-stamp img { max-height: 62px; }


@media print {
  /* La marge de la page est nulle et c'est la feuille qui porte la sienne, en
     marge propre (voir .sheet plus bas). Le navigateur n'écrit sa date, le
     titre de l'onglet, le numéro de page et son adresse que s'il reste une
     marge de page où les poser : sans marge, il n'a plus de place et le
     document ne porte plus que ce que l'école y a mis. Un bulletin remis aux
     parents ne peut pas dépendre d'une case « en-têtes et pieds de page » que
     la secrétaire penserait à décocher. L'emploi du temps faisait déjà ainsi.
     Le blanc autour du texte, lui, ne bouge pas d'un millimètre. */
  @page { size: A4; margin: 0; }
  /* Tout sauf la fiche disparaît : barre latérale, en-tête, voile de la
     fenêtre, notifications. Le bouton « Imprimer » n'est pas copié dans
     #printRoot, il ne peut donc pas se retrouver sur le papier. */
  body > *:not(#printRoot) { display: none !important; }
  /* Le blanc de 14 mm est ici, en remplissage du cadre qui porte la feuille, et
     non en marge de la feuille : une marge du haut posée sur le premier élément
     d'une page s'échappe de son cadre, et le navigateur la supprime en haut de
     page — le texte se retrouvait collé au bord du papier.
     L'emploi du temps est la seule exception : il porte son blanc lui-même et a
     besoin de toute la feuille pour tenir en une page (voir plus bas). */
  #printRoot { display: block !important; padding: 14mm; }
  #printRoot:has(.sheet-emploi) { padding: 0; }
  html, body { background: var(--blanc); }
  /* Le bandeau de couleur est la seule bordure qu'on garde : c'est lui qui
     identifie l'établissement. print-color-adjust le protège du réglage
     « économie d'encre » des navigateurs, qui sinon l'effacerait — et sur une
     imprimante noir et blanc il ressort en gris, donc le repère tient quand
     même. */
  .sheet { border: 0; border-top: 6px solid var(--doc-primaire); border-radius: 0; margin: 0; padding: 10px 0 0; print-color-adjust: exact; -webkit-print-color-adjust: exact; }
  /* Une fiche tient sur une page : on interdit qu'un bloc soit coupé en deux. */
  .sheet-body, .sheet-line { break-inside: avoid; }
  /* — Les documents qui ne tiennent pas sur une feuille —
     Un rapport et la liste « Qui doit quoi » n'ont pas de longueur connue :
     une école de trois cents élèves en retard fait quatre pages. La règle
     ci-dessus leur était fatale — elle demandait au navigateur de faire tenir
     tout le corps sur une seule page, et ce qui dépassait était perdu à
     l'impression comme à l'enregistrement en PDF. On libère donc la coupure
     sur ces deux feuilles-là, et on la gouverne ligne par ligne. */
  .sheet-longue .sheet-body { break-inside: auto; }
  /* Chaque ligne, chaque rangée de tableau et chaque bilan part entier sur la
     feuille suivante : un nom coupé en deux à la pliure ne se lit plus. */
  .sheet-longue .sheet-line,
  .sheet-longue .bul-notes tr,
  .sheet-longue .bul-bilan { break-inside: avoid; }
  /* Un titre reste avec ce qu'il annonce : « Répartition par classe » seul en
     bas de page, son tableau sur la suivante, c'est un titre orphelin. */
  .sheet-longue h3 { break-after: avoid; }
  .sheet-longue .bul-notes { break-before: avoid; }
  /* Le cadre à défilement latéral de l'écran n'a pas de sens sur du papier :
     il y coupait la dernière colonne au lieu de la montrer. */
  .sheet-longue .bul-tableau { overflow: visible; }
  /* La ligne de titres se réimprime en haut de chaque feuille : sans elle, la
     deuxième page d'un tableau n'est qu'une colonne de nombres sans nom. */
  .sheet-longue .bul-notes thead { display: table-header-group; }
  .sheet-longue .bul-notes tfoot { display: table-footer-group; }
  /* L'en-tête de l'école et le pied ne se coupent jamais en deux. */
  .sheet-longue .doc-tete, .sheet-longue .sheet-foot { break-inside: avoid; }
  .sheet-longue .sheet-foot { margin-top: 14px; }
  /* Le reçu est le seul document qui peut demander deux feuilles : il écrit une
     ligne par mois réglé, et dix mois ne tiennent pas sur une page. On assume
     les deux feuilles plutôt que de tasser le texte, mais aucun bloc n'est
     scié : la somme, la signature et le pied partent entiers sur la seconde.
     Le tableau des mois, lui, se coupe où il veut — il réimprime sa ligne de
     titres en haut de la page suivante, comme tout tableau à l'impression. */
  .recu-tete, .recu-parties, .recu-bandeau, .recu-table tr,
  .recu-bilan, .recu-signature, .recu-pied { break-inside: avoid; }
  .sheet .data-table { box-shadow: none; border: 1px solid var(--papier-encre); }
  .sheet .data-table td { border-bottom: 1px solid var(--papier-grise); }
  .sheet .data-table tr { break-inside: avoid; }
  /* Les deux gabarits gardent à l'impression ce qui fait leur raison d'être :
     le cadre pour « classique » — c'est lui qui fait document officiel — et le
     resserrement pour « compact ». Sans ces deux lignes, la règle générale
     ci-dessus leur remettrait le remplissage de l'écran. */
  .sheet.classique { border: 2px solid var(--papier-encre); border-top: 6px solid var(--doc-primaire); padding: 12px; }
  .sheet.compact { padding: 8px 0 0; }
  /* Ce qui suit ne vaut que pour le papier : `100vh` y est la hauteur d'une
     feuille, alors qu'à l'écran c'est celle de la fenêtre. La hauteur de
     l'aperçu, elle, vient de la page A4 qui le porte. */
  #printRoot .sheet.sheet-bulletin { min-height: calc(100vh - 28mm); }
  /* Le cadre du modèle « classique » ne serre plus le bulletin contre le bord :
     l'en-tête tient déjà sur une ligne, il ne reste qu'à lui laisser sa marge. */
  /* Le pied de la fiche descend au bas de la feuille, comme celui du bulletin :
     remonté sous le bilan, il laissait 79 mm de blanc en dessous et le papier
     avait l'air coupé. Quand la fiche remplit la page, la marge vaut zéro et le
     blanc de 18 mm sous le bilan garde l'écart. */
  .sheet.sheet-fiche { border-top: 0; padding: 0; display: flex; flex-direction: column; min-height: calc(100vh - 28mm); }
  .sheet-fiche .fic-bilan { margin-bottom: 18px; }
  .sheet-fiche .doc-tete { margin-bottom: 22px; }
  .sheet-fiche .fic-haut { margin: 22px 0 20px; break-inside: avoid; }
  .sheet-fiche .fic-table { line-height: 1.35; margin-bottom: 16px; }
  .sheet-fiche .fic-table th, .sheet-fiche .fic-table td { padding: 8px; }
  .sheet-fiche .fic-table tr, .sheet-fiche .fic-bilan { break-inside: avoid; }
  .sheet-fiche .sheet-foot { margin-top: auto; padding-top: 12px; }
  .sheet.classique.sheet-bulletin { padding: 16px 18px; }
  .sheet.classique.sheet-fiche { padding: 16px 18px; }
  /* L'emploi du temps sort sur une page A4 couchée : cinq jours côte à côte
     tiennent dans la largeur, et les couleurs des matières restent.
     Sa marge propre est nulle, contrairement aux autres feuilles : la journée
     entière, de 6 h à 18 h, doit tenir sur une seule page — coupée en deux, la
     grille ne se lit plus. Le blanc du tour est dans son remplissage, et la
     grille se réduit juste assez pour tenir. */
  @page emploi { size: A4 landscape; margin: 0; }
  .sheet.sheet-emploi { page: emploi; border-top: 0; padding: 28px 46px; zoom: .82; print-color-adjust: exact; -webkit-print-color-adjust: exact; }
  .sheet-emploi .doc-tete { margin-bottom: 16px; }
  .sheet-emploi .bul-ident { margin-bottom: 14px; }
  .sheet-emploi .timetable-scroll { overflow: visible; border: 1px solid var(--line); border-radius: 10px; }
  .sheet-emploi .timetable-grid { min-width: 0; }
  .sheet-emploi .timetable-day-col { min-width: 0; }
  .sheet-emploi .cours-card { box-shadow: none; }
  .sheet-emploi .sheet-foot { margin-top: 12px; }
}


/* Fil de discussion du support. Les messages de l'autre camp sont à gauche et
   neutres, les nôtres à droite et teintés : la dernière bulle dit à qui est la
   parole avant même qu'on ait lu un mot. */
.thread{display:flex;flex-direction:column;gap:12px;margin-bottom:18px}
.thread-msg{max-width:min(600px,88%);padding:12px 14px;border-radius:14px;background:var(--fond-doux);border:1px solid var(--line)}
.thread-msg.mine{align-self:flex-end;background:color-mix(in srgb,var(--teal) 12%,transparent);border-color:color-mix(in srgb,var(--teal) 35%,transparent)}
.thread-meta{display:flex;flex-wrap:wrap;gap:4px 10px;align-items:baseline;margin-bottom:6px;font-size:.85rem}
.thread-msg p{margin:0;line-height:1.5;white-space:pre-wrap;overflow-wrap:anywhere}

/* Messagerie : les règles de Kooly.html, telles quelles. Seules les couleurs
   passent par les variables de l'application, qui ont les mêmes valeurs
   (--primary-pink = --teal, --badge-bg-green = var(--turquoise-pale)). */
.msg-layout { display: flex; height: calc(100vh - 170px); min-height: 420px; background-color: var(--card); border-radius: 20px; border: 1px solid var(--line-soft); box-shadow: 0 10px 30px rgba(0,0,0,0.03); overflow: hidden; }
.msg-list-pane { width: 340px; flex-shrink: 0; display: flex; flex-direction: column; border-right: 1px solid var(--line-soft); }
.msg-list-pane.in-sidebar { width: 100%; height: 100%; border-right: none; }
/* Sur ordinateur, la liste est dans la bande large : la bande perd ses marges
   de menu et c'est la liste qui défile, pas la bande. */
.app-shell.bande-messagerie .sidebar-menu { padding: 0; overflow: hidden; }
.app-shell.bande-messagerie .side-nav { height: 100%; }
/* Comme sur Kooly.html, la liste a ses 280 px à elle, en plus des icônes. */
@media (min-width: 901px) {
  .app-shell.bande-messagerie { padding-left: calc(70px + 280px); }
  .app-shell.bande-messagerie .sidebar { width: calc(70px + 280px); }
}
.msg-list-header { padding: 18px; border-bottom: 1px solid var(--line-soft); }
.msg-search { display: flex; align-items: center; gap: 8px; background-color: var(--survol); border-radius: 10px; padding: 9px 14px; }
.msg-search:focus-within { outline: 2px solid var(--teal); outline-offset: 2px; }
.msg-search i { color: var(--muted); font-size: 13px; }
.msg-search input { border: none; background: none; outline: none; font-size: 13px; width: 100%; color: var(--ink); font-family: inherit; }
.msg-search input::-webkit-search-cancel-button { display: none; }
.msg-conv-list { flex: 1; min-height: 0; overflow-y: auto; scrollbar-width: thin; scrollbar-color: var(--barre-defilement) transparent; }
.msg-conv-list::-webkit-scrollbar { width: 6px; }
.msg-conv-list::-webkit-scrollbar-track { background: transparent; }
.msg-conv-list::-webkit-scrollbar-thumb { background-color: var(--barre-defilement); border-radius: 10px; }
/* Un bouton remplace le <div> de Kooly.html : remise à zéro de sa bordure, de
   son fond et de son alignement. */
.msg-conv-item { display: flex; align-items: flex-start; gap: 12px; width: 100%; padding: 14px 18px; cursor: pointer; border: 0; border-bottom: 1px solid var(--fond-doux); background: none; font: inherit; text-align: left; transition: background 0.15s; position: relative; line-height: normal; }
.msg-conv-item:hover { background-color: var(--survol); }
.msg-conv-item.active { background-color: var(--turquoise-pale); }
.msg-conv-item:focus-visible { outline: 2px solid var(--teal); outline-offset: -2px; }
/* L'avatar de Kooly.html (.avatar-letter) : un rond de 28 px. */
.msg-conv-item .avatar, .msg-thread-header .avatar { width: 28px; height: 28px; border-radius: 50%; font-size: 12px; font-weight: 600; flex-shrink: 0; }
.msg-conv-body { display: block; flex: 1; min-width: 0; }
.msg-conv-top { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.msg-conv-name { font-size: 13.5px; font-weight: 600; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.msg-conv-time { font-size: 11px; color: var(--muted); flex-shrink: 0; }
.msg-conv-snippet { display: block; font-size: 12.5px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 2px; }
.msg-vide { padding: 18px; margin: 0; font-size: 13px; color: var(--muted); }
/* Les sections de la liste et la fenêtre « Nouveau canal », reprises de Kooly.html. */
.msg-conv-section { display: flex; align-items: center; justify-content: space-between; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); padding: 14px 18px 6px; }
.msg-conv-add-btn { width: 20px; height: 20px; border: none; border-radius: 50%; background-color: var(--line-soft); color: var(--muted); font-size: 10px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.15s; }
.msg-conv-add-btn:hover { background-color: var(--teal); color: var(--blanc); }
.msg-bubble-auteur { display: block; font-size: 11px; font-weight: 700; color: var(--muted); margin-bottom: 2px; }
.canal-role-filter { display: flex; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; }
.canal-role-filter-btn { padding: 7px 14px; border-radius: 20px; font-size: 12px; font-weight: 600; border: 1.5px solid var(--line); background-color: var(--card); color: var(--muted); cursor: pointer; transition: all 0.15s; }
.canal-role-filter-btn:hover { border-color: var(--trait-champ); }
.canal-role-filter-btn.active { background-color: var(--teal); color: var(--blanc); border-color: transparent; }
.canal-member-list { max-height: 320px; overflow-y: auto; border: 1px solid var(--line); border-radius: 8px; }
.canal-member-item.is-hidden { display: none; }
.canal-member-item { display: flex; align-items: center; gap: 10px; padding: 8px 12px; cursor: pointer; border-bottom: 1px solid var(--line); }
.canal-member-item:last-child { border-bottom: none; }
.canal-member-item:hover { background-color: var(--survol); }
.canal-member-item input[type="checkbox"] { width: 16px; height: 16px; flex-shrink: 0; }
.canal-member-item .avatar { width: 28px; height: 28px; border-radius: 50%; font-size: 12px; font-weight: 600; flex-shrink: 0; }
.canal-member-nom { display: flex; flex-direction: column; font-size: 13px; font-weight: 600; color: var(--ink); }
.canal-member-nom small { font-size: 11px; font-weight: 500; color: var(--muted); }
.msg-thread-pane { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.msg-thread-header { display: flex; align-items: center; gap: 12px; padding: 16px 22px; border-bottom: 1px solid var(--line-soft); }
.msg-back-btn { display: none; width: 32px; height: 32px; border-radius: 50%; border: none; background: none; color: var(--ink); font-size: 14px; cursor: pointer; align-items: center; justify-content: center; flex-shrink: 0; }
.msg-thread-title { display: flex; flex: 1; flex-direction: column; min-width: 0; line-height: normal; }
.msg-thread-name { font-size: 14.5px; font-weight: 700; color: var(--ink); }
.msg-thread-role { font-size: 12px; color: var(--muted); }
.msg-thread-action { width: 32px; height: 32px; border-radius: 50%; border: none; background: none; color: var(--muted); font-size: 13px; cursor: pointer; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.msg-thread-action:hover { background: var(--survol); color: var(--teal); }
.msg-thread-body { flex: 1; min-height: 0; overflow-y: auto; padding: 24px; display: flex; flex-direction: column; gap: 12px; scrollbar-width: thin; scrollbar-color: var(--barre-defilement) transparent; }
.msg-thread-body::-webkit-scrollbar { width: 6px; }
.msg-thread-body::-webkit-scrollbar-track { background: transparent; }
.msg-thread-body::-webkit-scrollbar-thumb { background-color: var(--barre-defilement); border-radius: 10px; }
.msg-bubble { position: relative; max-width: 60%; padding: 10px 14px 16px 14px; border-radius: 16px; font-size: 13.5px; line-height: 1.5; }
.msg-bubble.received { background-color: var(--bg); color: var(--ink); align-self: flex-start; border-bottom-left-radius: 4px; }
.msg-bubble.sent { background-color: var(--teal); color: var(--blanc); align-self: flex-end; border-bottom-right-radius: 4px; }
/* L'heure est posée dans le coin, comme sur Kooly.html ; la marge à droite du
   texte lui garde sa place quand la bulle est courte. */
.msg-bubble-texte { display: block; white-space: pre-wrap; overflow-wrap: anywhere; }
.msg-bubble-fichier { display: block; color: inherit; text-decoration: underline; overflow-wrap: anywhere; }
.msg-bubble-time { position: absolute; bottom: 6px; right: 14px; font-size: 11px; opacity: 0.6; white-space: nowrap; }
.msg-bubble { min-width: 90px; padding-bottom: 22px; }
.msg-composer { display: flex; align-items: flex-end; gap: 10px; padding: 16px 22px; border-top: 1px solid var(--line-soft); }
.msg-composer textarea { flex: 1; resize: none; border: 1px solid var(--line); border-radius: 16px; padding: 10px 16px; font-size: 13.5px; font-family: inherit; max-height: 120px; outline: none; color: var(--ink); background: var(--card); }
.msg-composer textarea:focus { border-color: var(--teal); }
.msg-attach-btn, .msg-send-btn { width: 38px; height: 38px; border-radius: 50%; display: flex; align-items: center; justify-content: center; border: none; cursor: pointer; flex-shrink: 0; font-size: 14px; transition: background 0.15s; position: relative; }
.msg-attach-btn { background-color: var(--line-soft); color: var(--muted); }
.msg-attach-btn:hover { background-color: var(--line); }
.msg-attach-btn:focus-within { outline: 2px solid var(--teal); outline-offset: 2px; }
/* Le trombone : un vrai champ de fichier, seulement rangé derrière son icône. */
.msg-attach-btn input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.msg-send-btn { background-color: var(--teal); color: var(--blanc); }
.msg-send-btn:hover { background-color: var(--turquoise-fonce); }
.msg-send-btn:disabled { opacity: .6; cursor: default; }
@media (max-width: 900px) {
  .msg-layout { height: calc(100vh - 220px); border-radius: 0; border-left: none; border-right: none; margin: 0 -20px; }
  .msg-list-pane { width: 100%; border-right: none; }
  .msg-thread-pane { display: none; }
  .msg-back-btn { display: flex; }
  .msg-layout.show-thread .msg-list-pane { display: none; }
  .msg-layout.show-thread .msg-thread-pane { display: flex; }
  .msg-bubble { max-width: 80%; }
}
@media (max-width: 640px) { .msg-layout { margin: 0 -16px; } }

.ticket-tags{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;margin:-6px 0 16px}

/* ----- Mon profil : Sécurité -----
   Une ligne de réglage : la phrase qui dit où l'on en est à gauche, le bouton
   qui en change à droite. */
.rangee-reglage { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.rangee-reglage p { margin: 0; }
.rangee-boutons { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* L'œil se pose dans le champ : il ne lui prend pas de largeur à côté. */
.champ-secret { position: relative; display: block; }
.champ-secret input { width: 100%; padding-right: 46px; }
.oeil-btn { position: absolute; top: 50%; right: 8px; transform: translateY(-50%); display: grid; place-items: center;
  width: 32px; height: 32px; padding: 0; border: 0; border-radius: 50%; background: transparent; color: var(--muted); cursor: pointer; }
.oeil-btn:hover { color: var(--teal); }
.oeil-btn svg { width: 18px; height: 18px; }

/* ----- Mon profil : les cartes au crayon (Kooly.html, .settings-card) -----
   Le cadre lui-même est déjà celui de .panel : mêmes 24 px, même rayon de
   20 px, même ombre. Ne s'ajoutent ici que le crayon en haut à droite, la
   rangée de la photo, et les deux boutons qui n'apparaissent qu'une fois la
   carte ouverte. Une carte fermée n'est pas un formulaire grisé : c'est une
   fiche qu'on lit, et le crayon dit que tout y est modifiable. */
.carte-crayon .panel-header { align-items: flex-start; }
.carte-crayon .panel-header .bouton-stylo { margin-left: auto; }
.carte-crayon .carte-actions { display: none; justify-content: flex-end; gap: 12px; margin-top: 20px; }
.carte-crayon.ouverte .carte-actions { display: flex; }
.carte-crayon .muted { margin-top: 12px; font-size: var(--fs-sm); }
/* Un champ verrouillé se lit comme du texte : pas de case blanche qui appelle
   le curseur alors que rien ne s'y écrira. */
.carte-crayon input[readonly] { background: var(--fond-doux); color: var(--muted); cursor: default; }
/* La photo du profil est ronde — c'est un visage, pas un logo. */
.rangee-photo { display: flex; align-items: center; gap: 18px; }
.rangee-photo.busy { opacity: .55; pointer-events: none; }
.photo-rond {
  display: grid;
  place-items: center;
  width: 72px;
  height: 72px;
  flex: 0 0 auto;
  overflow: hidden;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
  font-weight: 800;
  font-size: var(--fs-lg);
}
.photo-rond img { width: 100%; height: 100%; object-fit: cover; }
.photo-copy { flex: 1; min-width: 12rem; }
.photo-copy strong { display: block; font-size: var(--fs-base); }
.photo-copy span { display: block; margin-top: 2px; font-size: var(--fs-sm); color: var(--muted); }
.photo-actions { display: none; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 12px; }
.carte-photo.ouverte .photo-actions { display: flex; }
.photo-actions label { cursor: pointer; }
/* Le bouton Retirer s'efface quand il n'y a plus de photo : sans cette ligne,
   le display des boutons l'emporterait sur l'attribut hidden. */
.photo-actions [hidden] { display: none; }

/* Le bandeau de dépannage. Ambre plutôt que rouge : ce n'est pas une erreur,
   c'est un état inhabituel dont il ne faut pas s'habituer à la présence. */
.rescue-bar{position:fixed;left:0;right:0;bottom:0;z-index:60;display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px;
 padding:10px 18px;background:var(--navy);color:#FDE68A;border-top:2px solid var(--amber);font-size:.9rem}
.rescue-bar .muted{color:#C99A5B}
.rescue-bar .secondary-btn{margin-left:auto;background:var(--amber);border-color:var(--amber);color:var(--ink)}
.rescue-dot{width:9px;height:9px;border-radius:50%;background:var(--amber);animation:rescuePulse 1.8s ease-in-out infinite}
@keyframes rescuePulse{0%,100%{opacity:1}50%{opacity:.25}}
body.rescuing .main-content{padding-bottom:84px}

/* Le bandeau d'abonnement. Ni ambre — c'est la couleur du dépannage — ni rouge :
   une école qui n'a pas encore renouvelé n'est pas en faute, elle est en attente.
   Quand les deux bandeaux sont là, celui-ci se pose au-dessus de l'autre. */
.abonnement-bar{position:fixed;left:0;right:0;bottom:0;z-index:59;display:flex;flex-wrap:wrap;align-items:center;
 gap:6px 14px;padding:10px 18px;background:var(--navy);color:#F1EDFB;border-top:2px solid var(--teal);font-size:.9rem}
.abonnement-bar .muted{color:#9C8FC7}
body.lecture-seule .main-content{padding-bottom:84px}
body.rescuing .abonnement-bar{bottom:52px}
body.rescuing.lecture-seule .main-content{padding-bottom:136px}

/* Sur un téléphone, la barre du bas est la navigation de l'école : le bandeau
   se pose au-dessus d'elle, jamais par-dessus. Un message qui masque le menu
   ferait plus de dégâts que le silence qu'il remplace. */
@media (max-width:900px){
  .rescue-bar,.abonnement-bar{bottom:var(--mobilenav-h)}
  body.rescuing .abonnement-bar{bottom:calc(var(--mobilenav-h) + 52px)}
  body.rescuing .app-shell,body.lecture-seule .app-shell{padding-bottom:calc(var(--mobilenav-h) + 46px)}
  body.rescuing.lecture-seule .app-shell{padding-bottom:calc(var(--mobilenav-h) + 98px)}
}
@media (max-width:640px){.rescue-bar .secondary-btn{margin-left:0;width:100%}}

/* Sur un téléphone, l'intitulé d'un thème prend déjà toute la largeur : le filet
   qui le prolonge n'aurait plus de place, et un trait d'un millimètre ressemble
   à un défaut d'affichage plutôt qu'à une séparation. */
@media (max-width:640px){
  .journal-theme h4::after{display:none}
  .journal-version{padding-left:24px}
  .journal-carte{padding:6px 16px 16px}
}

/* Un refus de saisie reste sous les yeux, au-dessus du bouton, jusqu'à ce que
   la personne recommence. En bulle il s'effaçait au bout de trois secondes :
   le temps de relire son formulaire, on ne savait déjà plus ce qu'on nous
   reprochait — et c'est là qu'un secrétariat décroche le téléphone. */
.form-erreur {
  margin: 18px 0 0;
  padding: 11px 14px;
  border: 1px solid rgba(229, 72, 77, .28);
  border-left: 3px solid var(--red);
  border-radius: var(--small-radius);
  background: rgba(229, 72, 77, .07);
  color: var(--red);
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: 1.5;
}
.form-erreur + .form-actions { margin-top: 14px; }
/* La case fautive se montre elle-même : la phrase dit laquelle, le cadre la
   désigne, et le curseur s'y pose déjà. */
.champ-fautif { border-color: var(--red) !important; }
.champ-fautif:focus { box-shadow: 0 0 0 3px rgba(229, 72, 77, .18) !important; }

/* Entre 901 et 1180 px, le menu, le panneau de droite et la colonne de gauche ne tiennent plus ensemble sans couper leurs cadres (382 px de large pour trois cartes) : le tableau de bord se lit en descendant, comme sur une tablette. */
@media (min-width: 901px) and (max-width: 1180px) {
  .app-shell.ecran-fixe { display: block; height: auto; overflow: visible; }
  .app-shell.ecran-fixe > .main-content { overflow: visible; }
  .dir-page { height: auto; }
  .dir-cadre { display: flex; flex-direction: column; }
  .dir-gauche { overflow: visible; }
  .dir-argent, .dir-vedette, .dir-vedette:has(> .dir-cours), .dir-vedette:has(> .dir-cours) + .dir-duo { flex: 0 0 auto; max-height: none; }
  .dir-page .panel.tuile { min-height: 14rem; }
  .dir-bandes > .dir-cours { flex: 0 0 auto; }
  .courbe-zone { flex: 0 0 auto; height: 220px; }
}

/* Tarifs courants : un clic remplit le nom, le rythme et le caractère facultatif. */
.pastilles-frais { display: flex; flex-wrap: wrap; gap: 8px; }
.pastille-frais { padding: 7px 12px; border-radius: 999px; border: 1px solid var(--pastille-trait); background: var(--pastille-fond); color: var(--ink); font: inherit; font-size: 0.8125rem; cursor: pointer; }
.pastille-frais:hover, .pastille-frais.choisie { border-color: var(--teal); color: var(--teal); }
/* Écran de bureau étroit : les trois cartes du haut gardent leurs libellés sur une ligne. */
@media (min-width: 901px) and (max-width: 1500px) { .kq { padding: 14px; gap: 8px; } .kq .ico { width: 38px; height: 38px; font-size: 16px; } }
