/* Ecran de chargement plein ecran (voir templates/editor/_ecran_chargement.html pour le
   markup/le JS de retrait) — demande explicite ("ça prend du temps avant d'afficher les
   elements... on peut faire une sorte de splash avec le logo et Belsékens, jolie et
   moderne"). z-index volontairement bien AU-DESSUS du plafond le plus haut deja utilise
   ailleurs dans ce fichier (2400, .courbe-table-assistant-overlay) : cet ecran doit
   toujours gagner face a absolument tout, meme si le filet de securite (retrait de
   secours a 8s, voir le partial) venait a coincider avec une modale quelconque deja
   ouverte au moment ou canvas.js echoue. */
.editor-splash {
    position: fixed;
    inset: 0;
    z-index: 99999;
    display: flex;
    align-items: center;
    justify-content: center;
    background: radial-gradient(circle at 50% 42%, var(--color-surface) 0%, var(--color-bg) 72%);
    transition: opacity 0.5s ease, visibility 0.5s ease;
}

/* Ajoutee par window.hideEditorSplash() une fois le rendu initial reellement peint —
   opacity/visibility plutot qu'un simple display:none, pour un fondu de sortie plutot
   qu'une coupure brutale ("jolie et moderne" pris au serieux jusque dans la disparition,
   pas seulement l'apparition). pointer-events:none des le debut du fondu : sans lui, cet
   overlay plein ecran resterait cliquable (donc bloquant) pendant toute la duree de la
   transition. */
.editor-splash--hidden {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}

.editor-splash__content {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 26px;
    /* Entree en fondu/leger glissement — rien a voir avec le retrait ci-dessus (celui-la
       tourne UNE fois, au premier paint ; celle-ci ne rejoue jamais). */
    animation: editor-splash-in 0.55s cubic-bezier(0.16, 1, 0.3, 1) both;
}

/* Le fichier SVG (deja utilise tel quel sur la page d'accueil, voir templates/accounts/
   home.html:.lp-logo) contient DEJA icone + logotype "Belsékens" dessine a la main dans
   le meme graphique — inutile d'ajouter un texte separe a cote, ce serait redondant avec
   ce que le SVG affiche deja. */
.editor-splash__logo {
    width: 230px;
    height: auto;
    display: block;
    /* Respiration douce plutot qu'un rotate/spin classique — plus sobre, coherent avec
       l'identite "maison de luxe" du reste de l'editeur (voir tokens.css), jamais un
       spinner generique. */
    animation: editor-splash-pulse 2s ease-in-out infinite;
}

/* Barre de progression INDETERMINEE (un segment qui glisse, jamais un vrai pourcentage) —
   meme parti pris deja documente ailleurs dans ce projet pour l'overlay d'export PDF :
   rien ne rapporte d'avancement reel etape par etape ici non plus, un pourcentage inventé
   mentirait. Seul signal honnete : "quelque chose est en cours". */
.editor-splash__bar {
    width: 130px;
    height: 3px;
    border-radius: var(--radius-pill);
    background: var(--color-border);
    overflow: hidden;
}

.editor-splash__bar span {
    display: block;
    height: 100%;
    width: 42%;
    border-radius: var(--radius-pill);
    background: linear-gradient(90deg, transparent, var(--color-accent), transparent);
    animation: editor-splash-slide 1.35s ease-in-out infinite;
}

@keyframes editor-splash-in {
    from { opacity: 0; transform: translateY(10px) scale(0.96); }
    to { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes editor-splash-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.7; }
}

@keyframes editor-splash-slide {
    0% { transform: translateX(-130%); }
    100% { transform: translateX(340%); }
}

/* Colonne fixe des blocs a ajouter : icones seules (plus de place, plus sobre),
   le nom apparait au survol dans une etiquette custom (pas l'infobulle native
   du navigateur, pour rester dans le registre visuel "maison de luxe"). */
/* Retour client direct : 26 boutons en une seule colonne verticale finissaient par
   devenir inaccessibles sans defiler. Regroupes desormais en familles (voir
   .editor-sidebar__group ci-dessous et templates/editor/editor.html) affichees en
   grille — largeur elargie 64px -> 160px pour caser 3 boutons/ligne (40px chacun) sans
   les compresser. Toute la propagation de cette nouvelle largeur (margin-left des 4
   elements qui la compensent, repli JS du controle de zoom) est documentee a cote de
   chacune de ces regles plus bas dans ce fichier — jamais une seule source de verite
   partagee, meme duplication assumee que celle deja en place avant ce changement. */
.editor-sidebar {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    z-index: 60;
    width: 160px;
    display: flex;
    flex-direction: column;
    gap: 0;
    padding: var(--space-3) 0;
    background: var(--color-surface);
    border-right: 1px solid var(--color-border);
    /* Retour direct : "le sidebar... les boutons en bas ne sont pas visibles pour
       certaines hauteurs d'écran" — la colonne n'a jamais eu de defilement, seulement
       top:0/bottom:0 (donc une hauteur BORNEE par la fenetre) sans overflow declare :
       chaque nouveau bouton "Ajouter un bloc" au fil des sessions a fini par depasser du
       bas, sans aucun moyen d'y acceder sur un ecran assez court. Meme defilette fine que
       .page-thumbnails/.block-style-panel__body — un style "moderne", pas la barre de
       defilement brute du navigateur. Gardee en filet de securite meme apres le passage
       en grille par familles (la grille tient sans defiler sur l'immense majorite des
       resolutions desktop, mais un ecran tres court ou un zoom navigateur eleve peut
       encore la faire deborder). */
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: var(--color-border) transparent;
}

/* Une famille de blocs (voir templates/editor/editor.html) : ses boutons en grille
   (flex-wrap, jamais display:grid — reste coherent avec le reste de cette colonne, deja
   en flex avant ce changement), 3 par ligne a la largeur actuelle de la colonne. Un trait
   fin separe chaque famille de la precedente (jamais de titre en texte — "on garde juste
   les icones, et au survol on voit le titre" confirme explicitement pour les boutons
   eux-memes, un titre de famille en plus n'a pas ete demande). */
.editor-sidebar__group {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-2);
}

.editor-sidebar__group:not(:first-child) {
    border-top: 1px solid var(--color-border);
}

.editor-sidebar::-webkit-scrollbar {
    width: 4px;
}

.editor-sidebar::-webkit-scrollbar-track {
    background: transparent;
}

.editor-sidebar::-webkit-scrollbar-thumb {
    background: var(--color-border);
    border-radius: var(--radius-pill);
}

.editor-sidebar__btn {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--color-text);
    font-size: 16px;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
    /* Bug reel trouve en verifiant le correctif de defilement ci-dessus (overflow-y:auto
       sur .editor-sidebar) : sans ceci, un flex-item conserve par defaut flex-shrink:1 —
       des que le nombre de boutons depasse la hauteur disponible, le navigateur les
       COMPRIME tous uniformement (mesure directement : 40px -> ~21px) plutot que de
       laisser deborder le conteneur, ce qui annule silencieusement tout effet de
       overflow-y:auto (rien ne deborde jamais, donc rien ne defile jamais). flex-shrink:0
       force chaque bouton a garder sa taille reelle, ce qui est precisement ce qui fait
       deborder la colonne et declenche enfin le defilement attendu. */
    flex-shrink: 0;
}

.editor-sidebar__btn:hover {
    background: var(--color-surface-alt);
    border-color: var(--color-border);
    color: var(--color-accent);
}

.editor-sidebar__btn:disabled {
    opacity: 0.3;
    cursor: not-allowed;
}

.editor-sidebar__btn:disabled:hover {
    background: transparent;
    border-color: transparent;
    color: var(--color-text);
}

/* Ancien mecanisme (position:absolute; left:100%, revele au survol via opacity) —
   remplace par une infobulle flottante partagee, .sidebar-tooltip ci-dessous. Root
   cause du bug retour direct ("quand on hover le titre affiche (mais masque) ça
   aggrandit la largeur") : .editor-sidebar a du overflow-y:auto (voir son propre
   commentaire, ajoute pour son propre defilement vertical) — la regle CSS Overflow
   "overflow-x devient auto des que overflow-x est visible mais overflow-y ne l'est
   pas" s'applique alors SILENCIEUSEMENT, sans qu'aucune ligne de ce fichier ne
   declare explicitement overflow-x. Consequence mesuree directement (pas supposee) :
   .editor-sidebar reste bien a 64px de LARGEUR CSS, mais sa largeur SCROLLABLE
   (scrollWidth) grimpe a 240px des qu'une etiquette existe dans le DOM (meme a
   opacity:0, un enfant position:absolute contribue quand meme a la zone de
   defilement de son ancetre le plus proche qui defile) — et le morceau d'etiquette
   qui deborde du bord droit (x=64) est litteralement clippe par ce meme
   overflow-x:auto, d'ou "affiche mais masque". Aucune combinaison purement CSS ne
   permet a un enfant de s'echapper d'un ancetre qui defile sur un axe sans etre
   lui-meme clippe sur l'autre (regle du spec, s'applique meme si overflow-x est
   explicitement force a "visible" — le navigateur re-promeut quand meme) : seul un
   element position:fixed, positionne via JS (voir canvas.js:sidebarTooltip),
   echappe reellement a ce clipping. Les spans .editor-sidebar__label restent dans
   le DOM (une par bouton, deja presentes en HTML) comme simple SOURCE DE TEXTE pour
   ce nouveau mecanisme — jamais affichees elles-memes. */
.editor-sidebar__label {
    display: none;
}

/* Infobulle flottante PARTAGEE (une seule instance, deplacee/remplie par JS au
   survol de chaque bouton) — voir canvas.js pour la creation/le remplissage.
   position:fixed + ajoutee a document.body : echappe totalement au clipping/scroll
   de .editor-sidebar (voir le commentaire ci-dessus), donc sa propre largeur ne
   peut plus jamais influencer celle de la colonne. z-index volontairement le plus
   eleve de toute l'app (demande explicite, "le titre doit s'afficher avec le
   z-index le plus grand") — largement au-dessus des modales (500) et des menus
   deroulants de la barre d'outils (460/461), le plafond le plus haut deja utilise
   ailleurs dans ce fichier. */
.sidebar-tooltip {
    position: fixed;
    transform: translateY(-50%);
    background: var(--color-primary);
    color: var(--color-surface);
    padding: 5px 10px;
    border-radius: var(--radius-sm);
    font-family: var(--font-body);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    white-space: nowrap;
    pointer-events: none;
    z-index: 9999;
    display: none;
}

.sidebar-tooltip--visible {
    display: block;
}

.editor-toolbar {
    position: sticky;
    top: 0;
    z-index: 50;
    /* 160px, pas 64px : compense la sidebar elargie en familles (voir .editor-sidebar
       plus haut) — meme decalage que .editor-import-banner/.editor-formatbar/
       .editor-workspace ci-dessous, aucune source de verite partagee (duplication deja
       assumee avant ce changement, juste 4 valeurs a tenir a jour au lieu d'une). */
    margin-left: 160px;
    background: var(--color-surface);
    border-bottom: 1px solid var(--color-border);
    padding: var(--space-2) var(--space-4);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
}

/* Bug reel signale : un menu .editor-toolbar__dropdown-menu OUVERT (z-index:460, voir plus
   bas) peut peindre DERRIERE .editor-formatbar (la barre de mise en forme riche juste en
   dessous, "le toolbar secondaire") — piege de contexte d'empilement CSS, deja rencontre
   plusieurs fois dans ce fichier (voir le modal du bloc graphique, le panneau du bloc
   code...) : .editor-toolbar est lui-meme position:sticky + z-index:50 explicite, donc
   etablit SON PROPRE contexte d'empilement — un descendant comme le menu deroulant, meme
   avec un z-index nominal de 460, n'est jamais compare qu'A L'INTERIEUR de ce contexte ; au
   niveau racine, c'est le z-index de .editor-toolbar LUI-MEME (50) qui compte face a un
   AUTRE element du document. Or .editor-formatbar est aussi position:sticky + z-index:50
   (memes valeurs, egalite parfaite) et vient APRES .editor-toolbar dans le DOM ("toujours
   presente... comme le ruban Word/LibreOffice") — a z-index egal, l'ordre source depart
   age, et l'element le plus tardif peint par-dessus : .editor-formatbar gagne toujours ce
   duel, quel que soit le z-index interne du menu ouvert dans .editor-toolbar.
   Corrige en relevant le z-index de .editor-toolbar LUI-MEME (pas seulement le menu) des
   qu'il contient un dropdown ouvert — :has() re-evalue automatiquement a chaque
   ouverture/fermeture (voir canvas.js:closeAllToolbarDropdowns/toggle), aucun JS
   supplementaire necessaire. 461 : juste au-dessus du z-index local du menu (460, garde
   une numerotation coherente) et toujours strictement sous les overlays modaux (500), pour
   qu'un dropdown reste toujours au-dessus de .editor-formatbar SANS jamais passer devant
   une vraie fenetre modale (Palette/Gabarit...) qui pourrait s'ouvrir depuis ce meme menu. */
.editor-toolbar:has(.editor-toolbar__dropdown--open) {
    z-index: 461;
}

/* Bandeau "mode import" (voir canvas.js:IMPORT_MODE, editor.html) — full-width juste sous
   .editor-toolbar, meme decalage de sidebar (margin-left:64px).
   position:sticky, revenu sur un choix precedent qui le laissait defiler avec la page
   ("purement informatif, pas besoin de rester visible") — demande directe : "le bandeau
   avec les boutons Annuler/Importer doit rester fixe." En pratique, chercher un bloc a
   importer implique souvent de defiler un document source LONG, et sans ce bandeau
   visible en permanence, valider (ou annuler) l'import demandait de remonter tout en
   haut a chaque fois — friction reelle, pas seulement esthetique.
   top: 0 ici n'est qu'un REPLI (avant que le JS ci-dessous n'ait tourne) — la vraie valeur
   est ecrite en style inline par canvas.js:positionImportBanner(), egale a la hauteur
   REELLE rendue de .editor-toolbar (getBoundingClientRect(), pas une valeur fixe codee en
   dur, fragile au premier changement futur de la hauteur de la barre d'outils). .editor-
   formatbar juste en dessous (voir son propre `top:0` plus bas dans ce fichier, meme repli)
   est repositionne separement par sa PROPRE fonction, formatBar.js:positionBar() — ce
   fichier et formatBar.js restent deliberement decouples l'un de l'autre (aucune
   dependance croisee, voir l'en-tete de formatBar.js), donc chacun gere son propre
   bandeau plutot qu'une seule fonction partagee gerant les 3 a la fois.
   z-index:452, memes raisons que .editor-formatbar (voir plus bas) : desormais sticky, ce
   bandeau peut avoir du contenu de page (un bloc en .block--editing a 400, un guide
   d'alignement/lasso a 450) qui defile EN DESSOUS de lui — sans un z-index explicite
   superieur a ces deux valeurs, ce contenu pourrait visuellement le traverser. */
.editor-import-banner {
    position: sticky;
    top: 0;
    z-index: 452;
    margin-left: 160px;
    background: var(--color-accent);
    color: var(--color-surface);
    padding: var(--space-2) var(--space-4);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    font-family: var(--font-body);
    font-size: 14px;
}

.editor-import-banner__actions {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-shrink: 0;
}

/* .btn--quiet ("Annuler") suppose d'habitude un fond clair (--color-muted, un gris
   discret) — illisible sur le fond dore de ce bandeau. Reglage local plutot que toucher
   .btn--quiet lui-meme (utilise partout ailleurs sur fond clair, ou il reste correct). */
.editor-import-banner__actions .btn--quiet {
    color: var(--color-surface);
    border-color: rgba(255, 255, 255, 0.5);
}

.editor-import-banner__actions .btn--quiet:hover {
    background: rgba(255, 255, 255, 0.15);
    color: var(--color-surface);
}

.editor-toolbar__title {
    font-family: var(--font-display);
    font-size: 18px;
    color: var(--color-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    /* Sans ca, cet element (flex-item de .editor-toolbar, display:flex) garde son
       min-width:auto par defaut, qui l'empeche de retrecir en dessous de la largeur
       intrinseque de son propre texte — meme classe de bug deja documentee ailleurs
       dans ce fichier/CLAUDE.md pour d'autres flex-items tronques (overflow:hidden +
       text-overflow:ellipsis seuls ne suffisent jamais dans un contexte flex). Un titre
       d'activite normal reste rarement assez long pour le reveler, mais l'editeur de
       modele (bandeau "Modele" + nom du modele, voir editeur_modele.html) le declenchait
       systematiquement des qu'un nom de modele etait un peu long : la barre d'outils
       entiere, et donc toute la page, debordait horizontalement. */
    min-width: 0;
}

.editor-toolbar__group {
    display: flex;
    gap: 6px;
    align-items: center;
}

.editor-toolbar__sep {
    width: 1px;
    height: 24px;
    background: var(--color-border);
    margin: 0 4px;
}

/* ---- Menus deroulants de la barre principale — regroupement par ROLE (Grouper/Aligner/
   Calque/Mise en page/Document), demande explicite ("la barre en haut est chargée et ça
   depasse l'ecran... regrouper les elements en fonction de leur role, masques, affiches en
   cliquant"). Meme patron exact que .editor-formatbar__dropdown (barre de mise en forme,
   deja etabli plus bas dans ce fichier) : un bouton [data-tb-toggle] bascule une classe
   --open sur le wrapper, jamais un second mecanisme invente pour l'occasion — seul le
   prefixe de classe change (tb au lieu de dd) pour rester dans l'espace de noms de CETTE
   barre plutot que celui du format bar, deux fichiers/contextes independants. ---- */
.editor-toolbar__dropdown {
    position: relative;
}

.editor-toolbar__dropdown-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 34px;
    padding: 0 10px;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--color-text);
    font-family: var(--font-body);
    font-size: 13px;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.editor-toolbar__dropdown-btn:hover,
.editor-toolbar__dropdown--open > .editor-toolbar__dropdown-btn {
    background: var(--color-surface-alt);
    border-color: var(--color-border);
    color: var(--color-accent);
}

.editor-toolbar__dropdown-caret {
    font-size: 9px;
    color: var(--color-muted);
}

.editor-toolbar__dropdown-menu {
    display: none;
    flex-direction: column;
    gap: 2px;
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    min-width: 220px;
    max-height: calc(100vh - 80px);
    overflow-y: auto;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    box-shadow: 0 16px 40px -12px rgba(32, 30, 27, 0.35);
    padding: 6px;
    /* Au-dessus de .block--editing (400, voir plus bas) : un menu ouvert pres du bord de la
       page ne doit jamais se retrouver sous un bloc en cours d'edition. Sous les overlays
       modaux (500, ex. Palette/Gabarit ouverts DEPUIS ce menu) — ordre d'empilement normal,
       le menu se ferme de toute facon des qu'un de ces boutons est actionne (voir
       canvas.js). */
    z-index: 460;
}

.editor-toolbar__dropdown--open > .editor-toolbar__dropdown-menu {
    display: flex;
}

/* Bascule d'ancrage gauche->droite, appliquee dynamiquement en JS (canvas.js) au moment de
   l'ouverture — bug reel signale : le menu "Document", pres du bord droit de la barre
   (juste avant "Retour"), s'ouvrait avec son bord gauche cale sur celui du bouton
   (left:0), poussant son bord droit au-dela de la fenetre et forcant un defilement
   horizontal disgracieux sur toute la page. Un `overflow-x:hidden` global aurait cache le
   symptome sans le corriger (le menu resterait partiellement hors-champ, juste invisible
   au lieu de scrollable) — le vrai correctif est de ne jamais laisser le menu depasser,
   pas de masquer le depassement. `left:auto; right:0` fait pivoter l'ancrage sur le bord
   DROIT du bouton, donc le menu s'etend vers la gauche au lieu de la droite — exactement
   "vers la gauche sans depasser". Fonctionne pour n'importe quel dropdown, pas seulement
   "Document" : la detection cote JS mesure le debordement reel au moment de l'ouverture,
   plutot que de coder en dur "le dernier dropdown est toujours a droite" (resterait
   correct meme si un futur dropdown est ajoute/retire ou si la largeur de fenetre change). */
.editor-toolbar__dropdown-menu--right {
    left: auto;
    right: 0;
}

/* A l'interieur d'un menu deroulant, un .editor-toolbar__icon-btn redevient une rangee
   normale (icone + libelle VISIBLE en ligne, aligne a gauche) plutot qu'un petit carre
   avec etiquette-info-bulle au survol comme dans la barre elle-meme — la place n'est plus
   contrainte a l'interieur d'un menu vertical, autant montrer le texte directement. */
.editor-toolbar__dropdown-menu .editor-toolbar__icon-btn {
    width: 100%;
    height: auto;
    justify-content: flex-start;
    gap: 10px;
    padding: 8px 10px;
}

.editor-toolbar__dropdown-menu .editor-toolbar__icon-label {
    position: static;
    transform: none;
    background: none;
    color: inherit;
    padding: 0;
    opacity: 1;
    font-size: 13px;
    white-space: nowrap;
}

/* Champ "Espacement fixe" (voir canvas.js:applyFixedSpacing) — separee visuellement des
   boutons Aligner/Repartir juste au-dessus par un fin trait, meme famille d'input que le
   reste de l'app (bordure/rayon standard) mais volontairement compacte : c'est une valeur
   unique partagee par les deux boutons "Appliquer horizontalement/verticalement" juste en
   dessous, pas un reglage independant par bouton. */
.editor-toolbar__spacing-row {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 8px 10px 4px;
    margin-top: 4px;
    border-top: 1px solid var(--color-border);
}

.editor-toolbar__spacing-label {
    font-size: 12px;
    color: var(--color-muted);
    white-space: nowrap;
}

.editor-toolbar__spacing-input {
    width: 56px;
    padding: 4px 6px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    font-size: 13px;
    text-align: right;
    font-family: var(--font-body);
    color: var(--color-text);
}

.editor-toolbar__spacing-input:disabled {
    opacity: 0.4;
}

.editor-toolbar__spacing-unit {
    font-size: 12px;
    color: var(--color-muted);
}

/* Deuxieme ligne "Hauteur" du bloc redimensionner (voir canvas.js:matchSize) — reutilise
   .editor-toolbar__spacing-row a l'identique (meme layout flex), sans le trait de
   separation superieur : la ligne "Largeur" juste au-dessus a deja sa propre bordure
   marquant le debut de cette section, une deuxieme juste en dessous ferait doublon. */
.editor-toolbar__spacing-row--noborder {
    border-top: none;
    margin-top: 0;
    padding-top: 2px;
}

/* Boutons "Mini"/"Maxi" du bloc redimensionner — compacts, cote a cote dans la meme ligne
   que leur label (contrairement aux boutons Aligner/Repartir/Espacement au-dessus, qui
   occupent chacun toute la largeur du menu) : ce sont deux variantes d'une meme action
   (largeur/hauteur), pas deux actions independantes meritant chacune sa propre rangee. */
.editor-toolbar__mini-btn {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 4px 8px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--color-text);
    font-family: var(--font-body);
    font-size: 12px;
    cursor: pointer;
}

.editor-toolbar__mini-btn:hover {
    background: var(--color-surface-alt);
    border-color: var(--color-border);
    color: var(--color-accent);
}

.editor-toolbar__mini-btn:disabled {
    opacity: 0.3;
    cursor: not-allowed;
}

.editor-toolbar__mini-btn:disabled:hover {
    background: transparent;
    color: var(--color-text);
}

/* Actions du bandeau superieur : meme registre que .editor-sidebar__btn (icone seule,
   etiquette custom au survol) plutot que des boutons texte — la barre a fini par manquer
   de place au fil des ajouts (bascule de mode, etc.). L'etiquette s'affiche en dessous
   (pas a droite comme la sidebar, verticale elle) pour ne jamais deborder hors de l'ecran
   sur les boutons les plus a droite (Export PDF, Retour). */
.editor-toolbar__icon-btn {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--color-text);
    font-size: 14px;
    cursor: pointer;
    text-decoration: none;
    transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.editor-toolbar__icon-btn:hover {
    background: var(--color-surface-alt);
    border-color: var(--color-border);
    color: var(--color-accent);
}

.editor-toolbar__icon-btn:disabled {
    opacity: 0.3;
    cursor: not-allowed;
}

.editor-toolbar__icon-btn:disabled:hover {
    background: transparent;
    border-color: transparent;
    color: var(--color-text);
}

.editor-toolbar__icon-btn--danger {
    color: var(--color-danger);
}

.editor-toolbar__icon-btn--danger:hover {
    background: var(--color-danger);
    border-color: var(--color-danger);
    color: var(--color-surface);
}

.editor-toolbar__icon-btn--primary {
    background: var(--color-primary);
    color: var(--color-surface);
}

.editor-toolbar__icon-btn--primary:hover {
    background: var(--color-primary);
    border-color: var(--color-primary);
    color: var(--color-surface);
    opacity: 0.85;
}

/* Boutons "aligner en haut/centrer vertical/en bas" (voir editor.html) : reutilisent les
   memes icones fa-align-left/-center/-right que leurs equivalents horizontaux, tournees a
   90° — Font Awesome Free n'a pas d'icone dediee pour un alignement vertical d'OBJETS
   (seulement pour du texte). Seul l'icone tourne, jamais le bouton entier — le libelle
   au survol (.editor-toolbar__icon-label) doit rester lisible normalement. */
.editor-toolbar__icon-btn--rotated i {
    transform: rotate(90deg);
}

/* Controle de zoom du canevas (voir editor.html/canvas.js:setZoom) — trois elements en
   ligne (- / pourcentage / +), meme gabarit que les boutons icone voisins pour les deux
   boutons +/- ; le pourcentage central est lui-meme un bouton (clic = retour a 100%),
   pas juste un texte statique. */
/* Sorti de la barre d'outils (demande explicite : "le zoom tu le places sur le meme axe...
   mais en bas de l'ecran") — flotte desormais en position:fixed au bas de la zone
   d'edition, horizontalement centre sur la meme colonne que le canevas lui-meme (calc()
   ci-dessous compense le meme decalage de 64px reserve a la sidebar que .editor-toolbar/
   .editor-workspace utilisent deja, pour rester centre sur l'espace REELLEMENT disponible,
   pas sur toute la largeur de l'ecran sidebar comprise). Meme registre visuel (fond blanc,
   bordure, ombre) que les autres controles flottants de ce fichier (.page-thumbnails-
   toggle) plutot qu'invente a part. */
.editor-zoom-control {
    position: fixed;
    /* `left` n'est plus un calc() centre sur la zone d'edition — bug reel signale : centre
       sous la page, le controle finissait PILE sur des blocs places en bas de page,
       masquant leur contenu. Repositionne HORS de la page, a droite, avec une marge de
       20px depuis son propre bord droit — demande explicite ("le zoom doit sortir de la
       page... place le a droite a une marge de 20px de la page"). La largeur de la page
       change avec le zoom (transform:scale() sur .page-sheet) et sa position horizontale
       depend de la largeur de fenetre — aucune formule CSS statique ne peut donc suivre ce
       bord de façon fiable ; `left` est recalcule en JS (canvas.js:positionZoomControl,
       appele depuis applyZoom() a chaque changement de zoom/page/redimensionnement de
       fenetre) a partir de la vraie position ecran de la page (getBoundingClientRect()) +
       20px, avec une valeur de repli ici pour le tout premier rendu avant que ce calcul JS
       n'ait tourne une premiere fois. */
    left: calc(160px + (100vw - 160px) / 2 + 397px + 20px);
    bottom: 20px;
    display: flex;
    align-items: center;
    gap: 2px;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: 999px;
    padding: 3px;
    box-shadow: 0 12px 32px -12px rgba(32, 30, 27, 0.4);
    z-index: 60;
}

.editor-zoom-control .editor-toolbar__icon-btn {
    width: 28px;
    height: 28px;
    border: none;
    border-radius: 50%;
}

.editor-zoom-control__label {
    min-width: 44px;
    text-align: center;
    font-size: 12px;
    font-family: var(--font-body);
    color: var(--color-text);
    background: transparent;
    border: none;
    cursor: pointer;
    padding: 4px 2px;
    border-radius: var(--radius-sm);
}

.editor-zoom-control__label:hover {
    background: var(--color-surface-alt);
    color: var(--color-accent);
}

.editor-toolbar__icon-label {
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%) translateY(8px);
    background: var(--color-primary);
    color: var(--color-surface);
    padding: 5px 10px;
    border-radius: var(--radius-sm);
    font-family: var(--font-body);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.15s ease;
    z-index: 70;
}

.editor-toolbar__icon-btn:hover .editor-toolbar__icon-label {
    opacity: 1;
}

/* Bascule Edition / Cours : le mode "cours" simule ce que verrait un eleve — voir
   canvas.js (variable `mode`) pour la logique de restriction (pas de glisser/depot, pas
   d'ajout de bloc/page, seuls les blocs reponse/mindmap restent interactifs). */
.editor-mode-switch {
    padding: 3px;
    background: var(--color-surface-alt);
    border-radius: var(--radius-pill);
    gap: 0;
}

.editor-mode-switch__option {
    position: relative;
    display: flex;
    align-items: center;
    padding: 6px 14px;
    border-radius: var(--radius-pill);
    font-family: var(--font-body);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    color: var(--color-muted);
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
}

.editor-mode-switch__option input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}

.editor-mode-switch__option:has(input:checked) {
    background: var(--color-surface);
    color: var(--color-primary);
    box-shadow: 0 1px 3px rgba(32, 30, 27, 0.15);
}

/* Badge statique remplaçant la bascule quand le mode est impose par le serveur (copie
   verrouillee issue d'un partage, ou consultation en lecture seule) — jamais de radio
   pour en sortir, voir editor.html : {% if lecture_seule %}/{% elif activite.mode_verrouille %}. */
.editor-mode-switch__option--locked {
    padding: 6px 14px;
    color: var(--color-muted);
    cursor: default;
    gap: 6px;
}

/* ---- Barre de mise en forme riche ---- */
/* Toujours presente et occupe toujours sa hauteur (comme le ruban Word/LibreOffice) :
   ne bascule jamais entre display:none et display:flex, pour ne jamais decaler le
   canvas en dessous quand on entre/sort d'un champ riche — seul son etat actif/grise
   change au focus, voir formatBar.js. position: sticky avec un top mesure en JS
   (hauteur reelle de .editor-toolbar) pour rester juste en dessous du header fixe, jamais
   superposee, meme si le titre de l'activite fait retomber le header sur deux lignes.
   z-index releve a 452 — bug reel signale, au passage de la correction du dropdown
   ci-dessus : cette barre (comme .editor-toolbar) n'etablit son propre contexte
   d'empilement que via sa propre valeur de z-index, jamais celle d'un descendant ; a
   l'ancien 50, un bloc en cours d'edition (.block--editing, z-index:400) ou les guides
   d'alignement/lasso (450) — aucun des deux pieges dans un contexte d'empilement
   intermediaire a 100% de zoom, voir .page-sheet/.editor-workspace, ni position ni
   z-index propre — pouvaient peindre PAR-DESSUS cette barre sticky des que leur position
   ecran chevauchait la sienne en scrollant. 452 : juste au-dessus du plus haut palier de
   contenu de page "ordinaire" (450), et reste sous .editor-toolbar quand un dropdown y est
   ouvert (461, voir plus haut) — cette barre ne doit jamais repasser devant un menu
   deroulant qui, lui, s'etend visuellement par-dessus elle. */
.editor-formatbar {
    display: flex;
    position: sticky;
    top: 0;
    z-index: 452;
    margin-left: 160px;
    background: var(--color-surface);
    border-bottom: 1px solid var(--color-border);
    padding: var(--space-2) var(--space-4);
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-2);
}

/* Etat "aucun champ riche actif" — bug reel signale, meme temps que le z-index ci-dessus :
   l'ancienne regle mettait opacity:0.4 sur LE CONTENEUR entier, ce qui rendait aussi son
   propre fond (background:var(--color-surface)) semi-transparent — laissant deviner par
   transparence tout ce qui defile en dessous (canevas/blocs), au lieu d'un vrai fond blanc
   opaque comme le reste du chrome de l'editeur. Le fond doit rester plein en permanence,
   que du texte soit selectionne ou non — seuls les CONTROLES a l'interieur (icones/boutons/
   selects) doivent paraitre grises/inactifs. Fixe en deplacant l'opacite du conteneur vers
   ses enfants directs : le conteneur (fond + bordure) reste toujours 100% opaque, seuls
   .editor-formatbar__group/__sep (les elements visibles a l'interieur) recoivent le
   grisage. pointer-events:none reste sur le conteneur — desactiver l'interaction n'a pas
   besoin d'etre applique enfant par enfant, contrairement au fond visuel. */
.editor-formatbar--disabled {
    pointer-events: none;
}

.editor-formatbar--disabled > * {
    opacity: 0.4;
    transition: opacity 0.15s ease;
}

/* Exception au grisage/desactivation ci-dessus : le CTA "Export PDF" (voir editor.html,
   fin de #formatbar) n'a, contrairement a gras/couleur/alignement/etc., aucun besoin
   d'une selection de texte active pour avoir un sens — il doit rester plein et cliquable
   MEME quand aucun champ n'est selectionne, c'est-a-dire la plupart du temps. Plus
   specifique (deux classes) que la regle generique juste au-dessus (une classe +
   universel) : gagne par specificite, ordre non pertinent. pointer-events:auto
   contre-carre le pointer-events:none pose sur le conteneur .editor-formatbar--disabled
   lui-meme (heritee par tout descendant sauf reactivation explicite comme ici). */
.editor-formatbar--disabled > .editor-formatbar__cta {
    opacity: 1;
    pointer-events: auto;
}

.editor-formatbar__group {
    display: flex;
    gap: 6px;
    align-items: center;
}

.editor-formatbar__sep {
    width: 1px;
    height: 24px;
    background: var(--color-border);
    margin: 0 4px;
}

.editor-formatbar__btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    color: var(--color-text);
    cursor: pointer;
    font-size: 12px;
    transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.editor-formatbar__btn:hover {
    background: var(--color-surface-alt);
}

.editor-formatbar__btn--active {
    background: var(--color-accent-soft);
    border-color: var(--color-accent);
    color: var(--color-accent);
}

.editor-formatbar__btn--disabled {
    opacity: 0.35;
}

/* Bouton "ABC" (majuscules) : meme gabarit que gras/italique/souligne, mais une
   etiquette texte plutot qu'une icone Font Awesome — precedent deja etabli par le
   bouton "∑" (compilation LaTeX) present sur plusieurs blocs. */
.editor-formatbar__btn--text {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.02em;
}

.editor-formatbar__dropdown {
    position: relative;
}

.editor-formatbar__dropdown-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 30px;
    padding: 0 10px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    color: var(--color-text);
    cursor: pointer;
    font-family: var(--font-body);
    font-size: 12px;
}

.editor-formatbar__dropdown-btn:hover {
    background: var(--color-surface-alt);
}

.editor-formatbar__dropdown-btn i {
    font-size: 9px;
    color: var(--color-muted);
}

/* CTA "Export PDF" (voir editor.html) — demande explicite : "bien visible un vrai CTA.
   Sans vraiment casser le visuel existant". Meme gabarit/hauteur (30px) que les autres
   controles de cette barre (.editor-formatbar__dropdown-btn juste au-dessus), pour rester
   dans le meme rythme visuel plutot que d'introduire une taille etrangere — seul le
   remplissage plein (--color-accent, le meme or/dore deja utilise partout ailleurs comme
   signal "special/mise en avant" : pointille de selection, titres de section, badges)
   distingue ce bouton des autres, jamais reserve pour --color-primary (noir), deja le
   signal exclusif d'"Enregistrer" dans la barre principale — reutiliser cette meme
   couleur ici aurait dilue ce qu'elle signifie deja. margin-left:auto le pousse tout a
   droite de la rangee (un flex-item avec une marge auto absorbe tout l'espace disponible
   de ce cote), le separant visuellement des groupes de mise en forme sans avoir besoin
   d'un .editor-formatbar__sep supplementaire. */
.editor-formatbar__cta {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 30px;
    padding: 0 14px;
    margin-left: auto;
    border: 1px solid var(--color-accent);
    border-radius: var(--radius-sm);
    background: var(--color-accent);
    color: var(--color-surface);
    font-family: var(--font-body);
    font-size: 12px;
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
    transition: opacity 0.15s ease;
}

.editor-formatbar__cta:hover {
    opacity: 0.85;
}

.editor-formatbar__cta:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.editor-formatbar__cta i {
    font-size: 13px;
}

.editor-formatbar__menu {
    display: none;
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    min-width: 100%;
    max-height: 240px;
    overflow-y: auto;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    box-shadow: 0 8px 20px -8px rgba(32, 30, 27, 0.3);
    z-index: 80;
}

.editor-formatbar__dropdown--open .editor-formatbar__menu {
    display: block;
}

.editor-formatbar__menu-item {
    display: block;
    width: 100%;
    padding: 6px 12px;
    border: none;
    background: transparent;
    color: var(--color-text);
    text-align: left;
    cursor: pointer;
    font-size: 13px;
    white-space: nowrap;
}

.editor-formatbar__menu-item:hover {
    background: var(--color-surface-alt);
}

/* Police actuellement appliquee au texte selectionne, soulignee dans le menu — retour
   explicite ("on doit souligner le font-family selectionné"), meme principe que
   .editor-formatbar__btn--active pour gras/italique/souligne/alignement/listes juste
   au-dessus, adapte a une liste d'entrees textuelles plutot qu'a des boutons-icones
   (voir formatBar.js:refreshState, qui bascule cette classe via une comparaison
   normalisee sur le PREMIER nom de police de chaque pile, cf. firstFontName). */
.editor-formatbar__menu-item--active {
    text-decoration: underline;
    text-underline-offset: 3px;
    color: var(--color-accent);
    font-weight: 600;
}

/* Auto-apercu des entrees du menu "Titre" (Normal/Titre 1-4) — meme principe que le menu
   "Police" juste au-dessus (chaque entree se rend deja dans sa propre police), applique ici
   a la taille/graisse plutot qu'a la famille de police : tailles reduites par rapport au
   veritable rendu H1-H4 dans le canevas (voir blocks-shared.css) pour rester lisibles dans
   une ligne de menu compacte, tout en gardant une hierarchie visuelle immediatement
   perceptible (H1 nettement plus gros que H4) — le meme esprit que la galerie de styles de
   Word/Google Docs. */
.editor-formatbar__menu-item--h1 { font-size: 20px; font-weight: 700; }
.editor-formatbar__menu-item--h2 { font-size: 18px; font-weight: 700; }
.editor-formatbar__menu-item--h3 { font-size: 16px; font-weight: 600; }
.editor-formatbar__menu-item--h4 { font-size: 14px; font-weight: 600; }

/* Intitule de groupe dans le menu "Police" (Serif/Sans-serif/Script/Monospace, voir
   editor.html) — non cliquable (pas de bouton, jamais [data-font-family]), purement
   visuel : bar.querySelectorAll('[data-font-family]') dans formatBar.js ne le voit donc
   jamais, aucune modification JS necessaire pour l'introduire. Premier groupe sans marge
   superieure (:first-child), les suivants espaces du groupe precedent par une fine
   bordure plutot qu'un fond distinct — reste sobre dans un menu deja dense (35 entrees). */
.editor-formatbar__menu-heading {
    padding: 8px 12px 4px;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-muted);
    border-top: 1px solid var(--color-border);
}

.editor-formatbar__menu-heading:first-child {
    border-top: none;
    padding-top: 8px;
}

.editor-formatbar__color-btn {
    position: relative;
    gap: 2px;
}

.editor-formatbar__color-swatch {
    position: absolute;
    bottom: 3px;
    left: 6px;
    right: 6px;
    height: 3px;
    border-radius: 1px;
    background: var(--color-text);
}

.editor-formatbar__color-input {
    position: absolute;
    width: 0;
    height: 0;
    padding: 0;
    border: none;
    opacity: 0;
    pointer-events: none;
}

/* Le menu couleur n'a pas besoin de suivre la largeur (30px) de son bouton-icone comme
   .editor-formatbar__menu le fait par defaut pour police/taille — une rangee de pastilles
   a besoin de sa propre largeur, generalement plus etroite qu'un menu de texte mais plus
   large que le bouton lui-meme. */
.editor-formatbar__menu--color {
    min-width: 0;
    width: max-content;
    padding: 8px;
}

/* Grille de pastilles "couleurs recentes" (voir recentColors.js:renderColorSwatches) —
   classe partagee par TOUS les selecteurs de couleur de l'editeur (barre de mise en
   forme, fond de cellule du tableau, et tout widget cree via
   recentColors.js:createColorPickerButton — coup de pouce, icone, mindmap, graphique,
   panneau de style generique), un seul jeu de regles pour tous. Grille a colonnes FIXES
   (pas un simple flex-wrap) — demande explicite : reste lisible et facile a cliquer meme
   si la liste s'allonge a l'usage (grandit en hauteur par lignes completes de 5, jamais
   en densite/largeur ni en pastilles minuscules). */
.color-swatches {
    display: grid;
    grid-template-columns: repeat(5, 24px);
    gap: 6px;
}

.color-swatches__item {
    width: 24px;
    height: 24px;
    padding: 0;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    cursor: pointer;
}

/* Bouton "Nouvelle couleur" : pleine largeur de la grille, sur sa propre ligne, separe
   visuellement des pastilles par une bordure — jamais confondu avec une couleur recente,
   contrairement a l'ancien "+" nu de meme gabarit qu'une pastille. */
.color-swatches__custom {
    grid-column: 1 / -1;
    margin-top: 4px;
    padding: 6px 8px;
    border: none;
    border-top: 1px solid var(--color-border);
    border-radius: 0;
    background: transparent;
    color: var(--color-muted);
    font-size: 12px;
    text-align: left;
    cursor: pointer;
}

.color-swatches__custom:hover {
    color: var(--color-accent);
}

.color-swatches__custom i {
    margin-right: 4px;
    font-size: 10px;
}

/* Saisie directe d'un code hexa (recentColors.js:renderColorSwatches) — meme registre
   visuel que .color-swatches__custom juste au-dessus (pleine largeur de la grille, propre
   ligne, separee par une bordure), mais un champ + un bouton plutot qu'un simple bouton. */
.color-swatches__hex-row {
    grid-column: 1 / -1;
    display: flex;
    gap: 4px;
    margin-top: 4px;
    padding-top: 6px;
    border-top: 1px solid var(--color-border);
}

.color-swatches__hex-input {
    flex: 1;
    min-width: 0;
    padding: 4px 6px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    font-family: monospace;
    font-size: 12px;
    color: var(--color-text);
    background: var(--color-surface);
}

.color-swatches__hex-input:focus {
    outline: none;
    border-color: var(--color-accent);
}

/* Code incomplet/invalide au moment de valider — jamais applique, seulement signale
   jusqu'a correction (voir confirmHex dans recentColors.js). */
.color-swatches__hex-input--invalid {
    border-color: var(--color-danger);
}

.color-swatches__hex-confirm {
    width: 28px;
    height: 28px;
    flex-shrink: 0;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    color: var(--color-muted);
    cursor: pointer;
}

.color-swatches__hex-confirm:hover {
    color: var(--color-accent);
    border-color: var(--color-accent);
}

/* Widget de selection de couleur autonome (recentColors.js:createColorPickerButton) — le
   bouton .color-picker-btn__toggle passe lui-meme par la classe fournie par l'appelant
   (ex. .block-icone__color) pour son propre gabarit visuel (deja etabli avant cette
   fonctionnalite), seul .color-picker-btn__menu est generique/partage ici. */
.color-picker-btn {
    position: relative;
    display: inline-flex;
}

/* Gabarit par defaut du bouton-pastille (24x24) — les contextes qui avaient deja leur
   propre classe de taille avant ce widget (.block-icone__color 32x32,
   .block-graphique__series-color 30x30...) l'ecrasent simplement via leur propre regle,
   plus specifique (deux classes sur l'element). */
.color-picker-btn__toggle {
    width: 24px;
    height: 24px;
    padding: 0;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    cursor: pointer;
}

.color-picker-btn__menu {
    display: none;
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    padding: 8px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    box-shadow: 0 8px 20px -8px rgba(32, 30, 27, 0.35);
    z-index: 80;
}

.color-picker-btn--open .color-picker-btn__menu {
    display: block;
}

/* <input type="color"> natif, cache — declenche uniquement par le bouton "+" du popover
   (voir recentColors.js:renderColorSwatches), meme registre que
   .editor-formatbar__color-input/.block-table__bg-input. */
.color-picker-btn__native {
    position: absolute;
    width: 0;
    height: 0;
    padding: 0;
    border: none;
    opacity: 0;
    pointer-events: none;
}

.editor-workspace {
    display: flex;
    justify-content: center;
    margin-left: 160px;
    padding: var(--space-6) var(--space-3);
    background: var(--color-bg);
    min-height: calc(100vh - 60px);
}

.editor-pages {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-6);
}

.page-wrap {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);
}

.page-label-row {
    display: flex;
    align-items: center;
    gap: 6px;
}

.page-label {
    font-family: var(--font-body);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    color: var(--color-muted);
    transition: color 0.15s ease;
}

/* La page active se distingue par sa couleur d'accent — sur l'etiquette et sur
   le contour de la feuille — pour qu'on sache toujours ou atterrit un nouveau
   bloc, meme avec plusieurs pages a l'ecran. */
.page-wrap--active .page-label {
    color: var(--color-accent);
    font-weight: 600;
}

.page-delete-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    border: none;
    background: transparent;
    color: var(--color-muted);
    font-size: 10px;
    cursor: pointer;
    border-radius: var(--radius-sm);
    transition: background 0.15s ease, color 0.15s ease;
}

.page-delete-btn:hover {
    background: var(--color-danger);
    color: var(--color-surface);
}

.page-delete-btn:disabled {
    opacity: 0.25;
    cursor: not-allowed;
    background: transparent;
    color: var(--color-muted);
}

.page-sheet {
    position: relative;
    width: var(--page-width);
    height: var(--page-height);
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    box-shadow: 0 24px 48px -24px rgba(32, 30, 27, 0.25);
    flex-shrink: 0;
    transition: box-shadow 0.15s ease, border-color 0.15s ease;
    /* Ancre tout transform:scale() (zoom manuel du canevas, voir canvas.js:applyZoom) au
       coin haut-gauche plutot qu'au centre (defaut CSS) — sans cette regle, agrandir la
       page la fait grandir dans les DEUX sens depuis son centre, deplacant son coin
       haut-gauche vers des coordonnees NEGATIVES (confirme par mesure directe : -92.75px
       en y a 150% de zoom) tout en desynchronisant screenToPagePoint (qui suppose deja
       que pageRect.left/top correspondent au coin haut-gauche REEL de la page). Deja
       applique ailleurs dans ce fichier pour EXACTEMENT la meme raison (voir
       body.cours-actif .page-sheet et .page-thumbnail__stage .page-sheet plus bas) — cette
       regle de base manquait seulement pour le cas du zoom manuel, jamais couvert par ces
       deux portees existantes. Sans effet tant qu'aucun transform n'est applique (zoom a
       100%), donc aucun risque de regression visuelle la ou le zoom n'est pas utilise. */
    transform-origin: top left;
    /* Police par defaut du contenu de page (texte/question/qcm/situation/tableau/
       reponse/arbreproba...), Arial plutot que Jost — deliberement scope ici, pas dans
       tokens.css:--font-body (qui reste Jost partout ailleurs : sidebar, toolbar,
       tableau de bord, connexion). Deux declarations distinctes necessaires pour couvrir
       les deux facons dont un bloc obtient sa police : la plupart (.block-text,
       .block-question__enonce, .block-qcm__enonce, .block-situation__titre/__corps,
       .block-table, .block-coupdepouce, .block-reponse__line...) n'ont AUCUN
       font-family propre et heritent simplement de la propriete CSS normale — la
       redeclaration ici suffit pour eux. Mais quelques champs de blocks-shared.css
       (arbreproba : node/edge-label/result/level-caption) referencent explicitement
       var(--font-body) au lieu d'heriter — pour eux, seule une redefinition de la
       variable elle-meme (qui se resout a l'usage, plus bas dans cet arbre DOM) les
       fait suivre le meme changement ; sans elle ils resteraient figes sur la valeur
       Jost du :root. Le titre de l'activite (var(--font-display), Playfair) n'est
       delibirement pas touche par aucune des deux — seul le corps de texte change. Le
       menu "Police" de la barre de mise en forme ne lit plus var(--font-body) pour son
       entree "Jost (texte)" (voir editor.html) precisement a cause de cette
       redefinition : sinon choisir "Jost" dans le menu appliquerait Arial par la meme
       variable qu'on vient de detourner ici. */
    font-family: Arial, Helvetica, sans-serif;
    --font-body: Arial, Helvetica, sans-serif;
}

/* Masquee a l'ecran : .page-label (page-wrap) fait deja office de navigation. Revelee
   uniquement sous @media print (voir print.css), qui porte tout le style visuel — la
   meme pastille "n / total" que export_pdf.html:.page-number-print, pour que
   l'impression directe (bouton "Imprimer") et le PDF exporte se ressemblent. */
.page-number-print-only {
    display: none;
}

.page-wrap--active .page-sheet {
    border-color: var(--color-accent);
    box-shadow: 0 0 0 1px var(--color-accent), 0 24px 48px -24px rgba(32, 30, 27, 0.25);
}

/* Repere de marge (8mm) : invisible au repos, revele seulement pendant un
   glisser/redimensionnement pour guider le placement sans encombrer la page
   le reste du temps. En arriere-plan (ajoute avant les blocs) et insensible
   aux clics pour ne jamais gener l'interaction avec le contenu. */
.page-margin-guide {
    position: absolute;
    top: var(--page-margin);
    right: var(--page-margin);
    bottom: var(--page-margin);
    left: var(--page-margin);
    border: 1px dashed var(--color-accent);
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.15s ease;
}

.page-margin-guide--visible {
    opacity: 1;
}

/* Bande reservee en-tete/pied de page (voir canvas.js:HEADER_HEIGHT/FOOTER_HEIGHT et
   renderEnteteEtPiedDePage) — purement indicative, jamais une contrainte physique dure
   (meme philosophie que .page-margin-guide juste au-dessus : rien n'empeche physiquement
   un bloc normal de la chevaucher). Cree une fois par page (createPage()), jamais recree —
   seulement bascule visible/invisible selon entete.actif/piedDePage.actif. Contrairement a
   .page-margin-guide (visible uniquement pendant un glisser en cours), reste affichee en
   PERMANENCE tant que l'en-tete/pied de page est actif — un rappel discret et continu de
   la zone reservee, pas un simple feedback d'aimantation ponctuel. */
.page-entete-pied-guide {
    position: absolute;
    left: 0;
    right: 0;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.15s ease;
    background: color-mix(in srgb, var(--color-accent) 5%, transparent);
}

.page-entete-pied-guide--haut {
    top: 0;
    height: 70px; /* HEADER_HEIGHT, canvas.js — garder synchronise */
    border-bottom: 1px dashed color-mix(in srgb, var(--color-accent) 35%, transparent);
}

.page-entete-pied-guide--bas {
    bottom: 0;
    height: 70px; /* FOOTER_HEIGHT, canvas.js — garder synchronise */
    border-top: 1px dashed color-mix(in srgb, var(--color-accent) 35%, transparent);
}

.page-entete-pied-guide--visible {
    opacity: 1;
}

/* Conteneur des blocs d'en-tete/pied de page (voir canvas.js:buildZoneVivante) —
   deliberement SANS position propre (reste `position: static`, la valeur par defaut) : ses
   enfants .block sont deja position:absolute avec des x/y relatifs a LA PAGE elle-meme
   (exactement le meme espace de coordonnees qu'un bloc normal, voir le plan) — ce wrapper
   ne doit surtout PAS devenir un nouveau contexte de positionnement (position:relative/
   absolute), sous peine de casser ce calcul. Il ne sert qu'a grouper les blocs pour les
   manipuler/retirer ensemble en JS (voir renderEnteteEtPiedDePage). */
.page-entete-pied-zone--lecture-seule {
    /* Ceinture ET bretelles, meme convention que .page-thumbnail__shield : cloneReadOnly()
       retire deja contenteditable/tabindex sur chaque descendant, mais un clone reste
       techniquement une copie complete du DOM interactif (poignees de redimensionnement,
       barres d'outils par bloc...) — pointer-events:none sur tout le conteneur garantit
       qu'AUCUNE interaction n'atteint jamais un clone, quel que soit ce qui pourrait
       localement re-activer les evenements sur un descendant precis. */
    pointer-events: none;
}

/* Guides d'aimantation dynamiques entre BLOCS (pas seulement la marge de page ci-dessus,
   voir canvas.js:snapAxis/collectSiblingTargetsX/Y) — un trait fin qui apparait exactement
   a la position ou le bloc glisse vient d'accrocher le bord/centre d'un AUTRE bloc de la
   meme page, meme principe que les "guides intelligents" de Figma/Illustrator/Canva.
   Cree une fois par page (comme .page-margin-guide), jamais recree a chaque geste — juste
   repositionne (left/top) et bascule visible/invisible pendant le glisser. z-index:450,
   meme valeur que .lasso-select juste en dessous : doit rester visible par-dessus
   n'importe quel bloc, y compris un bloc encore .block--editing (400) au moment ou le
   glisser demarre. */
.block-align-guide {
    position: absolute;
    background: var(--color-accent);
    pointer-events: none;
    z-index: 450;
    display: none;
}

.block-align-guide--v {
    top: 0;
    bottom: 0;
    width: 1px;
}

.block-align-guide--h {
    left: 0;
    right: 0;
    height: 1px;
}

.block-align-guide--visible {
    display: block;
}

/* Rectangle de selection "au lasso" (voir canvas.js:startLassoSelect) — cree/detruit a
   chaque geste (jamais un element permanent reutilise), positionne en pixels dans le
   repere de la page comme un bloc ordinaire. pointer-events:none : ne doit jamais lui-meme
   intercepter le mousemove/mouseup ecoutes sur document pendant le glisser. z-index
   au-dessus de tout bloc, y compris .block--editing (400) : le rectangle doit rester
   visible en glissant par-dessus n'importe quel contenu. */
.lasso-select {
    position: absolute;
    border: 1px dashed var(--color-accent);
    background: color-mix(in srgb, var(--color-accent) 12%, transparent);
    pointer-events: none;
    z-index: 450;
}

/* Fantome de glisser-depose depuis un bouton de la barre laterale (voir
   canvas.js:startSidebarBlockDrag) — cree/detruit a chaque geste, position:fixed (suit le
   curseur en coordonnees ecran, pas relatif a une page comme .lasso-select ci-dessus,
   puisqu'aucun bloc/page n'existe encore a ce stade). pointer-events:none : ne doit jamais
   intercepter lui-meme le point sous le curseur, sinon document.elementFromPoint ne
   trouverait plus jamais la page survolee. z-index 500, meme palier que les overlays modaux
   partages (voir la note "Follow-up bug... modal z-index" dans CLAUDE.md) — au-dessus de
   .block--editing (400) et des guides d'alignement (450), coherent avec le fait qu'aucun
   modal ne peut etre ouvert pendant ce geste. */
.sidebar-drag-ghost {
    position: fixed;
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
    z-index: 500;
    background: var(--color-surface);
    border: 1.5px dashed var(--color-accent);
    border-radius: 8px;
    color: var(--color-accent);
    font-size: 22px;
    opacity: 0.85;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
}

/* Hors de toute page (ex. ramene au-dessus de la barre laterale) : le depot serait annule,
   signale visuellement avant meme le relachement plutot qu'une simple disparition surprise
   au mouseup. */
.sidebar-drag-ghost--invalid {
    border-color: var(--color-danger);
    color: var(--color-danger);
    opacity: 0.55;
}

/* Ajout manuel d'une page, toujours en fin de pile — meme largeur que les
   feuilles pour rester dans l'alignement, style "ghost" en pointilles comme
   les autres affordances d'ajout de l'editeur (ex. le placeholder image du bloc
   situation). */
.add-page-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    width: var(--page-width);
    padding: var(--space-3);
    border: 1px dashed var(--color-border);
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--color-muted);
    font-family: var(--font-body);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    cursor: pointer;
    transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}

.add-page-btn:hover {
    border-color: var(--color-accent);
    color: var(--color-accent);
    background: var(--color-accent-soft);
}

.add-page-btn:disabled {
    opacity: 0.3;
    cursor: not-allowed;
}

.add-page-btn:disabled:hover {
    border-color: var(--color-border);
    color: var(--color-muted);
    background: transparent;
}

/* ---- Bloc : position libre, aucune contrainte d'ordre, aucun padding/marge impose ---- */
.block {
    position: absolute;
    margin: 0;
    padding: 0;
    overflow: visible;
    /* z-index explicite (pas juste l'ordre du DOM) : necessaire pour que les blocs
       decoratifs (ligne/forme, juste en dessous) restent TOUJOURS a l'arriere-plan, peu
       importe l'ordre dans lequel ils ont ete crees/deplaces par rapport aux autres blocs
       — l'ordre source ne depart age que des elements a z-index EGAL, un nombre explicite
       plus bas gagne toujours, quel que soit l'ordre de creation. */
    z-index: 1;
}

/* Blocs decoratifs, sans texte (blocks/forme.js, blocks/fond.js) — demande explicite
   initiale : "ces formes doivent avoir le z-index le plus bas... on peut poser [d'autres
   blocs] dessus" (ex. reconstituer un bandeau de titre exotique avec du texte pose par-
   dessus) — meme palier pour `fond` (voir CLAUDE.md, choix delibere plutot qu'un palier
   encore plus bas : l'ordre naturel de composition — un fond ajoute en premier — suffit
   deja a le faire apparaitre derriere forme aussi). `ligne` a ete RETIRE de ce palier sur
   demande explicite ulterieure ("je veux les voir sur les blocs... sur des blocs parfois
   je veux ajouter des ligne et ça doit être visible") : une ligne decorative doit pouvoir
   se poser PAR-DESSUS un bloc deja existant (souligner/traverser un texte, par exemple) et
   y rester visible, contrairement a forme/fond qui restent, eux, toujours en arriere-plan
   pur. `ligne` retombe donc simplement sur le palier normal (z-index:1, la regle de base
   `.block` juste en dessous) — aucune regle dediee necessaire, un bloc ligne se comporte
   alors exactement comme n'importe quel autre bloc de contenu pour l'empilement (celui
   ajoute/deplace le plus recemment passe au-dessus, meme convention que le reste de
   l'editeur). Scope volontairement limite a ces 2 types restants (pas un systeme de
   calques general pour tous les blocs — option choisie explicitement, plus simple qu'un
   vrai "envoyer a l'arriere" disponible partout). Consequence acceptee pour forme/fond,
   comme dans tout outil a calques (Figma/Canva) : une fois reellement recouvert, seule la
   partie visible/depassante du bloc reste cliquable — cliquer sur la zone de chevauchement
   selectionne le bloc du dessus. */
.block[data-type="forme"],
.block[data-type="fond"] {
    z-index: 0;
}

/* `outline`, pas `box-shadow` — un box-shadow ne peut jamais etre en tirets (pas de
   border-style equivalent pour lui), et outline ne modifie jamais la mise en page
   (comme le ferait border), exactement le meme avantage qui avait fait choisir
   box-shadow a l'origine. outline-offset ecarte legerement le trait du bloc plutot que
   de le coller pile a son bord — rendu "flottant" plus moderne (Figma/Canva), demande
   explicite. */
.block--selected {
    outline: 1.5px dashed var(--color-accent);
    outline-offset: 2px;
}

/* Mode edition (double-clic) : contour plus marque que la simple selection, pour
   signaler clairement qu'on est en train d'editer le contenu et non plus de deplacer
   le bloc (.block__move-layer est masquee dans cet etat — voir plus bas).
   `z-index: 400` (une seule classe, comme la regle de base .block ci-dessus — l'emporte
   par ORDRE DE CASCADE, cette regle etant definie plus loin dans ce fichier) : le bloc
   actuellement edite passe devant TOUS les autres blocs de la page —
   necessaire pour que sa barre d'outils (desormais large de toute la page, voir
   .block-reponse__tools plus bas) ne se retrouve jamais visuellement recouverte par un
   bloc VOISIN qu'elle chevauche. Poser un z-index eleve seulement sur la barre elle-meme
   ne suffirait pas : elle vit a l'interieur de ce wrapper .block, qui a son PROPRE
   contexte d'empilement CSS (position + z-index explicite des sa regle de base) — un
   descendant en position:absolute/fixed n'en sort jamais pour l'ORDRE DE PEINTURE, seul
   son positionnement (ou il est place a l'ecran) s'en affranchit ; le z-index de la barre
   n'est donc jamais compare a celui d'un bloc voisin, seulement a celui de son PROPRE
   wrapper — meme lecon deja tiree, dans ce meme fichier, du bug de la modale du bloc
   graphique (voir .block-graphique--modal-open). Effet de bord assume et plutot
   bienvenu : le bloc activement edite passe entierement devant ses voisins pendant qu'on
   travaille dessus, pas juste sa barre — coherent avec ce que Figma/Canva font deja pour
   l'objet en cours d'edition. */
.block--editing {
    box-shadow: 0 0 0 2px var(--color-accent);
    z-index: 400;
}

.block__content {
    width: 100%;
    height: 100%;
    overflow: visible;
}

/* Couche de deplacement : couvre tout le bloc, un simple clic-glisser suffit a le
   deplacer (comme sur Canva) — plus besoin d'une petite poignee dediee. Un double-clic
   dessus bascule en mode edition (voir canvas.js:enterEdit), ce qui la masque pour
   laisser les clics atteindre normalement le contenu. */
.block__move-layer {
    position: absolute;
    inset: 0;
    /* "Signe deplacer" (les 4 fleches) au survol ET pendant le glisser actif — les deux
       partagent la meme valeur, donc un seul changement CSS couvre les deux etats sans
       avoir besoin d'une classe JS dediee "en cours de glisser" (voir canvas.js:
       attachDrag, dont le mousedown/mousemove/mouseup n'ont pas ete modifies pour ca). */
    cursor: move;
}

.block--editing .block__move-layer {
    display: none;
}

/* 4 poignees independantes (gauche/droite/haut/bas, toujours centrees sur leur bord)
   plutot qu'une seule au coin bas-droit qui redimensionnait largeur+hauteur ensemble —
   demande explicite, meme convention que Canva/Figma/Google Slides : chaque bord ne
   redimensionne QUE sa propre dimension (voir canvas.js:attachResize, parametre `edge`).
   Regles de base communes aux 4 (taille/fond/bordure/visibilite) restent sur la meme
   classe .block__resize-handle qu'avant — print.css et le repli mode Cours ci-dessous
   ciblent deja cette classe generiquement, aucun changement necessaire la-bas. */
.block__resize-handle {
    position: absolute;
    width: 12px;
    height: 12px;
    background: var(--color-accent);
    border: 1px solid var(--color-surface);
    border-radius: 50%;
    display: none;
}

.block--selected .block__resize-handle {
    display: block;
}

.block__resize-handle--e {
    top: 50%;
    right: -6px;
    transform: translateY(-50%);
    cursor: ew-resize;
}

.block__resize-handle--w {
    top: 50%;
    left: -6px;
    transform: translateY(-50%);
    cursor: ew-resize;
}

.block__resize-handle--s {
    left: 50%;
    bottom: -6px;
    transform: translateX(-50%);
    cursor: ns-resize;
}

.block__resize-handle--n {
    left: 50%;
    top: -6px;
    transform: translateX(-50%);
    cursor: ns-resize;
}

/* Icone commentaire (voir canvas.js:attachCommentIcon) : au meme niveau que la rangee
   d'outils du bloc (top:-28px, comme .block-reponse__tools/.mindmap-node__tools), juste a
   droite de la boite plutot que superposee dessus. Masquee par defaut — contrairement aux
   autres barres d'outils de bloc (visibles a la simple selection), celle-ci n'apparait que
   sur double-clic (`.block--comment-revealed`, voir canvas.js:attachDrag/commentRevealedId),
   OU en permanence tant qu'il reste au moins un commentaire "nouveau" (`--nouveau`,
   ci-dessous, voir commentsSeen dans canvas.js) — meme logique de pastille de notification
   qu'un reseau social : des qu'un fil a ete ouvert une fois, l'icone redevient masquee par
   defaut, meme si des commentaires plus anciens (deja lus) existent toujours. Positionnee
   en dehors de .block, donc hors de la zone couverte par .block__move-layer — cliquable
   meme quand le bloc n'entre jamais en edition, ce qui est justement le cas du proprietaire
   du partage consultant la copie de quelqu'un d'autre. */
.block-comment-icon {
    display: none;
    position: absolute;
    top: -28px;
    left: 100%;
    margin-left: 8px;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-muted);
    font-size: 12px;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    box-shadow: 0 2px 6px -2px rgba(32, 30, 27, 0.3);
    z-index: 25;
    transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}

.block--comment-revealed .block-comment-icon,
.block-comment-icon--nouveau {
    display: flex;
}

.block-comment-icon:hover {
    color: var(--color-accent);
    border-color: var(--color-accent);
}

.block-comment-icon--nouveau {
    background: var(--color-accent);
    color: var(--color-surface);
    border-color: var(--color-accent);
}

/* Pastille numerotee (nombre total d'entrees) posee sur l'icone commentaire, que ce soit
   celle d'un bloc (.block-comment-icon) ou celle du bandeau (#header-comment-indicator,
   voir editor.html) — les deux offrent deja un parent en `position` non-statique
   (absolute / relative), donc ce positionnement absolu se cale correctement sur l'un comme
   sur l'autre sans regle dediee. Masquee par defaut (voir canvas.js:refresh/
   refreshHeaderCommentIndicator, qui bascule `display` a la main selon le compte) : une
   pastille a "0" n'a aucun sens a montrer — n'apparait donc que si l'icone-hote est elle
   aussi visible (double-clic manuel sur un fil deja lu, ou etat --nouveau, voir plus haut).
   Neutre (--color-primary) quand l'icone n'est visible que par double-clic manuel sur un
   fil integralement lu ; passe en rouge (--nouveau, --color-danger) quand c'est au
   contraire la presence d'une entree jamais ouverte par ce navigateur (voir commentsSeen
   dans canvas.js) qui justifie la visibilite permanente de l'icone — la pastille reflete
   alors la meme raison que l'icone, jamais l'inverse. */
.comment-count-badge {
    display: none;
    position: absolute;
    top: -5px;
    right: -5px;
    min-width: 16px;
    height: 16px;
    padding: 0 3px;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-pill);
    background: var(--color-primary);
    color: var(--color-surface);
    font-size: 10px;
    line-height: 1;
    font-family: var(--font-body);
    box-shadow: 0 0 0 2px var(--color-surface);
    z-index: 26;
}

.comment-count-badge--nouveau {
    background: var(--color-danger);
}

/* Etat "fusionnee dans la barre __tools" pendant l'edition — voir
   canvas.js:mergeCommentIconIntoToolbar/unmergeCommentIconFromToolbar pour le pourquoi :
   bug reel signale ("le bouton commentaire est masque par le toolbar") depuis que ces
   barres occupent toute la largeur de la page, une barre a plusieurs lignes pouvait
   recouvrir visuellement l'icone restee a sa position fixe (top:-28px; left:100%).
   `position: relative`, PAS `static` (contrairement a .block__style-btn--merged) : cette
   icone porte un enfant absolument positionne (la pastille .comment-count-badge,
   top:-5px/right:-5px) — `static` ne fournirait plus de bloc englobant valide pour ce
   positionnement, `relative` le conserve tout en participant normalement au flux flex de
   la barre d'accueil (flex-wrap:wrap deja en place pour ses propres boutons) — "sur la
   meme ligne s'il y a de la place, sinon a la ligne suivante" en decoule gratuitement,
   sans aucune mesure de mise en page a la main. Reste volontairement ronde (border-radius
   herite de .block-comment-icon, jamais reinitialisee) plutot que calquee sur le gabarit
   carre de .block__style-btn--merged : une identite visuelle distincte (bulle de message)
   reste utile au sein de la barre, ce n'est pas un bouton de la meme famille. */
.block-comment-icon--merged {
    position: relative;
    top: auto;
    left: auto;
    margin-left: 0;
    width: 24px;
    height: 24px;
    box-shadow: none;
    z-index: auto;
}

/* Bouton generique "bordure/arrondi/fond" (voir canvas.js:attachBlockStyleButton) —
   pose directement sur le wrapper .block partage, fonctionne donc identiquement pour
   tous les types de blocs. Meme convention visuelle que .block-comment-icon
   (bouton rond flottant juste au-dessus du bloc), mais ancre a `left:0` — a l'oppose de
   .block-comment-icon (`left:100%`) — pour ne jamais se chevaucher avec lui, ni avec les
   barres __tools propres a certains types (qui restent, elles, ancrees a droite). */
.block__style-btn {
    display: none;
    position: absolute;
    top: -28px;
    left: 0;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-muted);
    font-size: 12px;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    box-shadow: 0 2px 6px -2px rgba(32, 30, 27, 0.3);
    z-index: 25;
}

.block--editing .block__style-btn {
    display: flex;
}

.block__style-btn:hover {
    color: var(--color-accent);
    border-color: var(--color-accent);
}

/* Bouton "Recadrer & filtres" (image.js) — meme gabarit exact que .block__style-btn
   ci-dessus (rond, 26px, fond blanc, bordure, ombre), juste decale de 34px (26 + 8px
   d'ecart) pour ne jamais se superposer a lui : le bloc image a les DEUX boutons (le
   generique bordure/fond ET celui-ci), contrairement a la plupart des autres blocs sans
   barre __tools, qui n'en ont qu'un seul. */
.block-image__masque-btn {
    display: none;
    position: absolute;
    top: -28px;
    left: 34px;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-muted);
    font-size: 12px;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    box-shadow: 0 2px 6px -2px rgba(32, 30, 27, 0.3);
    z-index: 25;
}

.block--editing .block-image__masque-btn {
    display: flex;
}

.block-image__masque-btn:hover {
    color: var(--color-accent);
    border-color: var(--color-accent);
}

/* ---- Poignees "deplacer"/"pivoter" : EN BAS du bloc (15px de marge), a l'oppose de
   .block__style-btn/.block-comment-icon (au-dessus) — demande explicite ("ces deux icones
   doivent être en bas du bloc à 15 px de marge... comme le toolbar un fond blanc de
   z-index plus grand si jamais ça tombe sur un autre bloc"). Meme habillage visuel que ces
   deux boutons generiques (rond, fond blanc, bordure, ombre) : c'est litteralement la
   meme famille de controle (un bouton generique pose sur le wrapper .block, hors des
   fichiers blocks/*.js), seule la position/l'icone changent. Visibles uniquement sur
   .block--editing (double-clic), jamais a la simple selection — meme convention que
   chaque barre __tools de cet editeur (voir la note "Per-block toolbars 'pres du bloc'").
   z-index local 25, comme .block__style-btn : deja suffisant en pratique puisque
   .block--editing eleve le wrapper .block ENTIER (icones comprises, elles en sont des
   descendantes) a z-index:400 au niveau de la pile d'empilement du CANEVAS — la ou un
   chevauchement avec un bloc voisin (lui a z-index:1 au repos) se joue reellement ; aucun
   z-index "de niveau fenetre" separe n'est necessaire ici. */
.block__move-handle,
.block__rotate-handle {
    display: none;
    position: absolute;
    top: calc(100% + 15px);
    width: 26px;
    height: 26px;
    border-radius: 50%;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-muted);
    font-size: 12px;
    align-items: center;
    justify-content: center;
    box-shadow: 0 2px 6px -2px rgba(32, 30, 27, 0.3);
    z-index: 25;
}

.block--editing .block__move-handle,
.block--editing .block__rotate-handle {
    display: flex;
}

.block__move-handle {
    left: 0;
    cursor: move;
}

.block__rotate-handle {
    left: 34px;
}

.block__move-handle:hover,
.block__rotate-handle:hover {
    color: var(--color-accent);
    border-color: var(--color-accent);
}

/* ---- Panneau "bordure/arrondi/fond" : meme patron overlay+panneau que .icon-picker/
   .palette-picker (voir plus bas dans ce fichier), voir canvas.js:openBlockStylePanel.
   Plus compact que les deux autres (3 reglages en ligne, pas une grille de choix).
   z-index:500, pas 300 (voir la note complete plus bas au niveau de .icon-picker-overlay)
   — un bloc en cours d'edition (.block--editing) est lui-meme a z-index:400 depuis que sa
   barre d'outils occupe toute la largeur de la page ; ce panneau, ouvert depuis le bouton
   generique bordure/fond de CE bloc, doit rester au-dessus de son propre bloc hote. ---- */
.block-style-panel-overlay {
    position: fixed;
    inset: 0;
    background: rgba(32, 30, 27, 0.45);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 500;
}

.block-style-panel {
    /* Elargi de 340 -> 400px — bug reel signale et mesure directement : une rangee avec
       libelle + curseur + valeur + bouton "Par defaut" (panneaux Filtres image / Style de
       texte) depassait le bord droit du panneau de 10 a 16px, et un libelle un peu long
       ("Noir et blanc") se repliait sur 3 lignes faute de place. */
    width: min(400px, 92vw);
    background: var(--color-surface);
    border-radius: var(--radius-md);
    box-shadow: 0 24px 60px -16px rgba(32, 30, 27, 0.5);
    /* PAS de overflow:hidden/auto ICI (contrairement au premier jet) : ni __header ni
       __body n'ont leur propre fond distinct (ils laissent voir celui du panneau, deja
       arrondi par border-radius ci-dessus) — overflow:hidden ne servait donc a rien
       visuellement, mais decoupait bel et bien tout enfant qui deborde, quel que soit son
       z-index (un z-index plus fort n'y change rien : overflow:hidden empeche le rendu
       meme d'exister hors de la boite, avant toute question d'empilement) — exactement ce
       qui rognait le popover "couleurs recentes" (recentColors.js) du selecteur de fond,
       signale par un utilisateur reel. Le defilement necessaire pour un contenu trop long
       (voir juste en dessous) est donc scope au SEUL __body, jamais pose ici — un popover
       de couleur ouvert depuis un bouton DANS le body reste a risque de chevaucher la
       limite de defilement de __body elle-meme (accepte, voir __body ci-dessous), mais au
       moins jamais tronque par CE conteneur-ci.
       display:flex column + max-height : bascule ce panneau en "en-tete fixe, corps
       defilant" — retour direct : "le modal du bloc figure géométrique doit être scrollé
       car je ne vois pas le texte en bas" (le panneau grandissait avant ca sans aucune
       limite, capable de deborder bien au-dela du bas de l'ecran sans aucun moyen d'y
       acceder). 48px = les 24px de padding haut+bas de l'overlay (.block-style-panel-
       overlay) qui l'entoure toujours. */
    display: flex;
    flex-direction: column;
    max-height: calc(100vh - 48px);
}

.block-style-panel__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 18px;
    border-bottom: 1px solid var(--color-border);
    font-family: var(--font-body);
    font-weight: 600;
    color: var(--color-text);
    /* Ne retrecit jamais pour laisser de la place a __body — reste toujours visible en
       haut du panneau, meme quand le corps defile. */
    flex-shrink: 0;
}

.block-style-panel__close {
    border: none;
    background: none;
    color: var(--color-muted);
    cursor: pointer;
    font-size: 16px;
    line-height: 1;
    padding: 4px;
}

.block-style-panel__close:hover {
    color: var(--color-danger);
}

.block-style-panel__body {
    display: flex;
    flex-direction: column;
    gap: 16px;
    padding: 18px;
    /* min-height:0 est necessaire pour qu'un enfant flex avec overflow-y:auto respecte
       reellement le max-height du parent (.block-style-panel) — sans ca, un enfant flex
       refuse par defaut de retrecir en dessous de la hauteur de son propre contenu
       (min-height:auto implicite), ce qui aurait rendu le max-height du parent inoperant
       et fait deborder .block-style-panel exactement comme avant ce correctif. Meme
       defilette fine que .page-thumbnails (deja etabli ailleurs dans ce fichier) — un
       style "moderne", pas la barre de defilement brute du navigateur. */
    overflow-y: auto;
    min-height: 0;
    scrollbar-width: thin;
    scrollbar-color: var(--color-border) transparent;
}

.block-style-panel__body::-webkit-scrollbar {
    width: 6px;
}

.block-style-panel__body::-webkit-scrollbar-track {
    background: transparent;
}

.block-style-panel__body::-webkit-scrollbar-thumb {
    background: var(--color-border);
    border-radius: var(--radius-pill);
}

.block-style-panel__row {
    display: flex;
    align-items: center;
    /* Ne deborde JAMAIS horizontalement — une rangee dont les champs ne tiennent pas sur
       une seule ligne (typiquement une carte a plusieurs champs empilee dans un
       conteneur etroit, ex. les traces Thales du bloc figure : 2 selects + 2 champs de
       nom + une case) se replie sur plusieurs lignes plutot que forcer un defilement
       horizontal — bug reel signale : "les points sont en ligne et ça dépasse il faut
       scroller horizontalement... il faut rester sur la largeur de l'écran". Sans effet
       sur une rangee qui tient deja sur une seule ligne (l'immense majorite des cas),
       le wrap ne se declenche que quand il y a reellement manque de place. */
    flex-wrap: wrap;
    gap: 10px;
    font-family: var(--font-body);
    font-size: 14px;
    color: var(--color-text);
}

/* Variante DEDIEE a une paire de champs x;y (decalage manuel d'etiquette, coordonnees d'un
   point...) — demande directe : "d'une maniere generale les champs x et y doivent être sur
   la meme ligne". Le wrap generique de .block-style-panel__row ci-dessus reste correct pour
   une rangee a plusieurs champs heterogenes (selects, cases, texte libre), mais une simple
   paire de 2 petits <input type=number> n'a jamais besoin de se replier — mesure reelle :
   avec le seul flex-wrap:wrap herite, ces 2 champs finissaient sur 2 lignes separees des
   que le panneau etait dans sa colonne etroite (modale plein ecran, colonne "reglages"),
   rendant impossible de voir x et y cote a cote. `min-width:0` sur le label permet a CE
   dernier de retrecir (jamais aux champs numeriques, volontairement fixes) si l'espace
   manque vraiment, plutot que de forcer un retour a la ligne. */
.block-style-panel__row--coords {
    flex-wrap: nowrap;
}

.block-style-panel__row--coords label {
    min-width: 0;
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.block-style-panel__row--coords input[type="number"] {
    flex: 0 0 60px;
    width: 60px;
    min-width: 60px;
}

/* Ne retrecit jamais et ne se replie jamais sur plusieurs lignes (contrairement au
   comportement flex par defaut) — bug reel mesure : un libelle de longueur normale
   ("Noir et blanc") se repliait sur 3 lignes des que la rangee manquait de place,
   plutot que de laisser le curseur (lui, flex:1 1 auto ci-dessous) ceder la place en
   premier — c'est justement lui qui doit retrecir avant le libelle. */
.block-style-panel__row label {
    flex-shrink: 0;
    white-space: nowrap;
}

.block-style-panel__row input[type="range"] {
    flex: 1 1 auto;
    min-width: 40px;
}

.block-style-panel__select {
    flex: 1 1 auto;
    /* Un <select> refuse par defaut de retrecir sous la largeur de sa propre option la
       PLUS LONGUE (min-width:auto implicite d'un item flex) — meme classe de bug deja
       documentee ailleurs dans ce fichier pour un texte non coupable. Avec
       .block-style-panel__row desormais flex-wrap:wrap ci-dessus, ce cas ne devrait
       plus guere se produire (la rangee se replie avant), mais ce filet de securite
       reste utile pour un select isole dont la seule option deja selectionnee est
       longue (ex. "Parallèle (Thalès)", "f1(x) = exp(-x^2/2)"). */
    min-width: 0;
    border: 1px solid var(--color-border);
    background: var(--color-bg);
    color: var(--color-text);
    border-radius: var(--radius-sm);
    padding: 5px 8px;
    font-family: var(--font-body);
    font-size: 13px;
}

.block-style-panel__value {
    flex-shrink: 0;
    width: 40px;
    text-align: right;
    color: var(--color-muted);
    font-size: 13px;
}

.block-style-panel__reset {
    flex-shrink: 0;
    border: 1px solid var(--color-border);
    background: var(--color-bg);
    color: var(--color-text);
    border-radius: var(--radius-sm);
    /* Marge de securite en plus de l'elargissement du panneau ci-dessus (400px) : padding
       et police legerement reduits, jamais au point de devenir illisible. */
    padding: 4px 8px;
    font-size: 11px;
    white-space: nowrap;
    cursor: pointer;
}

/* Titre de section a l'interieur d'un panneau reglant plusieurs groupes de controles
   distincts (ex. Recadrage + Filtres du bloc image) — jamais utilise par ligne.js/forme.js,
   qui n'ont chacun qu'UN seul groupe de reglages. */
.block-style-panel__section-title {
    font-family: var(--font-body);
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-muted);
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid var(--color-border);
}

.block-style-panel__section-title:first-child {
    margin-top: 0;
    padding-top: 0;
    border-top: none;
}

.block-style-panel__reset:hover {
    border-color: var(--color-accent);
    color: var(--color-accent);
}

/* Mode cours (voir canvas.js) : aucun bloc ne se deplace/redimensionne, quel que soit
   son type — seul le contenu interne des blocs reponse/mindmap reste interactif. */
body.mode-cours .block--selected .block__resize-handle {
    display: none;
}

/* Meme repli que ci-dessus pour les poignees deplacer/pivoter — `body.mode-cours` couvre
   deja mode Cours reel, cadenas "Lecture" d'une copie Edition ET IMPORT_MODE (voir
   applyModeUI/structureVerrouillee, canvas.js), donc une seule regle suffit pour les 3 cas.
   `.block--editing` ajoute dans le selecteur (jamais absent : ces poignees ne sont de toute
   facon visibles que la, voir la regle .block--editing .block__move-handle plus haut) pour
   obtenir 3 classes de specificite contre 2 a la regle d'affichage — l'emporte toujours,
   quel que soit l'ordre des regles dans ce fichier, meme garantie que la ligne juste
   au-dessus pour .block__resize-handle. Fonctionnellement deja bloque cote JS
   (structureVerrouillee() dans attachDrag/attachRotateHandle), mais cet editeur masque
   toujours completement un controle inapplicable plutot que de le laisser visible-mais-
   inerte (meme convention que [class*="__tools"] en mode lecture seule). */
body.mode-cours .block--editing .block__move-handle,
body.mode-cours .block--editing .block__rotate-handle {
    display: none;
}

body.mode-cours .block__move-layer {
    cursor: default;
}

/* Toutes les barres d'outils par bloc (compiler LaTeX, ajouter une ligne/colonne, changer
   une image, une forme...) ne s'affichent plus qu'en EDITION (`.block--editing`, double-
   clic) — un simple clic de selection ne les revele plus, demande explicite pour reduire
   l'encombrement visuel une fois plusieurs petits blocs cote a cote (ex. un tableau de
   variation compose a la main de plusieurs blocs). Cette regle de mode cours est donc
   desormais redondante en pratique (canvas.js:estContenuEditable bloque deja l'ENTREE en
   edition pour tout type hors reponse/mindmap en mode cours, donc `.block--editing` n'est
   jamais atteint sur un bloc verrouille) — gardee explicitement en filet de securite plutot
   que supprimee, au cas ou cette garantie venait a changer. `[class*="__tools"]` couvre les
   barres groupees (table/qcm/coupdepouce/graphique/video/icone/mindmap),
   `[class*="__compile"]` les boutons "∑" autonomes (text/situation/titre/question) qui ne
   sont pas dans un conteneur `__tools`. */
body.mode-cours .block:not([data-type="reponse"]):not([data-type="mindmap"]) [class*="__tools"],
body.mode-cours .block:not([data-type="reponse"]):not([data-type="mindmap"]) [class*="__compile"] {
    display: none !important;
}

/* Anti-triche en mode Cours (reel ou previsualise via le radio bouton) — voir
   canvas.js:applyModeUI, body.cours-actif. Deliberement distinct de body.mode-cours (qui
   couvre aussi IMPORT_MODE et le cadenas Lecture d'une copie Edition, deux cas ou masquer
   ces actions n'a pas de sens) : uniquement le vrai mode Cours. Importer un bloc externe,
   changer palette/gabarit, imprimer/exporter le PDF et publier/repartager sont tous des
   moyens de contourner l'evaluation — entierement retires de la barre d'outils (pas juste
   grises), il ne reste que Enregistrer et Retour.
   #btn-publier-modele ajoute apres coup, signale directement ("l'eleve ne doit pas
   pouvoir partager (comme template)... griser comme les autres") : ce bouton publie
   l'activite comme modele PUBLIC (voir apps/editor/views.py:publier_comme_modele) — un
   eleve sur sa propre copie Cours-verrouillee en est le proprietaire, donc rien ne
   l'empechait jusqu'ici de publier le contenu de son professeur. Meme fix cote
   tableau de bord pour le bouton "Partager" lui-meme, voir templates/courses/dashboard.html
   (deja grise dans les memes conditions que Copier/Renommer/Supprimer, hors de ce fichier). */
body.cours-actif #btn-palette,
body.cours-actif #btn-gabarit,
body.cours-actif #btn-style-document,
body.cours-actif #btn-import-bloc,
body.cours-actif #btn-print,
body.cours-actif #btn-export-pdf,
body.cours-actif #btn-publier-modele {
    display: none !important;
}

/* ---- Mode Cours responsive (telephone) ----
   Scope volontairement etroit : uniquement body.cours-actif (mode Cours reel OU
   previsualise, jamais desktop/Edition — voir canvas.js) ET sous MOBILE_BREAKPOINT
   (768px, doit rester synchronise avec la constante JS du meme nom dans canvas.js).
   Contexte complet dans CLAUDE.md "Mode Cours responsive (telephone)" — resume : aucun
   glisser/redimensionnement n'existe deja en mode Cours (structureVerrouillee()), donc
   ceci ne touche QUE de l'affichage (largeur laterale, en-tete, echelle visuelle de la
   page) — zero recalcul de coordonnees necessaire, contrairement a ce qu'un zoom sur un
   canevas encore glissable aurait exige. */
@media (max-width: 768px) {
    /* Filet de securite INCONDITIONNEL (ni body.cours-actif, ni aucune autre classe posee
       par JS) : canvas.js:updateMobilePageScale() ne peut s'executer qu'une fois le
       script module charge, apres le premier rendu de la page — pendant ce court
       intervalle, .page-sheet peint brievement a sa taille reelle (794px), plus large que
       l'ecran. Un navigateur mobile reel (et l'emulation Chromium/Playwright qui la
       reproduit fidelement) elargit alors sa "layout viewport" pour englober ce
       debordement transitoire — et NE la retrecit plus jamais spontanement une fois JS
       revenu corriger la largeur relle affichee, meme apres coup. Resultat observe et
       confirme via Playwright : window.innerWidth se retrouve fige bien au-dela de la
       largeur de l'ecran physique, malgre un canevas visuellement correct. Seul un
       overflow-x:hidden actif des la toute premiere peinture (donc jamais tributaire du
       timing JS) empeche le navigateur de percevoir ce debordement initial. Volontairement
       pas conditionne a body.cours-actif : l'edition normale (Edition/hors Cours) n'a de
       toute facon jamais ete pensee pour un ecran de telephone (glisser/redimensionner y
       restent necessaires), donc ce filet ne degrade rien qui fonctionnait deja. */
    html, body {
        overflow-x: hidden;
    }

    /* Le zoom manuel n'a aucun effet dans ce contexte (canvas.js:applyZoom() s'annule
       volontairement des que mode==='cours' && estMobile(), voir son propre commentaire) —
       la reduction automatique deja en place y suffit ; afficher un controle qui ne ferait
       rien pretendrait a une capacite inexistante ici. */
    body.cours-actif .editor-zoom-control {
        display: none;
    }

    /* Barre laterale d'ajout de bloc : sans objet en mode Cours (rien a ajouter), et
       prend une part significative d'un ecran de telephone. Les 4 elements suivants
       compensent tous le meme margin-left:160px (reserve pour cette barre, elargie
       64px -> 160px depuis son passage en grille de familles, voir .editor-sidebar plus
       haut) — sans ca, un vide de 160px resterait a gauche une fois la barre elle-meme
       masquee. */
    body.cours-actif .editor-sidebar {
        display: none;
    }
    body.cours-actif .editor-toolbar,
    body.cours-actif .editor-import-banner,
    body.cours-actif .editor-formatbar,
    body.cours-actif .editor-workspace {
        margin-left: 0;
    }

    /* En-tete reduit a Enregistrer + Retour, litteralement demande ("l'header on affiche
       juste les boutons retour et enregistrer") — le meme principe "liste blanche par
       exclusion" que l'anti-triche ci-dessus, en plus large : le switch Edition/Cours et
       le groupe entier Dupliquer/Supprimer/Grouper/Degrouper/Importer/Annuler/Retablir
       (deja tous sans objet ou deja verrouilles en mode Cours) disparaissent entierement ;
       dans le groupe restant (celui qui contient Enregistrer ET Retour), tout sauf ces
       deux boutons disparait aussi — couvre Palette/Gabarit/Dupliquer pour moi/
       l'indicateur de commentaires/l'horloge/Imprimer/Export PDF sans avoir a les lister
       un par un, et couvre aussi tout futur bouton ajoute a ce groupe sans qu'il faille se
       souvenir de revenir modifier cette regle. :has() avec une liste de selecteurs (deja
       utilise ailleurs dans ce fichier/le reste de l'app, ex. les cartes du tableau de
       bord) plutot qu'un id unique, puisque les deux boutons a garder ne vivent pas
       forcement toujours dans le meme groupe selon l'etat du gabarit HTML. `.editor-
       toolbar__title` n'a plus besoin de figurer ici : le titre du document a ete retire
       du header dans TOUS les modes (voir plus bas, "gain d'espace"), plus seulement sur
       mobile. */
    body.cours-actif .editor-mode-switch,
    body.cours-actif .editor-toolbar__group:not(:has(#save-btn, #btn-retour)) {
        display: none;
    }
    body.cours-actif .editor-toolbar__group:has(#save-btn, #btn-retour) {
        width: 100%;
        justify-content: space-between;
    }
    body.cours-actif .editor-toolbar__group:has(#save-btn, #btn-retour) > *:not(#save-btn):not(#btn-retour) {
        display: none;
    }
    /* Retour a gauche, Enregistrer a droite — convention mobile standard (bouton retour
       en haut a gauche), inversee par rapport a l'ordre naturel du DOM (Enregistrer vient
       avant Retour dans editor.html) ; `order` seul suffit, aucun changement de DOM. */
    body.cours-actif #btn-retour {
        order: -1;
    }

    /* Etiquette de page (numero + bouton supprimer) : sans objet, un eleve ne peut pas
       supprimer de page en mode Cours — masquee pour simplifier .page-wrap a un seul
       enfant (voir canvas.js:updateMobilePageScale, qui n'a alors pas a soustraire sa
       hauteur du calcul). */
    body.cours-actif .page-label-row {
        display: none;
    }
    /* .page-wrap devient la "scene" a l'echelle reduite (voir canvas.js pour le
       width/height/transform poses en JS, calcules dynamiquement selon la largeur
       d'ecran) — align-items passe de "center" (par defaut, pour une page a sa taille
       normale) a "flex-start" : .page-sheet garde sa largeur CSS NATIVE (794px, transform
       ne change jamais la boite de mise en page, seulement le rendu), centrer cette boite
       encore pleine grandeur dans une "scene" bien plus etroite couperait la moitie
       gauche visible au lieu d'aligner le coin haut-gauche deja mis a l'echelle par
       transform-origin:top left (voir canvas.js). */
    body.cours-actif .page-wrap {
        align-items: flex-start;
        overflow: hidden;
    }
    body.cours-actif .page-sheet {
        transform-origin: top left;
    }

    /* Un seul tap suffit pour taper une reponse ou cocher une case QCM — pas de double-
       tap (confondu avec le zoom natif du telephone, laisse actif expres pour le confort
       de frappe — voir CLAUDE.md). .block__move-layer normalement necessaire pour
       distinguer un clic de selection d'un double-clic d'edition (voir attachDrag) n'a
       plus lieu d'etre pour ces 2 types en mode Cours : aucun glisser n'est jamais permis
       ici de toute facon, donc rien ne justifie de continuer a intercepter le premier tap
       — le masquer laisse le tap atteindre directement la case a cocher ou le champ de
       reponse (comportement natif du navigateur, aucun JS necessaire). Scope
       volontairement etroit a ces 2 seuls types (voir canvas.js:
       COURS_INTERACTIVE_TYPES_MOBILE, qui exclut encore mindmap/image sur mobile) —
       jamais un simple `body.cours-actif .block .block__move-layer` generique, qui
       casserait aussi la selection/le double-clic sur les types NON interactifs. */
    body.cours-actif [data-type="reponse"] .block__move-layer,
    body.cours-actif [data-type="qcm"] .block__move-layer {
        display: none;
    }

    /* Horloge flottante : masquee entierement en mode Cours mobile, quelle que soit la
       preference localStorage (voir .editor-clock--hidden dans clock.js) — son propre
       bouton bascule (#btn-toggle-clock) est deja masque par la reduction de l'en-tete
       ci-dessus, donc sans cette regle un eleve qui l'avait un jour laissee affichee
       n'aurait plus aucun moyen de la faire disparaitre, et un cercle fixe de 150px
       flottant a droite de l'ecran occuperait une part disproportionnee d'un telephone. */
    body.cours-actif .editor-clock {
        display: none;
    }

    /* Note /20 : reste visible (utile a l'eleve de la voir), mais quitte le flottement
       fixe pour le flux normal — un pastille position:fixed ancree au bord droit de
       l'ecran, pensee pour ne jamais chevaucher la feuille sur un large ecran desktop,
       se retrouve sur un telephone etroit soit a cheval sur le canevas, soit hors champ.
       position:static + centree via des marges auto la fait simplement apparaitre en
       flux, juste apres l'en-tete reduit (voir le reordonnancement DOM dans editor.html,
       sans effet en desktop ou l'element reste position:fixed) — meme apparence en pilule
       (fond/rembourrage/coins arrondis inchanges), seul son mode de positionnement change. */
    body.cours-actif .editor-grade {
        position: static;
        transform: none;
        margin: 10px auto 0;
        width: fit-content;
    }
}

/* Lecture seule (proprietaire du partage consultant la copie de quelqu'un d'autre), ou
   copie mode Edition avec le cadenas sur "Lecture" : plus strict que le mode cours normal
   — meme reponse/mindmap n'ont plus leur barre d'outils, rien n'est interactif nulle part.
   canvas.js empeche deja l'entree en edition (voir estContenuEditable), ceci n'est que la
   contrepartie visuelle : sans lui, la barre d'un bloc reponse/mindmap resterait visible
   (juste inerte) au survol/a la selection. */
body.mode-lecture-seule [class*="__tools"],
body.mode-lecture-seule [class*="__compile"] {
    display: none !important;
}

/* Barres d'outils "pres du bloc" en edition (table/qcm/reponse/notion/ligne/forme/video/
   icone/code/graphique/le bouton generique bordure-fond .block__style-btn) — chacune reste
   simplement position:absolute; top:-Npx RELATIVE AU BLOC (voir la regle de base de chaque
   bloc plus bas dans ce fichier), rien de plus.
   Un mecanisme "docked" (position:fixed + recalcul JS au scroll/resize/ResizeObserver,
   masquage quand le bloc sort de l'ecran) a ete tente ici — cense resoudre le cas d'un bloc
   pres du haut de page (barre passant derriere l'en-tete sticky) ou d'un bloc tres haut
   (barre ancree a son bord superieur, hors champ en travaillant sur le bas du bloc) — puis
   retire ENTIEREMENT, apres plusieurs corrections successives (centree-fixe, puis
   ancree-au-bloc-mais-fixe) toutes rejetees par l'utilisateur : "ça doit pas etre fixe, on
   ne doit pas le voir si on ne voit pas le bloc". Un vrai bug reproductible en usage reel
   ("la barre s'affiche une fois puis plus jamais au clic suivant") a ete signale a
   plusieurs reprises sur plusieurs blocs differents (.block-latex-tools d'abord, puis
   .block-reponse__tools et .block-table__tools) sans jamais etre reproductible par les
   tests Playwright — plutot que de continuer a chasser ce bug dans un mecanisme dont le
   comportement voulu (rester visible a l'ecran independamment du defilement du bloc) n'etait
   de toute facon pas ce qui etait demande, la solution retenue est de ne plus l'utiliser du
   tout, pour aucune de ces barres. Un position:absolute simple defile NATURELLEMENT avec la
   page (meme flux que le bloc) : il disparait de la fenetre exactement quand le bloc en
   sort, sans le moindre code JS pour le detecter — voir CLAUDE.md pour l'historique complet.
   Les barres a nombreux boutons (le tableau, 13 boutons etiquetes) passent sur plusieurs
   lignes au besoin via flex-wrap + max-width, directement sur leur regle de base — voir
   plus bas dans ce fichier pour chaque bloc concerne. */

/* ---- Bloc texte ---- */
.block-text-wrap {
    position: relative;
    height: 100%;
}

.block-text {
    /* var(--default-font-size, 16px)/var(--default-text-color, ...) : suit le reglage
       "Style du document" par activite (voir Activite.police_defaut et alentours,
       apps/courses/models.py) — repli exact sur les valeurs d'avant ce reglage pour
       toute activite ne l'ayant jamais touche, donc aucun changement visuel pour elles. */
    font-size: var(--default-font-size, 16px);
    color: var(--default-text-color, var(--color-text));
    /* 1.5 (24px) juge trop aere par rapport au PDF de reference fourni par
       l'utilisateur, qui a un interlignage nettement plus serre — reduit a 1.2
       (~19px, environ -5px), un premier essai a affiner selon retour. */
    line-height: 1.2;
    outline: none;
    /* Override delibere du `overflow-wrap: break-word` herite de .block (voir
       blocks-shared.css) — demande explicite : "la plus petite largeur doit être la
       largeur d'une lettre" (le min-width mot-a-mot juge "un peu grand"). `break-word`
       ne reduit JAMAIS la taille minimale intrinseque en dessous du MOT le plus large
       (c'est precisement ce qui donnait, avant ce changement, un plancher a la largeur
       du mot entier — voir dynamicMinWidth() dans canvas.js) ; `anywhere` autorise le
       navigateur a couper n'importe ou, donc `min-content` (utilise par
       dynamicMinWidth()) peut descendre jusqu'a la largeur du plus large CARACTERE
       isole du texte, jamais moins (il doit toujours rester la place pour au moins un
       glyphe sans le tronquer). Scope a .block-text seul, jamais a .block/.block-print
       (qui restent en break-word pour tout le reste — le bloc table, lui, a deja sa
       propre raison independante d'utiliser `anywhere`, voir sa note dediee plus bas). */
    overflow-wrap: anywhere;
}

.block-text ul,
.block-text ol {
    margin: 4px 0 8px;
    padding: 0 0 0 18px;
}

.block-text li {
    margin: 0 0 2px;
    /* Meme raison que .block-text ci-dessus — explicite plutot que simplement
       herite, pour rester ajustable independamment du texte courant si besoin. */
    line-height: 1.2;
}

/* Le navigateur applique une marge par defaut aux <p>/<div> (separateur de
   paragraphe par defaut d'un contentEditable) : sans reset, ca cree un faux
   padding et decale le contenu du bord du bloc — exactement l'effet qu'on
   evite deja dans la grille du tableau, qui remplit son bloc au pixel pres. */
.block-text p,
.block-text div,
.block-question__enonce p,
.block-question__enonce div,
.block-qcm__choix-texte p,
.block-qcm__choix-texte div,
.block-situation__titre p,
.block-situation__titre div {
    margin: 0;
}

/* Bouton "inserer une formule" (voir formulaPicker.js) — meme apparence carree que le
   bouton "∑" a cote duquel il vit toujours, une seule classe partagee plutot que
   redupliquee par bloc puisque son apparence ne varie jamais d'un bloc a l'autre
   (contrairement a .block-text__compile et ses jumelles, qui predatent ce bouton et
   restent chacune leur propre classe). Pour qcm/notion/reponse/table, ce bouton est
   simplement ajoute dans le .xxx__tools existant (affichage/position deja geres par ce
   conteneur). Pour les 4 blocs dont le "∑" etait un bouton AUTONOME (text/question/
   situation/titre), voir .block-latex-tools plus bas — les deux boutons sont regroupes
   dans une seule barre, meme registre visuel que .block-table__tools/.block-qcm__tools
   (fond blanc, bordure, ligne horizontale) — voir CLAUDE.md pour l'historique complet des
   essais "docked" rejetes. */
.block-formula-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-accent);
    border-radius: var(--radius-sm);
    width: 24px;
    height: 24px;
    cursor: pointer;
    font-size: 11px;
}

.block-formula-btn:hover {
    background: var(--color-accent);
    color: var(--color-surface);
}

/* Meme gabarit que .block-formula-btn juste au-dessus (24x24, meme registre visuel) —
   voir mathliveEditor.js pour ce que ce bouton ouvre. */
.block-mathlive-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-accent);
    border-radius: var(--radius-sm);
    width: 24px;
    height: 24px;
    cursor: pointer;
    font-size: 11px;
}

.block-mathlive-btn:hover {
    background: var(--color-accent);
    color: var(--color-surface);
}

/* Conteneur partage du "∑" + "inserer une formule" pour les 4 blocs dont le "∑" est un
   bouton AUTONOME (text/question/situation/titre). Meme registre visuel que
   .block-table__tools/.block-qcm__tools (fond blanc, bordure, flex row) — chaque bouton
   a l'interieur garde en plus sa propre bordure/fond (meme convention que
   .block-table__tools button). Simple position:absolute; top:-42px; right:0 relative au
   bloc lui-meme, sans aucun calcul JS — comme toutes les autres barres d'outils par bloc
   de ce fichier (voir le commentaire juste avant .block-reponse__tools/.block-table__tools
   pour l'historique complet du mecanisme "docked" essaye puis retire partout). `top:-42px`
   (plutot que le -32px/-28px des autres barres) laisse une marge reelle entre le bas de la
   barre et le bord du bloc plutot que de le toucher — demande explicite ("entre le bloc et
   le toolbar on doit avoir une marge"). Contient aussi le bouton generique bordure/fond
   (.block__style-btn, voir canvas.js:attachBlockStyleButton) pour ces 4 blocs specifiquement
   — demande explicite ("tous ces elements doivent etre dans le toolbar"), voir
   attachBlockStyleButton pour le detail de la fusion. z-index a 30, une valeur locale a la
   pile de positionnement du bloc lui-meme (au-dessus du bouton bordure/fond standalone, 25,
   et de tout contenu de bloc, 1) — pas un z-index de niveau fenetre, cette barre n'etant
   jamais fixed. */
.block-latex-tools {
    position: absolute;
    /* bottom (pas top) : le bord BAS de la barre reste fixe a 10px au-dessus du bord
       superieur du bloc quel que soit le nombre de lignes sur lesquelles elle
       s'affiche (flex-wrap) — un `top` fixe grandirait la barre VERS LE BAS a mesure
       qu'elle passe sur plusieurs lignes, rognant la marge au lieu de la preserver.
       Voir le commentaire complet juste avant .block-reponse__tools plus bas. */
    bottom: calc(100% + 10px);
    right: 0;
    display: none;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 4px 5px;
    box-shadow: 0 6px 16px -6px rgba(32, 30, 27, 0.35);
    z-index: 30;
}

.block--editing .block-latex-tools {
    display: flex;
}

/* Bouton bordure/fond une fois imbrique dans .block-latex-tools (voir
   attachBlockStyleButton) : neutralise son positionnement/forme "standalone" (cercle
   flottant en position:absolute) pour redevenir un bouton carre normal DANS le flux de
   la barre qui l'accueille (table/qcm/reponse/notion/ligne/forme/video/icone/code/
   graphique/latex-tools) — meme gabarit que les autres boutons de barre (24x24,
   border-radius: var(--radius-sm)) plutot que le cercle 26x26 qu'il garde uniquement
   pour les rares types de bloc sans aucune barre __tools (image, mindmap, arbreproba,
   fleche). `.block__style-btn--merged` (ajoutee par attachBlockStyleButton uniquement
   dans ce cas, voir canvas.js) plutot qu'un selecteur descendant `.xxx__tools
   .block__style-btn` : une seule regle couvre les 11 barres possibles sans avoir a
   enumerer chacune ici. Specificite (2 classes) suffisante pour l'emporter sur la regle
   de base .block__style-btn (1 classe) sans avoir besoin de !important. */
.block__style-btn--merged {
    position: static;
    width: 24px;
    height: 24px;
    border-radius: var(--radius-sm);
    box-shadow: none;
    z-index: auto;
}

/* Bouton "compiler les formules" — apparence propre uniquement, position/affichage geres
   par le conteneur .block-latex-tools ci-dessus (comme .block-table__tools button). */
.block-text__compile {
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-accent);
    border-radius: var(--radius-sm);
    width: 24px;
    height: 24px;
    cursor: pointer;
    font-family: var(--font-display);
}

.block-text__compile:hover {
    background: var(--color-accent);
    color: var(--color-surface);
}

/* Colonnes/lettrine — deux controles supplementaires dans .block-latex-tools (voir text.js),
   meme gabarit 24px de haut/bordure/rayon que les autres boutons de cette barre, sans
   panneau dedie (un seul reglage ferme pour l'un, un simple interrupteur pour l'autre). */
.block-text__colonnes {
    height: 24px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    color: var(--color-text);
    font-size: 11px;
    padding: 0 4px;
    cursor: pointer;
}

.block-text__lettrine-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 24px;
    padding: 0 8px;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-text);
    border-radius: var(--radius-sm);
    font-size: 11px;
    cursor: pointer;
}

.block-text__lettrine-btn:hover {
    border-color: var(--color-accent);
    color: var(--color-accent);
}

.block-text__lettrine-btn--active {
    background: var(--color-accent);
    border-color: var(--color-accent);
    color: var(--color-surface);
}

/* Lettrine : premiere lettre du premier paragraphe agrandie, calee a gauche, le reste du
   paragraphe s'enroulant autour (technique CSS standard ::first-letter + float). Deux
   selecteurs, pas un seul — bug reel trouve a la verification : selectionner tout (Ctrl+A)
   puis retaper du texte peut laisser le navigateur remplacer le contenu par un simple
   NOEUD TEXTE directement enfant de .block-text (pas de <p>/<div> l'enveloppant), auquel
   cas "> *:first-child::first-letter" (qui exige un enfant ELEMENT) ne correspond a rien
   du tout — confirme par mesure directe (aucun changement de taille apres activation).
   ".block-text[...]::first-letter" seul (sans enfant) couvre ce cas — un ::first-letter
   pose directement sur un conteneur cible bien le premier texte, meme place a meme un
   noeud texte direct, tant qu'aucun enfant BLOC (comme <p>) ne s'interpose avant. Garder
   les deux : le cas le plus courant reste le <p> de DEFAULT_CONTENT.text.html, mais aucun
   des deux seuls ne couvre les deux formes de DOM possibles a lui seul. */
.block-text[data-lettrine="true"]::first-letter,
.block-text[data-lettrine="true"] > *:first-child::first-letter {
    float: left;
    font-family: var(--font-display);
    font-size: 3.2em;
    line-height: 0.82;
    font-weight: 700;
    padding: 4px 6px 0 0;
    color: var(--color-accent);
}

/* Ecart lettrine/suite du texte, demande explicite ("la position de la lettre en grand
   c'est bon... c'est la suite qui est un peu haute, ça ne doit pas etre au meme niveau que
   la lettrine"). Verifie empiriquement trois fois (mesure directe des rectangles de texte
   via Range, pas assume) que ni padding-top ni margin-top sur ::first-letter ne bougent
   JAMAIS le texte enroule a cote — ces proprietes ne deplacent que le glyphe dans sa PROPRE
   boite, jamais la position ou demarre la ligne voisine (celle-ci reste ancree au flottant,
   independamment de son espacement interne). Le seul levier qui deplace reellement la
   "suite" SANS toucher la lettrine : augmenter le line-height du CONTENEUR (jamais celui de
   ::first-letter, deja fixe independamment ci-dessus a 0.82) — le demi-interlignage ajoute
   par un line-height plus grand pousse les glyphes du texte normal vers le bas a l'interieur
   de leur propre boite de ligne, alors que le flottant (la lettrine) est positionne par
   l'algorithme de flottant, ancre au haut de la zone de contenu, totalement insensible au
   line-height des lignes qui l'entourent. Verifie par mesure directe : letterTop reste
   EXACTEMENT identique (210px, aucun mouvement), suiteTop descend de precisement 3px avec
   line-height:1.65 (1.2 -> 1.65) — exactement l'ecart demande, sans toucher la lettrine. */
.block-text[data-lettrine="true"] {
    line-height: 1.65;
}

/* ---- Bloc question ---- */
.block-question {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    height: 100%;
}

/* L'apparence PAR DEFAUT de .question-bullet (cercle 20x20, centrage flexbox, pointe
   ::after) vit desormais dans blocks-shared.css, partagee tel quel entre l'editeur et
   l'export PDF — voir le commentaire complet la-bas pour l'historique du bug que ce
   deplacement corrige (une regle sans condition, plus bas dans CE fichier, ecrasait
   silencieusement toutes les formes de gabarit ci-dessous des qu'aucune personnalisation
   n'etait active). Les regles qui suivent ne portent plus que les DELTAS par
   palette/gabarit — jamais l'apparence de base elle-meme. */

/* Palette "encre" (voir tokens.css) : --color-question-badge-bg vaut `transparent` —
   sans contour, la pastille de question disparaitrait (jamais eu besoin de bordure,
   son fond plein suffisait). Le petit triangle-pointeur (::after) n'a besoin d'aucun
   correctif ici : sa propre couleur suit la meme variable transparente, il s'efface
   deja tout seul. Dupliquee dans export_pdf.html — ce DELTA reste duplique dans les 2
   fichiers meme apres le deplacement de l'apparence PAR DEFAUT de .question-bullet vers
   blocks-shared.css (voir plus haut) : seule cette base commune valait la peine d'etre
   partagee, chaque delta de palette/gabarit reste local a son propre contexte
   editeur/PDF, meme convention que les gabarits titre juste a cote et que la remarque
   sur la derive CSS plus bas dans ce fichier. */
body.palette-encre .question-bullet {
    border: 1.5px solid var(--color-question-badge-fg);
}

/* Gabarit "Boudina" (voir Activite.Gabarit) : pastille ronde remplacee par un simple
   numero en gras suivi d'un point, comme dans static/images/template3.jpeg — pas de
   forme du tout autour du chiffre. --color-question-badge-bg (la couleur du DISQUE
   plein jusqu'ici) redevient la couleur du TEXTE, puisqu'il n'y a plus de disque a
   remplir ; --color-question-badge-fg (pensee pour contraster sur ce disque) n'a plus
   lieu d'etre ici. Le petit triangle-pointeur (::after) n'a de sens que colle a une
   pastille ronde : repris pour porter le "." a la place plutot que d'ajouter une
   regle ::before separee — deja le seul pseudo-element disponible sur cet element.
   Dupliquee dans export_pdf.html comme chaque delta de gabarit (voir la remarque
   juste au-dessus sur ce qui a bouge vers blocks-shared.css et ce qui reste ici). */
body.gabarit-boudina .question-bullet {
    width: auto;
    height: auto;
    background: transparent;
    border-radius: 0;
    color: var(--color-question-badge-bg);
    font-weight: bold;
    font-size: 16px;
}

body.gabarit-boudina .question-bullet::after {
    content: '.';
    position: static;
    width: auto;
    height: auto;
    border: none;
    color: inherit;
    /* Sans ce reset, le triangle-pointeur d'origine (base rule, plus haut) laisse sa
       propre transform: translateY(-50%) s'appliquer encore au "." repris ici — meme
       en position:static, transform continue de s'appliquer, ce qui faisait flotter
       le point au-dessus de la ligne (rapporte par l'utilisateur : "1*" au lieu de
       "1." — vu, reproduit, corrige) au lieu de rester colle a la ligne de base. */
    transform: none;
    top: auto;
    left: auto;
}

/* Gabarit "Carre" (voir Activite.Gabarit) : pastille pleine conservee (contrairement a
   Boudina) mais carree a coins arrondis plutot que ronde, comme dans
   static/images/template2.jpeg — meme changement minimal que .activity-title plus
   haut, un seul border-radius. Le petit triangle-pointeur original (::after) n'a plus
   de sens colle a un coin droit comme il l'avait sur un cercle : retire (`content:
   none`) plutot que reoriente, la reference n'a elle-meme aucun pointeur sur ses
   badges carres. */
body.gabarit-carre .question-bullet {
    border-radius: 6px;
}

body.gabarit-carre .question-bullet::after {
    content: none;
}

/* Gabarit "Suspendu" — corrige suite a une remarque explicite de l'utilisateur sur le
   premier jet (voir le commentaire complet dans blocks-shared.css juste au-dessus de
   .activity-title__number) : PAS de changement de font-size (le chiffre reste a la
   taille de base heritee) — seule la FORME change, d'un cercle a un losange, via
   `clip-path` plutot qu'un `transform: rotate(45deg)` : un rotate tournerait aussi le
   CHIFFRE a l'interieur (le texte est un noeud direct de .question-bullet, aucun
   element separe a contre-tourner), le rendant illisible a partir de 2 chiffres/lettres
   autres que "1" ; clip-path re-decoupe uniquement la boite, le texte a l'interieur
   reste parfaitement droit. Le triangle-pointeur original (::after) n'a pas de sens
   colle a un angle de losange (il visait a "designer" un cercle) : retire (`content:
   none`), meme choix et meme raisonnement que CARRE plus haut pour son badge carre.
   Taille de la boite (20px -> 23px) : demande explicite separee de l'utilisateur, une
   fois la forme validee — meme principe que .activity-title__number (une boite
   decorative qui grossit legerement reste sans risque, elle ne contient jamais qu'un
   chiffre qui ne reflow pas, contrairement a un paragraphe). */
body.gabarit-suspendu .question-bullet {
    width: 23px;
    height: 23px;
    border-radius: 0;
    clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
    /* Centrage du chiffre — deuxieme tentative, la premiere (padding fixe en px,
       1px 0 0 1px) etait calee sur le rendu de police de CET environnement de test et
       ne generalisait pas au rendu reel de l'utilisateur (confirme : toujours pas
       centre chez lui apres rechargement force, donc pas un souci de cache). Cause
       plus probable : `line-height: 1` (herite de la regle de base) force la hauteur
       de ligne a etre EXACTEMENT egale au font-size, ce qui peut couper la boite de
       ligne de facon asymetrique par rapport a l'ascendante/descendante reelle de la
       police active — un ecart connu et documente de la centration flexbox de texte
       court (un chiffre n'a pas de descendante, donc son encre visuelle n'est pas
       centree dans une boite de ligne calquee sur ascendante+descendante). Remplace
       par `line-height: normal`, qui laisse le navigateur utiliser la metrique propre
       de la police plutot qu'une valeur forcee — plus robuste d'une police/d'un
       environnement de rendu a l'autre qu'un decalage en pixels devine empiriquement.
       text-align:center ajoute en renfort (redondant avec justify-content:center du
       flex parent, mais sans risque). */
    line-height: normal;
    text-align: center;
}

body.gabarit-suspendu .question-bullet::after {
    content: none;
}

/* Gabarit "Hexa" — octogone plein, echo du badge hexagonal du titre (blocks-shared.css)
   sans etre la meme forme exacte : deux "familles" de decoupe angulaire (6 cotes pour
   le titre, 8 pour la question) qui restent visuellement de la meme famille "tech"
   sans etre interchangeables. Pas de font-size ni de largeur/hauteur touchees (voir le
   principe general documente sur SUSPENDU) — seule la decoupe change. Pointeur
   d'origine retire, meme raisonnement que CARRE/SUSPENDU : aucun sens colle a un angle
   qui n'est plus un cercle. */
body.gabarit-hexa .question-bullet {
    border-radius: 0;
    clip-path: polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%);
}

body.gabarit-hexa .question-bullet::after {
    content: none;
}

/* Gabarit "Brut" — meme logique que le titre (blocks-shared.css) : ombre portee douce,
   carree (pas de border-radius), SANS bordure (voir blocks-shared.css pour le retrait
   explicite de la bordure demande par l'utilisateur). Pointeur d'origine retire, meme
   raisonnement que CARRE/SUSPENDU/HEXA : un triangle "colle" a un angle droit n'a plus
   de sens des que la pastille elle-meme n'est plus un cercle. */
body.gabarit-brut .question-bullet {
    border-radius: 0;
    box-shadow: 3px 3px 0 rgba(32, 30, 27, 0.18);
}

/* Gabarit "Organique" — voir blocks-shared.css pour l'explication complete du motif
   "halo". La pastille reste le cercle deja herite de CLASSIQUE ; seul le halo (anneau
   box-shadow, echelle reduite par rapport a la pastille de titre puisque cette pastille
   elle-meme est plus petite, 20px contre 42px) est ajoute. Le triangle ::after
   d'origine, lui, n'a pas besoin d'etre retire ici (contrairement a CARRE/SUSPENDU/HEXA/
   BRUT) : la pastille reste un cercle, son emplacement/sens d'origine restent valides. */
body.gabarit-organique .question-bullet {
    box-shadow: 0 0 0 4px var(--color-situation-box);
}

body.gabarit-brut .question-bullet::after {
    content: none;
}

.block-question__enonce {
    flex: 1;
    /* Suit le reglage "Style du document" par activite — voir le meme commentaire sur
       .block-text plus haut. */
    font-size: var(--default-font-size, 16px);
    color: var(--default-text-color, var(--color-text));
    /* Sans ca, un flex-item garde min-width:auto par defaut — sa largeur ne peut alors
       jamais descendre sous la largeur INTRINSEQUE de son contenu, ce qui annule
       overflow-wrap (voir blocks-shared.css:.block/.block-print) pour une longue suite
       de caracteres sans espace : le mot pousse la boite plus large au lieu d'etre
       coupe. Confirme par un vrai bug (taper une lettre en boucle faisait deborder le
       bloc ET la page entiere). Meme correctif applique a .block-qcm__choix-texte plus
       bas, memes symptomes memes causes. */
    min-width: 0;
    outline: none;
    line-height: 1.4;
}

/* ---- Sous-questions lettrees (a, b, c...) — voir CLAUDE.md "Bloc question". Une
   question qui se decoupe en plusieurs parties reste UN SEUL bloc (numerotation
   auto/deplacement/gabarit deja partages), jamais plusieurs blocs Question distincts qui
   casseraient la sequence 1,2,3 auto-numerotee du document (voir
   canvas.js:renumberQuestions) — le decoupage en a)/b)/c) est donc gere entierement en
   interne par question.js, jamais par canvas.js. ---- */

/* Conteneur de TOUT le contenu du bloc (rangee principale + liste eventuelle de
   sous-questions) — c'est LUI que autoFit mesure desormais (voir question.js), jamais
   .block-question elle-meme (restee `height:100%`, etiree par son parent .block__content
   — la mesurer directement retomberait dans le piege "un element deja etire ne revele
   jamais sa propre taille de contenu", deja documente et corrige ailleurs pour
   .block-reponse). Sans hauteur imposee ici, sa scrollHeight reflete toujours la VRAIE
   hauteur necessaire, qu'elle grandisse (ajout d'une sous-question) ou retrecisse
   (suppression) — exactement le meme "autoFit bidirectionnel, jamais de vide residuel"
   deja garanti pour l'enonce principal seul avant cette fonctionnalite. */
.block-question__contenu {
    display: flex;
    flex-direction: column;
    gap: 6px;
    flex: 1;
    min-width: 0;
}

/* Enveloppe la rangee principale ET son propre espace de reponse eventuel (voir plus
   bas, "Espace de reponse") — porte data-zone="principal" en JS (question.js), le meme
   mecanisme de ciblage que chaque .block-question__sous-wrap : le bouton "+ espace de
   reponse" du toolbar retrouve ainsi, via closest('[data-zone]') depuis
   document.activeElement, a quelle question/sous-question ajouter une ligne. */
.block-question__zone {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

/* Rangee principale (pastille + enonce) — structurellement identique a ce que
   .block-question elle-meme contenait avant cette fonctionnalite, juste descendue d'un
   niveau pour cohabiter avec .block-question__sous-liste ci-dessous. */
.block-question__principal {
    display: flex;
    align-items: flex-start;
    gap: 10px;
}

/* Indentee (30px = largeur de la pastille, 20px, + son gap, 10px) pour lire visuellement
   comme "a l'interieur" de la question — s'aligne approximativement sous le debut du
   texte de l'enonce principal, jamais sous la pastille elle-meme. */
.block-question__sous-liste {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-left: 30px;
}

/* Reel, mesure via Playwright, pas suppose — report utilisateur : "il faut pas de padding
   dans les blocs (vraiment padding zero)". Cet element (contrairement a son pendant PDF,
   voir _export_pdf_bloc.html, qui n'existe carrement pas dans le DOM tant qu'aucune
   sous-question n'existe — {% if block.contenu.sousQuestions %}) est TOUJOURS present
   cote editeur (question.js:createQuestion appelle contenuEl.append(principalZone,
   sousListe) sans condition), meme completement vide (0 sous-question). Un flex-item
   VIDE compte quand meme comme un flex-item a part entiere pour le calcul du `gap`
   (6px) de son parent .block-question__contenu — resultat : ~6px d'espace mort
   systematiquement reserves EN BAS de chaque question SANS sous-question (la grande
   majorite), mesures directement (bas reel de l'enonce a 241.39px, bas du bloc a
   247.39px) plutot que suppose. :empty (un <div> jamais rempli — innerHTML reste
   litteralement '' tant que `sous` est vide, confirme via .matches(':empty')) sort
   entierement cet element du flux flex des qu'il n'a plus aucun enfant : plus de
   flex-item du tout a cet endroit, donc plus rien pour le `gap` d'espacer. Des qu'une
   VRAIE sous-question est ajoutee (renderSousQuestions le remplit), :empty ne matche
   plus, display repasse a flex normalement, et le gap de 6px avant elle redevient
   entierement legitime (separer visuellement l'enonce principal des sous-questions). */
.block-question__sous-liste:empty {
    display: none;
}

.block-question__sous {
    display: flex;
    align-items: flex-start;
    gap: 8px;
}

/* Lettre volontairement SANS forme/pastille (contrairement a .question-bullet) : garder
   une hierarchie visuelle claire entre "la question" (badge colore, personnalisable via
   le style pedagogique document-entier) et "une sous-partie" (simple texte, jamais aussi
   proeminent — sinon deux badges de meme poids visuel se disputeraient l'attention sur
   la meme ligne de lecture). La couleur reprend quand meme le meme ton que la pastille
   (--color-question-badge-bg) pour rester visuellement rattachee a sa question, sans
   avoir besoin d'une forme propre. */
.block-question__sous-lettre {
    flex-shrink: 0;
    min-width: 18px;
    padding-top: 1px;
    font-weight: 700;
    font-size: var(--default-font-size, 16px);
    color: var(--color-question-badge-bg);
    user-select: none;
}

.block-question__sous-enonce {
    flex: 1;
    font-size: var(--default-font-size, 16px);
    color: var(--default-text-color, var(--color-text));
    min-width: 0;
    outline: none;
    line-height: 1.4;
}

.block-question__sous-enonce p,
.block-question__sous-enonce div {
    margin: 0;
}

/* Retrait d'une sous-question precise — jamais "retirer la derniere seulement"
   (contrairement a .block-reponse__tools) : chaque sous-partie est individuellement
   significative (une lettre, un enonce propre), retirer une lettre du milieu doit rester
   possible sans devoir d'abord retirer celles qui suivent. Meme registre visuel que
   .graphique-modal__remove-item (survol en rouge) mais a l'echelle d'un petit bouton
   inline plutot qu'un bouton de panneau.
   `display: none` par defaut, revele uniquement sur `.block--editing` (regle juste en
   dessous) — retour direct : "on ne doit pas les voir que si on est en mode edition".
   Contrairement aux boutons de .block-latex-tools (deja masques par defaut via ce meme
   mecanisme, voir plus bas dans ce fichier), ce bouton est un enfant NORMAL du contenu du
   bloc (pas d'une barre d'outils flottante), donc jamais couvert par la regle generique
   `.block-latex-tools { display:none }` — il lui fallait sa propre paire de regles. */
.block-question__sous-remove {
    flex-shrink: 0;
    display: none;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    margin-top: 1px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    color: var(--color-muted);
    font-size: 12px;
    line-height: 1;
    cursor: pointer;
}

.block--editing .block-question__sous-remove {
    display: flex;
}

.block-question__sous-remove:hover {
    border-color: var(--color-danger);
    background: rgba(185, 74, 72, 0.1);
    color: var(--color-danger);
}

/* Bouton "+ sous-question", dans .block-latex-tools a cote de "∑"/formule/mathlive (voir
   question.js) — meme gabarit exact que .block-formula-btn/.block-mathlive-btn (24x24,
   meme registre visuel) plutot qu'une classe partagee : suit la meme convention deja
   etablie pour ce fichier ("chaque bouton garde sa propre bordure/fond"), pas une
   simplification qui vaille la peine ici. */
.block-question__sous-add {
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-accent);
    border-radius: var(--radius-sm);
    width: 24px;
    height: 24px;
    cursor: pointer;
    font-size: 11px;
}

.block-question__sous-add:hover {
    background: var(--color-accent);
    color: var(--color-surface);
}

/* ---- Espace de reponse imbrique (question ET sous-question) — demande explicite : "sur
   chaque question (resp sous question) on doit avoir la possibilite d'ajouter une ou des
   lignes d'espaces de reponses... le bouton dans le toolbar de la question". Memes
   proprietes visuelles que .block-reponse__line (bloc dedie, deja etabli) : texte rouge
   fonce distinctif d'une reponse d'eleve, trait pointille tant que la ligne est vide,
   gap de 2px entre lignes (meme reduction deja documentee pour .block-reponse). ---- */

/* Enveloppe une sous-question (lettre+enonce+retrait) ET son propre espace de reponse
   eventuel — pendant direct de .block-question__zone plus haut, meme raison, meme
   mecanisme de ciblage (data-zone="sous-N", pose en JS). */
.block-question__sous-wrap {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.block-question__reponse-liste {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

/* Indentation DIFFERENTE selon le proprietaire : la liste de la question principale
   s'aligne sous le debut de SON enonce (30px = largeur pastille 20px + gap 10px, meme
   valeur que .block-question__sous-liste plus haut) ; celle d'une sous-question s'aligne
   sous le debut de SON PROPRE enonce (26px = largeur lettre 18px + gap 8px) — jamais la
   meme valeur, chacune nichee dans un contexte de largeur different. */
.block-question__zone > .block-question__reponse-liste {
    margin-left: 30px;
}

.block-question__sous-wrap > .block-question__reponse-liste {
    margin-left: 26px;
}

.block-question__reponse-row {
    display: flex;
    align-items: flex-start;
    gap: 6px;
}

.block-question__reponse-line {
    flex: 1;
    min-width: 0;
    outline: none;
    min-height: 22px;
    padding: 0 2px;
    /* Taille suit le reglage "Style du document" par activite — la COULEUR, elle, reste
       volontairement le rouge fonce fixe ci-dessous, meme convention deja etablie pour
       .block-reponse__line (distinguer visuellement une reponse d'eleve). */
    font-size: var(--default-font-size, 16px);
    line-height: 1.4;
    color: #AA0911;
}

/* Reel, mesure via Playwright (getBoundingClientRect avant/apres frappe), pas suppose :
   line-height:1.4 (22.4px) ci-dessus s'applique aussi a une ligne totalement VIDE (zero
   enfant) — Chromium reserve ce "strut" meme pour un contentEditable sans le moindre
   noeud, pour garder une hauteur de curseur coherente. Resultat visuel : une ligne vide
   occupe deja sa PLEINE hauteur normale, entierement blanche a l'exception du trait
   pointille tout en bas — percu comme "une marge" au-dessus du trait, qui redevient
   normale des qu'un caractere est tape (le meme espace est alors rempli de texte, plus
   percu comme vide). Signale directement : "le margin devient normal [en tapant]... s'il
   y a rien la marge est un peu grand". Reduit ici min-height/line-height SPECIFIQUEMENT a
   l'etat :empty — line-height:1 (16px, le font-size lui-meme, jamais plus petit pour
   eviter que le curseur clignotant deborde visuellement d'une boite trop compressee) —
   des le premier caractere tape, :empty ne matche plus et la regle normale ci-dessus
   (min-height:22px, line-height:1.4) reprend la main, sans saut brusque puisque c'est
   exactement le moment ou du contenu reel a besoin de cet espace.
   :has(> br:only-child) en plus de :empty — bug reel trouve en verifiant le filet de
   secours cense s'appliquer a une ligne totalement videe (le placeholder tirets efface
   via selectionner-tout + Suppr, voir _shared.js:LIGNE_REPONSE_PLACEHOLDER) : Chromium
   laisse systematiquement un <br> unique dans un contentEditable une fois tout son
   contenu supprime (confirme directement, innerHTML === '<br>') — cet element n'a donc
   JAMAIS zero enfant dans ce cas precis, :empty ne matche donc jamais, et le filet de
   secours restait silencieusement inactif pour la facon la plus naturelle de vider une
   ligne. :has(> br:only-child) couvre exactement ce cas (un unique <br>, sans aucun autre
   enfant/texte) sans jamais matcher une ligne qui contient reellement du texte, meme
   coupe par un retour a la ligne manuel (le <br> n'y est alors plus le seul enfant). */
.block-question__reponse-line:empty,
.block-question__reponse-line:has(> br:only-child) {
    border-bottom: 1px dotted var(--color-muted);
    min-height: 0;
    line-height: 1;
}

/* Report direct : le pointille :empty ci-dessus, colle en border-bottom tout en bas de la
   boite, laissait toute la ligne vide au-dessus de lui — percu comme une grosse marge.
   Remplace desormais par du VRAI texte (voir question.js:updatePlaceholderState et
   LIGNE_REPONSE_PLACEHOLDER dans _shared.js) : une longue serie de tirets qui remplit
   visuellement toute la largeur, coupee proprement au bord du bloc quelle que soit sa
   taille (overflow-x SEUL, jamais le raccourci overflow:hidden — une formule LaTeX
   compilee dans cette meme ligne peut deborder verticalement, ex. une fraction, et ne
   doit jamais etre clippee sur cet axe). white-space:nowrap l'empeche de retourner a la
   ligne pendant qu'il s'agit encore du placeholder ; les deux disparaissent des la
   moindre frappe reelle (voir la fonction JS citee plus haut), la ligne redevenant alors
   un champ de texte normal qui s'enroule comme n'importe quel autre. */
.block-question__reponse-line--placeholder {
    white-space: nowrap;
    overflow-x: hidden;
}

.block-question__reponse-line p,
.block-question__reponse-line div {
    margin: 0;
}

/* Retrait d'une ligne precise — meme raisonnement/meme registre visuel que
   .block-question__sous-remove juste au-dessus (masque hors mode edition). */
.block-question__reponse-remove {
    flex-shrink: 0;
    display: none;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    margin-top: 1px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    color: var(--color-muted);
    font-size: 12px;
    line-height: 1;
    cursor: pointer;
}

.block--editing .block-question__reponse-remove {
    display: flex;
}

.block-question__reponse-remove:hover {
    border-color: var(--color-danger);
    background: rgba(185, 74, 72, 0.1);
    color: var(--color-danger);
}

/* Bouton "+ espace de reponse" du toolbar — meme gabarit exact que
   .block-question__sous-add juste au-dessus (24x24, meme registre visuel). */
.block-question__reponse-add {
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-accent);
    border-radius: var(--radius-sm);
    width: 24px;
    height: 24px;
    cursor: pointer;
    font-size: 11px;
}

.block-question__reponse-add:hover {
    background: var(--color-accent);
    color: var(--color-surface);
}

/* Formule LaTeX compilee : ilot non editable au clavier au sein du texte editable
   (evite de casser les spans internes de KaTeX), avec une icone crayon au survol
   pour revenir au code source de cette formule precise. */
.katex-chip {
    position: relative;
    display: inline-block;
    margin: 0 3px;
    padding: 0 2px;
    border-radius: 3px;
    cursor: default;
    /* "Frontiere" TOUJOURS visible, pas seulement au survol — demande explicite : sans
       elle, rien ne montrait a l'oeil ou une formule compilee commence/finit dans le
       texte environnant, ce qui rendait un curseur place juste a cote ambigu ("on dirait
       qu'il rentre dans le LaTeX"). Teinte volontairement DISCRETE — le fond ivoire deja
       utilise ailleurs pour l'espace de travail (--color-bg), jamais --color-accent-soft
       (reserve au survol juste en dessous, qui doit rester une marche visuelle nette
       au-dessus du repos) : une formule doit se lire comme une pastille distincte au
       premier coup d'oeil, sans dominer visuellement une page de texte par ailleurs
       normale. Jamais applique en mode affichage bloc (.katex-chip--display juste en
       dessous, $$...$$) : une equation deja seule sur sa propre ligne, centree, n'a pas
       cette ambiguite avec du texte voisin — une bande de couleur pleine largeur y
       aurait juste alourdi le rendu sans repondre a un besoin reel. */
    background: var(--color-bg);
    transition: background-color 0.12s ease;
}

/* $$...$$ et \[...\] suivent dans l'editeur la meme semantique que KaTeX dans le PDF :
   une vraie ligne de formule, pleine largeur et centree. Comme le chip est un element
   block, le marqueur de caret place juste apres par _shared.js commence naturellement la
   ligne suivante au lieu de rester colle sur le bord droit de la formule. */
.katex-chip--display {
    display: block;
    width: 100%;
    margin: .65em 0;
    padding: 0;
    text-align: center;
    background: none;
}

.katex-chip--display .katex-display {
    margin: 0;
}

.katex-chip:hover {
    background: var(--color-accent-soft);
}

/* max-width/max-height : sur une formule tres courte (ex. "$x$"), un bouton 22x22 fixe
   deborderait largement le chip et couvrirait tout son pourtour, empechant tout clic
   d'y placer un curseur (avant/apres) plutot que de rouvrir l'edition — voir
   handleChipClick. Se limiter a la taille du chip garde toujours une marge cliquable
   sur les bords, meme pour une formule d'un seul caractere. */
.katex-chip__edit {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    display: none;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    max-width: calc(100% - 4px);
    max-height: 100%;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-accent);
    border-radius: var(--radius-sm);
    cursor: pointer;
    font-size: 11px;
    z-index: 20;
}

.katex-chip:hover .katex-chip__edit {
    display: flex;
}

.katex-chip__edit:hover {
    background: var(--color-accent);
    color: var(--color-surface);
}

.block-question__compile {
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-accent);
    border-radius: var(--radius-sm);
    width: 24px;
    height: 24px;
    cursor: pointer;
    font-family: var(--font-display);
}

.block-question__compile:hover {
    background: var(--color-accent);
    color: var(--color-surface);
}

/* ---- Bloc reponse : lignes de correction editables ---- */
.block-reponse {
    /* height:auto EXPLICITE (pas juste "pas de height:100%") : canvas.js:createNode
       ajoute la classe .block__content (height:100% — voir plus haut dans ce fichier)
       directement sur l'element renvoye par createReponse(), donc .block-reponse et
       .block__content sont la MEME balise, pas deux niveaux embotes comme on pourrait
       le croire (contrairement a .block-text, ou .block__content s'ajoute sur
       .block-text-wrap, un niveau AU-DESSUS du veritable champ mesure par autoFit,
       .block-text lui-meme). Sans cette regle explicite, retirer height:100% d'ici ne
       suffit pas : les deux selecteurs ont la meme specificite (une classe), donc
       .block__content (definie plus haut dans ce fichier) continue de s'appliquer sans
       opposition — .block-reponse doit gagner explicitement par l'ordre des regles, pas
       par une propriete absente. Sans lui, autoFit(wrap, data) (voir reponse.js, appele
       avec wrap = cet element lui-meme) ne pouvait JAMAIS detecter un besoin de
       retrecir : scrollHeight d'un element deja fixe a 100% de son parent ne revele
       jamais qu'il est plus grand que necessaire (rien ne "deborde" tant que le
       contenu tient dedans) — mesure circulaire confirmee en debogant avec CDP
       (getMatchedStylesForNode montrait .block__content comme regle reellement
       appliquee ici, .block-reponse n'ayant plus aucune declaration de height pour lui
       faire concurrence). */
    height: auto;
    display: flex;
    flex-direction: column;
    /* Reduit de 6px a 2px — demande explicite ("reduire l'ecart entre les lignes si on
       en rajoute, sans trop reduire") : ce gap s'ajoutait au padding vertical de chaque
       ligne ci-dessous (2px+4px), soit ~12px d'espace mort entre deux lignes en plus de
       l'interligne naturel du texte (16px/1.4 ~= 22px) — nettement plus que ce qu'un
       interligne normal donnerait, ce qui faisait "des cartes empilees" plutot qu'un
       bloc de texte a plusieurs lignes. Pas 0 : les pointilles de chaque ligne vide (voir
       :empty ci-dessous) ont besoin d'un minimum d'espace pour rester lisibles comme
       lignes distinctes plutot que de se toucher. */
    gap: 2px;
}

/* Pas de trait fixe sous le texte : chaque ligne grandit avec son contenu (autoFit,
   comme .block-text/.block-question__enonce) au lieu d'etre etiree dans une case
   reglee. Padding vertical reduit au minimum (juste assez pour le confort du curseur,
   voir plus haut) — le principal repere de rythme entre les lignes reste desormais
   line-height (coherent avec le reste du texte de l'app), pas un padding+gap qui
   s'additionnait par-dessus. */
.block-reponse__line {
    outline: none;
    min-height: 22px;
    padding: 0 2px;
    /* Taille suit le reglage "Style du document" par activite (voir .block-text plus
       haut) — la COULEUR, elle, reste volontairement le rouge fonce fixe ci-dessous :
       demande explicite d'origine, propre a ce bloc (distinguer visuellement une
       reponse d'eleve), sans rapport avec ce nouveau reglage plus general. */
    font-size: var(--default-font-size, 16px);
    line-height: 1.4;
    /* Couleur par defaut du texte de l'espace de reponse (demande explicite) — comme
       tout le reste de la mise en forme riche, une couleur choisie ensuite via la barre
       de mise en forme (span inline) l'emporte normalement sur cette regle de classe. */
    color: #AA0911;
}

/* Les pointilles sont un placeholder, pas une ligne permanente : un border-bottom
   pose sur :empty s'etire naturellement sur toute la largeur du bloc (quelle que
   soit sa taille, pas de chaine de points a longueur fixe) et disparait tout seul
   des que la ligne n'est plus vide.
   min-height:0 / line-height:1 — meme correctif, EXACTEMENT le meme raisonnement, deja
   applique a .block-question__reponse-line:empty plus bas dans ce fichier (ligne-height:1.4
   reserve un "strut" de 22.4px meme pour un contentEditable totalement vide, percu comme
   une marge au-dessus du trait pointille) mais jamais reporte ici — reste "un peu enorme"
   signale directement, plusieurs lignes vides empilees (report utilisateur : "la question
   et l'espace de reponse est un peu enorme... l'espace de reponse cree un marg top qui
   casse le visuel"). Chaque ligne vide retombe desormais a 16px (font-size seul) au lieu de
   22.4px, un caractere tape la fait immediatement revenir a min-height:22px/line-height:1.4
   sans saut brusque, exactement comme la version imbriquee-dans-question.
   :has(> br:only-child) — meme correctif, meme raison, que
   .block-question__reponse-line:empty plus haut dans ce fichier (voir son commentaire
   complet) : sans lui, vider completement une ligne via selectionner-tout + Suppr
   laissait un <br> unique dans le DOM (comportement Chromium standard), et le filet de
   secours ci-dessous ne s'appliquait alors jamais. */
.block-reponse__line:empty,
.block-reponse__line:has(> br:only-child) {
    border-bottom: 1px dotted var(--color-muted);
    min-height: 0;
    line-height: 1;
}

/* Meme correctif, meme raison, que .block-question__reponse-line--placeholder plus haut
   dans ce fichier (voir son commentaire complet) — deux classes distinctes plutot qu'une
   partagee, meme convention que le reste de ce bloc/de blocks/question.js pour ces deux
   implementations paralleles d'un meme concept. */
.block-reponse__line--placeholder {
    white-space: nowrap;
    overflow-x: hidden;
}

.block-reponse__line p,
.block-reponse__line div {
    margin: 0;
}

/* Largeur pleine PAGE (pas juste largeur du bloc), fond blanc, marge fixe de 10px —
   demande explicite ("le toolbar... prend toute la largeur de l'editeur... si le nombre
   de bouton augmente [et qu']il y a pas d'espace on passe a la ligne suivante"),
   appliquee identiquement a TOUTES les barres d'outils par bloc de ce fichier (table/
   qcm/reponse/notion/ligne/forme/video/icone/code/graphique/latex-tools). `left`/`width`
   sont fixes en JS (`canvas.js:updateBlockToolbarsWidth`, calcule a partir de `data.x` du
   bloc et de la largeur reelle de la page — jamais du viewport) ; `right: 0` ci-dessous
   n'est que la valeur de repos, ecrasee en `auto` par le JS des que la barre devient
   visible. `bottom: calc(100% + 10px)` (jamais `top: -Npx`) garde une marge EXACTE de
   10px entre le bas de la barre et le haut du bloc quel que soit le nombre de lignes sur
   lesquelles elle s'affiche : un `top` fixe grandirait la barre vers le bas au fil des
   retours a la ligne (flex-wrap), rognant progressivement la marge au lieu de la
   preserver ; `bottom` grandit la barre vers le HAUT depuis un point fixe, donc la marge
   reste identique peu importe sa hauteur totale. Fond blanc + bordure + ombre = la meme
   carte visuelle que .block-table__tools/.block-video__tools/.block-code__tools avaient
   deja avant ce changement — desormais partagee par CETTE barre aussi (elle n'avait
   auparavant aucun fond propre, juste des boutons individuellement bordes). Le z-index
   "le plus grand" demande (pour ne jamais etre recouverte par un bloc VOISIN une fois
   qu'elle peut visuellement le chevaucher, la barre etant desormais large) n'est PAS pose
   ici sur la barre elle-meme : voir `.block.block--editing` plus haut dans ce fichier —
   la barre vit a l'interieur du wrapper .block, qui possede son PROPRE contexte
   d'empilement CSS (position + z-index explicite) des sa regle de base ; un z-index
   pose seulement sur la barre resterait piege a l'interieur de ce contexte et ne
   gagnerait jamais face a un bloc voisin (meme lecon deja tiree du bug de la modale du
   graphique, voir plus haut). C'est donc le wrapper .block LUI-MEME qu'il faut faire
   gagner, une fois pour toutes, pas chaque barre individuellement. */
.block-reponse__tools {
    position: absolute;
    bottom: calc(100% + 10px);
    right: 0;
    display: none;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 4px 5px;
    box-shadow: 0 6px 16px -6px rgba(32, 30, 27, 0.35);
}

.block--editing .block-reponse__tools {
    display: flex;
}

.block-reponse__tools button {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    padding: 3px 8px;
    cursor: pointer;
    border-radius: var(--radius-sm);
}

.block-reponse__tools button:disabled {
    opacity: 0.3;
    cursor: not-allowed;
}

/* Mode cours : on peut repondre (taper dans les lignes, compiler ses formules, utiliser le
   palette de formules/l'editeur visuel MathLive) mais pas ajouter/retirer des "espaces de
   reponse" — seuls les 2 boutons explicitement marques STRUCTURELS (- Ligne / + Ligne, voir
   reponse.js) sont vises. Cible par CLASSE, jamais par position (":not(:last-child)")
   comme avant : ce dernier ciblage etait fragile et avait deja silencieusement desactive
   formulaBtn/mathliveBtn en mode Cours des leur ajout — precisement le cas d'usage le plus
   important pour un eleve qui ne maitrise pas la syntaxe LaTeX brute. */
body.mode-cours .block-reponse__tools .block-reponse__tools-structural {
    opacity: 0.3;
    cursor: not-allowed;
    pointer-events: none;
}

/* ---- Bloc tableau ---- */
.block-table {
    height: 100%;
}

/* Grille CSS a pistes fixes (jamais de croissance au fil de la frappe,
   contrairement a un <table> dont la hauteur de ligne est un minimum, jamais
   un maximum) : lignes ET colonnes ont chacune une taille en % individuelle
   (grid-template-rows/-columns poses en JS par table.js), ajustables via les
   poignees .block-table__row-handle / .block-table__col-handle. La poignee de
   redimensionnement du bloc lui-meme agrandit toujours le tableau dans
   l'ensemble, independamment de ces proportions internes. */
.block-table__grid {
    position: relative;
    display: grid;
    width: 100%;
    height: 100%;
    border-top: 1px solid var(--color-text);
    border-left: 1px solid var(--color-text);
}

.block-table__cell {
    /* Bug reel signale : "le texte passe a la ligne suivante meme si y a de la largeur
       disponible" (verifie sur une formule LaTeX compilee en fin de texte, mesure sur une
       cellule large de 380px : le texte precedent s'arretait a 650px, largement en
       dessous de la largeur disponible, et la formule "passait" quand meme a la ligne
       suivante). Cause : n'importe quel ELEMENT enfant direct d'un conteneur flex/grid
       devient son PROPRE item — seuls les NOEUDS TEXTE consecutifs sont regroupes dans un
       item anonyme commun ; un `.katex-chip` (span) compile n'est PAS un noeud texte,
       donc il devenait un item SEPARE de celui du texte qui le precede, empile sur sa
       propre ligne par flex-direction:column, independamment de la largeur restante.
       Deux tentatives ecartees avant celle-ci : (1) `display:table-cell` pose directement
       sur CETTE cellule — semblait fonctionner isolement mais cassait le centrage vertical
       ET la croissance/retrecissement de ligne une fois integre : cette cellule est
       TOUJOURS un item de la GRILLE CSS parente (.block-table__grid, display:grid), et un
       display "interne" de table est lui-meme blockifie des que l'element qui le porte est
       un item flex/grid (`getComputedStyle(cell).display` valait "block", jamais
       "table-cell" — confirme par mesure). (2) Un wrapper interne flex (l'inverse de la
       solution actuelle : cellule EXTERIEURE flex, wrapper CENTRE mais non etire) —
       resolvait le centrage mais laissait un "angle mort" : le wrapper, centre a sa seule
       taille de contenu, ne remplit pas toute la cellule des que celle-ci est plus grande
       (ligne agrandie par une AUTRE colonne) ; un double-clic dans cette zone "vide"
       n'atteint alors aucun element contentEditable (canvas.js:enterEdit cherche
       `.closest('[contenteditable="true"]')` en REMONTANT depuis le point clique — jamais
       en descendant vers un enfant), rendant cette zone de la cellule injoignable en
       edition. Solution retenue : cette cellule EXTERIEURE reste un simple bloc (PAS flex,
       PAS grid — seul son display *interne* la protege desormais de tout risque de
       blockification, puisqu'elle n'a plus qu'UN SEUL enfant a positionner, jamais besoin
       de flex/grid pour ca) ; c'est le wrapper .block-table__cell-content ci-dessous,
       lui-meme display:table-cell (NON blockifie ici : SON parent — cette cellule — n'est
       ni flex ni grid), qui porte le centrage vertical ET remplit 100% de la cellule
       (largeur ET hauteur) — plus d'angle mort possible pour le double-clic. Le contenu
       riche reel (texte, puces LaTeX, <div> d'Entree) vit dans ce wrapper table-cell, qui
       n'est PAS lui-meme flex/grid : ses propres enfants suivent le flux HTML normal, ou
       seul un veritable element BLOCK (le <div> d'Entree) demarre une nouvelle ligne,
       jamais un span inline qui a encore de la place.
       `display: table` (pas `block`) est necessaire ici, pas cosmetique : verifie par
       mesure (fichier de comparaison isole) que `width:100%; height:100%` sur le wrapper
       table-cell ne se resout de facon fiable QUE si son parent immediat est un veritable
       `display:table` (198px sur 200px disponibles) — avec un parent `display:block`, le
       wrapper ne recoit que sa hauteur de contenu naturelle (18px sur 200px), laissant un
       "angle mort" non contentEditable dans le reste de la cellule (voir plus haut, meme
       bug que la tentative precedente). `display:table`, comme `flex`/`grid`, echappe a
       la blockification d'item de grille (seuls les displays purement INTERNES comme
       table-cell/table-row, sans boite exterieure a eux, sont blockifies) — confirme :
       `getComputedStyle(cell).display` reste bien "table" ici, jamais "block". */
    display: table;
    position: relative;
    border-right: 1px solid var(--color-text);
    border-bottom: 1px solid var(--color-text);
    outline: none;
    /* overflow: hidden seul ne suffit pas sur une piste en fr : min-height/
       min-width: 0 desactive l'agrandissement automatique que la grille
       applique par defaut pour eviter de tronquer le contenu (comportement
       CSS Grid standard, pas un contournement). */
    overflow: hidden;
    min-height: 0;
    min-width: 0;
}

/* Wrapper interne, seul enfant de .block-table__cell (voir sa note ci-dessus) — c'est LUI
   qui porte contentEditable, le centrage, le padding/font-size visuel, et tous les
   listeners d'edition (frappe, collage, focus, clic sur une puce LaTeX). display:table-cell
   ici n'est PAS blockifie (son parent .block-table__cell n'est ni flex ni grid), donc
   vertical-align fonctionne reellement ; ses propres enfants suivent le flux HTML normal,
   pas un flux d'items flex/grid independants. */
.block-table__cell-content {
    display: table-cell;
    vertical-align: middle;
    width: 100%;
    height: 100%;
    text-align: center;
    /* Suit le reglage "Style du document" par activite — voir le meme commentaire sur
       .block-text plus haut. */
    font-size: var(--default-font-size, 16px);
    color: var(--default-text-color, var(--color-text));
    /* anywhere, pas juste break-word (deja hors de ce fichier via .block/.block-print,
       heritee) : break-word ne change PAS le calcul de taille minimale intrinseque d'une
       boite par specification -- seul "anywhere" le fait. Sans ca, une suite de
       caracteres sans espace ne peut pas retrecir sous sa largeur naturelle, deborde
       silencieusement de la cellule, et overflow:hidden (cellule parente) se contente de
       la rendre invisible plutot que de la renvoyer a la ligne -- confirme par mesure
       reelle (debordement de 168px sur une suite de 60 caracteres non coupee). */
    overflow-wrap: anywhere;
    padding: 4px 6px;
    outline: none;
    box-sizing: border-box;
}

/* Surbrillance de la selection de cellule(s) pour fusionner/colorer (voir
   attachCellSelection dans table.js) — purement visuel cote editeur, jamais serialise ni
   present dans l'export PDF. outline plutot que border pour ne pas decaler le contenu
   (memes raisons que .block--selected, voir plus haut dans ce fichier). */
.block-table__cell--selected {
    outline: 2px solid var(--color-accent);
    outline-offset: -2px;
    z-index: 1;
}

/* Poignee de redimensionnement d'une colonne : une bande fine centree sur la
   frontiere entre deux colonnes, visible seulement quand le bloc est
   selectionne (meme registre que .block__resize-handle). Invisible au repos
   (aucun fond tant qu'on ne survole pas) — le curseur natif col-resize a lui
   seul suffit a signaler la zone interactive, un trait visible en permanence
   alourdirait la grille pour rien (retour explicite de l'utilisateur : "le
   trait... alourdi le visuel"). Le survol/glisser bascule sur un trait DORE
   (--color-accent, la couleur de marque) plutot qu'une teinte plus discrete —
   demande explicite : "la ligne devient dorée". Largeur reduite 6px -> 4px, plus
   discrete/precise (retour explicite : "le handle est trop epais") — reste
   confortablement cliquable, et sert de reference a la marge de croisement des
   poignees de ligne (voir renderRowHandles dans table.js, 2px = la moitie de
   cette largeur). */
.block-table__col-handle {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 4px;
    transform: translateX(-50%);
    cursor: col-resize;
    z-index: 3;
    display: none;
}

.block--selected .block-table__col-handle {
    display: block;
}

.block-table__col-handle:hover,
.block-table__col-handle:active {
    background: var(--color-accent);
}

/* Poignee de redimensionnement de LIGNE — meme construction que .block-table__col-handle
   (bande fine centree sur la frontiere entre deux lignes), mais axe horizontal au lieu de
   vertical (sur toute la largeur plutot que toute la hauteur) ET, contrairement a la poignee
   de colonne, visible seulement en mode EDITION (.block--editing, double-clic) plutot qu'a la
   simple selection — demande explicite de l'utilisateur : le trait horizontal ne doit
   apparaitre qu'une fois entre dans le bloc, jamais au simple survol/selection. Divergence
   volontaire entre les deux axes, pas un oubli : la poignee de colonne reste sur
   .block--selected. */
/* Chaque frontiere de ligne est en realite decoupee en PLUSIEURS segments cliquables
   (voir table.js:renderRowHandles, pour eviter la collision de hit-test avec les
   poignees de colonne aux croisements) — ce segment reste donc volontairement SANS
   AUCUN style visuel propre (jamais de fond au survol/glisser ici) : le donner
   allumerait seulement le petit morceau reellement sous le curseur, une surbrillance
   visiblement COURTE plutot que de couvrir toute la largeur du tableau (signale
   explicitement par l'utilisateur). Toute la retroaction visuelle est deportee sur
   .block-table__row-handle-visual juste en dessous, une ligne separee, jamais
   decoupee, que table.js allume/eteint en JS des qu'un segment quelconque de la meme
   frontiere est survole ou glisse. */
.block-table__row-handle {
    position: absolute;
    left: 0;
    right: 0;
    height: 4px;
    transform: translateY(-50%);
    cursor: row-resize;
    z-index: 3;
    display: none;
}

.block--editing .block-table__row-handle {
    display: block;
}

/* Ligne visuelle plein-largeur associee a une frontiere de ligne — jamais decoupee en
   segments (contrairement a .block-table__row-handle ci-dessus), et pointer-events:none
   pour ne JAMAIS participer au hit-test (elle ne peut donc pas reintroduire la collision
   de croisement que le decoupage des vrais segments existe pour eviter). Purement
   decorative : invisible tant qu'aucun segment de sa frontiere n'est survole/glisse
   (voir table.js:setRowBoundaryVisualActive), doree une fois active — meme couleur,
   meme convention que .block-table__col-handle ci-dessus. */
.block-table__row-handle-visual {
    position: absolute;
    left: 0;
    right: 0;
    height: 4px;
    transform: translateY(-50%);
    pointer-events: none;
    z-index: 3;
    display: none;
}

.block--editing .block-table__row-handle-visual {
    display: block;
}

.block-table__row-handle-visual--active {
    background: var(--color-accent);
}

/* Barre d'actions contextuelle, flottante au-dessus du bloc quand il est
   selectionne — meme registre visuel que .block-reponse__tools et
   .block-question__compile, pas de modale. */
/* Barre a nombreux boutons etiquetes (13) — c'est ELLE qui a motive le passage de toutes
   les barres de ce fichier a "largeur pleine page" (voir le commentaire complet avant
   .block-reponse__tools, plus haut) : meme plafonnee a la largeur de son propre bloc
   (l'etape intermediaire precedente), elle restait a l'etroit des qu'un tableau etait
   compose sur un bloc pas tres large. `left`/`width` (pleine largeur de la PAGE, pas du
   bloc) sont fixes en JS (`updateBlockToolbarsWidth`) ; `flex-wrap: wrap` continue de la
   faire passer sur plusieurs lignes le jour ou le nombre de boutons depasse meme cette
   largeur bien plus genereuse. */
.block-table__tools {
    position: absolute;
    bottom: calc(100% + 10px);
    right: 0;
    display: none;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 4px 5px;
    box-shadow: 0 6px 16px -6px rgba(32, 30, 27, 0.35);
}

.block--editing .block-table__tools {
    display: flex;
}

.block-table__tools button {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    padding: 3px 8px;
    cursor: pointer;
    border-radius: var(--radius-sm);
}

.block-table__tools button:disabled {
    opacity: 0.3;
    cursor: not-allowed;
}

/* Fond de cellule(s) : bouton-pastille + petit popover de couleurs recentes (voir
   recentColors.js), meme principe que le menu couleur de la barre de mise en forme
   (.editor-formatbar__menu--color) mais sans les mecanismes generiques dropdowns.forEach/
   closeAllMenus de formatBar.js — ce fichier gere son propre etat local d'ouverture. */
.block-table__bg-wrap {
    position: relative;
}

.block-table__bg-toggle {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    padding: 3px 8px;
    cursor: pointer;
    border-radius: var(--radius-sm);
}

.block-table__bg-toggle:disabled {
    opacity: 0.3;
    cursor: not-allowed;
}

.block-table__bg-menu {
    display: none;
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    padding: 8px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    box-shadow: 0 8px 20px -8px rgba(32, 30, 27, 0.35);
    z-index: 80;
}

.block-table__bg-wrap--open .block-table__bg-menu {
    display: block;
}

/* <input type="color"> natif, cache — declenche uniquement par le bouton "+" du menu
   (voir recentColors.js:renderColorSwatches), meme registre que
   .editor-formatbar__color-input. */
.block-table__bg-input {
    position: absolute;
    width: 0;
    height: 0;
    padding: 0;
    border: none;
    opacity: 0;
    pointer-events: none;
}

/* ---- Bloc situation (mise en contexte) ---- */
.block-situation {
    height: 100%;
    overflow: visible;
    /* Suit le reglage "Style du document" par activite — voir le meme commentaire sur
       .block-text plus haut. Se propage par heritage a .block-situation__corps (aucune
       regle font-size/color propre) — .block-situation__titre, lui, a deja la sienne
       (accent de palette, jamais touchee par ce reglage : c'est un sous-titre, pas du
       texte courant). */
    font-size: var(--default-font-size, 16px);
    color: var(--default-text-color, var(--color-text));
    /* Meme correctif que .block-text (voir son commentaire) : PDF de reference
       nettement plus serre, 1.5 -> 1.2 (~-5px), premier essai a affiner. Se propage
       par heritage a .block-situation__corps et a ses <ul>/<li> (voir plus bas, ou
       une regle explicite fixe la meme valeur pour rester ajustable independamment). */
    line-height: 1.2;
}

.block-situation__titre {
    font-size: 1.35rem;
    line-height: 1.6rem;
    font-weight: 600;
    color: var(--color-situation-accent);
    font-family: var(--font-body);
    outline: none;
    /* Marge/padding vertical resserres — retour explicite ("la marge est grande")
       ramenant le total titre->corps de 18px (5+8+5) a 6px (2+2+2), en jouant sur
       les 3 sources d'espace vertical a la fois plutot qu'une seule. */
    margin-bottom: 2px;
    padding: 5px 10px 2px;
}

.block-situation__image-wrap {
    position: relative;
    float: right;
    width: 220px;
    margin: 0 0 10px 15px;
}

.block-situation__image {
    display: block;
    width: 220px;
    height: 150px;
    object-fit: cover;
    border-radius: var(--radius-sm);
}

.block-situation__image[hidden] {
    display: none;
}

/* Calque d'actions au survol de l'image : discret tant qu'on ne survole pas,
   pour ne jamais alourdir visuellement une image deja en place. */
.block-situation__image-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    background: rgba(32, 30, 27, 0.4);
    border-radius: var(--radius-sm);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.15s;
}

.block-situation__image-overlay[hidden] {
    display: none;
}

.block-situation__image-wrap:hover .block-situation__image-overlay {
    opacity: 1;
    pointer-events: auto;
}

.block-situation__image-btn,
.block-situation__image-remove {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    border: none;
    background: var(--color-surface);
    color: var(--color-text);
    padding: 5px 10px;
    cursor: pointer;
    border-radius: var(--radius-pill);
}

.block-situation__image-btn:hover,
.block-situation__image-remove:hover {
    background: var(--color-accent);
    color: var(--color-surface);
}

.block-situation__image-placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 220px;
    height: 150px;
    border: 1px dashed var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface-alt);
    color: var(--color-muted);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    cursor: pointer;
}

.block-situation__image-placeholder[hidden] {
    display: none;
}

.block-situation__corps {
    outline: none;
    /* Padding-top resserre en miroir de .block-situation__titre juste au-dessus —
       voir son commentaire pour le detail des 3 sources d'espace reduites ensemble. */
    padding: 2px 10px 5px;
}

.block-situation__corps p {
    margin: 0 0 8px;
}

.block-situation__corps ul,
.block-situation__corps ol {
    margin: 4px 0 8px;
    padding: 0 0 0 18px;
}

.block-situation__corps li {
    margin: 0 0 2px;
    /* Explicite plutot que simplement herite de .block-situation (voir son
       commentaire) — reste ajustable independamment du texte courant si besoin. */
    line-height: 1.2;
}

/* Meme traitement des listes pour les autres champs riches (barre de mise en forme) :
   marges par defaut du navigateur neutralisees comme pour .block-situation__corps. */
.block-question__enonce ul,
.block-question__enonce ol,
.block-qcm__choix-texte ul,
.block-qcm__choix-texte ol,
.block-situation__titre ul,
.block-situation__titre ol {
    margin: 4px 0 8px;
    padding: 0 0 0 18px;
}

.block-question__enonce li,
.block-qcm__choix-texte li,
.block-situation__titre li {
    margin: 0 0 2px;
}

/* Gabarit "Suspendu" (voir Activite.Gabarit) — fond plein teinte pale derriere tout le
   bloc situation, pour un effet "zone matiere posee sur la page" plutot qu'un simple
   texte courant. --color-situation-box est reutilise tel quel (deja une teinte "douce"
   pilotee par la palette active) plutot que d'inventer une nouvelle variable — coherent
   avec le principe qu'un gabarit ne doit jamais fixer sa propre couleur en dur. */
body.gabarit-suspendu .block-situation {
    /* var(--color-situation-box) en plein etait juge "trop fonce" par l'utilisateur une
       fois vu en contexte (l'intensite d'une teinte "douce" pensee pour un petit encart
       lit tres differemment etiree sur tout un bloc, et certaines palettes ont un
       --color-situation-box nettement plus sombre que d'autres — "chacun son gout",
       incoherent d'une palette a l'autre). color-mix() reduit son intensite a une simple
       teinte quasi-transparente (20%) SANS fixer de couleur en dur : ca marche pour
       n'importe quelle valeur de la variable, y compris quand elle est deja une rgba
       partiellement transparente (ardoise) ou totalement transparente (encre). Benefice
       secondaire : le fond desormais quasi-transparent laisse remonter visiblement le
       fond personnalise que l'utilisateur choisit via le panneau de style generique du
       bloc (canvas.js:openBlockStylePanel, applique sur le wrapper .block, juste en
       dessous de .block-situation dans l'empilement) — avant ce correctif, le fond
       plein et opaque de .block-situation recouvrait entierement ce choix, le rendant
       invisible ("je ne peux pas changer le fond"). */
    background: color-mix(in srgb, var(--color-situation-box) 20%, transparent);
    padding: 8px 12px;
    border-radius: 10px;
}

/* Gabarit "Hexa" — le bloc situation reste sur fond transparent mais gagne un fin
   contour + un coin coupe en diagonale (meme technique que .activity-title plus haut,
   coin bas-gauche pour eviter tout risque de chevauchement avec l'image flottante a
   droite ou le texte) — un "panneau" au trace net plutot qu'un aplat de couleur,
   coherent avec le reste du langage visuel anguleux de ce gabarit. */
body.gabarit-hexa .block-situation {
    background: transparent;
    border: 1px solid var(--color-situation-accent);
    padding: 10px 14px;
    border-radius: 0;
    clip-path: polygon(0 0, 100% 0, 100% 100%, 22px 100%, 0 calc(100% - 22px));
}

/* Gabarit "Brut" — meme traitement "carte" que le titre : ombre portee douce, SANS
   bordure (voir blocks-shared.css pour le retrait explicite demande par
   l'utilisateur : "enleve le contour noir... on doit rester elegant, faut pas que ca
   charge trop"), fond plein (pas transparent comme HEXA) pour lire comme un bloc
   matiere pose sur la page plutot qu'un simple aplat. --color-situation-box est
   reutilise tel quel (deja une teinte "douce" pilotee par la palette active), meme
   raisonnement que SUSPENDU : un gabarit ne fixe jamais sa propre couleur en dur. */
body.gabarit-brut .block-situation {
    /* Meme correctif "trop fonce -> quasi-transparent" que SUSPENDU, meme raisonnement
       complet (voir ce commentaire) : color-mix() plutot qu'une couleur fixee en dur,
       fonctionne pour n'importe quelle valeur de --color-situation-box selon la palette
       active, et laisse remonter un fond personnalise choisi via le panneau de style
       generique du bloc. */
    background: color-mix(in srgb, var(--color-situation-box) 20%, transparent);
    box-shadow: 6px 6px 0 rgba(32, 30, 27, 0.18);
    border-radius: 0;
    padding: 12px 16px;
}

/* Gabarit "Organique" — l'encart lui-meme troque le rayon uniforme herite (radius-sm,
   plat/discret) pour un rayon ALTERNE grand/petit sur les 4 coins
   (50px 14px 50px 14px) : une courbure deliberement irreguliere (pas un simple radius
   agrandi uniformement, qui aurait juste l'air d'un rectangle plus arrondi) — un vrai
   parti-pris organique, coherent avec la capsule du bandeau et les halos des pastilles.
   --color-situation-box reutilise tel quel pour le fond (meme raisonnement que
   SUSPENDU/BRUT : un gabarit ne fixe jamais sa propre couleur en dur). */
body.gabarit-organique .block-situation {
    /* Meme correctif "trop fonce -> quasi-transparent" que SUSPENDU/BRUT, meme
       raisonnement complet (voir le commentaire sur SUSPENDU plus haut). */
    background: color-mix(in srgb, var(--color-situation-box) 20%, transparent);
    border-radius: 50px 14px 50px 14px;
    padding: 14px 18px;
}

/* Bouton "compiler les formules", meme registre que .block-question__compile */
.block-situation__compile {
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-accent);
    border-radius: var(--radius-sm);
    width: 24px;
    height: 24px;
    cursor: pointer;
    font-family: var(--font-display);
}

.block-situation__compile:hover {
    background: var(--color-accent);
    color: var(--color-surface);
}

/* ---- Bloc QCM ---- */
/* v2 — plus de numero/enonce (retires, voir CLAUDE.md "Bloc QCM") : le bloc n'est plus
   qu'une liste de choix cochables, la question elle-meme se pose dans un bloc separe
   (texte/question) juste a cote. .block-qcm__header/__enonce n'existent donc plus. */
.block-qcm {
    height: 100%;
    overflow: visible;
}

.block-qcm__choix-liste--colonne {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

/* En ligne : chaque choix s'etire pour occuper une part EGALE de toute la largeur du
   bloc (flex:1), quelle que soit cette largeur — demande explicite ("les questions en
   ligne doit prendre toute la largeur du bloc, n'importe la largeur"). flex-wrap reste
   present pour ne pas ecraser illisiblement les choix si le bloc est trop etroit pour
   tous les afficher sur une seule ligne a leur largeur minimale. */
.block-qcm__choix-liste--ligne {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-4);
}

.block-qcm__choix-liste--ligne .block-qcm__choix {
    flex: 1 1 0;
    min-width: 120px;
}

.block-qcm__choix {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

/* Vraie case a cocher (jamais seulement visuelle, voir qcm.js) — demande explicite :
   "fais juste des Check (comme ca les eleves peuvent bien checker et le prof plus tard
   peut savoir la reponse)". accent-color (propriete CSS native, pas de widget custom
   reimplementee) teinte la case cochee avec la meme couleur de marque que le reste de
   l'app, sans avoir a reconstruire l'apparence d'une case a cocher a la main. */
.block-qcm__check {
    flex-shrink: 0;
    width: 20px;
    height: 20px;
    accent-color: var(--color-accent);
    cursor: pointer;
}

.block-qcm__choix-texte {
    /* Meme correctif que .block-question__enonce plus haut — flex-item enfant de
       .block-qcm__choix (display:flex), meme piege min-width:auto par defaut. */
    flex: 1;
    min-width: 0;
    outline: none;
    /* Suit le reglage "Style du document" par activite — voir le meme commentaire sur
       .block-text plus haut. */
    font-size: var(--default-font-size, 16px);
    color: var(--default-text-color, var(--color-text));
    line-height: 1.4;
}

/* Barre d'actions contextuelle, flottante au-dessus du bloc quand il est
   selectionne — meme registre visuel que .block-table__tools. */
.block-qcm__tools {
    position: absolute;
    bottom: calc(100% + 10px);
    right: 0;
    display: none;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 4px 5px;
    box-shadow: 0 6px 16px -6px rgba(32, 30, 27, 0.35);
}

.block--editing .block-qcm__tools {
    display: flex;
}

.block-qcm__tools button {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    padding: 3px 8px;
    cursor: pointer;
    border-radius: var(--radius-sm);
}

.block-qcm__tools button:disabled {
    opacity: 0.3;
    cursor: not-allowed;
}

/* ---- Bloc en-tete de seance ---- */
/* Wrapper exterieur = .block__content, remplit toute la boite reservee du bloc (comme
   .block-text-wrap dans text.js) : c'est lui qui recoit le clic dans la zone vide autour
   du badge (voir titre.js) et sert de repere pour positionner .activity-title__compile. */
.activite_header-wrap {
    position: relative;
    height: 100%;
}

/* Regles visuelles (.activite_header, .activity-title...) dans blocks-shared.css,
   partagees avec export_pdf.html — voir ce fichier pour l'explication. */

/* Bouton "compiler les formules", meme registre que .block-question__compile */
.activity-title__compile {
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-accent);
    border-radius: var(--radius-sm);
    width: 24px;
    height: 24px;
    cursor: pointer;
    font-family: var(--font-display);
}

.activity-title__compile:hover {
    background: var(--color-accent);
    color: var(--color-surface);
}

/* ---- Bloc graphique (courbe de fonction ou graphique statistique) ---- */
.block-graphique {
    height: 100%;
}

.block-graphique canvas {
    width: 100%;
    height: 100%;
}

/* Colonne "apercu" — au repos (bloc non edite), remplit tout le bloc exactement comme
   le faisait directement <canvas> avant que ce conteneur n'existe (voir plus bas pour
   son role de colonne de gauche une fois la modale ouverte). position:relative reprend
   ici le role que portait .block-graphique lui-meme avant ce correctif : ancrer
   .block-graphique__error (position:absolute) — l'erreur reste "collee" a l'apercu, pas
   a toute la modale, ce qui a plus de sens visuellement. */
.block-graphique__preview {
    position: relative;
    height: 100%;
}

/* Bouton unique qui ouvre la modale d'edition (voir plus bas) — meme registre visuel
   que .block-icone__tools/.block-icone__change : une petite barre position:absolute
   pres du bloc, un seul bouton borde avec icone+libelle. */
/* Pas de fond/bordure sur CE conteneur (contrairement aux autres barres de ce fichier) —
   son unique bouton (.block-graphique__edit-btn, plus bas) porte deja sa propre carte
   complete (fond, bordure, ombre) ; dupliquer le meme style sur le conteneur produirait
   une double bordure/ombre imbriquee, purement cosmetique et sans interet ici. Largeur
   pleine page + marge de 10px restent identiques a toutes les autres barres. */
.block-graphique__edit-tools {
    position: absolute;
    bottom: calc(100% + 10px);
    right: 0;
    display: none;
    flex-wrap: wrap;
}

.block--editing .block-graphique__edit-tools {
    display: flex;
}

.block-graphique__edit-btn {
    display: flex;
    align-items: center;
    gap: 6px;
    font-family: var(--font-body);
    font-size: 12px;
    font-weight: 600;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-text);
    padding: 6px 12px;
    cursor: pointer;
    border-radius: var(--radius-sm);
    box-shadow: 0 6px 16px -6px rgba(32, 30, 27, 0.35);
}

.block-graphique__edit-btn:hover {
    background: var(--color-accent);
    border-color: var(--color-accent);
    color: var(--color-surface);
}

/* ---- Edition du graphique : modale plein ecran a DEUX COLONNES (apercu | reglages)
   ----
   Round 1 : les reglages vivaient directement dans une barre `position:absolute`
   flottant au-dessus du bloc, en flex-wrap, sans limite de hauteur ni defilement propre
   — avec assez de lignes (categories, courbes), ses boutons "+ Variable"/"+ Catégorie"
   finissaient pousses hors de l'ecran. Signale par l'utilisateur (capture a l'appui)
   comme "mal stylé et pas ergonomique... les boutons sont masqués, y a pas de bouton
   pour fermer ou annuler".
   Round 2 (ce round) : Round 1 avait resolu l'ergonomie (modale centree, overlay+panneau
   comme .icon-picker/.palette-picker, boutons d'ajout/suppression sortis de la zone qui
   defile, tout stylee) mais introduit un nouveau probleme signale aussitot : la modale
   centree cachait ENTIEREMENT le graphique pendant qu'on le modifiait ("on ne voit pas
   les changements"). Deux options proposees par l'utilisateur (modale ancree a droite en
   laissant voir le bloc, OU modale pleine largeur avec apercu integre a gauche) — la 2e
   est retenue et implementee ici : la position du bloc sur le canevas est arbitraire (il
   peut etre n'importe ou, scrolle hors champ...), donc ancrer la modale a droite
   n'aurait jamais garanti que le bloc reste visible a cote ; un apercu integre A LA
   MODALE ELLE-MEME (voir .block-graphique__preview) garantit qu'on le voit toujours.
   Reference visuelle donnee par l'utilisateur pour les DEUX rounds
   (static/images/like_canva_com.png) : panneau Canva "Graphique", reglages a gauche,
   apercu du graphique a droite sur le reste de l'ecran — la disposition reprise ici est
   la meme idee, cote reglages/apercu inverses (apercu a gauche) pour rester coherent
   avec le sens de lecture FR (on regarde d'abord le resultat, on ajuste ensuite).
   Performance : c'est la MEME instance Chart.js qui tourne des le depart (voir
   graphique.js:draw(), inchangee) — aucun moteur de rendu duplique, seule la mise en
   page CSS change. `chart.resize()` est appele explicitement a l'ouverture/fermeture
   (graphique.js) plutot que de compter uniquement sur le ResizeObserver interne de
   Chart.js (`responsive:true`, deja actif) — evite toute dependance a un timing non
   garanti a la milliseconde pres. */
.block-graphique__tools {
    display: none;
}

/* Simple fond assombri, aucune donnee dedans — separe du reste (voir plus bas)
   precisement pour ne jamais avoir a deplacer les champs hors de `wrap`. */
.graphique-modal-overlay {
    position: fixed;
    inset: 0;
    background: rgba(32, 30, 27, 0.45);
    z-index: 1600;
}

/* En-tete PLEINE LARGEUR (titre + croix), au-dessus des deux colonnes plutot qu'a
   l'interieur du panneau de reglages — cache par defaut, ne redevient visible que dans
   .block-graphique--modal-open plus bas. */
.graphique-modal__header {
    display: none;
    flex-shrink: 0;
    align-items: center;
    justify-content: space-between;
    padding: 16px 24px;
    border-bottom: 1px solid var(--color-border);
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 16px;
    color: var(--color-text);
}

.graphique-modal__close {
    border: none;
    background: none;
    color: var(--color-muted);
    cursor: pointer;
    font-size: 18px;
    line-height: 1;
    padding: 4px;
}

.graphique-modal__close:hover {
    color: var(--color-danger);
}

/* Conteneur des deux colonnes — au repos, un simple passe-plat (seul `.block-graphique
   __preview` y est visible, `tools` reste display:none), voir plus bas pour son
   comportement en rangee flex une fois la modale ouverte. */
.block-graphique__body {
    height: 100%;
}

.block-graphique__preview-canvas {
    position: relative;
    width: 100%;
    height: 100%;
}

/* Real bug, user-reported : "la couche sombre est dessous de tous les deux blocs... on
   ne peut acceder au bloc de droit [le panneau de reglages]" — confirme par mesure directe
   (document.elementFromPoint a un point clairement a l'interieur du panneau de reglages
   resolvait vers .graphique-modal-overlay, pas vers le panneau), pas juste rapporte par
   l'utilisateur. Cause reelle : un piege de contexte d'empilement CSS, pas un probleme de
   z-index en apparence — z-index:301 ci-dessous s'applique bien a CET element (confirme
   via getComputedStyle), mais cet element (`wrap`, la classe .block-graphique, retournee
   par createGraphique()) n'est jamais le wrapper .block lui-meme : canvas.js:createNode()
   l'ajoute comme ENFANT d'un wrapper .block exterieur distinct (position:absolute; z-index:1
   — voir plus haut dans ce fichier). Ce wrapper .block, etant positionne avec un z-index
   EXPLICITE (pas auto), etablit son PROPRE contexte d'empilement — et un descendant en
   position:fixed ne s'en EVADE JAMAIS pour les besoins de l'empilement (seul son
   POSITIONNEMENT/layout s'ancre au viewport ; son ORDRE DE PEINTURE reste celui de son
   contexte d'empilement parent). Consequence : le z-index:301 de `wrap` n'est donc jamais
   compare a celui de .graphique-modal-overlay (300, ajoute directement a document.body) —
   il est seulement compare aux AUTRES enfants du wrapper .block (poignees de
   redimensionnement, bouton bordure/fond...). Ce qui EST reellement compare a l'overlay,
   au niveau racine, c'est le wrapper .block lui-meme, avec son z-index de repos, 1 — donc
   TOUTE la modale (301, mais piegee) perd face a l'overlay (300) et se retrouve peinte
   EN DESSOUS, malgre son z-index nominal plus eleve. Corrige en relevant le z-index du
   wrapper .block lui-meme (pas seulement celui de `wrap`) des qu'il contient une modale de
   graphique ouverte, via :has() (deja utilise ailleurs dans cette app, ex. les cartes de
   template) — plus simple et plus sur qu'un deplacement du noeud DOM `wrap` hors de son
   wrapper (qui casserait le meme invariant deja documente pour `tools` : `wrap` doit
   toujours rester un descendant direct de son wrapper .block au moment ou N'IMPORTE QUEL
   bloc de la page declenche un instantane d'annulation, sans quoi
   wrapper.querySelector('.block__content') renverrait null pour ce bloc pendant que sa
   modale est ouverte). */
.block:has(.block-graphique--modal-open) {
    z-index: 1602;
}

/* Une rotation libre est stockee comme `transform:rotate(...)` directement sur .block.
   Or tout ancetre transforme devient le repere d'un descendant `position:fixed` : la
   modale se retrouvait alors dimensionnee relativement au petit bloc tourne au lieu du
   viewport. Pendant l'ouverture seulement, on neutralise ce transform sur le WRAPPER ;
   la rotation inline enregistree n'est ni effacee ni modifiee et reprend automatiquement
   effet des que la classe --modal-open disparait. */
.block:has(.block-graphique--modal-open),
.block:has(.block-figure--modal-open),
.block:has(.block-courbe--modal-open) {
    transform: none !important;
}

/* `wrap` (.block-graphique) lui-meme DEVIENT la modale plein ecran — voir
   graphique.js:openGraphiqueModal, qui ajoute cette classe plutot que d'en construire
   une nouvelle : canvas (dans .block-graphique__preview) est deja un descendant de
   `wrap`, donc aucun deplacement DOM n'est necessaire pour qu'il devienne visible a
   cote du panneau de reglages. */
.block-graphique--modal-open {
    position: fixed;
    inset: 0;
    /* .block__content (base.css/editor.css, cascade partagee par tout bloc) fixe
       `width: 100%; height: 100%` — meme specificite qu'ici (une seule classe), donc la
       seule propriete que CETTE regle ne redeclare pas explicitement continue d'etre
       gouvernee par cette autre regle malgre l'ordre de cascade. `width` doit donc etre
       explicitement remis a `auto` ici aussi (pas seulement `height`), sans quoi
       `width:100%` se recalcule contre la LARGEUR DU VIEWPORT (position:fixed) au lieu
       de laisser `left`/`right` la determiner — confirme par un vrai bug (la modale
       debordait du viewport, le bouton de fermeture devenait inatteignable, capture
       d'ecran/mesures Playwright a l'appui). */
    width: auto;
    height: auto;
    display: flex;
    flex-direction: column;
    background: var(--color-surface);
    border-radius: 0;
    box-shadow: 0 24px 60px -16px rgba(32, 30, 27, 0.5);
    z-index: 1601;
    overflow: hidden;
}

.block-graphique--modal-open .graphique-modal__header {
    display: flex;
}

/* Le bouton "Modifier les données" n'a plus de sens une fois la modale deja ouverte
   (et se retrouverait de toute facon recouvert par elle). */
.block-graphique--modal-open .block-graphique__edit-tools {
    display: none;
}

.block-graphique--modal-open .block-graphique__body {
    display: flex;
    flex-direction: row;
    flex: 1 1 auto;
    height: auto;
    min-height: 0;
    overflow: hidden;
}

/* Colonne de gauche : l'apercu en direct. Fond legerement distinct (--color-bg, pas le
   blanc --color-surface du panneau de reglages) pour bien separer visuellement les deux
   zones, meme sans bordure — la bordure est quand meme ajoutee, en renfort. */
.block-graphique--modal-open .block-graphique__preview {
    flex: 0 0 30%;
    max-width: 30%;
    min-width: 0;
    padding: 28px;
    background: var(--color-bg);
    border-right: 1px solid var(--color-border);
    box-sizing: border-box;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Même principe que Figure : l'aperçu garde la taille du bloc sur la page. Le JS ne le
   réduit que s'il ne peut réellement pas tenir dans les 30 % disponibles. */
.block-graphique--modal-open .block-graphique__preview-canvas {
    flex-shrink: 0;
    background: var(--color-surface);
    box-shadow: 0 4px 24px -8px rgba(32, 30, 27, 0.35);
    border-radius: var(--radius-sm);
    overflow: hidden;
}

/* Colonne de droite prioritaire : 70 % pour les reglages et le tableau de donnees. */
.block-graphique--modal-open .block-graphique__tools {
    display: flex;
    flex-direction: column;
    flex: 1 1 70%;
    max-width: 70%;
    min-width: 0;
    min-height: 0;
}

/* Sections clairement separees (mode, titre, champs) plutot qu'une seule ligne qui
   s'enroule — c'est ici, pas au niveau de `tools`, que vit le defilement propre du
   panneau de reglages (`tools` prend toute la hauteur disponible par flex-direction:
   column + le footer juste apres en flex-shrink:0, donc seul CE corps doit deborder en
   scroll, jamais le header ni le footer). */
.graphique-modal__body {
    flex: 1 1 auto;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 18px;
    padding: 18px 20px;
    overflow-x: auto;
}

.graphique-modal__footer {
    flex-shrink: 0;
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    padding: 14px 20px;
    border-top: 1px solid var(--color-border);
}

.graphique-modal__done {
    border: none;
    background: var(--color-accent);
    color: var(--color-surface);
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 14px;
    padding: 10px 22px;
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.graphique-modal__done:hover {
    filter: brightness(1.08);
}

.graphique-modal__row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
}

.graphique-modal__label {
    flex-shrink: 0;
    width: 130px;
    font-family: var(--font-body);
    font-size: 13px;
    color: var(--color-muted);
}

.graphique-modal__section-label {
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 13px;
    color: var(--color-text);
    margin-top: 4px;
}

/* Bouton d'ajout/suppression — avant ce correctif, un <button> totalement nu (aucune
   classe), rendu avec l'apparence par defaut du navigateur : la cause directe du "les
   boutons sont masqués, pas ergonomique" (ni visibles comme des actions, ni assez
   grands pour un clic confiant). */
.graphique-modal__btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-family: var(--font-body);
    font-size: 13px;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-text);
    padding: 8px 14px;
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.graphique-modal__btn:hover:not(:disabled) {
    background: var(--color-accent);
    border-color: var(--color-accent);
    color: var(--color-surface);
}

.graphique-modal__btn:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

.block-graphique__titre {
    min-width: 120px;
    flex: 1 1 120px;
}

.block-graphique__titre,
.block-graphique__type,
.block-graphique__series-name,
.block-graphique__row-label,
.block-graphique__row-value {
    font-size: 13px;
    font-family: var(--font-body);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 7px 10px;
    background: var(--color-surface);
    color: var(--color-text);
}

.block-graphique__row-value {
    width: 72px;
}

.block-graphique__row-label,
.block-graphique__series-name {
    width: 100px;
}

.block-graphique__series-color {
    width: 30px;
    height: 30px;
    flex-shrink: 0;
    padding: 0;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.block-graphique__fields {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.block-graphique__fields--hidden {
    display: none;
}

/* Tableau de donnees : une serie est une colonne et une observation est une ligne. */
.block-graphique__data-grid {
    max-height: 320px;
    overflow: auto;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-bg);
}

.block-graphique__series-heads {
    position: sticky;
    top: 0;
    z-index: 3;
    display: grid;
    grid-template-columns: var(--graphique-grid-columns, 140px minmax(150px, 1fr) 32px);
    gap: 6px;
    align-items: center;
    min-width: max-content;
    padding: 8px;
    border-bottom: 1px solid var(--color-border);
    background: var(--color-surface);
}

.block-graphique__series-header {
    display: flex;
    gap: 6px;
    align-items: center;
    min-width: 0;
}

.block-graphique__observation-header {
    font-family: var(--font-body);
    font-size: 12px;
    font-weight: 600;
    color: var(--color-muted);
}

.block-graphique__series-header .block-graphique__series-name {
    min-width: 76px;
    width: auto;
    flex: 1 1 auto;
}

.graphique-modal__remove-item {
    width: 30px;
    height: 30px;
    padding: 0;
    flex: 0 0 30px;
    justify-content: center;
    color: var(--color-muted);
}

.graphique-modal__remove-item:hover:not(:disabled) {
    border-color: var(--color-danger);
    background: rgba(185, 74, 72, 0.1);
    color: var(--color-danger);
}

/* Le menu couleur reste dans le panneau lorsqu'un bouton touche son bord droit. */
.graphique-modal__body .color-picker-btn__menu {
    right: 0;
    left: auto;
    z-index: 120;
}

@media (min-width: 900px) and (max-width: 1279px) {
    .block-graphique--modal-open .block-graphique__tools {
        flex-basis: 70%;
        max-width: 70%;
    }
}

@media (min-width: 734px) and (max-width: 899px) {
    .block-graphique--modal-open .block-graphique__tools {
        flex-basis: 70%;
        max-width: 70%;
    }

    .graphique-modal__body {
        padding: 12px;
        gap: 12px;
    }

    .graphique-modal__label {
        width: 105px;
        font-size: 11px;
    }

    .block-graphique__titre,
    .block-graphique__type,
    .block-graphique__series-name,
    .block-graphique__row-label,
    .block-graphique__row-value {
        padding: 5px 7px;
        font-size: 11px;
    }
}

.block-graphique__cat-rows {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 8px;
    min-width: max-content;
}

.block-graphique__cat-row {
    display: grid;
    grid-template-columns: var(--graphique-grid-columns, 140px minmax(150px, 1fr) 32px);
    gap: 6px;
    align-items: center;
    flex-shrink: 0;
}

.block-graphique__observation-cell {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
}

.block-graphique__observation-cell .block-graphique__row-label,
.block-graphique__cat-row .block-graphique__row-value {
    width: 100%;
    min-width: 0;
}

.block-graphique__error {
    position: absolute;
    bottom: 4px;
    left: 4px;
    font-size: 11px;
    color: var(--color-danger);
    background: var(--color-surface);
    padding: 2px 6px;
    border-radius: var(--radius-sm);
    pointer-events: none;
}

/* ---- Bloc notion (Coup de pouce/Définition/Propriété/Démonstration — voir notion.js) :
   coupdepouce.js a ete retire comme type de bloc separe, fusionne ici — meme silhouette
   encadree, mais icone ET titre sont tous deux modifiables/retirables au lieu d'etre
   figes en dur. ---- */
.block-notion {
    position: relative;
    height: 100%;
    overflow: visible;
    border-radius: var(--radius-sm);
    /* border-left permanent (comme l'ancien coupdepouce.js) : marque l'encart meme sans
       fond choisi. Couleur derivee de --notion-color (posee par notion.js : la couleur DE
       CE BLOC si choisie, sinon --color-accent) plutot que --color-accent fixe — retour
       explicite : le dore fixe "est en dephasage", la couleur du bloc doit primer, juste
       un peu plus foncee (color-mix avec du noir a 20%, un assombrissement leger,
       "juste un peu"). */
    border-left: 3px solid color-mix(in srgb, var(--notion-color, var(--color-accent)) 80%, black);
    /* padding-top : laisse la place au badge/pastille flottants (voir
       .block-notion__badge/__titre ci-dessous, chevauchant le bord superieur de ~18px)
       sans que le corps du texte ne vienne les toucher. Resserre de 26px a 20px sur
       retour explicite ("la marge est grande... meme chose pour le bloc Encadré") — ne
       laisse plus que 2px de degagement sous le badge (18px de haut) au lieu de 8px,
       mais un ecart franchement reduit sans icone (le cas le plus visible, seule la
       pastille de titre — pas le badge — determine alors le degagement reel). */
    padding: 20px var(--space-3) var(--space-2);
    /* Pas de fond par defaut ici (contrairement a l'ancien .block-coupdepouce et sa
       --color-hint-soft) : le fond de ce bloc est explicitement optionnel, pose en
       inline style par notion.js des qu'une couleur est choisie, sinon transparent. */
}

/* Preset "Encadre simple" : aucun espace fantome reserve a un badge/titre absent. */
.block-notion:not(.block-notion--has-icon):not(.block-notion--has-title) {
    padding-top: var(--space-2);
}

/* Badge circulaire flottant pour l'icone — meme idee que .activity-title__number/
   .question-bullet (une icone/un numero dans une pastille ronde) plutot qu'une icone
   posee a plat dans une ligne, chevauchant le bord superieur du bloc via
   position:absolute. Vide (aucune icone) : reduit a 0 par
   .block-notion:not(.block-notion--has-icon), pour ne jamais afficher un rond vide
   flottant sans raison. Fond derive de --notion-color, meme raisonnement que le
   border-left ci-dessus. */
.block-notion__badge {
    position: absolute;
    top: -18px;
    left: 0;
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    /* --notion-icon-color (posee par notion.js:updateIconAccent, jamais fixee en dur ici)
       prend le pas sur --notion-color quand l'utilisateur choisit explicitement une
       couleur d'icone independante du fond du bloc — absente (cas par defaut), le badge
       continue de deriver de --notion-color exactement comme avant, aucun changement de
       comportement pour un bloc jamais touche par ce nouveau controle. */
    background: color-mix(in srgb, var(--notion-icon-color, var(--notion-color, var(--color-accent))) 80%, black);
    color: var(--color-surface);
    font-size: 15px;
    box-shadow: 0 3px 8px -2px rgba(32, 30, 27, 0.35);
}

.block-notion:not(.block-notion--has-icon) .block-notion__badge {
    display: none;
}

/* Titre : pastille flottante (position:absolute, chevauche le coin superieur, fond
   plein + texte clair, forme pilule) plutot qu'aligne a plat dans une ligne d'en-tete
   avec l'icone — c'est precisement ce qui permet de retaper "Définition" en
   "Démonstration" tout en gardant la meme composition visuelle "badge + pastille" que le
   bandeau de titre de l'activite. Colle contre le badge (voir .block-notion--has-icon
   ci-dessous) ; sans icone, commence pile sur le bord gauche (left:0). Fond derive de
   --notion-color, meme raisonnement que le badge. */
.block-notion__titre {
    position: absolute;
    top: -14px;
    left: 0;
    width: fit-content;
    max-width: calc(100% - 10px);
    padding: 5px 12px;
    background: color-mix(in srgb, var(--notion-color, var(--color-accent)) 80%, black);
    color: var(--color-surface);
    border-radius: var(--radius-pill);
    font-family: var(--font-body);
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    outline: none;
    box-shadow: 0 3px 8px -2px rgba(32, 30, 27, 0.35);
}

.block-notion--has-icon .block-notion__titre {
    /* 36px = largeur exacte du badge parti de left:0 : aucune marge avant l'icone et
       aucun espace entre son bord droit et la pastille de titre. */
    left: 36px;
    max-width: calc(100% - 46px);
}

.block-notion__titre p,
.block-notion__titre div {
    margin: 0;
}

.block-notion__corps {
    /* Suit le reglage "Style du document" par activite — voir le meme commentaire sur
       .block-text plus haut. .block-notion__titre (la pastille de titre) garde sa
       propre couleur derivee de --notion-color, jamais touchee ici. */
    font-size: var(--default-font-size, 16px);
    line-height: 1.5;
    outline: none;
    color: var(--default-text-color, var(--color-text));
}

.block-notion__corps p,
.block-notion__corps div {
    margin: 0;
}

/* Barre d'actions contextuelle, meme registre visuel que .block-coupdepouce__tools. */
.block-notion__tools {
    position: absolute;
    bottom: calc(100% + 10px);
    right: 0;
    display: none;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 4px 5px;
    box-shadow: 0 6px 16px -6px rgba(32, 30, 27, 0.35);
}

.block--editing .block-notion__tools {
    display: flex;
}

.block-notion__icon-btn,
.block-notion__color {
    width: 24px;
    height: 24px;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.block-notion__no-bg,
.block-notion__icon-color-reset {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    padding: 3px 8px;
    cursor: pointer;
    border-radius: var(--radius-sm);
    white-space: nowrap;
}

/* Bouton "compiler les formules", meme registre que .block-coupdepouce__compile — le
   titre ET le corps sont compiles en un seul clic (meme "un bouton, plusieurs champs"
   que arbreproba.js/qcm.js). */
.block-notion__compile {
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-accent);
    border-radius: var(--radius-sm);
    width: 24px;
    height: 24px;
    cursor: pointer;
    font-family: var(--font-display);
}

.block-notion__compile:hover {
    background: var(--color-accent);
    color: var(--color-surface);
}

/* ---- Bloc ligne (decoratif, sans texte — voir blocks/ligne.js/ligneRender.js) ----
   Le dimensionnement de base (.block-ligne/.block-ligne__svg) vit dans blocks-shared.css,
   pas ici — voir sa note pour le pourquoi (export_pdf.html a besoin d'une taille reelle
   AVANT d'executer son propre script de rendu, comme .block-fleche avant lui). ---- */
/* Pas de fond/bordure sur ces conteneurs — leur unique bouton (.block-ligne__settings/
   .block-forme__settings/.block-fond__settings) porte deja sa propre carte complete, meme
   raison que .block-graphique__edit-tools plus haut dans ce fichier. */
.block-ligne__tools,
.block-forme__tools,
.block-fond__tools,
.block-courbe__tools,
.block-variation__tools,
.block-signe__tools,
.block-figure__tools,
.block-espace__tools,
.block-solide__tools,
.block-circuit__tools {
    position: absolute;
    display: none;
    flex-wrap: wrap;
}

/* La forme peut etre placee sur un autre bloc et ses propres poignees d'edition vivent
   deja sur une couche locale (z-index:5). Sa barre doit donc etre une vraie surface
   opaque, au-dessus de tous les contenus/poignees du bloc actif — le wrapper
   .block--editing (z-index:400) assure ensuite son passage devant les blocs voisins. */
.block-forme__tools,
.block-figure__tools {
    z-index: 50;
    padding: 4px;
    background: #fff;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    box-shadow: 0 5px 14px -7px rgba(32, 30, 27, 0.38);
}

.block-ligne__tools,
.block-forme__tools,
.block-courbe__tools,
.block-variation__tools,
.block-signe__tools,
.block-figure__tools,
.block-espace__tools,
.block-solide__tools,
.block-circuit__tools {
    bottom: calc(100% + 10px);
    right: 0;
}

/* .block-fond__tools ne reprend PAS bottom:calc(100% + 10px)/right:0 de ligne/forme
   ci-dessus : un fond se place le plus souvent plein bord de page (y:0, voir
   canvas.js:addBlock), ce qui pousserait sa barre "au-dessus du bloc" hors du document,
   au-dessus meme de la toute premiere page — reellement inatteignable, aucun defilement
   ne remonte plus haut que le sommet du document (bug reel, reproduit : le bouton restait
   introuvable/inaccessible). Ancree A L'INTERIEUR du bloc a la place (coin haut-gauche,
   10px de marge) — toujours accessible tant que le haut du bloc, forcement visible des
   qu'on interagit avec lui, l'est aussi. Consequence acceptee : peut recouvrir un coin de
   l'image/couleur de fond juste en dessous — un seul petit bouton, jamais genant en
   pratique, et de toute facon masque hors edition. Volontairement PAS dans
   FULL_WIDTH_TOOLBAR_SELECTOR (canvas.js) pour la meme raison : etirer cette barre a la
   largeur de la PAGE la deconnecterait de la position reelle du bloc (utile pour un fond
   partiel, pas seulement plein bord). */
.block-fond__tools {
    top: 10px;
    left: 10px;
}

.block--editing .block-ligne__tools,
.block--editing .block-forme__tools,
.block--editing .block-fond__tools,
.block--editing .block-courbe__tools,
.block--editing .block-variation__tools,
.block--editing .block-signe__tools,
.block--editing .block-figure__tools,
.block--editing .block-espace__tools,
.block--editing .block-solide__tools,
.block--editing .block-circuit__tools {
    display: flex;
}

.block-ligne__settings,
.block-forme__settings,
.block-fond__settings,
.block-courbe__settings,
.block-variation__settings,
.block-signe__settings,
.block-figure__settings,
.block-espace__settings,
.block-solide__settings,
.block-circuit__settings {
    width: 24px;
    height: 24px;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-accent);
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.block-courbe__create-table {
    width: auto;
    gap: 6px;
    padding: 0 9px;
    font-family: var(--font-body);
    font-size: 11px;
}

/* Assistant Courbe -> Tableau : une seule fenetre courte, au-dessus de toutes les
   modales de l'editeur. Les controles restent visibles et le brouillon seul defile. */
.courbe-table-assistant-overlay {
    position: fixed;
    inset: 0;
    z-index: 2400;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: rgba(32, 30, 27, 0.55);
}

.courbe-table-assistant {
    width: min(920px, 96vw);
    max-height: 92vh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    border-radius: var(--radius-md);
    background: var(--color-surface);
    box-shadow: 0 24px 60px -16px rgba(32, 30, 27, 0.55);
}

.courbe-table-assistant__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
    padding: 16px 20px;
    border-bottom: 1px solid var(--color-border);
}

.courbe-table-assistant__header div {
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.courbe-table-assistant__header span {
    color: var(--color-muted);
    font-size: 12px;
}

.courbe-table-assistant__header button {
    border: 0;
    background: transparent;
    color: var(--color-muted);
    font-size: 18px;
    cursor: pointer;
}

.courbe-table-assistant__controls {
    display: grid;
    grid-template-columns: minmax(220px, 2fr) minmax(150px, 1fr) 100px 100px auto;
    gap: 10px;
    align-items: end;
    padding: 16px 20px;
}

.courbe-table-assistant__controls label {
    display: flex;
    flex-direction: column;
    gap: 5px;
    color: var(--color-muted);
    font-size: 11px;
}

.courbe-table-assistant__controls input,
.courbe-table-assistant__controls select,
.courbe-table-assistant__grid input,
.courbe-table-assistant__grid select {
    min-width: 0;
    height: 34px;
    padding: 6px 8px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    color: var(--color-text);
    font: 13px var(--font-body);
}

.courbe-table-assistant__controls button,
.courbe-table-assistant__footer button {
    min-height: 34px;
    padding: 7px 14px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    color: var(--color-text);
    cursor: pointer;
}

.courbe-table-assistant__notice {
    margin: 0 20px 12px;
    padding: 9px 12px;
    border-radius: var(--radius-sm);
    background: var(--color-accent-soft);
    color: var(--color-text);
    font-size: 12px;
}

.courbe-table-assistant__zero-option {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: -2px 20px 12px;
    color: var(--color-text);
    font-size: 12px;
}

.courbe-table-assistant__zero-option input {
    width: 16px;
    height: 16px;
}

.courbe-table-assistant__zero-option[hidden] {
    display: none;
}

.courbe-table-assistant__result {
    flex: 1 1 auto;
    min-height: 150px;
    overflow: auto;
    padding: 0 20px 18px;
}

.courbe-table-assistant__grid {
    min-width: 620px;
    overflow: hidden;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
}

.courbe-table-assistant__grid-row {
    display: grid;
    grid-template-columns: 1.1fr 1fr 1.1fr 1.2fr;
    gap: 8px;
    align-items: center;
    padding: 8px;
    border-bottom: 1px solid var(--color-border);
}

.courbe-table-assistant__grid-row:last-child {
    border-bottom: 0;
}

.courbe-table-assistant__grid-row--header {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--color-bg);
    color: var(--color-muted);
    font-size: 11px;
    font-weight: 600;
}

.courbe-table-assistant__sign-cell {
    display: flex;
    align-items: center;
    min-width: 0;
    color: var(--color-muted);
}

.courbe-table-assistant__sign-cell select {
    width: 100%;
}

.courbe-table-assistant__value-cell,
.block-variation-panel__limits {
    display: flex;
    gap: 6px;
    min-width: 0;
}

.courbe-table-assistant__value-cell input,
.block-variation-panel__limits input {
    width: 100%;
    min-width: 0;
}

.courbe-table-assistant__error {
    padding: 20px;
    border: 1px solid var(--color-danger);
    border-radius: var(--radius-sm);
    color: var(--color-danger);
}

.courbe-table-assistant__footer {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    padding: 14px 20px;
    border-top: 1px solid var(--color-border);
}

.courbe-table-assistant__footer .courbe-table-assistant__create {
    border-color: var(--color-accent);
    background: var(--color-accent);
    color: var(--color-surface);
}

.courbe-table-assistant__footer .courbe-table-assistant__create:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

@media (max-width: 900px) {
    .courbe-table-assistant__controls {
        grid-template-columns: 1fr 1fr;
    }
}

/* Ombre portee sur le bouton du fond specifiquement (pas ligne/forme, deja etablis sans —
   pas de raison de changer leur apparence pour ce fait): posee sur une couleur/image de
   fond potentiellement tres claire, un simple fond blanc + bordure fine peut se fondre
   davantage qu'au-dessus du canevas neutre ou vivent ligne/forme. */
.block-fond__settings {
    box-shadow: 0 2px 6px -2px rgba(32, 30, 27, 0.4);
}

/* ---- Bloc forme (decoratif, sans texte — voir blocks/forme.js/formeRender.js) ---- */
/* Dimensionnement de base (.block-forme/.block-forme__shape) dans blocks-shared.css,
   meme raison que .block-ligne ci-dessus. */

/* Poignees de sommets du mode "libre" — plein-cadre, pointer-events:none par defaut
   (meme convention que .arbreproba-labels-layer/.mindmap-edges-svg dans ce meme dossier :
   une couche pleine posee AU-DESSUS d'un contenu interactif doit rester transparente aux
   clics sauf pour ses propres controles reels) pour ne jamais intercepter le clic-glisser
   du bloc entier ni le bouton de reglages, tous deux geres ailleurs. Cachees hors
   .block--editing (comme .arbreproba-node__dot) — ce sont des poignees d'edition, jamais
   un element du rendu final/imprime. */
.block-forme__points-layer {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 5;
}

.block-forme__point-handle {
    display: none;
    position: absolute;
    width: 14px;
    height: 14px;
    margin-left: -7px;
    margin-top: -7px;
    border-radius: 50%;
    background: var(--color-accent);
    border: 2px solid var(--color-surface);
    box-shadow: 0 1px 4px -1px rgba(32, 30, 27, 0.5);
    cursor: move;
    pointer-events: auto;
}

.block--editing .block-forme__point-handle {
    display: block;
}

/* "×" de suppression — visible seulement au survol du point (jamais en permanence,
   14 points affiches avec chacun sa propre croix en permanence serait tres charge
   visuellement pour une action secondaire) ; desactive par forme.js des que
   points.length <= MIN_FREE_POINTS (jamais moins de 3 sommets, un polygone degenere
   n'a pas de sens). */
.block-forme__point-remove {
    display: none;
    position: absolute;
    top: -8px;
    right: -8px;
    width: 14px;
    height: 14px;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: 50%;
    background: #B23B3B;
    color: var(--color-surface);
    font-size: 10px;
    line-height: 1;
    cursor: pointer;
    padding: 0;
}

.block-forme__point-handle:hover .block-forme__point-remove {
    display: flex;
}

.block-forme__point-remove:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

/* Sections blob/polygone du panneau de reglage — une seule visible a la fois (voir
   forme.js:refreshSections), meme reglage generique .block-style-panel__row/__value que
   le panneau de style de bloc (canvas.js:openBlockStylePanel). */
.block-forme-panel__section {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* Gabarit visuel partage par "Aléatoire" (mode blob) et "Ajouter un point" (mode libre) —
   deux boutons d'action ponctuelle au meme role (proposer/faire avancer la forme d'un
   coup), classe commune pour le style, chacun garde en plus sa propre classe dediee
   (.block-forme-panel__random / __add-point-btn) pour rester ciblable individuellement
   (CSS ou test) puisque les deux coexistent en permanence dans le DOM. */
.block-forme-panel__action-btn {
    align-self: flex-start;
    display: flex;
    align-items: center;
    gap: 6px;
    border: 1px solid var(--color-border);
    background: var(--color-bg);
    color: var(--color-text);
    padding: 6px 12px;
    border-radius: var(--radius-sm);
    cursor: pointer;
    font-family: var(--font-body);
    font-size: 13px;
}

.block-forme-panel__action-btn:hover {
    border-color: var(--color-accent);
    color: var(--color-accent);
}

/* Section "libre" du panneau (forme.js) — texte d'aide + bouton "Ajouter un point"
   (reutilise .block-forme-panel__random tel quel, meme gabarit qu'"Aléatoire" du mode
   blob) + galerie de formes de depart. */
.block-forme-panel__hint {
    margin: 0;
    font-size: 12px;
    line-height: 1.5;
    color: var(--color-muted);
}

.block-forme-panel__presets-label {
    font-size: 12px;
    color: var(--color-muted);
}

.block-forme-panel__presets {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.block-forme-panel__preset-btn {
    border: 1px solid var(--color-border);
    background: var(--color-bg);
    color: var(--color-text);
    padding: 5px 10px;
    border-radius: var(--radius-sm);
    cursor: pointer;
    font-family: var(--font-body);
    font-size: 12px;
}

.block-forme-panel__preset-btn:hover {
    border-color: var(--color-accent);
    color: var(--color-accent);
}

/* Panneaux de reglage ligne/forme/fond : DOCKES A DROITE, fond transparent (pas de voile
   plein ecran) plutot que centres avec un fond assombri — retour explicite : "le modal
   masque la forme donc on peut pas voir ce qu'on fait" (meme raison exacte pour le fond :
   voir son propre rendu en direct — couleur/image/overlay — pendant le reglage compte
   au moins autant que pour une forme). Classes dediees plutot que toucher
   .block-style-panel-overlay/.block-style-panel (partagees par le panneau generique de
   bordure/fond ET la palette de couleurs recentes) — ne concerne QUE ces 3 panneaux, tout
   le reste du patron overlay+panneau garde son comportement centre+voile habituel ailleurs
   dans l'app. L'overlay reste `position:fixed; inset:0` (donc un clic n'importe ou en
   dehors du panneau le ferme toujours), seul son fond devient transparent et son
   alignement passe de centre a "colle a droite" — le canevas, y compris le bloc en cours
   de reglage, reste entierement visible derriere.
   .text-style-panel-overlay partage ce meme docking/transparence cosmetique, mais PAS le
   "clic dehors ferme toujours" : voir la regle dediee juste en dessous — bug reel
   remonte ("le modal disparait quand on veut ajouter un nombre et la selection aussi") :
   ce panneau reste ouvert pendant qu'on selectionne/clique du texte sur le canevas
   derriere lui, contrairement aux 3 autres qui ne font qu'OBSERVER le canevas pendant le
   reglage. */
.block-forme-panel-overlay,
.block-ligne-panel-overlay,
.block-fond-panel-overlay,
.block-image-panel-overlay,
.block-courbe-panel-overlay,
.block-variation-panel-overlay,
.block-signe-panel-overlay,
.text-style-panel-overlay {
    background: transparent;
    justify-content: flex-end;
    align-items: flex-start;
    padding: 24px;
}

/* Petite case "tangente" en ligne dans la ligne d'un point du cercle (Chantier B, figure.js)
   — un label inline compact plutot qu'une ligne de panneau dediee (checkboxRow), puisqu'elle
   partage deja sa ligne avec les champs nom/angle de ce meme point. */
.block-figure-panel__mini-check {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 11px;
    color: var(--color-text-muted, var(--color-hint));
    white-space: nowrap;
    flex-shrink: 0;
}

/* Une CARTE claire (bordure + fond legerement teinte) par point/droite dans les listes
   modifiables du bloc figure (points sur le cercle, traces auxiliaires du triangle, points
   et droites de la Construction libre) — retour utilisateur direct : "on doit séparer par
   des ligne ou par un fond pour les point et leurs champs... aussi les droite et leur
   champs", plusieurs entrees consecutives se lisaient auparavant comme un seul bloc continu
   de champs sans aucune limite visuelle entre elles. S'ajoute a .block-style-panel__row
   (jamais un remplacement) — n'affecte donc jamais la disposition flex existante, seulement
   l'habillage visuel. */
.block-figure-panel__card {
    padding: 10px 12px;
    margin-bottom: 10px;
    gap: 6px;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
}

/* Tableau (grille CSS) pour des champs REPETES a l'identique sur plusieurs elements
   homogenes (sommets, cotes...) — voir figure.js:tableauChamps. Demande directe : "comme
   les titres sont presque pareils... on peut faire un tableau dont les colonnes c'est les
   lettres des sommets... je pense c'est plus esthétique." Une colonne de largeur fixe pour
   les libelles de ligne (jamais compressee), puis une colonne EGALE par element — le nombre
   d'elements variant selon la forme (3 sommets pour un triangle, 4 pour les autres), passe
   via la variable CSS --nb-colonnes plutot qu'une classe par arite. */
.block-figure-panel__tableau {
    display: grid;
    grid-template-columns: minmax(90px, auto) repeat(var(--nb-colonnes), minmax(0, 1fr));
    column-gap: 8px;
    row-gap: 6px;
    align-items: center;
    margin-bottom: 18px;
}

.block-figure-panel__tableau-entete {
    font-family: var(--font-body);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.04em;
    color: var(--color-accent);
    text-align: center;
    padding-bottom: 6px;
    border-bottom: 1px solid var(--color-border);
    /* Le row-gap:6px du grid parent separe deja les lignes entre elles, mais le TRAIT
       (border-bottom) lui-meme se lit comme colle a la premiere ligne de donnees sans un
       peu d'air en plus sous lui — demande directe : "pour les trait horizontal il faut
       faire un margin-bottom pour que ça colle pas aux éléments". */
    margin-bottom: 4px;
}

.block-figure-panel__tableau-libelle {
    font-family: var(--font-body);
    font-size: 12px;
    color: var(--color-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.block-figure-panel__tableau-cellule {
    display: flex;
    align-items: center;
    justify-content: center;
}

.block-figure-panel__tableau-cellule input[type="text"],
.block-figure-panel__tableau-cellule input[type="number"],
.block-figure-panel__tableau-cellule select {
    width: 100%;
    min-width: 0;
    text-align: center;
    padding-left: 4px;
    padding-right: 4px;
}

.block-figure-panel__tableau-cellule input[type="checkbox"] {
    width: 18px;
    height: 18px;
    accent-color: var(--color-accent);
}

/* ---- Bloc figure : MODALE PLEIN ECRAN a deux colonnes (apercu | reglages), meme patron
   que graphique.js/.block-graphique--modal-open — demande explicite de l'utilisateur
   ("le modal peut être comme celui du bloc graph, qui prend le plein écran et on voit les
   changements en temps réel") une fois le panneau flottant docke a droite devenu trop
   charge pour tenir a cote du bloc (listes de points/droites potentiellement longues en
   mode Construction libre). Voir .block-graphique--modal-open plus haut dans ce fichier
   pour la derivation complete des 2 pieges deja rencontres et evites ici des le depart :
   (1) width:auto/height:auto necessaires car .block__content impose width:100%, qui se
   resoudrait sinon contre le VIEWPORT une fois position:fixed et sur-contraindrait
   left/right ; (2) .block:has(...) doit relever le z-index du WRAPPER .block lui-meme (pas
   seulement de `wrap`), un descendant position:fixed ne s'evade jamais du contexte
   d'empilement de son parent positionne (z-index explicite non-auto). ---- */
.block-figure__preview {
    position: relative;
    height: 100%;
}

/* Descendant intermediaire de .block-figure__preview, ce que renderFigure() cible reellement
   (voir figure.js:redraw) — au repos (bloc inline sur la page), remplit exactement son
   parent, byte pour byte le meme comportement qu'avant l'introduction de ce niveau. En
   modale (voir .block-figure--modal-open .block-figure__preview-canvas plus bas), figure.js
   lui donne une taille EXPLICITE en px, egale a la taille reelle du bloc — jamais etiree
   pour remplir le grand panneau d'apercu. */
.block-figure__preview-canvas {
    position: relative;
    width: 100%;
    height: 100%;
}

.block-figure__modal-header {
    display: none;
}

.block-figure-modal-overlay {
    position: fixed;
    inset: 0;
    background: rgba(32, 30, 27, 0.45);
    z-index: 300;
}

.block-figure-modal-overlay--plane {
    z-index: 2000;
}

/* Le bloc courbe (courbe.js) reutilise directement cette meme coquille de modale plein
   ecran — voir le commentaire d'en-tete de courbe.js:createCourbe pour le detail complet
   de ce qui est reutilise tel quel (habillage generique : header/body-settings/footer/
   overlay) vs. dedie (.block-courbe__body/__preview/__preview-canvas/__tools-pane, qui
   REMPLISSENT l'espace disponible plutot que de figer une taille reelle — un graphique
   mathematique n'a pas les memes contraintes de proportions qu'une figure geometrique
   parametrique). Chaque regle ci-dessous etendue a `.block-courbe--modal-open` en plus de
   `.block-figure--modal-open` la ou le comportement est identique pour les deux blocs. */
.block:has(.block-figure--modal-open),
.block:has(.block-courbe--modal-open),
.block:has(.block-espace--modal-open),
.block:has(.block-solide--modal-open),
.block:has(.block-circuit--modal-open) {
    z-index: 302;
}

.block:has(.block-figure--modal-open) {
    z-index: 2002;
}

.block-figure--modal-open,
.block-courbe--modal-open,
.block-espace--modal-open,
.block-solide--modal-open,
.block-circuit--modal-open {
    position: fixed;
    top: 4vh;
    left: 4vw;
    right: 4vw;
    bottom: 4vh;
    width: auto;
    height: auto;
    display: flex;
    flex-direction: column;
    background: var(--color-surface);
    border-radius: var(--radius-md);
    box-shadow: 0 24px 60px -16px rgba(32, 30, 27, 0.5);
    z-index: 301;
    overflow: hidden;
}

/* La modale de geometrie plane utilise tout le viewport ; les autres modales conservent
   leurs marges actuelles. La repartition interne reste 30 % aperçu / 70 % reglages. */
.block-figure--modal-open {
    inset: 0;
    width: auto;
    height: auto;
    border-radius: 0;
    z-index: 2001;
}

.block-figure--modal-open .block-figure__modal-header,
.block-courbe--modal-open .block-figure__modal-header,
.block-espace--modal-open .block-figure__modal-header,
.block-solide--modal-open .block-figure__modal-header,
.block-circuit--modal-open .block-figure__modal-header {
    display: flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: space-between;
    padding: 16px 24px;
    border-bottom: 1px solid var(--color-border);
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 16px;
    color: var(--color-text);
}

.block-figure__modal-close {
    border: none;
    background: none;
    color: var(--color-muted);
    cursor: pointer;
    font-size: 18px;
    line-height: 1;
    padding: 4px;
}

.block-figure__modal-close:hover {
    color: var(--color-danger);
}

/* Le bouton reglages en coin de bloc n'a plus de sens (et se retrouverait de toute facon
   recouvert) une fois la modale deja ouverte — `tools` est un enfant DIRECT de `wrap`,
   qui porte la classe --modal-open, donc un simple combinateur descendant suffit. */
.block-figure--modal-open .block-figure__tools {
    display: none;
}

.block-figure__body {
    height: 100%;
}

.block-figure--modal-open .block-figure__body {
    display: flex;
    flex-direction: row;
    flex: 1 1 auto;
    height: auto;
    min-height: 0;
    overflow: hidden;
}

/* Colonne de gauche : l'apercu EN DIRECT (renderFigure dessine directement dans
   .block-figure__preview, deja un descendant de `wrap` — aucun moteur de rendu duplique,
   aucun deplacement DOM). Fond legerement distinct (--color-bg, pas le blanc --color-surface
   du panneau de reglages) pour bien separer visuellement les deux zones. */
.block-figure--modal-open .block-figure__preview {
    flex: 0 0 30%;
    max-width: 30%;
    box-sizing: border-box;
    min-width: 0;
    padding: 28px;
    background: var(--color-bg);
    border-right: 1px solid var(--color-border);
    /* Centre previewCanvas (desormais fige a la taille REELLE du bloc, voir plus haut) au
       milieu du grand espace disponible — demande directe, apres constat que la figure
       rendait presque 2x plus grande en modale que sur la page (mesure : jusqu'a 2.3x) :
       "la taille sur la page doit être égale sur le modal". */
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Taille reelle FIGEE en JS (figure.js:appliquerTailleNaturelle, style.width/height en px
   directs) — flex-shrink:0 est indispensable, sinon le flex parent compresserait quand meme
   ce canvas pour "l'aider" a tenir dans l'espace disponible, annulant l'effet recherche.
   Habillage "feuille de page" (fond + ombre) pour que la limite entre "ceci EST la taille
   reelle du bloc" et le reste de l'espace de la modale se voie clairement, plutot qu'un
   dessin flottant sans aucune bordure visible. */
.block-figure--modal-open .block-figure__preview-canvas {
    flex-shrink: 0;
    background: var(--color-surface);
    box-shadow: 0 4px 24px -8px rgba(32, 30, 27, 0.35);
    border-radius: var(--radius-sm);
}

/* ---- Coquille DEDIEE de la modale courbe.js (body/preview/preview-canvas) — previewCanvas
   remplit toujours 100% de son parent au repos (bloc inline sur la page, comportement
   inchange), mais est desormais FIGE a la taille REELLE du bloc en modale (voir
   .block-courbe--modal-open .block-courbe__preview-canvas plus bas et
   courbe.js:appliquerTailleNaturelle) — demande directe : "on doit avoir les memes
   dimensions du bloc (sur la page que sur le modal) c'est ce que j'ai fais pour
   figure/graphe/circuit", meme regle/meme raison que .block-figure--modal-open
   .block-figure__preview et .block-circuit--modal-open .block-circuit__preview (un choix
   different pour ce bloc precis avant cette demande, explicitement renverse pour la
   coherence entre tous les blocs mathematiques a modale plein ecran). ---- */
.block-courbe--modal-open .block-courbe__tools {
    display: none;
}

.block-courbe__body {
    height: 100%;
}

.block-courbe--modal-open .block-courbe__body {
    display: flex;
    flex-direction: row;
    flex: 1 1 auto;
    height: auto;
    min-height: 0;
    overflow: hidden;
}

.block-courbe__preview {
    position: relative;
    height: 100%;
}

/* Colonne d'apercu a 30% (et .block-courbe__tools-pane a 70%, voir plus bas) — demande
   directe : "il faut refaire les dimensions, la partie qui a les champs prend 70% et le
   visuel 30%... l'image doit etre centree", meme repartition exacte que
   .block-figure--modal-open .block-figure__preview/.block-figure__tools-pane (jamais le
   flex:1 1 auto "prend ce qu'il reste" utilise par circuit.js/espace.js, qui ne garantit
   aucun pourcentage precis). previewCanvas (desormais fige a la taille REELLE du bloc,
   voir plus bas) reste CENTRE au milieu de cette colonne. */
.block-courbe--modal-open .block-courbe__preview {
    flex: 0 0 30%;
    max-width: 30%;
    min-width: 0;
    box-sizing: border-box;
    padding: 28px;
    background: var(--color-bg);
    border-right: 1px solid var(--color-border);
    display: flex;
    align-items: center;
    justify-content: center;
}

.block-courbe__preview-canvas {
    position: relative;
    width: 100%;
    height: 100%;
}

/* Taille reelle FIGEE en JS (courbe.js:appliquerTailleNaturelle, style.width/height en px
   directs) — flex-shrink:0 est indispensable, sinon le flex parent compresserait quand
   meme ce canvas pour "l'aider" a tenir dans l'espace disponible, annulant l'effet
   recherche. Habillage "feuille de page" (fond + ombre) pour que la limite entre "ceci EST
   la taille reelle du bloc" et le reste de l'espace de la modale se voie clairement,
   meme regle que .block-figure--modal-open .block-figure__preview-canvas /
   .block-circuit--modal-open .block-circuit__preview-canvas. */
.block-courbe--modal-open .block-courbe__preview-canvas {
    flex-shrink: 0;
    background: var(--color-surface);
    box-shadow: 0 4px 24px -8px rgba(32, 30, 27, 0.35);
    border-radius: var(--radius-sm);
}

/* ---- Coquille DEDIEE de la modale espace.js (body/preview/preview-canvas) — un repere 3D
   reste entierement responsive a la taille du conteneur (voir
   espaceRender.js:calculerTransform), jamais fige a une taille reelle en px — a la
   difference de courbe.js/figure.js/circuit.js juste au-dessus (jamais demande pour
   espace.js/solide.js, hors scope de ce changement). ---- */
.block-espace--modal-open .block-espace__tools {
    display: none;
}

.block-espace__body {
    height: 100%;
}

.block-espace--modal-open .block-espace__body {
    display: flex;
    flex-direction: row;
    flex: 1 1 auto;
    height: auto;
    min-height: 0;
    overflow: hidden;
}

.block-espace__preview {
    position: relative;
    height: 100%;
}

.block-espace--modal-open .block-espace__preview {
    flex: 1 1 auto;
    min-width: 0;
    padding: 20px;
    background: var(--color-bg);
    border-right: 1px solid var(--color-border);
}

.block-espace__preview-canvas {
    position: relative;
    width: 100%;
    height: 100%;
}

/* ---- Coquille DEDIEE de la modale solide.js (body/preview/preview-canvas) — meme
   raisonnement que courbe.js/espace.js juste au-dessus : un solide reste entierement
   responsive a la taille du conteneur (voir solideRender.js:calculerTransform), jamais
   fige a une taille reelle en px. ---- */
.block-solide--modal-open .block-solide__tools {
    display: none;
}

.block-solide__body {
    height: 100%;
}

.block-solide--modal-open .block-solide__body {
    display: flex;
    flex-direction: row;
    flex: 1 1 auto;
    height: auto;
    min-height: 0;
    overflow: hidden;
}

.block-solide__preview {
    position: relative;
    height: 100%;
}

.block-solide--modal-open .block-solide__preview {
    flex: 1 1 auto;
    min-width: 0;
    padding: 20px;
    background: var(--color-bg);
    border-right: 1px solid var(--color-border);
}

.block-solide__preview-canvas {
    position: relative;
    width: 100%;
    height: 100%;
}

/* ---- Coquille DEDIEE de la modale circuit.js (body/preview/preview-canvas) — a la
   difference de courbe.js/espace.js/solide.js juste au-dessus, demande directe : "je veux
   que la figure du circuit soit de meme taille sur le modal" (comme figure.js) — un
   schema electrique se lit a l'echelle reelle de la page, pas etire pour "profiter" du
   grand panneau d'apercu. previewCanvas est fige a data.width x data.height en JS
   (circuit.js:appliquerTailleNaturelle, meme fonction/meme raisonnement que
   figure.js:appliquerTailleNaturelle) plutot que de remplir 100% de previewPane. ---- */
.block-circuit--modal-open .block-circuit__tools {
    display: none;
}

.block-circuit__body {
    height: 100%;
}

.block-circuit--modal-open .block-circuit__body {
    display: flex;
    flex-direction: row;
    flex: 1 1 auto;
    height: auto;
    min-height: 0;
    overflow: hidden;
}

.block-circuit__preview {
    position: relative;
    height: 100%;
}

/* Centre previewCanvas (fige a la taille REELLE du bloc) au milieu du grand espace
   disponible — meme regle/meme raison que .block-figure--modal-open .block-figure__preview. */
.block-circuit--modal-open .block-circuit__preview {
    flex: 1 1 auto;
    min-width: 0;
    padding: 28px;
    background: var(--color-bg);
    border-right: 1px solid var(--color-border);
    display: flex;
    align-items: center;
    justify-content: center;
}

.block-circuit__preview-canvas {
    position: relative;
    width: 100%;
    height: 100%;
}

/* Taille reelle FIGEE en JS (circuit.js:appliquerTailleNaturelle) — flex-shrink:0
   indispensable (sinon le flex parent compresserait quand meme ce canvas), habillage
   "feuille de page" pour marquer clairement la limite "ceci EST la taille reelle du bloc",
   meme regle que .block-figure--modal-open .block-figure__preview-canvas. */
.block-circuit--modal-open .block-circuit__preview-canvas {
    flex-shrink: 0;
    background: var(--color-surface);
    box-shadow: 0 4px 24px -8px rgba(32, 30, 27, 0.35);
    border-radius: var(--radius-sm);
}

.block-figure__tools-pane {
    display: none;
}

.block-figure--modal-open .block-figure__tools-pane,
.block-courbe--modal-open .block-figure__tools-pane,
.block-espace--modal-open .block-figure__tools-pane,
.block-solide--modal-open .block-figure__tools-pane,
.block-circuit--modal-open .block-figure__tools-pane {
    display: flex;
    flex-direction: column;
    /* Elargie (420px -> 620px), demande directe — maintenant que l'aperçu (previewCanvas)
       est fige a sa taille REELLE et ne "profite" plus de l'espace en trop (voir plus haut),
       une colonne de reglages plus large ne le comprime plus visuellement du tout ; les
       nombreux champs (Cotes, Sommets et angles, Traces auxiliaires...) ont davantage de
       place pour respirer, sans jamais se replier sur plusieurs lignes. Meme largeur
       reutilisee pour la modale de courbe.js (toolsPane y porte aussi cette classe, voir
       courbe.js:createCourbe) — les nouveaux tableaux "Repère"/"De-À" profitent de la
       meme place pour respirer. */
    flex: 0 0 620px;
    max-width: 62vw;
    min-height: 0;
}

/* Placee apres la largeur partagee ci-dessus afin que la modale Figure utilise reellement
   toute la largeur visible : 30 % aperçu et 70 % champs, sans espace residuel. */
.block-figure--modal-open .block-figure__tools-pane {
    flex: 0 0 70%;
    width: 70%;
    max-width: 70%;
    min-width: 0;
    min-height: 0;
    box-sizing: border-box;
}

/* Meme regle, meme raison, pour la modale Courbe (voir le commentaire au niveau de
   .block-courbe--modal-open .block-courbe__preview plus haut) — DOIT rester apres la
   largeur partagee (.block-figure__tools-pane, flex:0 0 620px plus haut) : a
   specificite egale (un seul selecteur de classe de chaque cote), c'est l'ORDRE DANS LE
   FICHIER qui tranche entre les deux regles concurrentes sur la propriete `flex`, jamais
   l'intention — la placer avant (erreur reelle faite une premiere fois ici) laisse
   silencieusement la regle partagee (620px fixe) gagner. */
.block-courbe--modal-open .block-courbe__tools-pane {
    flex: 0 0 70%;
    width: 70%;
    max-width: 70%;
    min-width: 0;
    min-height: 0;
    box-sizing: border-box;
}

.block-figure-modal__body {
    flex: 1 1 auto;
    overflow-y: auto;
    /* Garde-fou explicite : meme une fois la cause reelle du debordement corrigee
       (.block-style-panel__row desormais flex-wrap:wrap), ce defilement horizontal ne
       doit plus jamais pouvoir revenir, quelle que soit une future rangee mal formee —
       "il faut rester sur la largeur de l'écran" pris au pied de la lettre. */
    overflow-x: hidden;
    display: flex;
    flex-direction: column;
    gap: 18px;
    padding: 18px 20px;
    scrollbar-width: thin;
    scrollbar-color: var(--color-border) transparent;
}

.block-figure-modal__body::-webkit-scrollbar {
    width: 6px;
}

.block-figure-modal__body::-webkit-scrollbar-thumb {
    background: var(--color-border);
    border-radius: 3px;
}

.block-figure-modal__footer {
    flex-shrink: 0;
    display: flex;
    justify-content: flex-end;
    padding: 14px 20px;
    border-top: 1px solid var(--color-border);
}

.block-figure-modal__done {
    border: none;
    background: var(--color-accent);
    color: var(--color-surface);
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 14px;
    padding: 10px 22px;
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.block-figure-modal__done:hover {
    filter: brightness(1.08);
}

/* Root cause du bug ci-dessus : cet overlay est transparent mais reste, par defaut,
   `position:fixed; inset:0` DEVANT tout le canevas — un clic destine a selectionner du
   texte dans le bloc en cours d'edition atterrissait en realite sur l'overlay lui-meme
   (invisible mais bien present), qui n'a par ailleurs aucun listener "clic dehors ferme"
   (retire expres, voir formatBar.js:openTextStylePanel) : le clic ne faisait donc
   simplement RIEN, la selection du navigateur se retrouvant videe par la simple perte de
   focus du champ. pointer-events:none laisse tout clic traverser l'overlay jusqu'au
   canevas ; pointer-events:auto sur le panneau lui-meme (son propre enfant) reste
   necessaire pour que les boutons/champs qu'il contient restent cliquables malgre
   l'ancetre non-interactif. Seul le bouton "×" ferme desormais ce panneau. */
.text-style-panel-overlay {
    pointer-events: none;
}

.text-style-panel {
    pointer-events: auto;
}

.block-forme-panel,
.block-ligne-panel,
.block-fond-panel,
.block-courbe-panel,
.block-variation-panel,
.block-signe-panel,
.text-style-panel {
    max-height: calc(100vh - 48px);
    overflow-y: auto;
}

/* ---- Bloc "Tableau de variation" — panneau de reglages (blocks/variation.js). ---- */
.block-variation-panel__borne-row {
    gap: 6px;
    flex-wrap: wrap;
}

.block-variation-panel__borne-index {
    font-size: 12px;
    color: var(--color-hint);
    flex: none;
    width: 16px;
}

/* ---- Bloc "Tableau de signes" — panneau de reglages (blocks/signe.js). Chaque ligne
   (facteur) forme un petit encart (.block-signe-panel__ligne) regroupant son nom + ses
   marqueurs par colonne + ses signes par intervalle, pour rester lisible malgre le
   nombre de controles quand plusieurs colonnes existent. ---- */
.block-signe-panel__ligne {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 8px;
    margin-bottom: 8px;
}

.block-signe-panel__marqueurs-row,
.block-signe-panel__signes-row {
    gap: 4px;
    flex-wrap: wrap;
}

.block-signe-panel__mini-select {
    width: 44px;
    flex: none;
    padding: 2px;
}

/* ---- Bloc "Courbe" (graphique mathematique) — panneau de reglages (blocks/courbe.js).
   Reutilise .block-style-panel__row/.block-forme-panel__action-btn tels quels pour
   chaque champ simple ; seule la ligne "Points" a besoin d'une mise en page dediee (nom
   court + x + case a cocher + y conditionnel + suppression, 5 elements sur une seule
   ligne). ---- */
.block-courbe-panel__point-row {
    flex-wrap: wrap;
    gap: 6px;
}

.block-courbe-panel__point-nom {
    width: 44px;
    flex: none;
}

.block-courbe-panel__point-sur-courbe {
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: 12px;
    white-space: nowrap;
}

.block-courbe-panel__point-function {
    flex: 1 1 180px;
}

.block-courbe-panel__expression-field {
    flex: 1 1 260px;
    min-width: 0;
}

.block-courbe-panel__expression-field > input {
    width: 100%;
    box-sizing: border-box;
}

.block-courbe-panel__field-error {
    margin-top: 4px;
    color: var(--color-danger);
    font-family: var(--font-body);
    font-size: 11px;
    line-height: 1.35;
}

.block-courbe-modal__body input.is-invalid {
    border-color: var(--color-danger);
    box-shadow: 0 0 0 3px rgba(185, 74, 72, 0.12);
}

.block-courbe-panel__section-nav {
    position: sticky;
    top: -18px;
    z-index: 8;
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin: -18px -20px 2px;
    padding: 10px 20px;
    border-bottom: 1px solid var(--color-border);
    background: rgba(255, 255, 255, 0.96);
    backdrop-filter: blur(8px);
}

.block-courbe-panel__section-nav button {
    padding: 5px 9px;
    border: 1px solid var(--color-border);
    border-radius: 999px;
    background: var(--color-surface);
    color: var(--color-muted);
    font: 600 10px/1.2 var(--font-body);
    cursor: pointer;
}

.block-courbe-panel__section-nav button:hover,
.block-courbe-panel__section-nav button:focus-visible {
    border-color: var(--color-accent);
    color: var(--color-accent);
    outline: none;
}

.block-courbe-modal__body > .block-style-panel__section-title {
    scroll-margin-top: 62px;
}

/* Dans la grande modale Courbe, le bouton couleur se trouve souvent au bord droit d'une
   rangee. Le popover generique s'ouvrait vers la droite (`left:0`) et sa moitie etait alors
   coupee par la zone de reglages defilante. On aligne ici son bord DROIT sur le bouton :
   il se developpe entierement vers la gauche et peut toucher le bord du grand panneau sans
   jamais le depasser. Cette regle est strictement limitee a la modale Courbe. */
.block-courbe-modal__body .color-picker-btn__menu {
    right: 0;
    left: auto;
    z-index: 120;
}

/* ---- Panneaux "mathematiques" (Courbe / Tableau de variation / Tableau de signes) —
   traitement visuel plus soigne, demande explicite au moment de retravailler le tableau
   de variation ("il faut bien style le modal ca doit etre vraiment moderne et
   elegant"). Classe ADDITIONNELLE (.block-math-panel, ajoutee a cote de
   .block-style-panel sur ces 3 panneaux precisement), jamais une modification des
   regles .block-style-panel elles-memes — celles-ci restent partagees par forme.js/
   ligne.js/fond.js/le masque d'image/le panneau Style de texte, qu'il n'a jamais ete
   demande de retoucher ici. Pas de overflow:hidden sur le panneau (meme raison deja
   documentee sur .block-style-panel plus haut : courbe.js ouvre un popover de couleurs
   recentes qui deborderait sinon d'une boite decoupee). ---- */
.block-math-panel {
    border-radius: 16px;
    border: 1px solid rgba(169, 120, 46, 0.18);
    box-shadow: 0 30px 70px -20px rgba(32, 30, 27, 0.55), 0 6px 20px -8px rgba(32, 30, 27, 0.25);
}

.block-math-panel .block-style-panel__header {
    background: linear-gradient(135deg, rgba(169, 120, 46, 0.12), rgba(169, 120, 46, 0));
    /* Le degrade seul suffirait a deborder des coins arrondis du panneau (le header est
       son tout premier enfant, colle au bord) — border-radius ici uniquement sur les 2
       coins du HAUT, jamais un overflow:hidden sur le panneau entier (voir plus haut). */
    border-radius: 16px 16px 0 0;
    padding: 16px 20px;
    font-family: var(--font-display);
    font-size: 17px;
    letter-spacing: 0.01em;
    border-bottom-color: rgba(169, 120, 46, 0.22);
}

.block-math-panel .block-style-panel__close {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.15s ease, color 0.15s ease;
}

.block-math-panel .block-style-panel__close:hover {
    background: rgba(185, 74, 72, 0.12);
    color: var(--color-danger);
}

.block-math-panel .block-style-panel__body {
    padding: 20px;
    gap: 18px;
}

.block-math-panel .block-style-panel__section-title,
.block-figure-modal__body .block-style-panel__section-title {
    font-size: 11px;
    letter-spacing: 0.08em;
    color: var(--color-accent);
    border-top-color: rgba(169, 120, 46, 0.18);
}

/* Titres de section NETTEMENT plus marques, specifiquement dans la modale du bloc figure —
   demande directe : "il faut appliquer le border des titres des champs (Côtés, Sommets et
   angles, Étiquettes des côtés, Tracés auxiliaires etc...)". Le fin trait partage ci-dessus
   (border-top a 18% d'opacite, AVANT le titre) se lisait comme un simple espacement, pas
   comme un repere visuel clair entre les nombreuses sections de ce panneau specifique — un
   panneau bien plus long/dense que les 3 autres qui partagent .block-math-panel. Remplace
   par un veritable SOULIGNEMENT plein sous le texte du titre lui-meme (jamais un trait
   avant, qui se lit comme la fin de la section precedente plutot que le debut de celle-ci),
   avec assez d'espace au-dessus pour que chaque section se lise comme un vrai groupe
   independant plutot qu'une liste plate de champs. */
.block-figure-modal__body .block-style-panel__section-title {
    border-top: none;
    border-bottom: 2px solid var(--color-accent);
    padding-top: 22px;
    padding-bottom: 8px;
    margin-top: 4px;
    /* Le trait horizontal (border-bottom) lui-meme collait directement a l'element suivant
       (rangee ou tableau) — rien dans ce panneau ne les separait, chaque section n'etant
       qu'un <div> nu sans gap propre. Demande directe : "pour les trait horizontal il faut
       faire un margin-bottom pour que ça colle pas aux éléments". */
    margin-bottom: 10px;
    font-size: 12px;
    letter-spacing: 0.1em;
}

.block-figure-modal__body .block-style-panel__section-title:first-child {
    padding-top: 0;
    margin-top: 0;
}

/* Aucune regle .block-style-panel__row n'a jamais style les champs texte/nombre —
   seuls select/input[range] l'etaient (voir plus haut) — ces 3 panneaux en sont les
   premiers a dependre lourdement, d'ou l'apparence navigateur brute sans ceci.
   `.block-figure-modal__body` ajoute au meme selecteur (jamais `.block-math-panel`
   lui-meme, dont le border/box-shadow/border-radius du CONTENEUR sont penses pour un
   panneau flottant autonome, pas pour une colonne integree dans la modale plein ecran
   du bloc figure) : bug signale directement, "les champs sont un peu vieillot" — la
   modale plein ecran du bloc figure (voir figure.js:rebuildSettingsUI) n'a jamais herite
   de cet habillage, ses champs texte/nombre restaient donc a l'apparence brute du
   navigateur. Memes valeurs, jamais dupliquees — juste un selecteur de plus par regle. */
.block-math-panel input[type="text"],
.block-math-panel input[type="number"],
.block-figure-modal__body input[type="text"],
.block-figure-modal__body input[type="number"] {
    border: 1px solid var(--color-border);
    border-radius: 8px;
    padding: 6px 10px;
    font-family: var(--font-body);
    font-size: 13px;
    background: var(--color-bg);
    color: var(--color-text);
    flex: 1 1 auto;
    min-width: 0;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.block-math-panel input[type="text"]:focus,
.block-math-panel input[type="number"]:focus,
.block-figure-modal__body input[type="text"]:focus,
.block-figure-modal__body input[type="number"]:focus {
    outline: none;
    border-color: var(--color-accent);
    box-shadow: 0 0 0 3px rgba(169, 120, 46, 0.16);
}

.block-math-panel .block-style-panel__select,
.block-figure-modal__body .block-style-panel__select {
    border-radius: 8px;
}

.block-math-panel .block-forme-panel__action-btn,
.block-figure-modal__body .block-forme-panel__action-btn {
    border-radius: 999px;
    border: 1px solid var(--color-accent);
    color: var(--color-accent);
    background: transparent;
    font-weight: 600;
    transition: background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}

.block-math-panel .block-forme-panel__action-btn:hover,
.block-figure-modal__body .block-forme-panel__action-btn:hover {
    background: var(--color-accent);
    color: var(--color-surface);
    box-shadow: 0 8px 18px -8px rgba(169, 120, 46, 0.55);
    transform: translateY(-1px);
}

.block-figure-panel__add-item {
    margin-top: 14px;
}

.block-figure-panel__mode-gallery {
    display: grid;
    grid-template-columns: repeat(7, minmax(82px, 1fr));
    gap: 9px;
    margin-bottom: 8px;
}

.block-figure-panel__mode-card {
    min-width: 0;
    min-height: 82px;
    padding: 9px 6px 7px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 7px;
    border: 1px solid var(--color-border);
    border-radius: 10px;
    background: var(--color-surface);
    color: var(--color-text);
    font: 600 11px/1.2 var(--font-body);
    text-align: center;
    cursor: pointer;
    transition: border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}

.block-figure-panel__mode-card:hover {
    border-color: var(--color-accent);
    transform: translateY(-1px);
}

.block-figure-panel__mode-card.is-active {
    border-color: var(--color-accent);
    background: var(--color-accent-soft);
    color: var(--color-accent);
    box-shadow: 0 0 0 2px rgba(169, 120, 46, 0.14);
}

.block-figure-panel__mode-shape {
    position: relative;
    display: block;
    width: 38px;
    height: 29px;
    box-sizing: border-box;
    color: currentColor;
}

.block-figure-panel__mode-shape--triangle {
    background: currentColor;
    clip-path: polygon(50% 2%, 98% 96%, 2% 96%);
}

.block-figure-panel__mode-shape--triangle::after {
    content: '';
    position: absolute;
    inset: 4px 5px 5px;
    background: var(--color-surface);
    clip-path: inherit;
}

.block-figure-panel__mode-card.is-active .block-figure-panel__mode-shape--triangle::after,
.block-figure-panel__mode-card.is-active .block-figure-panel__mode-shape--trapeze::after {
    background: var(--color-accent-soft);
}

.block-figure-panel__mode-shape--cercle {
    width: 30px;
    height: 30px;
    border: 2px solid currentColor;
    border-radius: 50%;
}

.block-figure-panel__mode-shape--rectangle {
    border: 2px solid currentColor;
}

.block-figure-panel__mode-shape--losange {
    width: 26px;
    height: 26px;
    margin: 2px 6px;
    border: 2px solid currentColor;
    transform: rotate(45deg);
}

.block-figure-panel__mode-shape--parallelogramme {
    border: 2px solid currentColor;
    transform: skewX(-16deg);
}

.block-figure-panel__mode-shape--trapeze {
    background: currentColor;
    clip-path: polygon(20% 0, 80% 0, 100% 100%, 0 100%);
}

.block-figure-panel__mode-shape--trapeze::after {
    content: '';
    position: absolute;
    inset: 2px;
    background: var(--color-surface);
    clip-path: inherit;
}

.block-figure-panel__mode-shape--construction {
    background:
        radial-gradient(circle at 4px 24px, currentColor 0 3px, transparent 3.5px),
        radial-gradient(circle at 34px 5px, currentColor 0 3px, transparent 3.5px),
        linear-gradient(to bottom right, transparent 47%, currentColor 48% 52%, transparent 53%);
}

/* Ecrans moyens : un peu plus d'air pour l'aperçu, tandis que la galerie passe sur deux
   lignes courtes. Les champs gardent 65 % du viewport et leur defilement reste interne. */
@media (min-width: 900px) and (max-width: 1279px) {
    .block-figure--modal-open .block-figure__preview {
        flex-basis: 35%;
        max-width: 35%;
        padding: 20px;
    }

    .block-figure--modal-open .block-figure__tools-pane {
        flex-basis: 65%;
        width: 65%;
        max-width: 65%;
    }

    .block-figure-panel__mode-gallery {
        grid-template-columns: repeat(4, minmax(82px, 1fr));
        gap: 8px;
    }

    .block-figure-modal__body {
        padding: 16px 18px;
        gap: 16px;
    }

    .block-figure-panel__tableau {
        grid-template-columns: minmax(76px, auto) repeat(var(--nb-colonnes), minmax(0, 1fr));
        column-gap: 6px;
    }

    .block-figure-modal__body .block-style-panel__row {
        gap: 8px;
    }
}

/* Tablettes (portrait large et paysage) : la modale reste en deux colonnes jusqu'a 734px.
   Le panneau de travail est prioritaire, mais les controles conservent une hauteur tactile
   suffisante. La galerie est volontairement plus basse pour ne pas manger l'espace utile. */
@media (min-width: 734px) and (max-width: 899px) {
    .block-figure--modal-open .block-figure__modal-header {
        padding: 11px 16px;
        font-size: 14px;
    }

    .block-figure--modal-open .block-figure__preview {
        flex-basis: 28%;
        max-width: 28%;
        padding: 12px;
    }

    .block-figure--modal-open .block-figure__tools-pane {
        flex-basis: 72%;
        width: 72%;
        max-width: 72%;
    }

    .block-figure-modal__body {
        padding: 11px 12px;
        gap: 11px;
    }

    .block-figure-modal__body .block-style-panel__section-title {
        padding-top: 13px;
        padding-bottom: 5px;
        margin-bottom: 6px;
        font-size: 10px;
    }

    .block-figure-panel__mode-gallery {
        grid-template-columns: repeat(4, minmax(68px, 1fr));
        gap: 6px;
        margin-bottom: 2px;
    }

    .block-figure-panel__mode-card {
        min-height: 60px;
        padding: 5px 4px;
        gap: 4px;
        border-radius: 8px;
        font-size: 9px;
    }

    .block-figure-panel__mode-shape {
        width: 29px;
        height: 22px;
    }

    .block-figure-panel__mode-shape--cercle {
        width: 23px;
        height: 23px;
    }

    .block-figure-panel__mode-shape--losange {
        width: 20px;
        height: 20px;
        margin: 1px 5px;
    }

    .block-figure-modal__body .block-style-panel__row {
        gap: 6px;
        font-size: 12px;
    }

    .block-figure-modal__body input[type="text"],
    .block-figure-modal__body input[type="number"],
    .block-figure-modal__body .block-style-panel__select {
        min-height: 30px;
        padding: 4px 7px;
        font-size: 11px;
    }

    .block-figure-panel__tableau {
        grid-template-columns: minmax(64px, auto) repeat(var(--nb-colonnes), minmax(0, 1fr));
        column-gap: 4px;
        row-gap: 4px;
        margin-bottom: 12px;
    }

    .block-figure-panel__tableau-libelle,
    .block-figure-panel__tableau-entete {
        font-size: 9px;
    }

    .block-style-panel__row--coords input[type="number"] {
        flex-basis: 48px;
        width: 48px;
        min-width: 48px;
    }

    .block-figure-modal__footer {
        padding: 9px 12px;
    }

    .block-figure-modal__done {
        padding: 8px 17px;
        font-size: 12px;
    }
}

.block-figure-panel__inline-check,
.block-figure-panel__point-check {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    cursor: pointer;
    white-space: nowrap;
}

.block-figure-panel__inline-check input,
.block-figure-panel__point-check input {
    flex: 0 0 auto;
}

.block-figure-panel__line-points {
    padding: 9px 10px;
    border: 1px solid var(--color-border);
    border-radius: 8px;
    background: var(--color-bg);
}

.block-figure-panel__point-checks {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 12px;
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid var(--color-border);
}

.block-math-panel .block-style-panel__reset,
.block-figure-modal__body .block-style-panel__reset {
    border-radius: 999px;
    transition: border-color 0.15s ease, color 0.15s ease, transform 0.15s ease;
}

.block-math-panel .block-signe-panel__ligne {
    border-radius: 10px;
    background: var(--color-bg);
    transition: box-shadow 0.15s ease;
}

.block-math-panel .block-signe-panel__ligne:hover {
    box-shadow: 0 4px 14px -6px rgba(32, 30, 27, 0.18);
}

.block-math-panel .block-signe-panel__mini-select {
    border-radius: 6px;
}

/* ---- Panneau "Style de texte" (formatBar.js:openTextStylePanel) — taille libre,
   interligne, espacement des lettres, surlignage, barré, ombre portée, contour,
   dégradé. Pas d'encadre d'apercu dedie (".text-style-panel__preview" a existe puis a
   ete retire — retour explicite : "on voit les changement direct sur l'élément à
   styliser") : chaque reglage s'applique desormais reellement, en direct, sur le texte
   selectionne lui-meme (voir formatBar.js:createLiveTextEffect). ---- */
/* ---- Styles de texte reutilisables ("Styles enregistrés", en tete du panneau ci-dessus)
   — meme registre visuel general que .formula-picker__add-form/-toggle (bouton "+", zone
   de saisie repliable), adapte a une grille de PILULES plutot qu'une grille d'icones :
   chaque style se presente lui-meme (police/couleur/degrade appliques directement sur son
   propre libelle par formatBar.js), pas de swatch separe necessaire. */
.text-style-panel__styles-section {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-bottom: 4px;
}

.text-style-panel__styles-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.text-style-panel__styles-empty {
    font-family: var(--font-body);
    font-size: 12px;
    color: var(--color-muted);
    font-style: italic;
}

.text-style-panel__style-pill {
    position: relative;
    display: inline-flex;
    align-items: center;
    padding: 5px 20px 5px 10px;
    border: 1px solid var(--color-border);
    border-radius: 999px;
    background: var(--color-surface);
    font-size: 13px;
    cursor: pointer;
    max-width: 160px;
}

.text-style-panel__style-pill span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.text-style-panel__style-pill:hover {
    border-color: var(--color-accent);
}

.text-style-panel__style-remove {
    position: absolute;
    top: 50%;
    right: 4px;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    color: var(--color-muted);
    font-size: 9px;
    cursor: pointer;
}

.text-style-panel__style-remove:hover {
    background: var(--color-danger);
    color: var(--color-surface);
}

.text-style-panel__add-style-btn {
    align-self: flex-start;
    border: 1px solid var(--color-accent-soft);
    background: var(--color-surface);
    color: var(--color-accent);
    border-radius: var(--radius-sm);
    padding: 5px 10px;
    font-size: 12px;
    cursor: pointer;
}

.text-style-panel__add-style-btn:hover {
    background: var(--color-accent);
    color: var(--color-surface);
}

.text-style-panel__add-style-form {
    display: flex;
    gap: 6px;
}

.text-style-panel__add-style-form input {
    flex: 1 1 auto;
    min-width: 0;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-bg);
    color: var(--color-text);
    padding: 5px 8px;
    font-size: 13px;
    font-family: var(--font-body);
}

.text-style-panel__add-style-form button {
    flex-shrink: 0;
    border: 1px solid var(--color-accent);
    background: var(--color-accent);
    color: var(--color-surface);
    border-radius: var(--radius-sm);
    padding: 5px 10px;
    font-size: 12px;
    cursor: pointer;
}

.text-style-panel__number {
    width: 72px;
    flex: 0 0 auto;
    border: 1px solid var(--color-border);
    background: var(--color-bg);
    color: var(--color-text);
    border-radius: var(--radius-sm);
    padding: 5px 8px;
    font-family: var(--font-body);
    font-size: 13px;
}

/* Regroupe les controles d'un effet repliable (ombre/contour/degrade) — meme role que
   .block-forme-panel__section/.block-fond-panel__section (espacement coherent entre
   plusieurs lignes), classe dediee pour rester coherent avec la convention "une classe
   par panneau" du reste de ce fichier. Une fine bordure superieure les distingue
   visuellement de la case a cocher qui les revele/masque juste au-dessus. */
.text-style-panel__section {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding-top: 10px;
    margin-top: -4px;
    border-top: 1px dashed var(--color-border);
}

/* Section repliable "overlay" du panneau fond (couleur+opacite), masquee/affichee selon
   la case a cocher — meme role/mise en page que .block-forme-panel__section (grouper
   plusieurs lignes avec un espacement coherent), classe dediee plutot que reutilisee :
   nom coherent avec le reste du fichier (une classe par type de bloc). */
.block-fond-panel__section {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* ---- Bloc image ---- */
.block-image {
    position: relative;
    height: 100%;
    overflow: visible;
}

.block-image__img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
    border-radius: var(--radius-sm);
    background: transparent;
}

.block-image__img[hidden] {
    display: none;
}

/* Calque d'actions au survol, discret tant qu'on ne survole pas — meme registre que
   .block-situation__image-overlay. */
.block-image__overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    background: rgba(32, 30, 27, 0.4);
    border-radius: var(--radius-sm);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.15s;
}

.block-image__overlay[hidden] {
    display: none;
}

.block-image:hover .block-image__overlay {
    opacity: 1;
    pointer-events: auto;
}

.block-image__btn,
.block-image__remove {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    border: none;
    background: var(--color-surface);
    color: var(--color-text);
    padding: 5px 10px;
    cursor: pointer;
    border-radius: var(--radius-pill);
}

.block-image__btn:hover,
.block-image__remove:hover {
    background: var(--color-accent);
    color: var(--color-surface);
}

.block-image__placeholder {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    width: 100%;
    height: 100%;
    border: 1px dashed var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface-alt);
    color: var(--color-muted);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    cursor: pointer;
}

.block-image__placeholder i {
    font-size: 22px;
}

.block-image__placeholder[hidden] {
    display: none;
}

/* ---- Bloc video ---- */
.block-video {
    position: relative;
    height: 100%;
    overflow: visible;
}

.block-video__preview {
    position: relative;
    width: 100%;
    height: 100%;
}

.block-video__iframe,
.block-video__player {
    display: block;
    width: 100%;
    height: 100%;
    border: none;
    border-radius: var(--radius-sm);
    background: #000;
}

/* Capture le premier clic sur l'iframe YouTube pour selectionner le bloc (une iframe est
   un document a part, ses clics ne remontent jamais aux ecouteurs du parent) — devient
   transparente aux clics une fois le bloc selectionne, pour laisser un second clic
   atteindre vraiment la video. Voir video.js pour le detail. */
.block-video__shield {
    position: absolute;
    inset: 0;
    cursor: pointer;
}

.block--selected .block-video__shield {
    pointer-events: none;
}

.block-video__placeholder {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    width: 100%;
    height: 100%;
    border: 1px dashed var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface-alt);
    color: var(--color-muted);
    font-size: 11px;
    text-align: center;
    padding: var(--space-2);
}

.block-video__placeholder i {
    font-size: 22px;
}

/* Barre d'actions contextuelle (mode + lien/televersement), flottante au-dessus du bloc
   quand il est selectionne — meme registre visuel que .block-graphique__tools. */
.block-video__tools {
    position: absolute;
    bottom: calc(100% + 10px);
    right: 0;
    display: none;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 6px 8px;
    box-shadow: 0 8px 20px -10px rgba(32, 30, 27, 0.3);
    z-index: 10;
}

.block--editing .block-video__tools {
    display: flex;
}

.block-video__mode-group {
    display: flex;
    gap: 4px;
}

.block-video__mode-btn {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    padding: 5px 8px;
    cursor: pointer;
    border-radius: var(--radius-sm);
}

.block-video__mode-btn--active {
    background: var(--color-accent);
    border-color: var(--color-accent);
    color: var(--color-surface);
}

.block-video__url {
    flex: 1 1 200px;
    min-width: 160px;
    font-size: 12px;
    font-family: var(--font-body);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 5px 8px;
    background: var(--color-surface);
    color: var(--color-text);
}

.block-video__upload-btn {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-text);
    padding: 5px 10px;
    cursor: pointer;
    border-radius: var(--radius-sm);
}

.block-video__upload-btn:hover {
    background: var(--color-accent);
    border-color: var(--color-accent);
    color: var(--color-surface);
}

/* ---- Bloc icone ---- */
.block-icone {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    overflow: visible;
}

.block-icone__glyph {
    line-height: 1;
    user-select: none;
}

.block-icone__tools {
    position: absolute;
    bottom: calc(100% + 10px);
    right: 0;
    display: none;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 5px 6px;
    box-shadow: 0 6px 16px -6px rgba(32, 30, 27, 0.35);
}

.block--editing .block-icone__tools {
    display: flex;
}

.block-icone__change {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-text);
    padding: 5px 10px;
    cursor: pointer;
    border-radius: var(--radius-sm);
}

.block-icone__change:hover {
    background: var(--color-accent);
    border-color: var(--color-accent);
    color: var(--color-surface);
}

.block-icone__size {
    width: 60px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 4px 6px;
    font-size: 11px;
    background: var(--color-surface);
}

.block-icone__color {
    width: 32px;
    height: 32px;
    padding: 0;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    cursor: pointer;
}

/* ---- Bloc numero de page ----
   Styles deplaces dans blocks-shared.css (voir CLAUDE.md "En-tete et pied de page") —
   ce bloc doit rendre IDENTIQUEMENT dans l'editeur ET dans l'export PDF (export_pdf.html
   charge blocks-shared.css mais jamais editor.css), meme raison que .activity-title. */

/* ---- Palette d'icones (modale) — reutilisee telle quelle par .formula-picker-overlay
   (voir formulaPicker.js, les deux classes cohabitent sur le meme element). z-index:500,
   pas 300 : cette modale est appendue a document.body, donc un sibling direct de chaque
   .block — quand elle est ouverte depuis le bouton d'un bloc en cours d'edition
   (.block--editing, z-index:400 depuis que sa barre d'outils occupe toute la largeur de
   la page), un z-index inferieur la faisait peindre DERRIERE ce bloc et sa barre. Meme
   raisonnement pour .block-style-panel-overlay/.palette-picker-overlay/
   .annotation-modal-overlay plus bas dans ce fichier — les quatre overlays modaux
   partages de l'app doivent tous rester au-dessus du bloc qui les a ouverts. ---- */
.icon-picker-overlay {
    position: fixed;
    inset: 0;
    background: rgba(32, 30, 27, 0.45);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 500;
}

.icon-picker {
    width: min(560px, 90vw);
    max-height: 80vh;
    display: flex;
    flex-direction: column;
    background: var(--color-surface);
    border-radius: var(--radius-md);
    box-shadow: 0 24px 60px -16px rgba(32, 30, 27, 0.5);
    overflow: hidden;
}

.icon-picker__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 18px;
    border-bottom: 1px solid var(--color-border);
    font-family: var(--font-body);
    font-weight: 600;
    color: var(--color-text);
}

.icon-picker__close {
    border: none;
    background: none;
    color: var(--color-muted);
    cursor: pointer;
    font-size: 16px;
    line-height: 1;
    padding: 4px;
}

/* Editeur de formule visuel (MathLive) — voir mathliveEditor.js. Meme coquille
   overlay+panneau que .icon-picker-overlay/.icon-picker juste au-dessus (fond assombri,
   centre, meme z-index 500 — le plafond partage par toutes les modales de cette app) : un
   picker/editeur ferme, jamais un panneau d'ajustement en direct comme .block-style-panel
   (docke a droite, transparent) — ici il n'y a rien a "voir en direct sur le bloc derriere",
   toute la composition se fait a l'interieur du champ MathLive lui-meme. */
.mathlive-editor-overlay {
    position: fixed;
    inset: 0;
    background: rgba(32, 30, 27, 0.45);
    display: none;
    align-items: center;
    justify-content: center;
    z-index: 500;
}

.mathlive-editor-overlay--open {
    display: flex;
}

.mathlive-editor-panel {
    width: min(640px, 92vw);
    display: flex;
    flex-direction: column;
    background: var(--color-surface);
    border-radius: var(--radius-md);
    box-shadow: 0 24px 60px -16px rgba(32, 30, 27, 0.5);
    overflow: hidden;
}

.mathlive-editor-panel__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 18px;
    border-bottom: 1px solid var(--color-border);
}

.mathlive-editor-panel__title {
    font-family: var(--font-body);
    font-weight: 600;
    color: var(--color-text);
}

.mathlive-editor-panel__close {
    border: none;
    background: none;
    color: var(--color-muted);
    cursor: pointer;
    font-size: 16px;
    line-height: 1;
    padding: 4px;
}

.mathlive-editor-panel__field-wrap {
    padding: 20px 18px 8px;
    min-height: 56px;
    display: flex;
    align-items: center;
    color: var(--color-muted);
    font-family: var(--font-body);
    font-size: 13px;
}

/* Le <math-field> lui-meme (element personnalise MathLive) gere entierement son propre
   style interne (bordure/police au survol/focus, voir mathliveEditor.js) — seule sa
   largeur/taille de police sont ajustees ici pour rester coherentes avec le reste du
   panneau plutot que la taille par defaut, pensee pour une page web generique. */
.mathlive-editor-field {
    width: 100%;
    font-size: 24px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 10px 12px;
}

.mathlive-editor-panel__hint {
    margin: 0;
    padding: 0 18px 4px;
    font-family: var(--font-body);
    font-size: 11px;
    color: var(--color-muted);
    line-height: 1.5;
}

.mathlive-editor-panel__footer {
    display: flex;
    justify-content: flex-end;
    padding: 12px 18px 18px;
}

.mathlive-editor-panel__insert {
    border: none;
    background: var(--color-accent);
    color: var(--color-surface);
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 13px;
    padding: 9px 20px;
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.mathlive-editor-panel__insert:hover {
    filter: brightness(0.92);
}

.mathlive-editor-panel__insert:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

/* Bug reel, mesure directement (Playwright) : le clavier virtuel de MathLive (singleton
   global, monte sur <body> comme un FRERE de .mathlive-editor-overlay, jamais un
   descendant) s'injecte avec son propre z-index (105 par defaut) — largement sous celui de
   cet overlay (500). Sans ce correctif, le clavier reste visuellement masque par le fond
   assombri de l'overlay partout ou les deux se superposent, ET un clic "sur le clavier"
   atterrit en realite sur le fond de l'overlay (au-dessus), qui le referme aussitot
   (event.target === overlay -> closeFormulaEditor()) — exactement le "tout disparait"
   signale par l'utilisateur. Cible directement la classe que MathLive applique lui-meme a
   son conteneur (jamais un wrapper invente ici) ; !important necessaire car MathLive injecte
   sa propre feuille de style a l'execution, potentiellement apres celle-ci dans l'ordre du
   document, ce qui lui donnerait sinon la priorite en cascade a specificite egale. */
.ML__keyboard {
    z-index: 600 !important;
}

.icon-picker__close:hover {
    color: var(--color-danger);
}

/* Recherche (voir iconPicker.js) — 1873 icones libres, une simple grille curee ne peut
   pas rester "parcourable" a cette echelle ; la recherche est ce qui rend le reste
   effectivement exploitable. */
.icon-picker__search {
    margin: 0 18px;
    padding: 8px 12px;
    font-family: var(--font-body);
    font-size: 13px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-bg);
    color: var(--color-text);
    flex-shrink: 0;
}

.icon-picker__search:focus {
    outline: none;
    border-color: var(--color-accent);
}

/* Conteneur scrollable unique pour toutes les categories/resultats — remplace le scroll
   auparavant porte par .icon-picker__grid lui-meme, devenu un simple enfant non
   scrollable de chaque section maintenant que plusieurs categories cohabitent. */
.icon-picker__body {
    flex: 1;
    overflow-y: auto;
    padding: 14px 18px 18px;
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.icon-picker__category-title {
    margin: 0 0 8px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    color: var(--color-muted);
}

.icon-picker__grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(52px, 1fr));
    gap: 8px;
}

.icon-picker__item {
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 1;
    border: 1px solid var(--color-border);
    background: var(--color-bg);
    color: var(--color-accent);
    border-radius: var(--radius-sm);
    font-size: 18px;
    cursor: pointer;
}

.icon-picker__item:hover {
    background: var(--color-accent);
    border-color: var(--color-accent);
    color: var(--color-surface);
}

/* Champ "classe personnalisee" — reponse directe a "pouvoir ajouter par nous un champ
   input pour ajouter <i class=...>" (voir iconPicker.js:buildCustomForm). Reste visible
   en permanence sous le corps scrollable, jamais a l'interieur de celui-ci — c'est
   l'echappatoire de secours, elle ne doit jamais defiler hors de vue. */
.icon-picker__custom {
    flex-shrink: 0;
    padding: 12px 18px;
    border-top: 1px solid var(--color-border);
    background: var(--color-bg);
}

.icon-picker__custom-label {
    margin: 0 0 6px;
    font-size: 11px;
    color: var(--color-muted);
}

.icon-picker__custom-row {
    display: flex;
    align-items: center;
    gap: 8px;
}

.icon-picker__custom-preview {
    flex-shrink: 0;
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    color: var(--color-accent);
    font-size: 16px;
}

.icon-picker__custom-preview--invalid {
    color: var(--color-muted);
}

.icon-picker__custom-input {
    flex: 1;
    min-width: 0;
    font-family: var(--font-body);
    font-size: 13px;
    padding: 7px 10px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    color: var(--color-text);
}

.icon-picker__custom-input:focus {
    outline: none;
    border-color: var(--color-accent);
}

.icon-picker__custom-use {
    flex-shrink: 0;
    font-family: var(--font-body);
    font-size: 12px;
    padding: 7px 14px;
    border: 1px solid var(--color-accent);
    background: var(--color-accent);
    color: var(--color-surface);
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.icon-picker__custom-use:disabled {
    border-color: var(--color-border);
    background: var(--color-border);
    color: var(--color-muted);
    cursor: not-allowed;
}

/* ---- Palette de formules LaTeX (voir formulaPicker.js) : meme coque .icon-picker*
   reutilisee telle quelle (voir le commentaire d'en-tete de formulaPicker.js) — seule
   la grille interne differe, puisqu'une formule a besoin d'un rendu KaTeX + un libelle
   sous chaque tuile, regroupees par categorie, la ou la grille d'icones n'est qu'une
   seule rangee de glyphes sans sous-titre ni section. ---- */
.formula-picker__grid {
    display: flex;
    flex-direction: column;
    gap: 18px;
    padding: 18px;
    overflow-y: auto;
}

.formula-picker__category-title {
    margin: 0 0 8px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    color: var(--color-muted);
}

.formula-picker__row {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(68px, 1fr));
    gap: 8px;
}

.formula-picker__item {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 5px;
    min-height: 56px;
    padding: 8px 4px;
    border: 1px solid var(--color-border);
    background: var(--color-bg);
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.formula-picker__item:hover {
    border-color: var(--color-accent);
    background: var(--color-accent-soft);
}

.formula-picker__preview {
    font-size: 16px;
    color: var(--color-text);
    line-height: 1;
    /* Certaines formules (integrale, somme, limite...) rendent plus larges que la tuile —
       jamais coupees/debordantes sur les voisines, KaTeX gere deja son propre retour a la
       ligne interne le cas echeant. */
    max-width: 100%;
    overflow: hidden;
}

.formula-picker__label {
    font-size: 9px;
    color: var(--color-muted);
    text-align: center;
    line-height: 1.2;
}

/* ---- "Mes formules" (voir formulaPicker.js) : ajout/retrait d'une formule
   personnelle, bibliotheque client (localStorage), jamais plafonnee. ---- */
.formula-picker__header-left {
    display: flex;
    align-items: center;
    gap: 10px;
}

.formula-picker__add-toggle {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    border: 1px solid var(--color-accent-soft);
    background: var(--color-accent-soft);
    color: var(--color-accent);
    font-size: 11px;
    font-family: var(--font-body);
    padding: 4px 10px;
    border-radius: var(--radius-pill);
    cursor: pointer;
}

.formula-picker__add-toggle:hover {
    background: var(--color-accent);
    color: var(--color-surface);
}

.formula-picker__add-form {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 14px 18px;
    background: var(--color-bg);
    border-bottom: 1px solid var(--color-border);
}

.formula-picker__add-input {
    font-family: var(--font-body);
    font-size: 13px;
    padding: 7px 10px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    color: var(--color-text);
}

.formula-picker__add-input:focus {
    outline: none;
    border-color: var(--color-accent);
}

.formula-picker__add-preview {
    min-height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 6px;
    border: 1px dashed var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    color: var(--color-text);
    font-size: 15px;
}

.formula-picker__add-actions {
    display: flex;
    gap: 8px;
    justify-content: flex-end;
}

.formula-picker__add-save,
.formula-picker__add-cancel {
    font-family: var(--font-body);
    font-size: 12px;
    padding: 6px 14px;
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.formula-picker__add-save {
    border: 1px solid var(--color-accent);
    background: var(--color-accent);
    color: var(--color-surface);
}

.formula-picker__add-cancel {
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-muted);
}

/* Position relative sur .formula-picker__item (voir formulaPicker.js) uniquement pour
   accueillir ce badge de retrait en coin — jamais applique aux tuiles du socle commun,
   qui n'en ont pas besoin (non retirables depuis l'interface). */
.formula-picker__item {
    position: relative;
}

.formula-picker__item-remove {
    position: absolute;
    top: -6px;
    right: -6px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    border: 1px solid var(--color-border);
    border-radius: 50%;
    background: var(--color-surface);
    color: var(--color-muted);
    font-size: 8px;
    cursor: pointer;
}

.formula-picker__item-remove:hover {
    background: var(--color-danger);
    border-color: var(--color-danger);
    color: var(--color-surface);
}

/* ---- Selecteur de palette : meme patron overlay+panneau que .icon-picker ci-dessus,
   voir canvas.js:openPalettePicker. Une carte par palette (bandeau de 3 couleurs
   d'apercu + libelle) plutot qu'une grille d'icones, sinon structure identique.
   Reutilisee telle quelle par .gabarit-picker-overlay et .notion-preset-picker-overlay
   (les deux classes cohabitent sur le meme element, voir canvas.js). z-index:500, meme
   raison que .icon-picker-overlay ci-dessus. ---- */
.palette-picker-overlay {
    position: fixed;
    inset: 0;
    background: rgba(32, 30, 27, 0.45);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 500;
}

.palette-picker {
    width: min(360px, 90vw);
    max-height: 80vh;
    display: flex;
    flex-direction: column;
    background: var(--color-surface);
    border-radius: var(--radius-md);
    box-shadow: 0 24px 60px -16px rgba(32, 30, 27, 0.5);
    overflow: hidden;
}

.palette-picker__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 18px;
    border-bottom: 1px solid var(--color-border);
    font-family: var(--font-body);
    font-weight: 600;
    color: var(--color-text);
}

.palette-picker__close {
    border: none;
    background: none;
    color: var(--color-muted);
    cursor: pointer;
    font-size: 16px;
    line-height: 1;
    padding: 4px;
}

.palette-picker__close:hover {
    color: var(--color-danger);
}

.palette-picker__grid {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 18px;
    overflow-y: auto;
}

.palette-picker__item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 12px;
    border: 1px solid var(--color-border);
    background: var(--color-bg);
    border-radius: var(--radius-sm);
    cursor: pointer;
    font-family: var(--font-body);
    font-size: 14px;
    color: var(--color-text);
    text-align: left;
}

.palette-picker__item:hover {
    border-color: var(--color-accent);
}

.palette-picker__item--active {
    border-color: var(--color-accent);
    border-width: 2px;
    font-weight: 600;
}

.palette-picker__swatch {
    flex-shrink: 0;
    width: 48px;
    height: 24px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--color-border);
}

/* Apercu du selecteur de gabarit (voir canvas.js:openGabaritPicker) — memes
   dimensions que .palette-picker__swatch pour que les deux grilles s'alignent, mais
   affiche un "1"/"1." stylise selon le gabarit plutot qu'un degrade de couleurs,
   puisqu'un gabarit ne change justement aucune couleur. */
.gabarit-picker__swatch {
    flex-shrink: 0;
    width: 48px;
    height: 24px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--color-border);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--color-bg);
    font-family: var(--font-body);
    font-weight: bold;
    font-size: 13px;
    color: var(--color-text);
}

.gabarit-picker__swatch--classique span,
.gabarit-picker__swatch--classique {
    background: var(--color-accent);
    color: var(--color-surface);
    border-radius: var(--radius-pill);
}

.gabarit-picker__swatch--boudina {
    color: var(--color-accent);
    font-style: italic;
}

/* Carre : badge plein comme classique, mais coins nets (border-radius reduit, pas la
   pilule/le cercle) — c'est precisement ce contraste de geometrie qui distingue ce
   gabarit, donc l'apercu du picker doit deja le montrer. */
.gabarit-picker__swatch--carre {
    background: var(--color-accent);
    color: var(--color-surface);
    border-radius: 6px;
}

/* Suspendu : le trait distinctif de ce gabarit n'est pas la pastille de question
   elle-meme (inchangee, cercle plein comme Classique) mais le badge du TITRE — non
   representable ici (le picker n'apercoit que le numero de question) — donc le
   swatch reste volontairement identique a Classique plutot que d'inventer une
   distinction visuelle qui n'existe pas a cet endroit precis. */
.gabarit-picker__swatch--suspendu {
    background: var(--color-accent);
    color: var(--color-surface);
    border-radius: var(--radius-pill);
}

/* Hexa : octogone plein, echo direct de la forme reelle de .question-bullet dans ce
   gabarit (voir plus bas) — le seul des 5 apercus ou la forme du swatch correspond
   exactement a la forme reelle, puisque l'octogone se prete bien a un petit aperçu
   contrairement au chevauchement de Suspendu ou a l'accolade de Boudina. */
.gabarit-picker__swatch--hexa {
    background: var(--color-accent);
    color: var(--color-surface);
    border-radius: 0;
    clip-path: polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%);
}

/* Brut : ombre portee douce, SANS bordure, comme la vraie pastille de ce gabarit
   (voir plus haut — bordure retiree suite au retour "trop charge"). L'ombre reste sur
   --color-text (en rgba attenue) plutot que la couleur d'accent, meme raisonnement que
   partout ailleurs dans ce gabarit : un contraste invariant de palette. */
.gabarit-picker__swatch--brut {
    background: var(--color-accent);
    color: var(--color-surface);
    border-radius: 0;
    box-shadow: 2px 2px 0 rgba(32, 30, 27, 0.18);
}

/* Organique : cercle plein (comme Classique) + halo, echo direct du motif reel de ce
   gabarit (voir plus haut). --color-border (un token neutre global, pas lie a une
   palette d'activite) plutot que --color-situation-box ici specifiquement : ce picker
   n'est jamais rendu dans le contexte d'une activite/palette particuliere, seuls les
   tokens generiques de l'UI (--color-accent/--color-surface/--color-border) sont
   pertinents a cet endroit. */
.gabarit-picker__swatch--organique {
    background: var(--color-accent);
    color: var(--color-surface);
    border-radius: 50%;
    box-shadow: 0 0 0 3px var(--color-border);
}

/* Apercu du selecteur d'encadre (voir canvas.js:openNotionPresetPicker) — memes
   dimensions que .palette-picker__swatch/.gabarit-picker__swatch pour que les 3 grilles
   s'alignent visuellement, mais affiche l'icone du preset (pas un degrade de couleurs ni
   un numero stylise) sur son fond de depart, l'apercu le plus direct pour ce cas puisque
   preset = exactement {icone, couleur} au premier coup d'oeil. */
.notion-preset-picker__icon {
    flex-shrink: 0;
    width: 40px;
    height: 40px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--color-border);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 16px;
    color: var(--color-accent);
}

/* ---- Modale d'annotation (commentaire ou note) : meme patron overlay+panneau que
   .icon-picker ci-dessus, voir canvas.js:openAnnotationModal. En lecture (le proprietaire
   de la copie consultant un commentaire du prof), l'input passe en :read-only — pas de
   bouton Enregistrer du tout dans ce cas (absent du DOM, pas juste desactive). Reutilisee
   telle quelle par openCommentThreadModal (mode Edition). z-index:500, meme raison que
   .icon-picker-overlay ci-dessus — s'ouvre depuis l'icone de commentaire d'un bloc qui
   peut tres bien etre .block--editing au moment du clic. ---- */
.annotation-modal-overlay {
    position: fixed;
    inset: 0;
    background: rgba(32, 30, 27, 0.45);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 500;
}

.annotation-modal {
    width: min(440px, 90vw);
    display: flex;
    flex-direction: column;
    background: var(--color-surface);
    border-radius: var(--radius-md);
    box-shadow: 0 24px 60px -16px rgba(32, 30, 27, 0.5);
    overflow: hidden;
}

.annotation-modal__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 18px;
    border-bottom: 1px solid var(--color-border);
    font-family: var(--font-body);
    font-weight: 600;
    color: var(--color-text);
}

.annotation-modal__close {
    border: none;
    background: none;
    color: var(--color-muted);
    cursor: pointer;
    font-size: 16px;
    line-height: 1;
    padding: 4px;
}

.annotation-modal__close:hover {
    color: var(--color-danger);
}

.annotation-modal__input {
    margin: 18px;
    padding: 10px 12px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    font-family: var(--font-body);
    font-size: 14px;
    color: var(--color-text);
    background: var(--color-surface);
}

textarea.annotation-modal__input {
    min-height: 120px;
    resize: vertical;
}

.annotation-modal__input:read-only {
    background: var(--color-surface-alt);
    color: var(--color-muted);
}

.annotation-modal__actions {
    display: flex;
    justify-content: flex-end;
    padding: 0 18px 18px;
}

/* Fil de discussion (mode Edition uniquement, voir canvas.js:openCommentThreadModal) —
   chaque entree est attribuee (auteur en gras, "Moi" pour l'utilisateur courant), jamais
   editable ni supprimable une fois envoyee : repondre se fait en ajoutant une nouvelle
   entree, pas en modifiant les precedentes. */
.annotation-thread {
    max-height: 260px;
    overflow-y: auto;
    padding: 4px 18px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.annotation-thread__vide {
    color: var(--color-muted);
    font-size: 13px;
    margin: 8px 0;
}

.annotation-thread__entree {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 8px 10px;
    background: var(--color-surface-alt);
    border-radius: var(--radius-sm);
}

.annotation-thread__auteur {
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    color: var(--color-accent);
}

.annotation-thread__texte {
    font-size: 14px;
    color: var(--color-text);
    white-space: pre-wrap;
}

.annotation-thread__form {
    display: flex;
    gap: 8px;
    padding: 12px 18px 18px;
    border-top: 1px solid var(--color-border);
}

.annotation-thread__form .annotation-modal__input {
    flex: 1;
    margin: 0;
    min-height: 44px;
}

.annotation-thread__form .btn {
    align-self: flex-end;
}

/* ---- Bloc carte mentale (mindmap) : mobilier interactif seulement — la structure
   (.block-mindmap, .mindmap-node, .mindmap-edges-svg...) vit dans blocks-shared.css
   puisque l'export PDF ne charge pas ce fichier-ci. ---- */
.mindmap-node--selected {
    box-shadow: 0 0 0 2px var(--color-accent);
}

/* .mindmap-node__text est toujours contentEditable une fois le bloc en edition, meme
   quand ce noeud precis n'est que deplacable (pas encore focus) — sans ceci, le
   navigateur affiche par defaut le curseur texte "I" en permanence, rendant impossible
   de distinguer visuellement "je peux glisser ce noeud" de "je suis en train de l'editer". */
.mindmap-node__text {
    cursor: pointer;
}

.mindmap-node__text:focus {
    cursor: text;
}

.mindmap-node__tools {
    position: absolute;
    transform: translate(-50%, -100%);
    display: none;
    align-items: center;
    gap: 6px;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 5px 6px;
    box-shadow: 0 6px 16px -6px rgba(32, 30, 27, 0.35);
    white-space: nowrap;
}

/* Le prefixe .block--editing est essentiel : sans lui, sortir du mode edition du bloc
   exterieur (clic ailleurs) laisserait la barre d'un noeud encore "selectionne" en
   interne visuellement affichee, juste non cliquable sous le move-layer restaure. */
.block--editing .mindmap-node__tools--visible {
    display: flex;
}

.mindmap-node__tools button {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-text);
    border-radius: var(--radius-sm);
    cursor: pointer;
    font-size: 11px;
}

.mindmap-node__tools button:hover {
    background: var(--color-accent);
    border-color: var(--color-accent);
    color: var(--color-surface);
}

.mindmap-node__tools button:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

.mindmap-node__color,
.mindmap-node__bg {
    width: 26px;
    height: 26px;
    padding: 0;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    cursor: pointer;
}

.mindmap-node__tools button.is-active {
    background: var(--color-accent);
    border-color: var(--color-accent);
    color: var(--color-surface);
}

/* ---- Bloc arbre de probabilite : mobilier interactif seulement — la structure
   (.block-arbreproba, .arbreproba-node, .arbreproba-edges-svg...) vit dans
   blocks-shared.css puisque l'export PDF ne charge pas ce fichier-ci. ---- */
/* Le point n'est utile que comme point d'ancrage pendant le glisser — masque par
   defaut (voir blocks-shared.css), revele uniquement une fois le bloc en edition. Les
   noeuds ne sont de toute facon interactifs (selectionnables/deplacables) qu'a ce
   moment-la (voir arbreproba.js), donc le masquer en dehors n'a aucun effet sur ce
   qu'on peut faire, seulement sur ce qui encombre visuellement la carte au repos. */
.block--editing .arbreproba-node__dot {
    display: block;
}

.arbreproba-node--selected .arbreproba-node__dot {
    box-shadow: 0 0 0 3px var(--color-accent-soft);
}

/* .arbreproba-node__text/.arbreproba-edge-label__text sont toujours contentEditable
   une fois le bloc en edition, meme quand ce noeud precis n'est que deplacable (pas
   encore focus) — meme raison que .mindmap-node__text : sans ceci, le curseur "I"
   s'afficherait en permanence, rendant impossible de distinguer visuellement "je peux
   glisser ce noeud" de "je suis en train de l'editer". */
.arbreproba-node__text,
.arbreproba-edge-label__text,
.arbreproba-node__result-text {
    cursor: pointer;
}

/* Bug reel signale directement ("il y a les champs qui recoivent du texte mais ca
   reste invisible, je le trouve en tatonnant") : sans la moindre bordure/fond, ces
   trois champs (lettre d'evenement, probabilite, resultat) etaient visuellement
   indiscernables d'un simple texte decoratif, meme une fois le bloc en edition — la
   seule difference (cursor:pointer ci-dessus) n'est visible qu'AU SURVOL, inutile pour
   les DECOUVRIR au premier coup d'oeil. Uniquement pendant .block--editing (comme le
   point d'ancrage plus haut) : un champ encadre en permanence, meme hors edition,
   alourdirait visuellement l'arbre "termine" sans y ajouter de sens — cette regle est
   donc absente de blocks-shared.css (charge aussi par l'export PDF, qui n'a aucune
   notion d'edition). En pointilles (jamais un trait plein) pour rester discret cote
   souligne "il y a quelque chose ici" plutot qu'une vraie boite fermee, qui alourdirait
   la lecture de l'arbre entier une fois plusieurs branches en place. */
.block--editing .arbreproba-node__text,
.block--editing .arbreproba-edge-label__text,
.block--editing .arbreproba-node__result-text {
    padding: 2px 6px;
    border-radius: var(--radius-sm);
    border: 1px dashed var(--color-border);
    background: var(--color-surface);
}

.block--editing .arbreproba-node__text:hover,
.block--editing .arbreproba-edge-label__text:hover,
.block--editing .arbreproba-node__result-text:hover {
    border-color: var(--color-accent);
    border-style: solid;
}

.arbreproba-node__text:focus,
.arbreproba-edge-label__text:focus,
.arbreproba-node__result-text:focus {
    cursor: text;
    border-color: var(--color-accent);
    border-style: solid;
    background: var(--color-surface);
    box-shadow: 0 0 0 3px var(--color-accent-soft);
}

.arbreproba-node__tools {
    position: absolute;
    transform: translate(-50%, -100%);
    display: none;
    align-items: center;
    gap: 6px;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 5px 6px;
    box-shadow: 0 6px 16px -6px rgba(32, 30, 27, 0.35);
    white-space: nowrap;
}

/* Le prefixe .block--editing est essentiel : sans lui, sortir du mode edition du bloc
   exterieur (clic ailleurs) laisserait la barre d'un noeud encore "selectionne" en
   interne visuellement affichee, juste non cliquable sous le move-layer restaure. */
.block--editing .arbreproba-node__tools--visible {
    display: flex;
}

.arbreproba-node__tools button {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-text);
    border-radius: var(--radius-sm);
    cursor: pointer;
    font-size: 11px;
}

.arbreproba-node__tools button:hover {
    background: var(--color-accent);
    border-color: var(--color-accent);
    color: var(--color-surface);
}

.arbreproba-node__tools button:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

/* ---- Notification ---- */
.toast {
    position: fixed;
    bottom: 28px;
    right: 28px;
    background: var(--color-primary);
    color: var(--color-surface);
    padding: 10px 20px;
    border-radius: var(--radius-sm);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    transform: translateY(100px);
    opacity: 0;
    transition: 0.25s;
    z-index: 200;
}

.toast--show {
    transform: translateY(0);
    opacity: 1;
}

/* ---- Ecran de chargement export PDF (voir canvas.js:exporterPdf) ---- */
/* La generation PDF se fait entierement cote serveur (Playwright) AVANT que la reponse
   ne parte — ce n'est jamais un telechargement lent, mais une attente lente en amont.
   Un spinner indetermine plutot qu'une fausse barre de progression : rien cote serveur
   ne permet de connaitre un pourcentage d'avancement reel, une barre qui progresserait
   arbitrairement mentirait sur ce qui se passe. Meme convention d'overlay plein ecran
   que .icon-picker-overlay/.palette-picker-overlay, mais sans possibilite de fermeture
   manuelle (pas de croix, pas de clic sur le fond) — l'utilisateur doit attendre la fin
   de la generation, jamais l'interrompre a mi-chemin depuis cette fenetre. */
.pdf-export-overlay {
    position: fixed;
    inset: 0;
    background: rgba(32, 30, 27, 0.55);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 400;
    animation: pdf-export-fade-in 0.2s ease;
}

@keyframes pdf-export-fade-in {
    from { opacity: 0; }
    to { opacity: 1; }
}

.pdf-export-panel {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 18px;
    width: min(360px, 88vw);
    padding: 40px 32px;
    background: var(--color-surface);
    border-radius: var(--radius-md);
    box-shadow: 0 30px 70px -18px rgba(32, 30, 27, 0.55);
    text-align: center;
}

.pdf-export-spinner {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: 4px solid var(--color-border);
    border-top-color: var(--color-accent);
    animation: pdf-export-spin 0.8s linear infinite;
}

@keyframes pdf-export-spin {
    to { transform: rotate(360deg); }
}

.pdf-export-title {
    font-family: var(--font-display);
    font-size: 19px;
    font-weight: 600;
    color: var(--color-text);
}

.pdf-export-subtitle {
    font-family: var(--font-body);
    font-size: 13px;
    line-height: 1.5;
    color: var(--color-muted);
}

/* ---- Horloge flottante ---- */
/* Purement indicative pendant la frappe en cours de projection : position: fixed par
   rapport a la fenetre (jamais dans le flux de .editor-workspace/.page-sheet), calee
   dans la marge droite pour ne jamais recouvrir la feuille. .no-print (voir print.css)
   la masque a l'impression ; export_pdf.html est de toute facon un template totalement
   separe qui ne l'inclut jamais, donc aucun risque cote export PDF. */
.editor-clock {
    position: fixed;
    top: 50%;
    right: var(--space-4);
    transform: translateY(-50%);
    z-index: 40;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 150px;
    height: 150px;
    border-radius: 50%;
    background: var(--color-accent);
    border: 4px solid var(--color-primary);
    box-shadow: 0 20px 40px -16px rgba(32, 30, 27, 0.45);
    pointer-events: none;
}

.editor-clock__time {
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 42px;
    color: var(--color-surface);
    letter-spacing: 0.02em;
}

/* Masquee via le bouton "Masquer/Afficher l'horloge" de la barre d'outils — voir clock.js.
   Preference retenue par navigateur (localStorage), pas propre a une activite. */
.editor-clock--hidden {
    display: none;
}

/* Retour client direct : l'horloge doit disparaitre en mode Edition (le panneau de
   vignettes de pages, deplace a droite juste au-dessus, prend alors toute la place) et
   revenir en mode Cours. `.clock-mode-hide` (voir canvas.js:applyModeUI, pose
   conditionnee par CLOCK_SUIT_MODE) est une classe DEDIEE, jamais body.cours-actif
   directement — templates/editor/editeur_modele.html (l'editeur de modele staff) charge
   ce meme editor.css mais n'a NI commutateur de mode NI concept de mode Cours du tout
   (BELACT_MODE_LOCKED reste toujours null la-bas, confirme directement) : une regle
   keyee sur cours-actif y serait invisible/sans effet de toute facon, mais autant
   utiliser une classe qui documente explicitement l'intention plutot que de reutiliser
   une classe de verrouillage de structure pour un role different. Masque aussi le
   bouton bascule de la barre d'outils : inutile de laisser un controle actif qui n'a
   plus aucun effet visible pendant que l'horloge est deja forcee masquee par le mode. */
body.clock-mode-hide .editor-clock,
body.clock-mode-hide #btn-toggle-clock {
    display: none;
}

/* Le panneau de vignettes de pages (voir plus bas dans ce fichier) fait de la place a
   l'horloge des qu'elle est REELLEMENT visible — combine les DEUX raisons independantes
   pour lesquelles elle peut etre masquee : `:not(.clock-mode-hide)` (le mode l'interdit,
   voir juste au-dessus) ET `:has(#editor-clock:not(.editor-clock--hidden))` (la
   preference manuelle localStorage l'interdit, voir clock.js) — .clock-mode-hide ne pose
   jamais de classe sur #editor-clock lui-meme (juste un display:none via un selecteur
   descendant sur <body>), donc un :has() seul sur l'element ne voit PAS cette premiere
   raison ; reproduit et corrige avant toute verification, pas suppose correct. Reste
   correct dans l'editeur de modele (ou clock-mode-hide n'existe jamais, voir
   CLOCK_SUIT_MODE dans canvas.js) : le premier terme y est alors toujours vrai, seule la
   preference manuelle gouverne, exactement le comportement attendu. Le reste du temps,
   .page-thumbnails garde son `top: 98px` d'origine (pleine hauteur disponible). */
body:not(.clock-mode-hide):has(#editor-clock:not(.editor-clock--hidden)) .page-thumbnails {
    top: calc(50% + 91px);
}

/* Note /20 : posee juste au-dessus de l'horloge (meme ancre fixe a droite). Le gabarit
   entier n'est rendu par editor.html que si une note existe deja OU si peut_annoter (voir
   apps/editor/views.py:editeur) — donc "masquee tant qu'aucune note n'a ete mise" pour
   l'eleve se fait par absence de l'element lui-meme, pas par une classe CSS a bascule. */
.editor-grade {
    position: fixed;
    right: var(--space-4);
    top: calc(50% - 75px - 16px);
    transform: translateY(-100%);
    z-index: 40;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 18px;
    border-radius: var(--radius-pill);
    background: var(--color-primary);
    color: var(--color-surface);
    box-shadow: 0 12px 24px -12px rgba(32, 30, 27, 0.45);
}

.editor-grade__value {
    font-family: var(--font-display);
    font-size: 18px;
    white-space: nowrap;
}

.editor-grade__edit {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    border: none;
    background: rgba(245, 241, 234, 0.15);
    color: var(--color-surface);
    cursor: pointer;
    font-size: 10px;
    transition: background 0.15s ease;
}

.editor-grade__edit:hover {
    background: rgba(245, 241, 234, 0.3);
}

/* ---- Bloc fleche : chrome d'interaction seulement (structure dans blocks-shared.css,
   chargee aussi par l'export PDF) ---- */
/* Signale le clic-pour-cycler (up -> down -> constant -> up) une fois le bloc en
   .block--editing — avant cela, le .block__move-layer exterieur intercepte tout clic. */
.block--editing .block-fleche[data-editable-direction] {
    cursor: pointer;
}

.block--editing .block-fleche[data-editable-direction]:hover .fleche-line {
    stroke: var(--color-accent);
}

.block--editing .block-fleche[data-editable-direction]:hover .fleche-arrowhead {
    fill: var(--color-accent);
}

/* ---- Bloc code (blocks/code.js) : extrait de code colore par highlight.js -----------
   Voir static/css/vendor/hljs-themes.css pour les 10 themes vendorises, chacun scope
   sous .code-theme-<id> — le theme choisi par CE bloc est pose comme classe sur
   .block-code (voir blocks/code.js), jamais un choix global : deux blocs de code sur la
   meme page peuvent donc afficher des themes differents sans se marcher dessus.
   --code-bg/--code-fg (custom properties posees en JS, lues depuis le computed style de
   l'element deja colore par le theme actif) evitent de dupliquer les 10x2 couleurs de
   fond/texte en dur ici — une seule source de verite, le CSS de theme vendorise. */
.block-code {
    display: flex;
    flex-direction: column;
    height: 100%;
    /* Defense de rendu en plus du clamp JS de blocks/code.js : un ancien document dont
       la hauteur a deja ete ecrasee ne doit jamais rendre la zone de saisie invisible,
       meme pendant la frame qui precede le premier autoFit(). */
    min-height: 110px;
    box-sizing: border-box;
    border-radius: var(--radius-sm);
    overflow: hidden;
    /* Derivee de --code-fg (la couleur de texte du theme ACTIF, deja garantie lisible sur
       son propre fond puisque c'est le theme lui-meme qui choisit ce contraste) plutot
       qu'une couleur fixe unique (--color-border, #E4DCC8) : ce beige clair se serait
       fondu dans un theme lui-meme clair (vscode-light, github-light, atom-light...) —
       exactement le probleme signale pour le fond blanc du PDF (voir plus bas), qui vaut
       tout autant pour l'editeur des qu'un theme clair est choisi. En derivant du fg de
       CHAQUE theme, la bordure reste automatiquement visible quel que soit le theme,
       sans jamais coder ses 10 couples de couleurs en dur. */
    border: 1px solid color-mix(in srgb, var(--code-fg, #DCDCDC) 35%, transparent);
    box-shadow: 0 2px 8px -2px rgba(32, 30, 27, 0.25);
    font-family: 'Cascadia Code', Consolas, 'SFMono-Regular', Menlo, monospace;
    font-size: 14px;
    line-height: 1.55;
    /* Repli avant le tout premier refresh() (voir blocks/code.js, differe a
       requestAnimationFrame) : couleur d'un theme sombre generique, jamais un flash
       blanc le temps d'une frame avant que le theme reel ne s'applique. */
    background: var(--code-bg, #1E1E1E);
    color: var(--code-fg, #DCDCDC);
}

/* Bandeau nom de fichier, facultatif (voir blocks/code.js) — meme esprit qu'un onglet
   d'editeur, jamais affiche si aucun nom n'a ete saisi (`hidden`, voir le JS). */
.block-code__filename {
    flex-shrink: 0;
    padding: 6px 12px;
    font-family: var(--font-body);
    font-size: 11px;
    background: color-mix(in srgb, var(--code-bg, #1E1E1E) 85%, white);
    color: var(--code-fg, #DCDCDC);
    border-bottom: 1px solid rgba(127, 127, 127, 0.25);
    opacity: 0.85;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.block-code__editor {
    position: relative;
    display: flex;
    flex: 1;
    min-height: 0;
}

/* Gouttiere de numeros de ligne (facultative, voir contenu.numeros) — un simple texte
   "\n"-joint (voir blocks/code.js:renderLineNumbers), jamais un <span> par ligne : avec
   la meme police/interlignage/padding-haut que le code, l'alignement ligne a ligne est
   garanti par le moteur de mise en page du texte lui-meme, sans mesure JS. Purement
   decorative : aucune selection/interaction possible (user-select/pointer-events none). */
.block-code__gutter {
    flex-shrink: 0;
    box-sizing: border-box;
    min-width: 34px;
    padding: 14px 8px 14px 4px;
    text-align: right;
    white-space: pre;
    font: inherit;
    line-height: inherit;
    background: var(--code-bg, #1E1E1E);
    color: var(--code-fg, #DCDCDC);
    opacity: 0.5;
    user-select: none;
    pointer-events: none;
    border-right: 1px solid rgba(127, 127, 127, 0.25);
}

/* Conteneur des deux couches superposees (voir l'en-tete de blocks/code.js pour la
   technique complete) : .block-code__pre (rendu colore, lecture seule, jamais
   interactif) en dessous, .block-code__textarea (texte transparent, seul le curseur/la
   selection natifs sont visibles) au-dessus, pixel pour pixel identiques en
   police/interligne/padding pour que les deux couches restent visuellement confondues. */
.block-code__stage {
    position: relative;
    flex: 1;
    min-width: 0;
    height: 100%;
}

.block-code__pre,
.block-code__textarea {
    position: absolute;
    inset: 0;
    margin: 0;
    width: 100%;
    height: 100%;
    box-sizing: border-box;
    font: inherit;
    line-height: inherit;
    tab-size: 4;
}

/* pointer-events:none : cette couche n'est jamais la cible d'un clic/glisser, seul le
   textarea au-dessus l'est. overflow:hidden + JS (block-code__textarea 'scroll' ->
   pre.scrollLeft, voir attachHorizontalSync) : PRE est le conteneur de defilement
   pilote par programme, jamais directement par l'utilisateur ni par sa propre barre. */
.block-code__pre {
    overflow: hidden;
    pointer-events: none;
    white-space: pre;
}

/* Deux classes (.hljs posee en JS + .block-code__highlight) : la specificite (2
   classes) doit deliberement depasser celle de la regle vendorisee "pre code.hljs"
   (static/css/vendor/hljs-themes.css, identique dans les 10 themes), qui pose
   `overflow-x: auto` sur l'element .hljs lui-meme — un second defilement horizontal
   independant de celui, deja gere a la main, du textarea aurait desynchronise les deux
   couches des qu'une ligne depasse la largeur du bloc. Le padding est repose ici en
   dur (14px, identique a .block-code__textarea plus bas) plutot que de s'appuyer sur le
   `padding: 1em` du theme vendorise : explicite et independant du font-size en
   cascade, jamais un desalignement silencieux si ce dernier changeait un jour. */
.block-code__pre .hljs.block-code__highlight {
    display: block;
    overflow: visible;
    white-space: inherit;
    margin: 0;
    padding: 14px;
}

.block-code__textarea {
    border: none;
    outline: none;
    resize: none;
    padding: 14px;
    background: transparent;
    color: transparent;
    caret-color: var(--code-fg, #FFFFFF);
    /* Safari a besoin de cette propriete non standard (mais tres largement supportee)
       en plus de color:transparent pour rendre le texte reellement invisible tout en
       gardant le curseur/la selection natifs visibles — technique standard des
       editeurs "textarea + rendu colore superpose". */
    -webkit-text-fill-color: transparent;
    /* Jamais de retour a la ligne (voir le commentaire complet dans blocks/code.js) :
       une ligne de code reste une ligne, defilement horizontal au besoin. */
    white-space: pre;
    overflow-x: auto;
    overflow-y: hidden;
}

/* ---- Barre d'outils : langage / theme / nom de fichier / numeros / copier ----
   Meme registre visuel que .block-video__tools — position:absolute simple relative au
   bloc, left:0 + right:0 (deja etire pleine largeur du bloc) + flex-wrap pour passer sur
   plusieurs lignes au besoin. */
.block-code__tools {
    position: absolute;
    bottom: calc(100% + 10px);
    right: 0;
    display: none;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 6px 8px;
    box-shadow: 0 8px 20px -10px rgba(32, 30, 27, 0.3);
    z-index: 10;
    /* Controles d'interface, jamais la police monospace du bloc lui-meme. */
    font-family: var(--font-body);
}

.block--editing .block-code__tools {
    display: flex;
}

.block-code__select {
    font-size: 11px;
    font-family: var(--font-body);
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-text);
    padding: 5px 6px;
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.block-code__filename-input {
    flex: 1 1 140px;
    min-width: 120px;
    font-size: 11px;
    font-family: var(--font-body);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 5px 8px;
    background: var(--color-surface);
    color: var(--color-text);
}

.block-code__numeros {
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: 11px;
    font-family: var(--font-body);
    color: var(--color-text);
    cursor: pointer;
    white-space: nowrap;
}

.block-code__copy {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    flex-shrink: 0;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-text);
    border-radius: var(--radius-sm);
    cursor: pointer;
    font-size: 12px;
}

.block-code__copy:hover,
.block-code__copy--done {
    background: var(--color-accent);
    border-color: var(--color-accent);
    color: var(--color-surface);
}

/* ---- Vignettes de pages (canvas.js:renderPageThumbnails) ---------------------------
   position:fixed, jamais dans le flux de .editor-workspace : ne pousse jamais le canvas.
   Deplace du bord GAUCHE (juste apres la sidebar) vers le bord DROIT (a l'emplacement de
   l'horloge, voir .editor-clock plus bas) — retour client direct : la sidebar elargie en
   familles (voir .editor-sidebar plus haut) laissait moins de place a gauche, et
   l'horloge n'occupe reellement de l'espace qu'en mode Cours (voir body.clock-mode-hide
   plus bas), rendant le cote droit disponible le reste du temps. Chaque valeur ci-dessous
   est un MIROIR exact de l'ancienne version gauche (meme largeur/hauteur/defilement),
   seul le cote change. `top: 98px` mesure directement (hauteur reelle empilee de
   .editor-toolbar + .editor-formatbar sur un seul ecran) plutot que devinee — imprecis
   uniquement dans le cas marginal ou .editor-formatbar passe sur 2 lignes (viewport tres
   etroit), deja hors de la plage ou cette colonne reste visible de toute facon. Quand
   l'horloge est reellement visible (voir la regle `body:has(...)` plus bas, juste apres
   .editor-clock), ce `top` est repousse pour lui laisser la place plutot que de la
   recouvrir. */
.page-thumbnails {
    position: fixed;
    right: 0;
    top: 98px;
    bottom: 0;
    width: 120px;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-2);
    background: var(--color-surface);
    border-left: 1px solid var(--color-border);
    z-index: 30;
    /* Repliage manuel (voir .page-thumbnails__toggle plus bas) : une translation plutot
       qu'un display:none/width:0, pour une vraie glissade animee "comme dans les
       navigateurs" (barre de favoris, panneau lateral...) plutot qu'une disparition
       brute. */
    transition: transform 0.2s ease, top 0.2s ease;
}

.page-thumbnails--hidden {
    display: none;
}

/* La classe d'etat vit sur <body> (posee par canvas.js), jamais directement sur
   .page-thumbnails — meme convention que body.mode-cours/body.palette-<id> ailleurs
   dans ce fichier, pour un etat qui doit rester lisible/pilotable depuis n'importe quel
   element de la page (ici, la poignee elle-meme, voir plus bas). Glisse desormais vers
   la DROITE (le panneau lui-meme y est ancre) — miroir de l'ancienne translation gauche. */
body.page-thumbnails-collapsed .page-thumbnails {
    transform: translateX(100%);
}

/* Scrollbar fine et discrete (demande explicite : "le trait scroll... doit etre
   subtile... fine") plutot que la scrollbar systeme par defaut, nettement plus epaisse
   et grise/contrastee — cette colonne est deja etroite (120px), une grosse scrollbar
   y mangeait une part disproportionnee de la largeur en plus de jurer visuellement avec
   le reste de l'interface. `scrollbar-gutter` non utilise ici : cette colonne n'a besoin
   de defiler que sur un document a beaucoup de pages, un cas deja marginal. */
.page-thumbnails {
    scrollbar-width: thin;
    scrollbar-color: var(--color-border) transparent;
}

.page-thumbnails::-webkit-scrollbar {
    width: 5px;
}

.page-thumbnails::-webkit-scrollbar-track {
    background: transparent;
}

.page-thumbnails::-webkit-scrollbar-thumb {
    background: var(--color-border);
    border-radius: var(--radius-pill);
}

.page-thumbnails::-webkit-scrollbar-thumb:hover {
    background: var(--color-muted);
}

/* Poignee de repli/depli — reste TOUJOURS a une position fixe connue (jamais enfant de
   .page-thumbnails lui-meme : un enfant suivrait le panneau hors champ des qu'il se
   replie, rendant impossible de le rouvrir). Sa propre position (right) suit l'etat
   replie/deplie via la classe posee sur <body> — voir canvas.js. Miroir exact de
   l'ancienne version gauche : ouvert, elle s'accroche au bord GAUCHE du panneau
   (right:120px = largeur du panneau, puisqu'il est lui-meme ancre a right:0) ; repliee,
   elle revient flush contre le vrai bord droit de l'ecran (right:0). */
.page-thumbnails-toggle {
    position: fixed;
    right: 120px;
    top: 112px;
    z-index: 31;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 36px;
    border: 1px solid var(--color-border);
    border-right: none;
    border-radius: var(--radius-sm) 0 0 var(--radius-sm);
    background: var(--color-surface);
    color: var(--color-muted);
    cursor: pointer;
    font-size: 10px;
    transition: right 0.2s ease;
}

.page-thumbnails-toggle:hover {
    color: var(--color-accent);
}

body.page-thumbnails-collapsed .page-thumbnails-toggle {
    right: 0;
    border-right: 1px solid var(--color-border);
    border-radius: var(--radius-sm) 0 0 var(--radius-sm);
}

/* Meme raison que la media query juste en dessous pour le panneau lui-meme : sous cette
   largeur, ni le panneau ni sa poignee n'ont assez de marge pour ne jamais chevaucher la
   feuille en cours d'edition. */
@media (max-width: 1150px) {
    .page-thumbnails-toggle {
        display: none;
    }
}

/* En dessous de cette largeur, la feuille centree n'a plus assez de marge a droite pour
   ne jamais chevaucher cette colonne — mieux vaut la masquer entierement que risquer un
   recouvrement visuel de la page reelle en cours d'edition. */
@media (max-width: 1150px) {
    .page-thumbnails {
        display: none;
    }
}

.page-thumbnail {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    width: 100%;
    padding: 0;
    border: none;
    background: none;
    font-family: var(--font-body);
}

.page-thumbnail__navigate {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    width: 100%;
    padding: 0;
    border: 0;
    background: transparent;
    color: inherit;
    cursor: pointer;
    font: inherit;
}

.page-thumbnail__controls {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    min-height: 22px;
    opacity: 0;
    pointer-events: none;
    transition: opacity .15s ease;
}

.page-thumbnail:hover .page-thumbnail__controls,
.page-thumbnail:focus-within .page-thumbnail__controls,
.page-thumbnail--active .page-thumbnail__controls {
    opacity: 1;
    pointer-events: auto;
}

.page-thumbnail__controls button {
    display: grid;
    width: 28px;
    height: 22px;
    padding: 0;
    place-items: center;
    border: 1px solid var(--color-border);
    border-radius: 6px;
    background: var(--color-surface);
    color: var(--color-muted);
    cursor: pointer;
    font-size: 9px;
}

.page-thumbnail__controls button:hover:not(:disabled) {
    border-color: var(--color-accent);
    color: var(--color-accent);
}

.page-thumbnail__controls button:disabled {
    opacity: .28;
    cursor: default;
}

@media (hover: none) {
    .page-thumbnail__controls { opacity: 1; pointer-events: auto; }
}

.page-thumbnail__stage {
    position: relative;
    width: 96px;
    height: 136px;
    overflow: hidden;
    border: 1.5px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    box-shadow: 0 1px 3px rgba(32, 30, 27, 0.15);
    /* Ceinture ET bretelles contre toute interaction depuis le clone (voir
       canvas.js:buildThumbnailClone, qui retire deja contentEditable/tabindex) : un
       calque plein transparent est en plus superpose par-dessus (voir
       .page-thumbnail__shield ci-dessous), au cas ou un descendant du clone porterait
       son propre pointer-events:auto explicite (ex. .arbreproba-edge-label) qui
       reactiverait localement les evenements malgre ce pointer-events:none ici. */
    pointer-events: none;
}

/* Calque plein, transparent, au-dessus du clone (voir canvas.js:rebuildPageThumbnailsList)
   — intercepte tout clic avant qu'il n'atteigne un descendant du clone qui porterait son
   propre pointer-events:auto explicite (ex. .arbreproba-edge-label), qui reactiverait
   sinon localement les evenements malgre le pointer-events:none de .page-thumbnail__stage
   lui-meme. Le clic est cense de toute facon remonter jusqu'au <button>.page-thumbnail
   englobant (bubbling normal), jamais gere ici directement. */
.page-thumbnail__shield {
    position: absolute;
    inset: 0;
    z-index: 5;
}

/* Cible specifiquement le clone (toujours .page-sheet, cloneNode en preserve la classe),
   jamais .page-thumbnail__shield juste au-dessus — seul le clone a besoin d'etre remis a
   sa taille native puis mis a l'echelle, le calque de protection doit rester a la taille
   de la vignette elle-meme (inset:0, voir sa propre regle). */
.page-thumbnail__stage .page-sheet {
    position: absolute;
    top: 0;
    left: 0;
    /* Memes dimensions natives que le vrai .page-sheet (voir tokens.css
       --page-width/--page-height) quel que soit ce nouveau conteneur, minuscule — sans
       ca le clone se redimensionne a la taille de .page-thumbnail__stage AVANT la mise
       a l'echelle, faussant tout. */
    width: var(--page-width);
    height: var(--page-height);
    transform-origin: top left;
    /* 96px (largeur de la vignette) / 794px (largeur reelle d'une page) — recalculer ici
       si l'une des deux valeurs change. */
    transform: scale(0.1209);
}

.page-thumbnail--active .page-thumbnail__stage {
    border-color: var(--color-accent);
    box-shadow: 0 0 0 2px var(--color-accent-soft);
}

.page-thumbnail__label {
    font-size: 11px;
    color: var(--color-muted);
}

.page-thumbnail--active .page-thumbnail__label {
    color: var(--color-accent);
    font-weight: 600;
}
.block-qcm__order {
    display: none;
    flex: 0 0 auto;
    flex-direction: column;
    gap: 1px;
    align-self: center;
}

/* Les commandes de classement sont contextuelles : un QCM au repos conserve exactement
   sa mise en page épurée. Un simple clic sur le bloc pose .block--selected (canvas.js),
   elles apparaissent alors ; le double-clic d'édition conserve naturellement cette
   sélection. La portée par enfant direct évite qu'un état sélectionné extérieur fasse
   apparaître les boutons dans les clones de miniature. */
.block--selected > .block__content .block-qcm__order {
    display: inline-flex;
}

.block-qcm__order-btn {
    display: grid;
    width: 20px;
    height: 15px;
    padding: 0;
    place-items: center;
    border: 0;
    border-radius: 4px;
    color: #5f5b55;
    background: transparent;
    cursor: pointer;
    font-size: 9px;
}

.block-qcm__order-btn:hover:not(:disabled) { background: rgba(83, 25, 93, .1); color: var(--color-primary); }
.block-qcm__order-btn:disabled { opacity: .22; cursor: default; }

.page-thumbnail--dragging { opacity: .38; }
.page-thumbnail[draggable="true"] { cursor: grab; }
.page-thumbnail[draggable="true"]:active { cursor: grabbing; }
.page-duplicate-btn { margin-left: auto; }

.editor-precision-panel {
    position: fixed;
    z-index: 1200;
    left: 50%;
    bottom: 18px;
    display: none;
    align-items: center;
    gap: 6px;
    max-width: calc(100vw - 36px);
    padding: 7px 9px;
    overflow: visible;
    transform: translateX(-50%);
    border: 1px solid rgba(32, 30, 27, .14);
    border-radius: 12px;
    background: rgba(255, 255, 255, .97);
    box-shadow: 0 10px 32px rgba(24, 18, 13, .18);
    color: #292622;
    font: 600 12px/1.2 Inter, Arial, sans-serif;
    white-space: nowrap;
}
.editor-precision-panel--visible { display: flex; }
.editor-precision-panel button {
    min-width: 30px;
    height: 30px;
    padding: 0 8px;
    border: 1px solid #dedad5;
    border-radius: 7px;
    background: #fff;
    color: inherit;
    cursor: pointer;
}
.editor-precision-panel button:hover:not(:disabled) { border-color: var(--color-primary); color: var(--color-primary); }
.editor-precision-panel button:disabled { opacity: .35; cursor: default; }
.editor-precision-panel label { display: inline-flex; align-items: center; gap: 3px; color: #706a63; }
.editor-precision-panel input[type="number"] { width: 54px; height: 30px; padding: 3px 5px; border: 1px solid #dedad5; border-radius: 6px; }
.editor-precision-panel input[type="color"] { width: 30px; height: 30px; padding: 2px; border: 1px solid #dedad5; border-radius: 6px; background: #fff; }
.editor-precision-panel__count { padding: 0 5px; color: var(--color-primary); }

.editor-precision-panel__geometry { display: inline-flex; align-items: center; gap: 6px; }
.editor-precision-menu { position: relative; }
.editor-precision-menu summary {
    display: flex;
    align-items: center;
    gap: 6px;
    height: 30px;
    padding: 0 9px;
    border: 1px solid #dedad5;
    border-radius: 7px;
    background: #fff;
    cursor: pointer;
    list-style: none;
    user-select: none;
}
.editor-precision-menu summary::-webkit-details-marker { display: none; }
.editor-precision-menu[open] summary,
.editor-precision-menu summary:hover { border-color: var(--color-primary); color: var(--color-primary); }
.editor-precision-menu--disabled { opacity: .38; pointer-events: none; }
.editor-precision-menu__popover {
    position: absolute;
    bottom: calc(100% + 10px);
    left: 0;
    z-index: 2;
    display: grid;
    gap: 5px;
    min-width: 190px;
    padding: 7px;
    border: 1px solid #dedad5;
    border-radius: 10px;
    background: #fff;
    box-shadow: 0 10px 28px rgba(24, 18, 13, .18);
}
.editor-precision-menu__popover--right { right: 0; left: auto; }
.editor-precision-menu__popover--grid { grid-template-columns: repeat(3, 34px); min-width: 0; }
.editor-precision-menu__popover button { justify-content: flex-start; text-align: left; }
.editor-precision-menu__popover--grid button { width: 34px; padding: 0; text-align: center; }
.editor-precision-menu__field { justify-content: space-between; min-height: 34px; padding: 2px 7px; }

@media (max-width: 1050px) {
    .editor-precision-panel { max-width: calc(100vw - 20px); }
    .editor-precision-panel__geometry label:nth-of-type(3),
    .editor-precision-panel__geometry label:nth-of-type(4),
    .editor-precision-panel__geometry label:nth-of-type(5) { display: none; }
    .editor-precision-menu summary { width: 32px; padding: 0; justify-content: center; font-size: 0; }
    .editor-precision-menu summary i { font-size: 12px; }
}

.question-bundle-picker { width: min(460px, calc(100vw - 32px)); }
.question-bundle-picker__choices { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; padding: 18px; }
.question-bundle-picker__choices button { display: grid; gap: 7px; min-height: 122px; padding: 18px; border: 1px solid #ded8d1; border-radius: 12px; background: #fff; color: #292622; text-align: left; cursor: pointer; }
.question-bundle-picker__choices button:hover { border-color: var(--color-primary); box-shadow: 0 6px 18px rgba(83, 25, 93, .1); }
.question-bundle-picker__choices i { color: var(--color-primary); font-size: 21px; }
.question-bundle-picker__choices span { color: #746e67; font-size: 12px; font-weight: 500; }

.global-replace-panel { width: min(440px, calc(100vw - 32px)); }
.global-replace-panel__body { display: flex; align-items: end; justify-content: center; gap: 20px; padding: 22px 20px 15px; }
.global-replace-panel__body label { display: grid; gap: 7px; color: #625d57; font: 600 12px/1.2 Inter, Arial, sans-serif; }
.global-replace-panel__body input { width: 120px; height: 42px; padding: 3px; border: 1px solid #d9d4ce; border-radius: 8px; background: #fff; }
.global-replace-panel__body i { margin-bottom: 14px; color: #8a837b; }
.global-replace-panel__apply { display: block; width: calc(100% - 40px); margin: 0 20px 20px; padding: 11px 14px; border: 0; border-radius: 8px; background: var(--color-primary); color: #fff; font-weight: 700; cursor: pointer; }

.sidebar-tooltip strong,
.sidebar-tooltip small { display: block; }
.sidebar-tooltip small { margin-top: 3px; color: rgba(255, 255, 255, .72); font-size: 10px; font-weight: 500; }

/* Personnalisation globale des seuls elements decoratifs Question/Titre. Ces regles sont
   placees apres les gabarits : un reglage explicite de l'utilisateur devient la source de
   verite, tandis que les valeurs par defaut reproduisent exactement le style classique.
   `body.custom-question-style` EST INDISPENSABLE ici (corrige un vrai bug — voir le
   commentaire complet dans blocks-shared.css a cote de la regle par defaut de
   .question-bullet) : sans cette condition, cette regle s'appliquait TOUJOURS (avec
   !important sur chaque propriete), silencieusement invisible tant qu'aucune
   personnalisation n'existait puisque ses valeurs de repli reproduisent le style
   classique — mais elle ecrasait alors aussi, sans le vouloir, toute forme de gabarit
   (voir body.gabarit-hexa .question-bullet etc. plus haut), qui n'a jamais eu la moindre
   chance de s'appliquer dans l'editeur. Meme registre que
   body.custom-title-style/body.custom-title-number-style juste en dessous, deja
   correctement scopees depuis le debut — cette regle-ci etait la seule des 3 a manquer
   sa propre condition. */
body.custom-question-style .question-bullet {
    width: var(--custom-q-width, 20px) !important;
    height: var(--custom-q-height, 20px) !important;
    background: var(--custom-q-fill, var(--color-question-badge-bg)) !important;
    color: var(--custom-q-text, var(--color-question-badge-fg)) !important;
    border: var(--custom-q-border, 0 solid transparent) !important;
    border-radius: var(--custom-q-radius, 50%) !important;
    clip-path: var(--custom-q-clip, none) !important;
    box-shadow: var(--custom-q-shadow, none) !important;
    font-size: var(--custom-q-font-size, 14px) !important;
    font-weight: normal !important;
    /* normal (pas 1) + text-align:center, meme correctif "numero trop bas" que la regle
       par defaut dans blocks-shared.css, applique ici aussi avec !important puisque
       cette regle-ci gagne des qu'une personnalisation est active — sans cette meme
       correction ICI, personnaliser la pastille (une simple couleur, par exemple)
       aurait silencieusement reintroduit le decalage vertical que la regle par defaut
       vient de corriger. */
    line-height: normal !important;
    text-align: center !important;
}
/* Ecart pastille/enonce ("Espace avec le texte" du panneau) — cible desormais
   .block-question__principal (la rangee pastille+enonce), plus jamais .block-question
   elle-meme : bug reel signale directement ("Espace avec le texte ne fonctionne plus"),
   introduit par l'ajout des sous-questions lettrees (voir CLAUDE.md "Bloc question")
   — .block-question ne contient plus qu'UN SEUL enfant flex
   (.block-question__contenu), le gap sur un conteneur a un seul enfant n'a plus aucun
   effet visible ; c'est desormais .block-question__principal qui porte reellement la
   rangee pastille+enonce dont cet ecart parle. */
body.custom-question-style .block-question__principal { gap: var(--custom-q-gap, 10px); }
body.custom-question-style[data-question-pointer="true"] .question-bullet::after {
    content: "" !important; position: absolute !important; top: 50% !important; left: 95% !important;
    width: 0 !important; height: 0 !important; transform: translateY(-50%) !important;
    border: 0 solid transparent !important; border-left: 7px solid var(--custom-q-fill) !important;
    border-top-width: 3px !important; border-bottom-width: 3px !important;
}
body[data-question-pointer="false"] .question-bullet::after { content: none !important; }
.block--editing .question-bullet,
.block--editing .activity-title { cursor: pointer; outline-offset: 3px; }
.block--editing .question-bullet:hover,
.block--editing .activity-title:hover { outline: 2px solid var(--color-accent-soft); }

body.custom-title-style .activity-title {
    width: var(--custom-title-width, 200px) !important;
    min-height: var(--custom-title-min-height, auto) !important;
    padding: var(--custom-title-pad-y, 10px) var(--custom-title-pad-x, 16px) !important;
    background: var(--custom-title-fill, var(--color-situation-accent)) !important;
    color: var(--custom-title-text, #fff) !important;
    border: var(--custom-title-border, 0 solid transparent) !important;
    border-radius: var(--custom-title-radius, 1% 99% 100% 0% / 56% 0% 100% 44%) !important;
    clip-path: var(--custom-title-clip, none) !important;
    box-shadow: var(--custom-title-shadow, none) !important;
}
body.custom-title-style .activity-title::before { content: none !important; }
body.custom-title-style .activity-title__icon { display: inline-block; }
body.custom-title-number-style .activity-title__number {
    width: var(--custom-title-number-width, 42px) !important;
    height: var(--custom-title-number-height, 42px) !important;
    background: var(--custom-title-number-fill, var(--color-activity-badge)) !important;
    color: var(--custom-title-number-text, var(--color-situation-accent)) !important;
    border: var(--custom-title-number-border, 0 solid transparent) !important;
    border-radius: var(--custom-title-number-radius, 50%) !important;
    clip-path: var(--custom-title-number-clip, none) !important;
    box-shadow: var(--custom-title-number-shadow, none) !important;
    margin-left: 15px !important;
    transform: none !important;
    font-size: 18px !important;
    font-weight: bold !important;
}

.pedagogical-style-panel { width: min(560px, calc(100vw - 30px)); max-height: calc(100vh - 40px); overflow-y: auto; }
.pedagogical-style-overlay {
    inset: 98px 0 0 auto;
    width: 430px;
    max-width: calc(100vw - 20px);
    align-items: stretch;
    justify-content: flex-end;
    background: transparent;
    pointer-events: none;
}
.pedagogical-style-overlay .pedagogical-style-panel {
    width: 100%;
    max-height: none;
    height: 100%;
    border-radius: 14px 0 0 0;
    border-left: 1px solid var(--color-border);
    box-shadow: -14px 0 38px rgba(32, 30, 27, .18);
    pointer-events: auto;
}
.pedagogical-style-panel__scope { margin: 0; padding: 11px 18px; background: var(--color-accent-soft); color: var(--color-accent); font-size: 12px; }
.pedagogical-style-panel__section { padding: 16px 18px 8px; }
.pedagogical-shapes { display: grid; grid-template-columns: repeat(8, 1fr); gap: 7px; margin-top: 10px; }
.pedagogical-shape { display: grid; height: 46px; place-items: center; border: 1px solid var(--color-border); border-radius: 8px; background: #fff; cursor: pointer; }
.pedagogical-shape--active { border-color: var(--color-accent); box-shadow: 0 0 0 2px var(--color-accent-soft); }
.pedagogical-shape span { width: 25px; height: 25px; background: var(--color-accent); }
.pedagogical-shape span[data-shape-preview="circle"] { border-radius: 50%; }
.pedagogical-shape span[data-shape-preview="rounded"] { border-radius: 7px; }
.pedagogical-shape span[data-shape-preview="pill"] { width: 34px; border-radius: 999px; }
.pedagogical-shape span[data-shape-preview="diamond"] { clip-path: polygon(50% 0,100% 50%,50% 100%,0 50%); }
.pedagogical-shape span[data-shape-preview="hexagon"] { clip-path: polygon(25% 0,75% 0,100% 50%,75% 100%,25% 100%,0 50%); }
.pedagogical-shape span[data-shape-preview="organic"] { width: 34px; border-radius: 1% 99% 100% 0% / 56% 0% 100% 44%; }
.pedagogical-shape span[data-shape-preview="tag"] { width: 34px; clip-path: polygon(0 0,88% 0,100% 50%,88% 100%,0 100%); }
.pedagogical-shape span[data-shape-preview="blob"] { width: 34px; border-radius: 22% 78% 65% 35% / 58% 28% 72% 42%; }
.pedagogical-shape span[data-shape-preview="free"] { width: 34px; clip-path: polygon(25% 0,75% 0,100% 100%,0 100%); }
.pedagogical-style-panel__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 14px; padding: 10px 18px 20px; }
.pedagogical-style-panel__grid label { display: grid; grid-template-columns: 1fr 120px; align-items: center; gap: 8px; color: var(--color-muted); font-size: 12px; font-weight: 600; }
.pedagogical-style-panel__grid input:not([type="checkbox"]) { width: 100%; min-width: 0; height: 34px; box-sizing: border-box; border: 1px solid var(--color-border); border-radius: 7px; }
.pedagogical-style-panel__grid .color-picker-btn { justify-self: end; }
.pedagogical-style-panel__grid .color-picker-btn__menu { right: 0; left: auto; z-index: 40; }
.pedagogical-style-panel__check { display: flex !important; grid-template-columns: none !important; }
.pedagogical-style-panel__icon-btn { min-height: 36px; border: 1px solid var(--color-border); border-radius: 8px; background: #fff; color: var(--color-accent); cursor: pointer; }
.pedagogical-free-editor {
    display: none;
    position: relative;
    height: 160px;
    margin: 0 28px 12px;
    border: 1px solid var(--color-border);
    border-radius: 10px;
    background-color: var(--color-canvas, #f7f5f1);
    background-image: linear-gradient(rgba(83,25,93,.06) 1px, transparent 1px), linear-gradient(90deg, rgba(83,25,93,.06) 1px, transparent 1px);
    background-size: 16px 16px;
    overflow: visible;
}
.pedagogical-free-editor--visible { display: block; }
.pedagogical-free-editor__shape { position: absolute; inset: 0; cursor: crosshair; }
.pedagogical-free-editor .block-forme__point-handle { display: block; }
.pedagogical-free-editor__tools { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 0 18px 18px; }
.pedagogical-free-editor__tools[hidden] { display: none; }
.pedagogical-free-editor__tools button { min-height: 34px; padding: 0 11px; border: 1px solid var(--color-border); border-radius: 7px; background: #fff; color: var(--color-accent); cursor: pointer; }
.pedagogical-free-editor__tools label { display: flex; align-items: center; gap: 5px; color: var(--color-muted); font-size: 12px; }
.pedagogical-free-editor__tools small { flex-basis: 100%; color: var(--color-muted); font-size: 10px; }
.pedagogical-blob-editor { margin: 0 18px 18px; padding: 12px; border: 1px solid var(--color-border); border-radius: 10px; background: var(--color-canvas, #f7f5f1); }
.pedagogical-blob-editor[hidden] { display: none; }
.pedagogical-blob-editor__preview { width: 150px; height: 100px; margin: 4px auto 14px; transition: border-radius .12s ease; }
.pedagogical-blob-editor__controls { display: grid; grid-template-columns: 1fr 1fr; gap: 7px 12px; margin-bottom: 12px; }
.pedagogical-blob-editor__controls label { display: grid; grid-template-columns: 80px 1fr 34px; align-items: center; gap: 5px; color: var(--color-muted); font-size: 10px; }
.pedagogical-blob-editor__controls input { width: 100%; min-width: 0; }
.pedagogical-blob-editor__controls output { text-align: right; font-variant-numeric: tabular-nums; }
.pedagogical-style-panel__reset { display: block; width: calc(100% - 36px); margin: 0 18px 18px; padding: 10px; border: 1px solid var(--color-border); border-radius: 8px; background: #fff; color: var(--color-muted); cursor: pointer; }

/* Assistance de l'editeur — chargee uniquement avec editor.css, jamais sur le reste du site. */
.editor-help-button { position: fixed; right: 18px; bottom: 18px; z-index: 850; width: 42px; height: 42px; border: 0; border-radius: 50%; background: var(--color-accent); color: #fff; box-shadow: 0 6px 20px rgba(32,30,27,.24); cursor: pointer; font-size: 16px; }
.editor-command-menu { position: fixed; z-index: 1200; width: 360px; max-height: 420px; overflow: hidden; border: 1px solid var(--color-border); border-radius: 12px; background: #fff; box-shadow: 0 18px 50px rgba(32,30,27,.22); }
.editor-command-menu__search,.editor-help-search { display: flex; align-items: center; gap: 9px; padding: 11px 13px; border-bottom: 1px solid var(--color-border); color: var(--color-muted); }
.editor-command-menu__search input,.editor-help-search input { flex: 1; min-width: 0; border: 0; outline: 0; background: transparent; font: inherit; }
.editor-command-menu__results { max-height: 330px; overflow-y: auto; padding: 6px; }
.editor-command-menu__results button { display: flex; width: 100%; align-items: center; gap: 11px; padding: 9px 10px; border: 0; border-radius: 8px; background: transparent; text-align: left; cursor: pointer; }
.editor-command-menu__results button.is-active { background: var(--color-accent-soft); color: var(--color-accent); }
.editor-command-menu__results button>i { width: 24px; text-align: center; }
.editor-command-menu__results button span { display: flex; flex: 1; justify-content: space-between; align-items: center; }
.editor-command-menu__results small,.editor-command-menu footer { color: var(--color-muted); font-size: 10px; }
.editor-command-menu footer { display: block; padding: 8px 12px; border-top: 1px solid var(--color-border); }
.editor-help-overlay { position: fixed; inset: 0; z-index: 1250; background: rgba(32,30,27,.18); }
.editor-help-panel { position: absolute; inset: 0 0 0 auto; display: flex; flex-direction: column; width: min(430px, 100vw); background: #fff; box-shadow: -14px 0 40px rgba(32,30,27,.2); }
.editor-help-panel>header { display: flex; align-items: center; justify-content: space-between; padding: 18px; border-bottom: 1px solid var(--color-border); }
.editor-help-panel>header div { display: flex; flex-direction: column; gap: 3px; }.editor-help-panel>header span { color: var(--color-muted); font-size: 11px; }
.editor-help-panel>header button { border: 0; background: none; font-size: 25px; cursor: pointer; }
.editor-help-panel nav { display: flex; padding: 0 12px; border-bottom: 1px solid var(--color-border); }
.editor-help-panel nav button { flex: 1; padding: 11px 5px; border: 0; border-bottom: 2px solid transparent; background: none; color: var(--color-muted); cursor: pointer; }
.editor-help-panel nav button.is-active { border-color: var(--color-accent); color: var(--color-accent); font-weight: 700; }
.editor-help-content { flex: 1; overflow-y: auto; padding: 12px; }
.editor-help-content article { display: flex; gap: 11px; padding: 12px; border-bottom: 1px solid var(--color-border); }.editor-help-content article>i { margin-top: 3px; color: var(--color-accent); }.editor-help-content article p { margin: 4px 0 0; color: var(--color-muted); font-size: 12px; line-height: 1.45; }
.editor-help-shortcuts>div { display: flex; align-items: center; justify-content: space-between; gap: 15px; padding: 10px; border-bottom: 1px solid var(--color-border); font-size: 12px; }.editor-help-shortcuts kbd { padding: 4px 7px; border: 1px solid var(--color-border); border-bottom-width: 2px; border-radius: 5px; background: var(--color-bg); white-space: nowrap; }
.editor-help-panel>footer { padding: 12px 18px; border-top: 1px solid var(--color-border); }.editor-help-panel>footer button { width: 100%; padding: 10px; border: 1px solid var(--color-border); border-radius: 8px; background: #fff; color: var(--color-accent); cursor: pointer; }
.editor-help-guide { margin-bottom: 9px; border: 1px solid var(--color-border); border-radius: 9px; overflow: hidden; }.editor-help-guide summary { display: flex; align-items: center; gap: 9px; padding: 12px; cursor: pointer; font-weight: 700; }.editor-help-guide summary .fa-chevron-down { margin-left: auto; }.editor-help-guide ol { margin: 0 16px 12px 34px; padding: 0; color: var(--color-muted); font-size: 12px; }.editor-help-guide li { margin: 6px 0; }
.editor-help-demo { position: relative; height: 70px; margin: 10px; overflow: hidden; border-radius: 7px; background: var(--color-accent-soft); }.editor-help-demo span { position: absolute; width: 32px; height: 22px; border-radius: 4px; background: var(--color-accent); animation: editor-help-demo 3s infinite ease-in-out; }.editor-help-demo span:nth-child(2) { animation-delay: .25s; opacity: .65; }.editor-help-demo span:nth-child(3) { animation-delay: .5s; opacity: .35; }.editor-help-demo small { position: absolute; right: 8px; bottom: 5px; color: var(--color-muted); }
@keyframes editor-help-demo { 0%,100% { transform: translate(18px,24px); } 50% { transform: translate(190px,24px); } }
.editor-tour-overlay { position: fixed; inset: 0; z-index: 1400; pointer-events: none; background: rgba(20,16,21,.5); }.editor-tour-target { position: relative !important; z-index: 1401 !important; box-shadow: 0 0 0 5px #fff,0 0 0 8px var(--color-accent) !important; }.editor-tour-card { position: fixed; z-index: 1402; width: 310px; box-sizing: border-box; padding: 18px; border-radius: 12px; background: #fff; box-shadow: 0 18px 50px rgba(0,0,0,.3); pointer-events: auto; }.editor-tour-card__step { display: block; margin-bottom: 6px; color: var(--color-accent); font-size: 11px; font-weight: 700; }.editor-tour-card p { color: var(--color-muted); font-size: 13px; line-height: 1.5; }.editor-tour-card>div { display: flex; justify-content: flex-end; gap: 7px; }.editor-tour-card button { padding: 7px 10px; border: 1px solid var(--color-border); border-radius: 7px; background: #fff; cursor: pointer; }.editor-tour-card button.is-primary { border-color: var(--color-accent); background: var(--color-accent); color: #fff; }
@media (max-width:768px) { .editor-command-menu { left: 10px !important; right: 10px; width: auto; }.editor-help-button { right: 10px; bottom: 10px; }.editor-tour-card { left: 12px !important; right: 12px; width: auto; } }

/* ---- Mode invite : modale d'inscription (voir CLAUDE.md "Essai gratuit sans compte",
   templates/editor/editeur_essai.html, canvas.js: openSignupGateModal). N'existe pas hors
   de ce template — jamais charge sur editor.html/editeur_modele.html. Volontairement plus
   de bandeau persistant "mode essai" (retire sur retour utilisateur direct : devait
   rester invisible tant que rien ne le declenche, seule cette modale — ouverte uniquement
   au clic sur Export PDF/Imprimer/ajout d'un media — doit jamais interrompre le simple
   chargement/composition du document). ---- */

/* Meme convention exacte que .palette-picker-overlay/.palette-picker (voir plus haut
   dans ce fichier) : z-index 500, fond attenue identique, panneau blanc arrondi/ombre
   identiques — cette modale suit le meme langage visuel que tout le reste de l'app,
   plutot que d'introduire un style de modale supplementaire. Bascule display:none/flex
   geree par canvas.js:openSignupGateModal/closeSignupGateModal, jamais de classe --open
   ici (contrairement aux dropdowns de la barre d'outils) : un seul element, jamais
   recree, donc un style inline direct suffit. */
.essai-signup-modal-overlay {
    position: fixed;
    inset: 0;
    z-index: 500;
    align-items: center;
    justify-content: center;
    background: rgba(32, 30, 27, 0.45);
    padding: 20px;
}

.essai-signup-modal {
    position: relative;
    width: min(420px, 100%);
    max-height: 90vh;
    overflow-y: auto;
    padding: 32px;
    border-radius: var(--radius-md);
    background: var(--color-surface);
    box-shadow: 0 24px 60px -16px rgba(32, 30, 27, 0.5);
}

.essai-signup-modal__close {
    position: absolute;
    top: 14px;
    right: 14px;
    width: 30px;
    height: 30px;
    border: none;
    background: transparent;
    color: var(--color-muted);
    font-size: 16px;
    cursor: pointer;
}

.essai-signup-modal__close:hover {
    color: var(--color-accent);
}

.essai-signup-modal__title {
    margin: 8px 0 10px;
    font-family: var(--font-display);
    font-size: 24px;
    font-weight: 500;
}

.essai-signup-modal__message {
    margin: 0 0 20px;
    color: var(--color-muted);
    font-size: 14px;
    line-height: 1.5;
}

.essai-signup-modal__error {
    margin: 0 0 14px;
    color: var(--color-danger);
    font-size: 13px;
}

.essai-signup-modal__help {
    display: block;
    margin-top: 4px;
    color: var(--color-muted);
    font-size: 11px;
}

.essai-signup-modal__field-error {
    display: block;
    margin-top: 4px;
    color: var(--color-danger);
    font-size: 12px;
}

.essai-signup-modal__login-link {
    margin-top: 18px;
    color: var(--color-muted);
    font-size: 13px;
    text-align: center;
}

/* ---- "Publier comme modèle" (voir CLAUDE.md "Modèles partagés par les utilisateurs",
   canvas.js:openPublierModal) — construite/detruite dynamiquement (createElement/remove),
   jamais un toggle display:none/flex sur un element deja present dans le DOM :
   contrairement a .essai-signup-modal-overlay (un seul exemplaire fixe, reutilise a
   chaque ouverture), cette modale n'existe qu'en editeur reel (jamais en mode invite),
   donc sa presence/absence dans le DOM EST directement l'etat ouvert/ferme, meme
   convention que .palette-picker-overlay. ---- */
.publier-modele-overlay {
    position: fixed;
    inset: 0;
    z-index: 500;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(32, 30, 27, 0.45);
    padding: 20px;
}

.publier-modele-panel {
    position: relative;
    width: min(440px, 100%);
    max-height: 90vh;
    overflow-y: auto;
    padding: 32px;
    border-radius: var(--radius-md);
    background: var(--color-surface);
    box-shadow: 0 24px 60px -16px rgba(32, 30, 27, 0.5);
}

.publier-modele-panel__close {
    position: absolute;
    top: 14px;
    right: 14px;
    width: 30px;
    height: 30px;
    border: none;
    background: transparent;
    color: var(--color-muted);
    font-size: 16px;
    cursor: pointer;
}

.publier-modele-panel__close:hover {
    color: var(--color-accent);
}

.publier-modele-panel__title {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 8px 0 12px;
    font-family: var(--font-display);
    font-size: 22px;
    font-weight: 500;
}

.publier-modele-panel__title i {
    color: var(--color-accent);
    font-size: 18px;
}

.publier-modele-panel__intro {
    margin: 0 0 20px;
    padding: 12px 14px;
    border-radius: var(--radius-sm);
    background: var(--color-surface-alt);
    color: var(--color-muted);
    font-size: 13px;
    line-height: 1.55;
}

.publier-modele-panel__optionnel {
    color: var(--color-muted);
    font-weight: 400;
    text-transform: none;
    letter-spacing: normal;
}

.publier-modele-panel textarea {
    display: block;
    width: 100%;
    margin-top: var(--space-2);
    resize: vertical;
    font-family: inherit;
}

.publier-modele-panel__erreur {
    margin: 0 0 10px;
    color: var(--color-danger);
    font-size: 13px;
}
