/* Regles visuelles partagees entre l'editeur (editor.css) et l'export PDF
   (export_pdf.html) : les deux rendent les memes classes sur un DOM different
   (contentEditable vs statique), donc tout ce qui doit rester visuellement
   identique dans les deux vit ici, une seule fois, au lieu d'etre duplique et
   de diverger silencieusement. Ne pas y mettre de regles propres a l'un des
   deux contextes (etats interactifs, boutons d'edition, positionnement de
   bloc) — ca reste local a editor.css / export_pdf.html. */

/* Une longue suite de caracteres sans espace (ex: "nnnnnn...n" tape sans interruption)
   est traitee par le navigateur comme UN SEUL "mot" — sans ceci, rien ne l'empeche de
   deborder hors du bloc, et meme hors de l'editeur entier (confirme : bug reel trouve
   en tapant une lettre en boucle dans le bloc question). Pose une seule fois ici, sur
   les wrappers de plus haut niveau (.block cote editeur, .block-print cote export PDF)
   plutot que sur chaque classe de champ riche individuellement (.block-text,
   .block-question__enonce, .block-situation__corps, .block-qcm__choix-texte...) :
   overflow-wrap est une propriete HERITEE en CSS, donc une seule declaration ici
   redescend automatiquement dans tout champ de texte de tout type de bloc, present ou
   futur, sans avoir a enumerer chaque classe une par une (evite exactement le genre de
   derive/oubli deja documente ailleurs dans ce fichier pour d'autres proprietes CSS). */
.block,
.block-print {
    overflow-wrap: break-word;
}

/* ---- Titres (H1-H4) et citation (BLOCKQUOTE) — voir formatCommands.js:setHeadingLevel/
   toggleBlockquote, wires depuis le menu deroulant "Titre" et le bouton "Citation" de la
   barre de mise en forme riche. Postes ICI, sur les memes wrappers de plus haut niveau que
   la regle overflow-wrap juste au-dessus, pour la MEME raison : une seule declaration,
   descend automatiquement dans n'importe quel champ riche de n'importe quel type de bloc
   (texte, question, situation, qcm, notion, tableau...), a l'ecran ET dans le PDF (ce
   fichier est charge par editor.html/editeur_modele.html ET export_pdf.html) — jamais
   besoin d'enumerer chaque classe de champ, ni de dupliquer une deuxieme fois dans le
   <style> propre a export_pdf.html. Taille/graisse explicites plutot que les valeurs par
   defaut du navigateur (meme convention "toujours explicite, jamais un heritage implicite"
   deja etablie partout ailleurs dans ce fichier pour les marges de paragraphe). --- */
.block h1, .block-print h1,
.block h2, .block-print h2,
.block h3, .block-print h3,
.block h4, .block-print h4 {
    margin: 0 0 6px;
    font-family: var(--font-display);
    color: var(--color-text);
    line-height: 1.25;
}
.block h1, .block-print h1 { font-size: 30px; font-weight: 700; }
.block h2, .block-print h2 { font-size: 25px; font-weight: 700; }
.block h3, .block-print h3 { font-size: 20px; font-weight: 600; }
.block h4, .block-print h4 { font-size: 17px; font-weight: 600; }

.block blockquote, .block-print blockquote {
    margin: 6px 0;
    padding: 4px 0 4px 14px;
    border-left: 3px solid var(--color-accent);
    color: var(--color-muted);
    font-style: italic;
}

/* Indice/Exposant — la taille reduite par defaut du navigateur (mot-cle "smaller") varie
   legerement d'un moteur a l'autre ; une valeur explicite en em (relative a la taille du
   texte courant, donc toujours coherente qu'on soit dans un titre agrandi ou du texte
   normal) reste identique partout. vertical-align:sub/super est deja le comportement natif
   de ces balises, redeclare ici uniquement pour rester explicite (meme convention que
   ci-dessus), pas parce qu'un correctif etait necessaire. */
.block sub, .block-print sub { font-size: 0.7em; vertical-align: sub; }
.block sup, .block-print sup { font-size: 0.7em; vertical-align: super; }

/* ---- Bloc "en-tete de seance" (titre) ---- */
.activite_header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0;
}

.activity-title {
    display: flex;
    align-items: center;
    flex-shrink: 0;
    width: 200px;
    font-family: var(--font-body);
    font-size: 24px;
    font-weight: bold;
    color: #fff;
    background-color: var(--color-situation-accent);
    border-radius: 1% 99% 100% 0% / 56% 0% 100% 44%;
    padding: 10px 52px 10px 16px;
    /* Sans ce box-sizing explicite, ce `width: 200px` fixe ne donne le meme rendu dans
       l'editeur et le PDF que par accident : base.css pose un reset global
       box-sizing:border-box (le padding ci-dessus, 52px+16px=68px, reste alors DANS les
       200px), mais export_pdf.html ne charge jamais base.css (voir la note en tete de ce
       fichier et le reste de la section "CSS duplication" dans CLAUDE.md) — sans lui, le
       navigateur retombe sur content-box par defaut, et le padding s'AJOUTE au 200px
       (268px de large au final), un bandeau visiblement plus large sur le PDF que dans
       l'editeur. Meme classe de bug que les autres cas deja corriges pour cette raison
       (.block-qcm-print__marker, .block-reponse-print div) — celui-ci les avait
       echappes jusqu'ici. */
    box-sizing: border-box;
}

.activity-title__icon {
    margin-right: 8px;
}

.activity-title__label {
    white-space: nowrap;
}

.activity-title__number {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 42px;
    height: 42px;
    margin-left: 15px;
    border-radius: 50%;
    background-color: var(--color-activity-badge);
    color: var(--color-situation-accent);
    font-size: 18px;
    font-weight: bold;
    user-select: none;
    /* Meme raison que .activity-title ci-dessus : sans lui, la palette "encre" (seule a
       poser une bordure ici, voir plus bas) rendrait cette pastille visiblement plus
       grande dans le PDF (42px + 2*1.5px de bordure en content-box, faute du reset
       global de base.css) que dans l'editeur (42px pile, en border-box). */
    box-sizing: border-box;
}

/* Palette "encre" (voir tokens.css) : --color-activity-badge vaut `transparent`, mais
   --color-situation-accent lui NE l'est jamais (meme dans cette palette) — il reste
   sciemment un hex plein (#1A1A1A) puisqu'il sert AUSSI de couleur de TEXTE ailleurs
   (titre de section, texte de la pastille). Ca a un piege direct sur .activity-title :
   son propre fond utilise ce meme --color-situation-accent (pas
   --color-activity-badge) — sans l'override `background: transparent` explicite
   ci-dessous, le bandeau restait un ruban plein NOIR avec un texte AUSSI noir dessus
   (color: var(--color-situation-accent) juste en dessous), donc invisible — trouve en
   verification visuelle, l'assertion "couleur de texte sombre" seule ne l'avait pas
   detecte puisque techniquement vraie.
   Demande explicite ensuite : .activity-title ("Activité", le ruban) reste SANS aucun
   contour — juste l'icone + le texte flottant, rien autour — contrairement a
   .activity-title__number (la pastille numerotee) qui, elle, garde son contour : sans
   texte propre autour d'elle pour se signaler, un simple chiffre flottant sans aucune
   forme serait trop peu defini. Vit ici plutot que dans editor.css : ces deux elements
   sont deja partages entre editeur et export PDF via ce fichier, donc ces regles
   s'appliquent aux deux sans rien dupliquer. */
body.palette-encre .activity-title {
    background: transparent;
    color: var(--color-situation-accent);
}

body.palette-encre .activity-title__number {
    border: 1.5px solid var(--color-situation-accent);
}

/* Apparence PAR DEFAUT de la pastille numerotee du bloc question — deplacee ici depuis
   editor.css/export_pdf.html (ou elle etait dupliquee independamment dans les deux, voir
   .activity-title/.activity-title__number juste au-dessus pour le meme precedent deja
   applique) suite a un vrai bug trouve puis corrige : editor.css avait fini par definir
   DEUX regles pour .question-bullet — une correctement scopee a
   body.custom-question-style (jamais reellement active, voir l'historique dans
   CLAUDE.md), et une SANS AUCUNE condition avec !important sur chaque propriete
   (necessaire pour que la personnalisation pedagogique globale puisse s'appliquer) — la
   seconde ecrasait silencieusement TOUTES les formes de gabarit (boudina/carre/suspendu/
   hexa/brut/organique, voir plus bas dans editor.css) des l'instant ou aucune
   personnalisation n'etait active, meme si le professeur avait choisi un gabarit
   "moderne" : la pastille restait un cercle plat dans l'editeur en direct, quelle que
   soit la forme reellement documentee pour ce gabarit — confirme par mesure precise
   (getComputedStyle) sur gabarit=hexa : border-radius:50%/clip-path:none dans l'editeur,
   contre border-radius:0/clip-path:octogone dans le PDF exporte (qui, lui, avait
   toujours correctement scope sa propre regle "personnalisee" a
   body.custom-question-style — c'est cette version-la, deja correcte, qui sert de
   reference ici). En centralisant l'apparence par defaut ici (jamais conditionnee a
   aucune classe), la regle de personnalisation globale (editor.css, desormais
   correctement scopee a body.custom-question-style, meme registre que
   body.custom-title-style/-number-style ci-dessus) peut a nouveau la surclasser SANS
   avoir a etre inconditionnelle elle-meme — supprime a la fois le bug (formes de gabarit
   a nouveau visibles dans l'editeur) et la duplication (un seul point de definition pour
   la pastille "nue", partage editeur+PDF, au lieu de deux copies risquant de diverger). */
.question-bullet {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    background-color: var(--color-question-badge-bg);
    border-radius: 50%;
    font-weight: normal;
    color: var(--color-question-badge-fg);
    position: relative;
    font-size: 14px;
    /* Vrai bug trouve EN VERIFIANT le correctif de centrage plus bas : sans cette
       ligne, ce chiffre heritait silencieusement de "Playfair Display" — jamais un choix
       delibere pour cet element. Cause : base.css (charge site-entier via base.html, y
       compris sur la page editeur) definit son PROPRE composant ".question-bullet" sans
       aucun rapport (une pastille numerotee generique pour d'autres pages du site), qui
       fixe explicitement font-family: var(--font-display) — comme aucune regle de CE
       fichier ni de editor.css ne definissait font-family pour ce meme nom de classe ici,
       cette declaration etrangere s'appliquait sans concurrence. `inherit` restaure le
       comportement normal attendu (suivre --font-body/la police du document, comme le
       reste du texte du bloc), sans introduire de police codee en dur ici. */
    font-family: inherit;
    /* MEME classe de fuite que font-family juste au-dessus, une propriete differente :
       base.css:.question-bullet (le composant SANS RAPPORT de la vitrine du site) pose
       margin-right:var(--space-2) (8px) — jamais reprise ici ni dans editor.css, donc
       hereditee sans concurrence. Consequence directe, mesuree via Playwright : le
       chiffre se retrouve espace de son enonce par gap:10px (.block-question__principal,
       editor.css) PLUS ces 8px de marge en trop, 18px au total dans l'editeur — contre
       exactement 10px dans le PDF, qui lui ne charge jamais base.css (voir la note en
       tete de ce fichier) et n'a donc jamais herite cette marge. Signale directement :
       "le bandeau numero de question est trop colle au texte" [sur le PDF] — c'est en
       realite l'EDITEUR qui etait, par accident, trop espace ; retirer cette fuite plutot
       que d'ajouter une marge compensatoire cote PDF fait converger les deux vers la
       seule valeur reellement voulue (gap:10px, deja identique des deux cotes). */
    margin-right: 0;
    /* line-height: normal (pas 1) — retour direct : "le numero est trop bas", le meme
       ecart deja diagnostique et corrige une fois pour le gabarit "suspendu" (voir plus
       bas dans ce fichier, .activity-title__number) : line-height:1 force la hauteur de
       ligne a etre EXACTEMENT egale au font-size, ce qui coupe la boite de ligne de
       facon asymetrique par rapport a l'ascendante/descendante reelle de la police (un
       chiffre n'a pas de descendante, donc son encre visuelle n'est jamais centree dans
       une boite de ligne calquee sur ascendante+descendante) — plus visible encore
       depuis que la taille du numero est devenue personnalisable (l'ecart grandit avec
       le font-size). line-height:normal laisse le navigateur utiliser la metrique
       propre de la police plutot qu'une valeur forcee. text-align:center ajoute en
       renfort (redondant avec justify-content:center du flex parent, mais sans risque).
       Applique ici, a la regle PAR DEFAUT (jamais seulement a un gabarit precis) : tous
       les gabarits/tailles personnalisees en heritent desormais, pas seulement
       "suspendu", qui avait deja ce correctif en propre. */
    line-height: normal;
    text-align: center;
    border: none;
    user-select: none;
}

/* top: 50% + translateY(-50%) centre la pointe sur la pastille quelle que soit la taille
   de celle-ci (pas de decalage code en dur a recalculer si la pastille change de
   taille). */
.question-bullet::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 95%;
    transform: translateY(-50%);
    width: 0;
    height: 0;
    border-left: 7px solid var(--color-question-badge-bg);
    border-top: 3px solid transparent;
    border-bottom: 3px solid transparent;
}

/* ---- Gabarit "Boudina" (voir Activite.Gabarit, apps/courses/models.py) : bandeau
   plein remplace par une accolade ouvrante stylisee, inspiree d'une page de manuel
   scolaire fournie par l'utilisateur (static/images/template3.jpeg) — l'icone
   generique disparait (le manuel de reference a sa propre puce decorative, pas une
   icone de liste de taches), le numero redevient un simple chiffre en gras dans le
   texte plutot qu'une pastille ronde. Vit ici (pas dans editor.css) pour la meme
   raison que le bloc palette-encre juste au-dessus : .activity-title/__number/__icon
   sont deja partages entre editeur et export PDF via ce fichier. */
body.gabarit-boudina .activity-title {
    width: auto;
    background: transparent;
    color: var(--color-situation-accent);
    padding: 0 10px 0 0;
    position: relative;
}

body.gabarit-boudina .activity-title::before {
    content: '(';
    font-family: Georgia, 'Times New Roman', serif;
    font-size: 56px;
    font-weight: 400;
    line-height: 1;
    margin-right: 6px;
    /* --color-situation-accent (pas le --color-accent global, fixe/or) : un gabarit ne
       doit jamais introduire une couleur qui lui soit propre, il reste purement une
       question de forme — la teinte suit toujours la palette active de l'activite,
       exactement comme .activity-title__number juste en dessous. */
    color: var(--color-situation-accent);
}

body.gabarit-boudina .activity-title__icon {
    display: none;
}

body.gabarit-boudina .activity-title__number {
    width: auto;
    height: auto;
    margin-left: 6px;
    background: transparent;
    color: var(--color-situation-accent);
    font-size: 24px;
}

/* ---- Gabarit "Carre" (voir Activite.Gabarit) : meme esprit que le ruban plein de
   CLASSIQUE (bandeau + pastille pleine, contrairement a BOUDINA qui retire tout
   remplissage) mais avec des angles nets plutot que la forme organique en goutte —
   inspire de static/images/template2.jpeg, dont le bandeau de titre et les pastilles
   de question sont des rectangles/carres a coins legerement arrondis, jamais des
   courbes. Un seul rayon de bordure change (`border-radius`), rien d'autre dans la
   forme du ruban lui-meme — c'est deliberement le changement le plus minimal des
   trois gabarits, pour bien montrer que "changer de forme" peut etre aussi discret
   qu'un simple angle. */
body.gabarit-carre .activity-title {
    border-radius: 6px;
}

body.gabarit-carre .activity-title__number {
    border-radius: 8px;
}

/* ---- Gabarit "Suspendu" (voir Activite.Gabarit) : inspire de
   static/images/template4.jpeg, dont le numero d'activite deborde volontairement de
   son bandeau, a cheval sur la limite entre l'en-tete et le corps de page — un
   deplacement vertical (translateY), pas un agrandissement.
   IMPORTANT, principe general a tous les gabarits (remarque explicite de l'utilisateur
   apres ce brouillon) : un gabarit ne doit JAMAIS changer un `font-size` (ni aucune
   autre propriete affectant le nombre de lignes qu'un texte occupe), seulement des
   FORMES (bordures, tailles de boites decoratives, positions). Chaque bloc a une
   largeur/hauteur FIXE (data.width/height, choisie a l'ajout du bloc) ; si la taille du
   texte differait d'un gabarit a l'autre, un meme document reflowerait differemment —
   deborderait ou laisserait un vide — a chaque fois qu'on change SEULEMENT l'habillage
   visuel, ce qui n'a pas de sens. Le premier jet de ce gabarit violait ce principe a 3
   endroits (bandeau de titre, pastille de titre, etiquette Problematique) : corrige
   partout, voir chaque regle ci-dessous.
   Deuxieme correctif, egalement explicite : la pastille avait aussi ete agrandie
   (42px -> 50px) en plus d'etre deplacee — demande de l'utilisateur de revenir aux
   dimensions de CLASSIQUE (42px, width/height simplement retires de cette regle, la
   valeur heritee de la regle de base s'applique de nouveau) et de ne garder QUE le
   deplacement vertical comme trait distinctif de ce gabarit pour cet element. */
body.gabarit-suspendu .activity-title__number {
    /* Volontairement modere (pas 35%) : le bloc titre garde sa hauteur fixe
       (data.height, definie a l'ajout du bloc) quelle que soit la position qu'on donne
       a cette pastille en CSS — un deplacement trop genereux la fait deborder dans le
       bloc suivant (empile juste en dessous par addBlock), pas juste dans la marge
       visuelle du ruban. 18% reste un chevauchement clairement visible sans manger le
       contenu du bloc suivant dans les cas courants. */
    transform: translateY(18%);
}

/* Bandeau lui-meme plus impose (plus de padding, une boite decorative, pas du texte),
   pour la meme raison que la pastille juste au-dessus : dans la reference, ce bandeau
   domine nettement plus la page qu'un simple ruban fin. Augmentation moderee
   (10px -> 14px) pour la meme raison que ci-dessus : la hauteur du bloc reste fixe, un
   padding trop genereux ferait deborder le ruban hors de sa propre boite. Pas de
   font-size ici non plus (voir le commentaire principal ci-dessus). */
body.gabarit-suspendu .activity-title {
    padding: 14px 56px 14px 18px;
}

/* ---- Gabarit "Hexa" (voir Activite.Gabarit) : pas issu d'une image de reference,
   demande libre "moderne, futuriste, plus style" — langage visuel construit a partir
   de decoupes angulaires (`clip-path`), coherent sur tout le bloc plutot qu'un seul
   trait isole. Le ruban perd son coin arrondi/organique pour une pointe nette (biseau
   a droite, comme une etiquette ou un chevron "tech"), et sa pastille de numero passe
   d'un cercle a un HEXAGONE — le motif recurrent qui donne son nom au gabarit. Pas de
   font-size touche nulle part (voir le principe general documente sur SUSPENDU
   ci-dessus), width/height de la pastille non plus : seule sa DECOUPE change. */
body.gabarit-hexa .activity-title {
    border-radius: 0;
    /* Coin coupe en diagonale en bas a GAUCHE plutot qu'a droite : l'icone (haut-gauche)
       et la pastille de numero (droite, dans les 52px de padding qui lui sont reserves)
       occupent les deux zones les plus a risque de chevauchement visuel avec une
       decoupe — le bas-gauche est la seule zone du ruban qui ne porte jamais de
       contenu, donc la seule ou une decoupe est garantie de ne jamais rogner un
       caractere ou la pastille. */
    clip-path: polygon(0 0, 100% 0, 100% 100%, 22px 100%, 0 calc(100% - 22px));
}

body.gabarit-hexa .activity-title__number {
    border-radius: 0;
    clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
}

/* ---- Gabarit "Brut" (voir Activite.Gabarit) : neo-brutalisme adouci — une ombre
   portee "dure" (offset net, sans flou/degrade) mais SANS bordure. Premier jet avec
   une bordure epaisse (2-3px, --color-text) sur chaque element : rejete explicitement
   par l'utilisateur ("enleve le contour noir... on doit rester elegant, faut pas que
   ca charge trop") — le contour plein cumule a l'ombre rendait l'ensemble trop lourd/
   charge. La bordure est donc entierement retiree ; seule l'ombre (deja adoucie a
   rgba(32,30,27,0.18) suite a un premier retour "trop noir") porte desormais l'effet
   "carte posee sur la page" de ce gabarit, en restant discrete. Rupture volontaire
   avec les 5 gabarits precedents (organique/pastel pour BOUDINA/SUSPENDU, anguleux
   mais fin pour HEXA) : ici c'est l'ombre seule, jamais un trait, qui detache le bloc
   de la page. Pas de font-size touche nulle part (principe general, voir SUSPENDU
   dans editor.css). */
body.gabarit-brut .activity-title {
    border-radius: 0;
    box-shadow: 5px 5px 0 rgba(32, 30, 27, 0.18);
}

body.gabarit-brut .activity-title__number {
    border-radius: 0;
    box-shadow: 3px 3px 0 rgba(32, 30, 27, 0.18);
}

/* ---- Gabarit "Organique" (voir Activite.Gabarit) : demande explicite en reaction aux
   3 gabarits precedents (CARRE/HEXA/BRUT), tous anguleux — "celui-ci doit etre pas
   droit, courbure, circulaire... une chose qui sort du cadre". Le bandeau devient une
   capsule entierement arrondie (`border-radius: 999px`) — une courbure UNIQUE et
   continue, differente du blob irregulier a 8 valeurs de CLASSIQUE (juste au-dessus)
   et evidemment des angles nets de CARRE/HEXA/BRUT. La pastille de numero garde son
   cercle deja herite de CLASSIQUE (rien a changer la, il est deja circulaire) mais
   gagne un HALO — un anneau (`box-shadow: 0 0 0 Npx ...`, jamais un blur/glow flou) qui
   deborde volontairement de sa propre boite de layout : l'incarnation la plus litterale
   possible de "sortir du cadre" sans toucher aux dimensions reelles du bloc, puisqu'un
   box-shadow n'est jamais compte dans le flux/la mise en page (donc aucun risque de
   reflow, meme raisonnement que le principe general anti-font-size). Couleur du halo :
   --color-situation-box, le role "doux" deja pilote par la palette active (meme
   reutilisation que SUSPENDU/BRUT plus haut) — jamais une couleur fixee en dur, l'anneau
   reste donc coherent quelle que soit la palette choisie. */
body.gabarit-organique .activity-title {
    border-radius: 999px;
}

body.gabarit-organique .activity-title__number {
    box-shadow: 0 0 0 5px var(--color-situation-box);
}

.activite_header_title {
    outline: none;
    flex: 1 1 0;
    min-width: 150px;
    font-size: 1.5rem;
    line-height: 1.75rem;
    color: var(--color-situation-accent);
    font-family: var(--font-body);
    margin: 0;
}

.activite_header_title p,
.activite_header_title div {
    margin: 0;
}

/* ---- Bloc numero de page (voir CLAUDE.md "En-tete et pied de page") ----
   Meme registre visuel que l'ancienne pastille automatique qu'il remplace
   (.page-number-print/.page-number-print-only) — une petite pilule, jamais l'accent
   dore (deja tres present ailleurs sur la page, une pagination doit rester discrete).
   Vit ici (pas dans editor.css) pour la meme raison que .activity-title juste au-dessus :
   ce bloc doit rendre IDENTIQUEMENT dans l'editeur ET dans l'export PDF, et
   export_pdf.html ne charge jamais editor.css. */
.block-numero-page {
    position: relative;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 2px;
    padding: 4px 14px;
    border-radius: var(--radius-pill);
    background: var(--color-surface);
    border: 1px solid var(--color-accent-soft);
    box-shadow: 0 2px 6px -2px rgba(32, 30, 27, 0.25);
    font-family: var(--font-display);
    letter-spacing: 0.02em;
    user-select: none;
    box-sizing: border-box;
}

.block-numero-page__current {
    font-size: 14px;
    font-weight: 600;
    color: var(--color-text);
}

.block-numero-page__sep,
.block-numero-page__total {
    font-size: 12px;
    color: var(--color-text);
}

.block-numero-page__sep {
    margin: 0 1px;
}

/* ---- Bloc carte mentale (mindmap) ---- */
/* Structure partagee entre l'editeur (mindmap.js ajoute par-dessus la selection, le
   glisser, l'edition et sa barre d'outils flottante — voir editor.css) et l'export PDF
   (rendu statique via mindmapRender.js, sans aucun gestionnaire d'evenement). */
.block-mindmap {
    position: relative;
    height: 100%;
    overflow: visible;
}

.mindmap-edges-svg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    overflow: visible;
    pointer-events: none;
}

.mindmap-edge {
    fill: none;
    stroke: var(--color-border);
    stroke-width: 2px;
}

.mindmap-nodes-layer {
    position: absolute;
    inset: 0;
}

.mindmap-node {
    position: absolute;
    transform: translate(-50%, -50%);
    width: max-content;
    max-width: 220px;
    min-width: 48px;
    padding: 8px 16px;
    background: var(--color-surface);
    border: 2px solid var(--color-accent);
    color: var(--color-primary);
    font-family: var(--font-body);
    font-size: 13px;
    line-height: 1.35;
    text-align: center;
    overflow-wrap: break-word;
}

.mindmap-node--rect {
    border-radius: var(--radius-md);
}

.mindmap-node--ellipse {
    border-radius: 50%;
    padding: 18px 20px;
}

.mindmap-node--circle {
    aspect-ratio: 1 / 1;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 14px;
}

/* clip-path ne fonctionne pas avec la propriete border (elle ne dessine que le rectangle
   d'origine puis le coupe, ne laissant visible que de minuscules fragments la ou ce
   rectangle croise les sommets du polygone) — technique classique a la place : un carre
   tourne de 45 degres, avec le texte contre-tourne pour rester lisible. */
.mindmap-node--losange {
    aspect-ratio: 1 / 1;
    transform: translate(-50%, -50%) rotate(45deg);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 14px;
}

.mindmap-node--losange .mindmap-node__text {
    transform: rotate(-45deg);
    max-width: 100px;
}

/* Le fond est pilote par node.bg (voir mindmapRender.js, style inline toujours
   prioritaire sur cette regle) — seul le gras distingue encore la racine par defaut. */
.mindmap-node--root {
    font-weight: 600;
}

.mindmap-node__text {
    outline: none;
}

/* ---- Bloc arbre de probabilite ---- */
/* Structure partagee entre l'editeur (arbreproba.js ajoute par-dessus la selection, le
   glisser, l'edition et sa barre d'outils flottante — voir editor.css) et l'export PDF
   (rendu statique via arbreprobaRender.js, sans aucun gestionnaire d'evenement).
   Contrairement au mindmap, les noeuds sont un point de taille fixe (pas une carte dont
   la taille varie selon le texte) : les connecteurs relient centre a centre sans jamais
   avoir besoin de mesurer le DOM. */
.block-arbreproba {
    position: relative;
    height: 100%;
    overflow: visible;
}

/* Occupe exactement la meme boite que .block-arbreproba lui-meme — seul son propre
   `transform` (pose par arbreprobaRender.js:fitArbreProbaToContainer) determine
   l'echelle visuelle reelle du dessin. Les 4 couches qu'il contient (svg/noeuds/
   etiquettes/resultats) gardent EXACTEMENT le meme CSS inset:0/100% qu'avant
   l'introduction de ce wrapper, desormais relatif a lui plutot qu'a .block-arbreproba
   directement. */
.arbreproba-scale-wrap {
    position: absolute;
    inset: 0;
}

.arbreproba-edges-svg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    overflow: visible;
    pointer-events: none;
}

.arbreproba-edge {
    fill: none;
    /* Noir (--color-text), pas --color-border (un ton tres clair, pense pour des
       separateurs a l'ecran) : une ligne trop pale disparait facilement a l'impression
       sur papier, la ou le contraste ecran la rendait encore visible. */
    stroke: var(--color-text);
    stroke-width: 2px;
}

.arbreproba-nodes-layer {
    position: absolute;
    inset: 0;
}

/* pointer-events:none sur la couche elle-meme (comme .arbreproba-edges-svg ci-dessus) :
   couvre toute la boite du bloc (inset:0) mais ses zones vides ne doivent jamais
   intercepter les clics destines aux noeuds en dessous, dont elle est peinte au-dessus
   dans l'ordre du DOM. Reactive explicitement sur chaque etiquette elle-meme, seul
   endroit ou un clic doit reellement etre capte. */
.arbreproba-labels-layer {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

/* Pas de transform: translate(-50%,-50%) ni de flex sur ce conteneur : (x,y) doit rester
   le vrai sommet ou les connecteurs se rejoignent EXACTEMENT, quel que soit l'etat du
   point (masque ou visible). Un flex row centre sur son contenu total deplacait le texte
   des que le point apparaissait/disparaissait en cours d'edition — pire, avec le point
   masque (l'etat normal hors edition), le texte se retrouvait centre PILE sur (x,y),
   exactement la ou les traits aboutissent, les faisant passer en plein milieu de la
   lettre. Point et texte sont maintenant positionnes independamment l'un de l'autre,
   chacun par rapport a ce meme (x,y) fixe — voir les deux regles ci-dessous. */
.arbreproba-node {
    position: absolute;
}

/* Centre exactement sur (x,y) via des decalages negatifs egaux a la moitie de sa taille
   (equivalent a translate(-50%,-50%) mais sans toucher au conteneur parent, qui doit
   rester un repere fixe pour le texte aussi). Masque par defaut — repere purement utile
   PENDANT l'edition (point d'ancrage a glisser), qui alourdissait visuellement l'arbre
   en permanence sans y ajouter de sens une fois la carte terminee. Revele uniquement par
   .block--editing (voir editor.css) — jamais visible en dehors de l'edition, y compris a
   l'export PDF/impression, puisque ce fichier ne definit que l'etat par defaut
   (blocks-shared.css n'a aucune notion de .block--editing, un concept propre a
   l'editeur interactif). */
.arbreproba-node__dot {
    display: none;
    position: absolute;
    left: -5px;
    top: -5px;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--color-accent);
}

/* left/top/transform ici sont un REPLI pour la racine uniquement (aucune branche
   entrante — voir arbreprobaRender.js : "Racine : aucune branche entrante, garde le
   decalage fixe par defaut"), a droite du point, jamais centre. Pour tout autre noeud,
   la regle plus specifique juste en dessous prend le dessus — voir son propre
   commentaire pour l'historique de ce choix de positionnement. Noir (--color-text) et
   16px, comme le reste des champs LaTeX de l'appli (question/qcm/situation...). */
.arbreproba-node__text {
    position: absolute;
    left: 24px;
    top: 0;
    transform: translateY(-50%);
    outline: none;
    font-family: var(--font-body);
    font-size: 16px;
    color: var(--color-text);
    white-space: nowrap;
}

.arbreproba-node--root .arbreproba-node__text {
    font-weight: 600;
}

/* Cinquieme passe — correction directe de la version precedente (v4, "continuation de
   la branche entrante") : "la position des lettres est masquee par les branches...
   il faut placer les lettres au dessus des noeuds... comme si on trace un vertical
   passant le point du noeud, la lettre est sur la verticale, avec une marge-top
   suffisante pour ne pas toucher le noeud." Continuer la branche ENTRANTE au-dela du
   noeud (v4) evitait bien cette branche-la et l'etiquette de probabilite qui l'accompagne
   — mais placait la lettre du cote ou les branches SORTANTES du meme noeud s'eventent
   aussi, un chevauchement reel non anticipe par ce fix precedent. Un decalage fixe,
   purement vertical, au-dessus du noeud (jamais lie a l'angle d'aucune branche, entrante
   ou sortante) ne peut plus etre traverse par aucune d'elles. `translate(-50%, -100%)` :
   centre horizontalement sur node.x ("sur la verticale [passant par] le noeud", au sens
   ou aucun decalage horizontal n'est applique) et ancre depuis le BAS du texte (jamais
   son centre) — le texte grandit donc uniquement vers le haut, garantissant qu'aucune
   partie de la lettre ne puisse jamais descendre plus bas que ce point d'ancrage, quel
   que soit le nombre de caracteres tapes. `top: -4px` est ce point d'ancrage lui-meme
   ("marge-top suffisante") — redescendu depuis 20px sur retour direct ("la marge est
   trop grande, essaie 4px") : la lettre se lit desormais nettement plus proche du
   noeud, tout en restant un ecart non-nul et toujours garanti par construction (jamais
   un simple "0" qui risquerait de toucher le point). */
.arbreproba-node:not(.arbreproba-node--root) .arbreproba-node__text {
    left: 0;
    top: -4px;
    transform: translate(-50%, -100%);
}

/* Positionnee au milieu de sa branche et tournee pour suivre son angle reel (voir
   arbreprobaRender.js:edgeLabelTransform) — c'est ce qui donne le rendu "manuel
   scolaire" authentique plutot qu'un texte horizontal generique. pointer-events:auto
   necessaire car sa couche parente (.arbreproba-labels-layer) le desactive globalement
   (voir plus haut) pour ne jamais intercepter les clics destines aux noeuds en dessous. */
.arbreproba-edge-label {
    position: absolute;
    pointer-events: auto;
}

.arbreproba-edge-label__text {
    outline: none;
    font-family: var(--font-body);
    /* Noir et 16px, meme raison que .arbreproba-node__text ci-dessus — --color-hint
       (un brun attenue) et 13px rendaient la probabilite a la fois trop discrete a
       l'impression et les formules LaTeX compilees dedans minuscules. */
    font-size: 16px;
    color: var(--color-text);
    white-space: nowrap;
    /* Sans ca, une probabilite encore vide (le cas par defaut de chaque branche) fait
       s'effondrer ce div et son parent .arbreproba-edge-label a une largeur quasi nulle
       — combine a la rotation (transform: translate(-50%,-50%) rotate(...)), la cible
       cliquable reelle devenait une ligne d'a peine 1px de large, quasi impossible a
       toucher pour commencer a taper. min-width garantit une zone cliquable exploitable
       meme sans aucun texte, sans rien changer une fois une probabilite tapee. */
    min-width: 24px;
    min-height: 20px;
    display: inline-block;
}

/* Colonne des resultats/gains : uniquement affichee pour les feuilles actuelles (voir
   arbreprobaRender.js:isLeaf), toutes partageant le meme `left` (resultColumnX, calcule
   au rendu) pour l'alignement horizontal parfait demande. `top` suit le `y` du noeud
   (une feuille par ligne), centre verticalement via translateY. */
.arbreproba-node__result {
    position: absolute;
    transform: translateY(-50%);
}

.arbreproba-node__result-text {
    outline: none;
    font-family: var(--font-body);
    font-size: 16px;
    color: var(--color-text);
    white-space: nowrap;
    /* Meme anti-cible-quasi-nulle que .arbreproba-edge-label__text ci-dessus : un
       resultat encore vide (cas par defaut de toute nouvelle feuille) ne doit jamais
       s'effondrer a une largeur/hauteur nulle, sinon impossible a cliquer pour
       commencer a taper. */
    min-width: 24px;
    min-height: 20px;
    display: inline-block;
}

/* ---- Bloc fleche (sens de variation : montante/decroissante/constante) ---- */
/* Volontairement minimal : une seule fleche remplissant toute la boite du bloc, aucun
   texte propre — voir flecheRender.js pour le pourquoi (redondant avec table.js pour la
   grille, avec text.js pour une valeur libre). */
.block-fleche {
    position: relative;
    height: 100%;
    overflow: visible;
}

.fleche-svg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    overflow: visible;
}

/* ---- Blocs decoratifs, sans texte (ligne/forme — voir blocks/ligne.js/forme.js,
   ligneRender.js/formeRender.js). Dimensionnement de base ici (pas editor.css) pour la
   MEME raison que .block-fleche ci-dessus : export_pdf.html charge blocks-shared.css mais
   pas editor.css, et son propre script de rendu (renderLignes()/renderFormes()) a besoin
   d'une taille reelle (getBoundingClientRect()) AVANT de dessiner quoi que ce soit — sans
   cette regle, le conteneur, encore vide au moment ou le script tourne, mesurait une
   hauteur de 0 (confirme : un `<svg viewBox="0 0 400 0">` et un clip-path pose sur une
   boite de hauteur 0, tous deux invisibles au rendu final). ---- */
.block-ligne {
    position: relative;
    width: 100%;
    height: 100%;
}

.block-ligne__svg {
    display: block;
    width: 100%;
    height: 100%;
    overflow: visible;
}

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

.block-forme__shape {
    width: 100%;
    height: 100%;
}

/* Mode "arc" uniquement (voir blocks/forme.js/formeRender.js:buildArcPathD) — meme raison
   de vivre ici (pas editor.css) que .block-forme__shape ci-dessus : export_pdf.html a
   besoin d'une taille reelle des le premier rendu. overflow:visible n'est pas necessaire
   ici (contrairement a .block-ligne__svg) : le chemin de l'arc reste toujours contenu
   dans son viewBox 0 0 100 100 par construction. */
.block-forme__arc-svg {
    display: block;
    width: 100%;
    height: 100%;
}

/* Mode "ruban" (fil libre a main levee, voir blocks/forme.js/formeRender.js:
   buildFilPathD) — meme raison de vivre ici que .block-forme__arc-svg juste au-dessus.
   cursor:crosshair uniquement pour signaler "on dessine ici" pendant l'edition — sans
   effet hors .block--editing (le calque move-layer, au-dessus, impose deja son propre
   curseur a ce moment-la). */
.block-forme__fil-svg {
    display: block;
    width: 100%;
    height: 100%;
}
.block--editing .block-forme__fil-svg {
    cursor: crosshair;
}

/* Bloc "Courbe" (graphique mathematique — voir blocks/courbeRender.js). Base de
   dimensionnement + TOUTE l'apparence visuelle (axes/grille/graduations/asymptotes/
   points) vivent ici, pas dans editor.css : export_pdf.html charge ce fichier mais pas
   editor.css, et son propre script de rendu (renderCourbe, identique a l'editeur) a
   besoin d'une taille reelle ET des memes couleurs des le premier rendu — meme raison
   deja etablie pour .block-ligne/.block-forme ci-dessus. Couleurs volontairement fixes
   (var(--color-text)/(--color-border), jamais les tons palette-dependants comme
   --color-situation-accent) : un repere mathematique doit rester lisible/neutre quelle
   que soit la palette de l'activite, meme principe deja applique aux textes/traits
   d'arbreproba. */
.block-courbe {
    position: relative;
    width: 100%;
    height: 100%;
}

.block-courbe__svg {
    display: block;
    width: 100%;
    height: 100%;
}

.block-courbe__grille line {
    stroke: var(--color-border);
    stroke-width: 1;
}

.block-courbe__axe {
    stroke: var(--color-text);
    stroke-width: 1.5;
}

.block-courbe__arrowhead {
    fill: var(--color-text);
}

.block-courbe__asymptote {
    stroke: var(--color-text);
    stroke-opacity: 0.45;
    stroke-width: 1.5;
    stroke-dasharray: 6 4;
}

.block-courbe__ticks line {
    stroke: var(--color-text);
    stroke-width: 1;
}

.block-courbe__point {
    fill: var(--color-text);
}

/* Segment (trait simple, sans fleche) — meme couleur/epaisseur par defaut que les autres
   traces de construction (asymptotes/axes), remplacable via un attribut stroke pose en
   ligne par courbeRender.js quand le segment a sa propre couleur personnalisee. */
.block-courbe__segment {
    stroke: var(--color-text);
    stroke-width: 2;
}

/* Vecteur — meme trait que .block-courbe__segment mais avec sa propre fleche (marker-end,
   voir courbeRender.js — reutilise le meme <marker> deja defini pour les axes). Couleur
   par defaut legerement accentuee (--color-accent plutot que --color-text) pour rester
   visuellement distinct d'un simple segment au premier coup d'oeil, meme sans etiquette. */
.block-courbe__vecteur {
    stroke: var(--color-accent);
    stroke-width: 2;
}

/* Arc d'angle oriente (Chantier "angle orienté, sens direct") — meme fleche que les
   vecteurs (marker-end pose en JS), trait fin pour rester lisible malgre le rayon fixe
   modeste (28px) choisi pour ne pas envahir le graphique. */
.block-courbe__angle-arc {
    stroke: var(--color-accent);
    stroke-width: 1.5;
}

/* Etiquettes KaTeX des points nommes (A(2;3)...) — calque HTML separe superpose au SVG
   (le SVG n'a pas de moyen simple de rendre du KaTeX directement), meme convention que
   .arbreproba-labels-layer/.mindmap-node__text : pointer-events:none par defaut sur le
   calque entier (ne doit jamais intercepter un clic destine au bloc/au SVG en dessous),
   chaque etiquette elle-meme n'a besoin d'aucune interaction. */
.block-courbe__labels {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

.block-courbe__label {
    position: absolute;
    font-size: 13px;
    color: var(--color-text);
    white-space: nowrap;
}

/* Graduation d'axe en fraction de pi (graduationsPi) — meme calque KaTeX que
   .block-courbe__label, mais centree HORIZONTALEMENT sur son propre point de graduation
   (translateX(-50%), jamais le decalage "en haut a droite" pense pour un point nomme) et a
   la meme taille que les graduations SVG voisines (.block-courbe__ticks text, 11px) pour
   rester visuellement coherente qu'une graduation tombe sur un multiple de pi ou non. */
.block-courbe__tick-label {
    position: absolute;
    transform: translateX(-50%);
    font-size: 11px;
    color: var(--color-text);
    white-space: nowrap;
}

/* Graduation de l'axe Y (voir renderTickLabel, courbeRender.js) — alignee a DROITE et
   centree VERTICALEMENT sur son point, contrairement a .block-courbe__tick-label seule
   (centree horizontalement, pensee pour l'axe X). Le "+1px" corrige un decalage MESURE
   (getBoundingClientRect sur .katex-html, jamais sur la boite <div> elle-meme — un
   glyphe KaTeX simple deborde legerement de sa propre boite de mise en page, environ 1px
   au-dessus du centre geometrique du <div> qui le contient, independamment du
   line-height du conteneur — teste directement, aucun effet) : sans lui, le CHIFFRE
   affiche (ex. "-1", "-2") apparait visiblement au-dessus du trait de graduation,
   jamais exactement au meme niveau. Demande directe : "sur l'axe Y le nombre n'est au
   meme niveau que le trait -1 -2". */
.block-courbe__tick-label--y {
    transform: translate(-100%, calc(-50% + 1px));
}

/* "O" d'origine (voir renderTickLabel, courbeRender.js) — CENTREE sur SES DEUX AXES
   (translate(-50%,-50%), meme technique que .block-courbe__angle-label plus bas), pas
   seulement alignee a droite comme une graduation d'axe Y — retour direct : l'ancien
   alignement "a droite" faisait lire le "O" comme un element de plus dans la colonne des
   nombres de l'axe Y (meme abscisse que "1"/"-1"), jamais comme un repere compact niche
   dans l'angle des 2 axes. */
.block-courbe__tick-label--origine {
    transform: translate(-50%, -50%);
}

/* Cercle trigonométrique (Chantier "cercle trigo") — meme poids visuel que les axes
   (.block-courbe__axe), un cercle de REFERENCE, pas une simple ligne de grille. */
.block-courbe__cercle-trigo {
    fill: none;
    stroke: var(--color-text);
    stroke-width: 1.5;
}

/* Rayon d'un point du cercle trigo (segment du centre au point, optionnel par point) —
   demande directe : "les fleches doivent avoir un rayon de 1", un veritable VECTEUR
   (trait plein + pointe de fleche, marker-end pose en JS avec le meme <marker> partage
   que les axes) plutot qu'un trait pointille de simple construction — le rayon d'un
   cercle unite EST un vecteur de norme exactement 1 par construction (le point d'arrivee
   est toujours sur le cercle), l'affichage doit le rendre evident au premier coup d'oeil.
   Plus fin que .block-courbe__axe pour rester secondaire visuellement. */
.block-courbe__rayon-trigo {
    stroke: var(--color-text);
    stroke-width: 1.5;
}

/* Projection d'un point du cercle trigo sur les axes X/Y (lecture classique de cos/sin) —
   pointille fin, meme registre visuel discret que .block-courbe__asymptote/
   .block-espace__projection-ligne (aide de construction, jamais un trace principal). */
.block-courbe__projection-trigo {
    stroke: var(--color-text);
    stroke-width: 1;
    stroke-dasharray: 3 3;
    stroke-opacity: 0.55;
}

/* Pied d'une projection sur l'axe (le petit trait perpendiculaire qui ancre la valeur
   exacte affichee, voir courbeRender.js) — meme poids visuel que les graduations
   habituelles (.block-courbe__ticks line), jamais dans le meme <g> (celles-ci suivent le
   pas regulier du repere, pas la position libre d'un point). */
.block-courbe__projection-tick {
    stroke: var(--color-text);
    stroke-width: 1;
}

/* Etiquette d'un angle remarquable autour du cercle trigo — voir courbeRender.js:
   renderAngleLabel pour pourquoi elle est centree sur SES DEUX axes (translate(-50%,-50%)),
   contrairement a .block-courbe__tick-label ci-dessus qui n'est centree qu'horizontalement. */
.block-courbe__angle-label {
    position: absolute;
    transform: translate(-50%, -50%);
    font-size: 12px;
    color: var(--color-text);
    white-space: nowrap;
}

/* Bloc "Figure geometrique" (triangle/cercle/rectangle — voir blocks/figureRender.js) —
   meme structure a deux couches que .block-courbe ci-dessus (SVG pour la geometrie, calque
   HTML separe pour les etiquettes KaTeX) et meme raisonnement pour vivre entierement ici
   (export_pdf.html execute le meme script de rendu, a besoin des memes couleurs des le
   premier rendu). */
.block-figure {
    position: relative;
    width: 100%;
    height: 100%;
}

.block-figure__svg {
    display: block;
    width: 100%;
    height: 100%;
}

.block-figure__triangle,
.block-figure__rectangle,
.block-figure__polygone {
    fill: none;
    stroke: var(--color-text);
    stroke-width: 2;
}

.block-figure__cercle {
    fill: none;
    stroke: var(--color-text);
    stroke-width: 2;
}

.block-figure__sommet {
    fill: var(--color-text);
}

.block-figure__angle-droit,
.block-figure__angle-arc {
    stroke: var(--color-text);
    stroke-width: 1.5;
}

.block-figure__rayon-ligne,
.block-figure__diagonale {
    stroke: var(--color-text);
    stroke-width: 1.5;
    stroke-dasharray: 5 4;
}

/* Traces auxiliaires du triangle (Chantier C : mediatrice/hauteur/bissectrice/mediane/
   parallele) — pointilles fins PAR DEFAUT pour rester visuellement distincts du triangle
   lui-meme (trait plein) et des diagonales/rayons (deja pointilles mais plus espaces) ;
   remplacable en trait plein via un attribut stroke-dasharray:none pose en ligne par
   figureRender.js quand le trace a son propre style personnalise (trace.style === 'plein'). */
.block-figure__trace-auxiliaire {
    stroke: var(--color-accent);
    stroke-width: 1.5;
    stroke-dasharray: 3 3;
}

/* Tangente a un cercle (Chantier B) — meme traitement visuel que les traces auxiliaires du
   triangle (couleur d'accent, pointille fin), pour la meme raison : un element de
   CONSTRUCTION geometrique, jamais confondu avec le contour plein de la figure elle-meme. */
.block-figure__tangente {
    stroke: var(--color-accent);
    stroke-width: 1.5;
    stroke-dasharray: 3 3;
}

/* Droite/segment du mode "Construction libre" (Chantier F) — trait plein PAR DEFAUT
   (contrairement aux traces auxiliaires/tangentes, qui restent des elements de
   CONSTRUCTION auxiliaires, celles-ci sont l'objet principal du dessin dans ce mode) ;
   couleur/epaisseur/style-de-trait par defaut, chacun remplacable individuellement via un
   attribut stroke/stroke-width/stroke-dasharray pose en ligne par figureRender.js quand la
   droite a sa propre couleur/epaisseur/style personnalise (ligne.style === 'pointille'). */
.block-figure__ligne {
    stroke: var(--color-text);
    stroke-width: 2;
}

/* Etiquettes KaTeX (noms de sommets, longueurs de cotes, valeurs d'angles) — meme calque
   separe superpose au SVG que .block-courbe__labels, pour la meme raison (le SVG n'a pas
   de moyen simple de rendre du KaTeX directement). Centree sur son point d'ancrage via
   transform plutot qu'un decalage en pixels devine au cas par cas — voir
   figureRender.js:renderLabel, chaque appelant calcule deja la position ou le CENTRE du
   texte doit tomber. */
.block-figure__labels {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

.block-figure__label {
    position: absolute;
    transform: translate(-50%, -50%);
    font-size: 13px;
    color: var(--color-text);
    white-space: nowrap;
}

.block-figure__erreur {
    position: absolute;
    transform: translate(-50%, -50%);
    max-width: 80%;
    text-align: center;
    font-family: var(--font-body);
    font-size: 13px;
    color: var(--color-danger);
}

/* Bloc "Geometrie dans l'espace" (repere a 3 axes, perspective cavaliere fixe — voir
   blocks/espaceRender.js) — meme structure a deux couches que .block-courbe/.block-figure
   ci-dessus (SVG pour la geometrie, calque HTML separe pour les etiquettes KaTeX), meme
   raisonnement pour vivre entierement ici (export_pdf.html execute le meme script de
   rendu). */
.block-espace {
    position: relative;
    width: 100%;
    height: 100%;
}

.block-espace__svg {
    display: block;
    width: 100%;
    height: 100%;
}

.block-espace__axe {
    stroke: var(--color-text);
    stroke-width: 1.5;
}

.block-espace__arrowhead {
    fill: var(--color-text);
}

/* Vecteur unite (i/j/k) — plus epais que le trait de l'axe lui-meme (1.5px) pour rester
   bien visible malgre sa courte longueur reelle (echelle reduite pour x, voir
   espaceRender.js:traitAxeXBrut), meme lecture que static/images/repere_espace.png ou les
   3 vecteurs unite sont traces plus "gras" que le reste des axes. */
.block-espace__vecteur-unite {
    stroke: var(--color-text);
    stroke-width: 2.5;
}

/* Projections en pointilles d'un point sur les 3 axes ("boite" de construction) — meme
   convention de trait fin/discret que .block-figure__trace-auxiliaire ou
   .block-courbe__asymptote pour une ligne de construction (jamais aussi marquee qu'un vrai
   segment/vecteur). */
.block-espace__projection-ligne {
    stroke: var(--color-text);
    stroke-opacity: 0.5;
    stroke-width: 1;
    stroke-dasharray: 4 3;
}

.block-espace__projection-pied {
    fill: var(--color-text);
    opacity: 0.6;
}

.block-espace__point {
    fill: var(--color-text);
}

.block-espace__segment {
    stroke: var(--color-text);
    stroke-width: 2;
}

/* Vecteur — meme trait que .block-espace__segment mais avec sa propre fleche (marker-end,
   voir espaceRender.js). Couleur legerement accentuee par defaut (--color-accent plutot
   que --color-text) pour rester visuellement distinct d'un simple segment au premier coup
   d'oeil, meme sans etiquette — meme convention que .block-courbe__vecteur. */
.block-espace__vecteur {
    stroke: var(--color-accent);
    stroke-width: 2;
}

.block-espace__labels {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

.block-espace__label {
    position: absolute;
    font-size: 13px;
    color: var(--color-text);
    white-space: nowrap;
}

/* Bloc "Figures de l'espace" (pave droit/cube/pyramide/cylindre, perspective cavaliere
   fixe — voir blocks/solideRender.js) — meme structure a deux couches que .block-espace
   ci-dessus (SVG pour la geometrie, calque HTML separe pour les etiquettes KaTeX). Jamais
   d'axes/repere visible ici (contrairement a .block-espace) : un solide se suffit a
   lui-meme, exactement comme .block-figure n'affiche aucun axe. */
.block-solide {
    position: relative;
    width: 100%;
    height: 100%;
}

.block-solide__svg {
    display: block;
    width: 100%;
    height: 100%;
}

.block-solide__arete {
    stroke: var(--color-text);
    stroke-width: 1.5;
    fill: none;
}

/* Arete cachee (convention standard des manuels francais) — meme trait/meme epaisseur que
   .block-solide__arete, seul le style pointille change. */
.block-solide__arete-cachee {
    stroke: var(--color-text);
    stroke-width: 1.5;
    stroke-dasharray: 5 4;
    fill: none;
}

/* Ligne de construction discrete (rayon/hauteur du cylindre) — meme convention fine/
   attenuee que .block-espace__projection-ligne pour une ligne auxiliaire, jamais aussi
   marquee qu'une vraie arete du solide. */
.block-solide__ligne-construction {
    stroke: var(--color-text);
    stroke-opacity: 0.5;
    stroke-width: 1;
    stroke-dasharray: 3 2;
}

.block-solide__sommet {
    fill: var(--color-text);
}

.block-solide__labels {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

.block-solide__label {
    position: absolute;
    font-size: 13px;
    color: var(--color-text);
    white-space: nowrap;
}

/* Bloc "Circuit électrique" (voir blocks/circuitRender.js) — schéma filaire (noeuds +
   fils), symboles IEC 60617 dessinés à la main en SVG. Même base position/svg/labels que
   figure.js/solide.js juste au-dessus. */
.block-circuit {
    position: relative;
    width: 100%;
    height: 100%;
}

.block-circuit__svg {
    display: block;
    width: 100%;
    height: 100%;
}

.block-circuit__fil {
    stroke: var(--color-text);
    stroke-width: 1.5;
    fill: none;
}

.block-circuit__symbole-trait {
    stroke: var(--color-text);
    stroke-width: 1.5;
    fill: none;
}

/* Trait "épais" (le contact court de la pile, la barre de cathode d'une diode/DEL) —
   volontairement plus marqué que le reste du symbole, convention standard IEC. */
.block-circuit__symbole-trait-epais {
    stroke: var(--color-text);
    stroke-width: 3;
    fill: none;
}

/* Trait "fin" (la croix de la lampe, les flèches d'émission de la DEL) — volontairement
   plus discret que le trait principal du symbole. */
.block-circuit__symbole-trait-fin {
    stroke: var(--color-text);
    stroke-width: 1;
    fill: none;
}

.block-circuit__contact {
    fill: var(--color-text);
}

.block-circuit__noeud {
    fill: var(--color-text);
}

.block-circuit__fleche-marqueur {
    fill: var(--color-text);
}

.block-circuit__labels {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

.block-circuit__label {
    position: absolute;
    transform: translate(-50%, -50%);
    font-size: 13px;
    color: var(--color-text);
    white-space: nowrap;
}

/* Lettre au centre d'un symbole cercle+lettre (voltmètre/ampèremètre/joulemètre/moteur) —
   même position que .block-circuit__label (centrée sur le point), jamais du KaTeX (une
   lettre seule, aucune raison de payer le rendu KaTeX pour ça). */
.block-circuit__lettre {
    position: absolute;
    transform: translate(-50%, -50%);
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 13px;
    color: var(--color-text);
}

/* Bloc "Tableau de variation" (voir blocks/variationRender.js) — meme raisonnement que
   .block-courbe ci-dessus pour vivre entierement ici (base + apparence), pas seulement
   le dimensionnement : export_pdf.html execute le meme script de rendu et a besoin des
   memes couleurs des le premier rendu. */
.block-variation {
    position: relative;
    width: 100%;
    height: 100%;
}

.block-variation__svg {
    display: block;
    width: 100%;
    height: 100%;
}

.block-variation__separateur {
    stroke: var(--color-text);
    stroke-width: 1.5;
}

/* Trait plein NOIR (jamais pointille — retour explicite "il faut des trait noir et non
   des pointillés"), separant la colonne d'etiquettes de gauche (x/f'(x)/f(x)) du reste
   du tableau, sur toute la hauteur — voir static/images/jolie_tdv.jpeg, ou ce trait est
   la seule ligne verticale visible. */
.block-variation__separateur-label {
    stroke: var(--color-text);
    stroke-width: 1.5;
}

/* Colonnes interieures — meme trait plein noir, mais tracees uniquement sous la ligne
   d'en-tete (voir variationRender.js:build, jamais y1=0) : "les traits verticaux ne
   touche pas les bornes", elles ne doivent jamais traverser la ligne ou les valeurs de x
   sont ecrites. */
.block-variation__colonne {
    stroke: var(--color-text);
    stroke-width: 1;
}

.block-variation__fleche {
    stroke: var(--color-text);
    stroke-width: 2;
}

.block-variation__arrowhead {
    fill: var(--color-text);
}

.block-variation__labels {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

.block-variation__label {
    position: absolute;
    font-size: 14px;
    color: var(--color-text);
    white-space: nowrap;
    transform: translateX(-50%);
}

/* Signe de f'(x) — meme convention rouge/neutre que .block-signe__label--moins (voir
   plus bas) : distinguer visuellement le "-" du "+" au premier coup d'oeil, exactement
   comme sur une ligne de tableau de signes classique. */
.block-variation__label--moins {
    color: #B94A48;
}

.block-variation__double-barre {
    stroke: var(--color-text);
    stroke-width: 1.5;
}

/* Bloc "Tableau de signes" (voir blocks/signeRender.js) — meme raisonnement que
   .block-courbe/.block-variation ci-dessus pour vivre entierement ici. */
.block-signe {
    position: relative;
    width: 100%;
    height: 100%;
}

.block-signe__svg {
    display: block;
    width: 100%;
    height: 100%;
}

.block-signe__separateur {
    stroke: var(--color-text);
    stroke-width: 1.5;
}

/* Trait plein NOIR (retour direct "les traits horizontaux doivent être noire"),
   jusqu'ici gris clair (--color-border) — corrige pour la meme raison/coherence que
   .block-signe__colonne juste en dessous. */
.block-signe__separateur-ligne {
    stroke: var(--color-text);
    stroke-width: 1;
}

/* Pointillee de nouveau — retour direct "comme avant on remet les pointillés des
   traits verticaux". Un temps rendue pleine/noire (pour repondre a "faire des lignes
   et non des pointillée", qui visait en realite les traits HORIZONTAUX, voir
   .block-signe__separateur-ligne ci-dessus), puis re-pointillee sur demande explicite —
   couleur gardee noire (jamais revenue au gris clair d'origine) pour rester coherente
   avec le reste du tableau, desormais tres largement noir. */
.block-signe__colonne {
    stroke: var(--color-text);
    stroke-width: 1;
    stroke-dasharray: 3 3;
}

/* Fermeture exterieure du tableau (haut/bas/gauche/droite) — meme trait plein noir que
   .block-variation__separateur-label, classe distincte de .block-signe__colonne pour
   ne jamais heriter de son pointille : l'exterieur du tableau reste une bordure
   pleine, seuls les traits reperant chaque borne redeviennent pointilles. */
.block-signe__colonne-fermeture {
    stroke: var(--color-text);
    stroke-width: 1.5;
}

.block-signe__double-barre {
    stroke: var(--color-text);
    stroke-width: 1.5;
}

.block-signe__labels {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

.block-signe__label {
    position: absolute;
    font-size: 14px;
    color: var(--color-text);
    white-space: nowrap;
    transform: translateX(-50%);
}

.block-signe__label--moins {
    color: #B94A48;
}


/* Bloc fond (blocks/fond.js) — meme raison que .block-ligne/.block-forme ci-dessus pour
   vivre ici plutot que dans editor.css : export_pdf.html charge ce fichier mais pas
   editor.css, et a besoin d'une taille reelle des le premier rendu (pas de script JS a
   executer ici, contrairement a ligne/forme — le fond est rendu par simple interpolation
   Django, voir export_pdf.html, donc ce dimensionnement de base est en fait TOUT ce dont
   le rendu imprime a besoin). `overflow: hidden` (contrairement a ligne/forme) : les deux
   calques ci-dessous ne doivent jamais deborder du bloc, y compris en mode d'ajustement
   "etirer"/"repeter" ou l'arrondi d'un calcul de taille pourrait sinon laisser depasser
   un pixel de l'image. */
.block-fond {
    position: relative;
    width: 100%;
    height: 100%;
    overflow: hidden;
}

.block-fond__image-layer,
.block-fond__overlay-layer {
    position: absolute;
    inset: 0;
}

.fleche-line {
    stroke: var(--color-text);
    stroke-width: 2.5;
    vector-effect: non-scaling-stroke;
}

.fleche-arrowhead {
    fill: var(--color-text);
}
