Files
Forge-Engine/static/document/document-editor.css
T
williamandClaude Sonnet 5 4db1269348 Audit complet de mise en forme — Liste à puces/numérotée (4e élément)
Implémente toutes les options manquantes identifiées pour les listes :
typographie complète, style/position/couleur/taille de puce (validés
selon le kind), puce personnalisée en SVG pour les listes à puces,
padding uniforme par élément (nouveau, il n'y en avait aucun), espacement
entre éléments réglable, et tous les attributs de boîte partagés sur la
liste entière. Bordure/fond/padding par élément individuel et sous-listes
imbriquées volontairement différés (portée actée avec l'utilisateur
avant implémentation : transformeraient le stockage des éléments en
objets structurés, chantier bien plus lourd).

Trois ajouts transversaux bénéficiant à plusieurs éléments : sections
"Contenu"/"Conteneur" dans tous les panneaux de propriétés, alignement
vertical du contenu dans son bloc (Titre/Paragraphe/Liste/Image
légendée), et une option pour retirer un thème appliqué ("Aucun modèle"
dans la modale, avec une nouvelle fonction db.remove_document_theme).

Quatre bugs réels trouvés et corrigés en chaîne pendant la validation
avec le thème "Sécurité incendie" : un badge de thème s'affichait
au-dessus du texte au lieu d'à côté ; le correctif a d'abord fait
disparaître les puces/numéros natifs de TOUTES les listes (bug plus
grave que celui corrigé) ; puis un marqueur natif redondant apparaissait
à côté du badge du thème ; puis une règle CSS site-large de spécificité
supérieure empêchait silencieusement ce dernier correctif. Chaque étape
vérifiée par navigateur automatisé sur un support jetable.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-26 15:13:14 +02:00

2757 lines
70 KiB
CSS

/* document-editor.css — éditeur de support de formation (voir
docs/plan/PLAN.md). Tout est scopé sous .docEditor3 : aucune règle ici
ne touche jamais html[data-theme] (figé "dark" pour le reste du site,
voir templates/base.html) — le thème clair/sombre de CET éditeur est un
attribut data-theme posé sur .docEditor3 lui-même (voir
static/document/js/document-editor.js), avec ses propres tokens
--doc-* ci-dessous plutôt que les --forge-* globaux. */
/* Demande explicite : l'éditeur occupe TOUTE la largeur de la fenêtre,
bord à bord — .content limite normalement à 760px (voir static/style.css)
et .content-wide à 1600px, tous deux avec un padding fixe. Neutralisé
ici pour cette page précise seulement (ce fichier n'est chargé QUE par
templates/document/document_edit.html) : même sélecteur que la règle du
site (`body.objectEditBody > main.content`, spécificité identique),
mais chargé après static/style.css dans <head> (voir son
{% block extra_head %}) donc prioritaire à spécificité égale. */
body.objectEditBody > main.content {
max-width: none;
width: 100%;
padding: 0;
margin: 0;
}
/* La modale (#docScenarioTreeModal) partage ces tokens --doc-* MAIS
reste volontairement un FRÈRE de .docEditor3 dans le HTML (jamais un
descendant) : main.content (voir body.objectEditBody dans
static/style.css) a déjà `overflow:hidden`, qui ROGNE VISUELLEMENT
tout descendant `position:fixed` à sa propre boîte (même bug déjà
trouvé le 20/09/2026 pour le bandeau d'outils, voir plus bas) — un
`position:fixed` doit donc rester un frère de .docEditor3, jamais un
enfant, pour couvrir le VRAI viewport. D'où la duplication de ce
sélecteur ici plutôt qu'un simple héritage de variables CSS (qui ne
franchit pas la frontière DOM entre frères) — voir aussi
forgeDocApplyTheme (static/document/js/document-editor.js), qui pose
le même data-theme sur les deux éléments. */
.docEditor3,
#docScenarioTreeModal,
#docTemplateModal {
--doc-bg: var(--forge-bg);
--doc-bg-2: var(--forge-bg-secondary);
--doc-card: var(--forge-card);
--doc-border: var(--forge-border);
--doc-text: var(--forge-text);
--doc-muted: var(--forge-text-muted);
--doc-accent: var(--accent);
--doc-accent-2: var(--accent-2);
--doc-glow: 0 0 0 1px rgb(255 95 46 / 25%), 0 8px 24px -6px rgb(255 95 46 / 25%);
--doc-quiz-success-bg: rgb(59 165 93 / 12%);
--doc-quiz-success-border: #3ba55d;
--doc-quiz-success-text: #8fe3ab;
--doc-quiz-danger-bg: rgb(255 95 46 / 12%);
--doc-quiz-danger-border: #ff5f2e;
--doc-quiz-danger-text: #ffb3a0;
}
.docEditor3 {
/* Remplit main.content (voir body.objectEditBody dans static/style.css :
`main.content` y est déjà `flex:1 1 auto; min-height:0; overflow:hidden;
display:flex; flex-direction:column;`) — jamais `position:fixed` : cet
ancêtre a `overflow:hidden`, qui ROGNE VISUELLEMENT tout descendant
`position:fixed` à sa propre boîte (laquelle commence sous la barre de
navigation du site, pas au vrai sommet du viewport) — bug réel trouvé
le 20/09/2026 : le bandeau d'outils (56px) tombait alors entièrement
dans la zone rognée, invisible, alors que le reste de l'éditeur
semblait décalé vers le haut à sa place. */
flex: 1 1 auto;
min-height: 0;
display: flex;
flex-direction: column;
background: var(--doc-bg);
color: var(--doc-text);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.docEditor3[data-theme="light"],
#docScenarioTreeModal[data-theme="light"],
#docTemplateModal[data-theme="light"] {
--doc-bg: #f6f5f2;
--doc-bg-2: #fff;
--doc-card: #fff;
--doc-border: #e3e1db;
--doc-text: #14161c;
--doc-muted: #6b7280;
--doc-glow: 0 0 0 1px rgb(255 95 46 / 30%), 0 8px 24px -6px rgb(255 95 46 / 30%);
--doc-quiz-success-bg: #e4f5ea;
--doc-quiz-success-border: #2e7d46;
--doc-quiz-success-text: #1d4c2c;
--doc-quiz-danger-bg: #fdede8;
--doc-quiz-danger-border: #c1451f;
--doc-quiz-danger-text: #7a1418;
}
.docEditor3 *,
#docScenarioTreeModal *,
#docTemplateModal * {
box-sizing: border-box;
}
.docEditor3 button {
font-family: inherit;
}
/* ===== TOPBAR ===== */
.docTopbar {
height: 56px;
flex-shrink: 0;
background: var(--doc-bg-2);
border-bottom: 1px solid var(--doc-border);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
gap: 12px;
}
.docCrumb {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
font-weight: 600;
min-width: 0;
overflow: hidden;
}
.docCrumbDim {
color: var(--doc-muted);
font-weight: 400;
white-space: nowrap;
text-decoration: none;
}
.docCrumbCur {
font-weight: 700;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: var(--doc-text);
}
.docTopbarActions {
display: flex;
align-items: center;
gap: 10px;
flex-shrink: 0;
}
.docIconBtn {
width: 34px;
height: 34px;
border-radius: 10px;
border: 1px solid var(--doc-border);
background: transparent;
color: var(--doc-text);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.docIconBtn:hover:not(:disabled) {
border-color: var(--doc-muted);
}
.docIconBtn:disabled {
opacity: 0.35;
cursor: default;
}
.docDividerV {
width: 1px;
height: 22px;
background: var(--doc-border);
margin: 0 4px;
}
.docBtnSecondary {
height: 34px;
padding: 0 14px;
border-radius: 10px;
border: 1px solid var(--doc-border);
background: transparent;
color: var(--doc-text);
font-size: 14px;
font-weight: 600;
display: flex;
align-items: center;
gap: 7px;
cursor: pointer;
}
.docBtnSecondary:hover {
border-color: var(--doc-muted);
}
.docBtnPrimary {
border-color: var(--doc-accent);
color: var(--doc-accent);
}
.docBtnPrimary:hover {
background: rgb(255 95 46 / 8%);
}
.docWidthPicker {
display: flex;
gap: 4px;
background: var(--doc-card);
border: 1px solid var(--doc-border);
border-radius: 10px;
padding: 3px;
}
.docWidthBtn {
border: none;
background: transparent;
color: var(--doc-muted);
font-size: 12.5px;
font-weight: 600;
padding: 5px 10px;
border-radius: 7px;
cursor: pointer;
}
.docWidthBtn.is-active {
background: rgb(255 95 46 / 12%);
color: var(--doc-accent);
}
/* ===== BODY LAYOUT ===== */
.docBodyWrap {
flex: 1;
display: flex;
min-height: 0;
position: relative;
}
.docSidebar {
flex-shrink: 0;
background: var(--doc-bg-2);
padding: 20px 16px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 22px;
}
.docSidebarLeft {
width: 248px;
border-right: 1px solid var(--doc-border);
}
.docSidebarRight {
width: 300px;
border-left: 1px solid var(--doc-border);
}
.docSectionLabel {
font-size: 12px;
font-weight: 700;
letter-spacing: 0.04em;
color: var(--doc-muted);
margin-bottom: 10px;
}
/* Sépare visuellement, DANS un même panneau Propriétés, les champs qui
touchent au CONTENU (propre à chaque kind) de ceux qui touchent au
CONTENEUR (position/boîte, partagés — voir forgeDocRenderBoxFieldsHtml)
— retour utilisateur du 26/09/2026 : "sinon c'est pas compréhensible". */
.docPropsSectionLabel {
margin-top: 20px;
padding-top: 14px;
border-top: 1px solid var(--doc-border);
}
.docLibItem {
border: 1px solid var(--doc-border);
border-radius: 10px;
cursor: grab;
background: var(--doc-card);
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.docLibItem:hover {
border-color: var(--doc-accent);
box-shadow: var(--doc-glow);
}
.docLibItem--row {
padding: 9px 10px;
display: flex;
align-items: center;
gap: 10px;
}
.docLibItem span:last-child {
font-size: 12.5px;
font-weight: 600;
}
.docLibList {
display: flex;
flex-direction: column;
gap: 6px;
}
.docLibIcon {
width: 18px;
height: 18px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 14px;
flex-shrink: 0;
}
.docLibIcon--paragraphe::before { content: "¶"; }
.docLibIcon--image::before { content: "🖼"; }
.docLibIcon--bouton::before { content: "⬚"; }
.docLibIcon--liste_puces::before { content: "☰"; }
.docLibIcon--liste_numerotee::before { content: "①"; }
.docLibIcon--badge::before { content: "◉"; }
.docLibIcon--carte::before { content: "▭"; }
.docLibIcon--titre::before {
content: "T";
font-weight: 800;
}
.docLibIcon--quiz::before {
content: "?";
color: var(--doc-accent);
font-weight: 800;
}
.docLibIcon--association::before {
content: "⇄";
color: var(--doc-accent);
}
.docLibIcon--memory::before {
content: "▤";
color: var(--doc-accent);
}
.docLibIcon--mots::before {
content: "▦";
color: var(--doc-accent);
}
.docLibIcon--scenario::before {
content: "◈";
color: var(--doc-accent);
}
.docLibIcon--zones::before {
content: "⚠";
color: var(--doc-accent);
}
/* ---- CANVAS ---- */
.docCanvasArea {
flex: 1;
background: var(--doc-bg);
padding: 36px 40px;
overflow: auto;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
}
/* Page au format A4 PAYSAGE, taille FIXE comme une vraie diapositive
(retour utilisateur du 23/09/2026 : "une page [...] dois etre un
format a4 orienter horizontalement comme une slide [...] le contenu
ne defile pas, si ya plus de place les contenu va sur une autre
page") — ratio 297:210 (A4 paysage) fixé par aspect-ratio, jamais
height:fit-content comme avant (la page ne grandit plus avec son
contenu). overflow:hidden : le contenu qui dépasse est clipsé, PAS
de défilement interne — au créateur de répartir sur plusieurs pages,
comme un vrai logiciel de diaporama ne reflow jamais automatiquement
une diapositive trop pleine. */
.docPage {
/* --doc-page-width : SOURCE UNIQUE utilisée à la fois par width et par
le calcul de max-height ci-dessous (jamais deux nombres séparés à
tenir synchronisés) — .docPage--fixedWidth et le point de rupture
mobile la redéfinissent, max-height suit automatiquement. */
--doc-page-width: 960px;
width: var(--doc-page-width);
max-width: 100%;
aspect-ratio: 297 / 210;
/* Filet de sécurité en plus d'aspect-ratio (retour utilisateur du
23/09/2026 : "le contenu ne dois jamais s'adapter verticalement" —
bug réel constaté : un <div> flex (voir .docCanvasArea) applique
une "taille minimale automatique" à ses enfants qui peut agrandir
la page au-delà de la hauteur dérivée d'aspect-ratio pour éviter de
rogner leur contenu ; max-height fixe un plafond dur qu'aucun
contenu ne peut dépasser, quelle que soit cette mécanique). */
max-height: calc(var(--doc-page-width) * 210 / 297);
min-height: 0;
/* Blanche FIXE, en édition ET en Aperçu (retour utilisateur du
24/09/2026 : "la page [...] soi blanche comme dans tous éditeur
normale") — jamais var(--doc-card), qui suit le thème clair/sombre
de L'ÉDITEUR (chrome autour), pas de la PAGE elle-même. */
background: #fff;
/* Le contenu écrit color:var(--forge-text) en style inline par défaut
(voir document_engine/rendering/render_document_element.py) — ce
token vaut #e8ecf4 (quasi blanc) partout ailleurs dans le site,
illisible sur une page blanche fixe. Redéfini ICI seulement (pas
globalement, le reste du site reste en thème sombre figé) — même
principe déjà appliqué aux vignettes de page (session du 21/09/2026,
depuis supprimées, mais le même besoin persiste ici). */
--forge-text: #14161c;
--forge-text-muted: #6b7280;
/* Les mini-jeux (cartes Quiz/Association/Memory/Mots mêlés/Scénario,
leurs options/cartes/boutons) s'appuient sur les tokens --doc-*
(--doc-card, --doc-bg-2, --doc-text, --doc-border, ceux de succès
et d'échec du Quiz) — sombres par défaut, assortis au CHROME de
l'éditeur, pas à la page (retour utilisateur
du 24/09/2026 : "le style des mini jeux dois etre revue pour etre
sur fond blanc"). Réutilise TELLES QUELLES les valeurs déjà
définies pour .docEditor3[data-theme="light"] (palette claire déjà
conçue et éprouvée dans ce fichier) plutôt que d'en inventer une
troisième version : la page étant DÉSORMAIS TOUJOURS blanche (voir
background ci-dessus), son contenu doit toujours utiliser la
palette claire, peu importe le thème choisi pour le chrome autour. */
--doc-bg: #f6f5f2;
--doc-bg-2: #fff;
--doc-card: #fff;
--doc-border: #e3e1db;
--doc-text: #14161c;
--doc-muted: #6b7280;
--doc-quiz-success-bg: #e4f5ea;
--doc-quiz-success-border: #2e7d46;
--doc-quiz-success-text: #1d4c2c;
--doc-quiz-danger-bg: #fdede8;
--doc-quiz-danger-border: #c1451f;
--doc-quiz-danger-text: #7a1418;
/* Bug réel constaté le 24/09/2026 (capture à l'appui) : redéfinir la
custom property --doc-text ci-dessus NE SUFFIT PAS pour les textes
de mini-jeu SANS déclaration `color` propre (ex. .docQuizPlayerTitle,
.docQuizQuestionText) — `color` est une propriété HÉRITÉE, et sans
déclaration explicite ici, ces éléments héritent la couleur déjà
CALCULÉE plus haut sur .docEditor3 (color:var(--doc-text) résolu
LÀ-BAS avec la palette sombre de l'éditeur, quasi blanche), jamais
une réévaluation de --doc-text à CE niveau. Poser `color` ICI casse
cette chaîne d'héritage et la relance avec la bonne valeur locale. */
color: var(--doc-text);
border: 1px solid var(--doc-border);
border-radius: 16px;
box-shadow: 0 1px 3px rgb(0 0 0 / 30%);
padding: 48px 52px;
position: relative;
overflow: hidden;
transform-origin: top center;
transition: width 0.2s ease;
}
/* Enfant qui reçoit le HTML rendu (voir #docPage vs #docPageContent dans
document-editor.js — le rafraîchissement du canevas ne remplace QUE ce
contenu, jamais .docPage elle-même). */
.docPageContent {
display: flex;
flex-direction: column;
gap: 18px;
height: 100%;
}
/* Bug réel constaté le 23/09/2026 (retour utilisateur, capture à
l'appui) : flex-shrink vaut 1 par défaut pour tout enfant flex —
une fois .docPage plafonnée en hauteur (voir plus haut), les
éléments top-niveau se COMPRESSAIENT tous pour continuer à tenir
(ex. .docImagePlaceholder, pourtant height:190px, rétrécissait
visiblement) au lieu de déborder. Sans ce débordement RÉEL,
forgeDocCheckPageOverflow (scrollHeight vs clientHeight) ne
détectait jamais rien à paginer. flex-shrink:0 empêche toute
compression : le contenu garde sa taille naturelle et déborde
franchement, ce qui déclenche la pagination automatique. */
.docPageContent > [data-element-id] {
flex-shrink: 0;
}
/* Alignement vertical du contenu d'une page (retour utilisateur du
24/09/2026 : réglable depuis le panneau Propriétés quand l'onglet
"Pages" est actif, voir document_engine.set_document_page_vertical_align
et static/document/js/document-editor.js::forgeDocRenderPageProps) —
"top" reste la valeur par défaut (justify-content:flex-start déjà
posé ci-dessus), aucune règle supplémentaire nécessaire pour elle. */
.docPageContent[data-vertical-align="center"] {
justify-content: center;
}
.docPageContent[data-vertical-align="bottom"] {
justify-content: flex-end;
}
.docPage.docPage--fixedWidth {
--doc-page-width: var(--doc-preview-width, 760px);
width: var(--doc-page-width);
padding: 28px 20px;
}
/* Un mini-jeu occupe TOUTE la page, à lui seul (retour utilisateur du
23/09/2026 : "un mini jeu dois occupper toute une page" — confirmé :
"une page avec mini-jeu = uniquement ce mini-jeu"). Garantie posée
côté serveur (routes/document/document_element_add.py : impossible
d'ajouter un mini-jeu sur une page non vide, ni d'ajouter autre chose
sur une page qui en contient déjà un) — :only-child ici n'est donc
jamais un cas ambigu, juste la mise en forme plein-cadre de cette
garantie. Le padding de .docPage disparaît (plein bord) et le badge
mini-jeu (nom/"2 paires"/etc.) disparaît ENTIÈREMENT (retour
utilisateur du 23/09/2026 : "ces éléments [...] doivent disparaitre
et le composant mini jeu pren toute la place") — plus une simple
réduction d'espace, le joueur seul occupe toute la hauteur. */
.docPage:has(> .docPageContent > .docMinigame:only-child) {
padding: 0;
}
.docPageContent > .docMinigame:only-child {
flex: 1;
display: flex;
flex-direction: column;
border-radius: 0;
border-width: 0;
}
.docPageContent > .docMinigame:only-child .docMinigameBadge {
display: none;
}
.docPageContent > .docMinigame:only-child > [class$="Player"] {
flex: 1;
min-height: 0;
}
.docRow {
position: relative;
}
.docRow > [data-element-id] {
flex: 1 1 220px;
min-width: 0;
}
[data-element-id] {
position: relative;
border-radius: 8px;
padding: 6px;
margin: -6px;
cursor: pointer;
transition: background 0.1s ease;
}
[data-kind="row"] > [data-element-id] {
margin: 0;
}
[data-element-id]:hover {
background: rgb(255 95 46 / 6%);
}
[data-element-id].docSelected {
background: rgb(255 95 46 / 10%);
outline: 1px dashed var(--doc-accent);
}
[data-element-id].docDropTarget--before {
box-shadow: inset 0 3px 0 0 var(--doc-accent);
}
[data-element-id].docDropTarget--after {
box-shadow: inset 0 -3px 0 0 var(--doc-accent);
}
[data-element-id].docDropTarget--mergeLeft {
box-shadow: inset 3px 0 0 0 var(--doc-accent);
}
[data-element-id].docDropTarget--mergeRight {
box-shadow: inset -3px 0 0 0 var(--doc-accent);
}
.docText {
outline: none;
/* Un mot sans espace plus long que la largeur du bloc doit se couper
plutôt que déborder hors de la page (retour utilisateur du
23/09/2026 : "les élément paragraphe ne vont pas à la ligne") —
overflow-wrap (jamais word-break:break-all, qui casserait même les
mots normaux au milieu sans raison) ne coupe QUE si le mot ne tient
vraiment pas sur une ligne. */
overflow-wrap: break-word;
/* flex-colonne (retour utilisateur du 26/09/2026 : "je peux augmenter
la hauteur d'un conteneur mais pas l'alignement vertical à
l'intérieur") : sans effet visuel tant que la hauteur du bloc
correspond à son contenu (comportement historique inchangé) —
n'entre en jeu que si une hauteur fixe/minimale (voir box_style.py)
dépasse la hauteur naturelle du texte, via justify-content
(render_content_align, appliqué uniquement si réglé). */
display: flex;
flex-direction: column;
}
.docImage.docImagePlaceholder {
width: 100%;
height: 190px;
border-radius: 10px;
background: var(--doc-bg-2);
border: 1px dashed var(--doc-border);
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
color: var(--doc-muted);
font-size: 13px;
font-weight: 600;
}
/* max-height : filet de sécurité pour une hauteur explicite (ou
l'absence de hauteur, image affichée à sa taille native) qui
dépasserait déraisonnablement une page (bug réel constaté le
26/09/2026 : "quand j'ajoute une image ça crée des pages à
l'infini") — le cadre FIXE par défaut (hauteur + object-fit:cover)
vit désormais dans les attributs par défaut d'un nouvel élément image
(voir element_kind_labels.element_default_attributes, retour
utilisateur : "ce cadre ne devrait pas changer de taille en fonction
de la taille de l'image mais être fixe"), pas ici en CSS : "Taille
réelle" (object_fit vide) doit rester un choix explicite possible
dans le panneau Propriétés, qu'un défaut CSS aveugle sur TOUTE image
casserait silencieusement (aucune façon de distinguer en CSS "jamais
réglé" de "réglé sur Taille réelle", les deux valant la chaîne vide).
Un réglage explicite de hauteur maximale prime toujours sur ce filet
(spécificité inline > feuille externe). */
img.docImage {
max-width: 100%;
max-height: calc(var(--doc-page-width, 960px) * 210 / 297 - 96px);
border-radius: 10px;
display: block;
}
/* display:inline-flex + gap (au lieu du bloc simple d'origine) : accueille
l'icône optionnelle avant/après le texte (retour utilisateur du
26/09/2026, audit — colonne "Icône" : "aucune icône à côté du texte du
bouton, contrairement au badge") sans avoir à gérer d'espacement en
dur — sans icône, .docButtonLabel reste seul enfant, gap n'a alors
aucun effet visible (rien à espacer). */
.docButton {
display: inline-flex;
align-items: center;
gap: 8px;
border: none;
border-radius: 999px;
padding: 10px 22px;
font-weight: 700;
font-size: 14px;
background: var(--doc-accent);
color: #14100c;
cursor: pointer;
transition: filter 0.15s ease, transform 0.08s ease;
}
/* États interactifs (retour utilisateur du 26/09/2026, audit — colonne
"États interactifs" : "aucune notion d'état n'existe") — un effet
UNIVERSEL (jamais configurable par bouton, volontairement : une
couleur de survol par instance aurait exigé une propriété CSS
personnalisée par bouton, fragile et sans grande valeur ajoutée pour
du contenu de formation) suffit à donner un vrai retour visuel
survol/actif à TOUS les boutons, y compris ceux déjà créés. "Désactivé"
volontairement laissé de côté : un bouton de contenu n'est pas un
vrai contrôle de formulaire, aucun état "désactivé" n'a de sens ici
(voir docs/plan/AUDIT_MISE_EN_FORME.md). */
.docButton:hover {
filter: brightness(0.92);
}
.docButton:active {
transform: scale(0.97);
}
.docButtonIcon {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 1em;
height: 1em;
}
.docButtonIcon svg {
width: 100%;
height: 100%;
}
.docList {
margin: 0;
padding-left: 1.4em;
font-size: 15px;
line-height: 1.6;
color: var(--forge-text);
}
/* item_padding/item_spacing/marker_color/marker_size (retour utilisateur
du 26/09/2026, audit — colonne "Liste à puces/numérotée") : une
valeur inline sur le <ul>/<ol> ne peut pas cibler directement un <li>
enfant ni son ::marker — passe par des PROPRIÉTÉS PERSONNALISÉES CSS,
posées en style inline sur le conteneur (voir _render_list,
document_engine/rendering/render_document_element.py) et héritées
jusqu'ici (les propriétés personnalisées traversent aussi bien les
descendants que leurs pseudo-éléments). Valeur par défaut = comportement
historique inchangé (4px entre éléments, pas de padding) tant qu'aucune
valeur n'est réglée. */
.docList li {
padding: var(--doc-item-padding, 0);
}
/* Bug réel corrigé une première fois le 26/09/2026 (capture à l'appui,
thème "Sécurité incendie") en posant display:flex ICI sur <li> pour
que le badge en ::before d'un thème (voir static/document/themes/
securite-incendie.css) s'affiche À CÔTÉ du texte plutôt qu'AU-DESSUS.
**Corrigé une seconde fois** (retour utilisateur : "quand j'enlève le
thème [...] on voit plus les puces ou les numéros") : display:flex
sur le <li> lui-même REMPLACE entièrement son display:list-item —
ça supprime le marqueur natif (puce/numéro) pour TOUTE liste, avec ou
sans thème, pas seulement pour celles qui ont un ::before (contraire
à ce qu'affirmait le commentaire précédent, qui était FAUX — vérifié
par un test en conditions réelles). La bonne cible pour la mise en
ligne du badge est le ::before LUI-MÊME (inline-block, jamais son
parent) : le <li> garde son display natif, donc son marqueur, et le
badge d'un thème s'aligne quand même à côté du texte qui le suit
immédiatement dans le flux normal. */
.docList li::before {
display: inline-block;
vertical-align: middle;
margin-right: 10px;
}
.docList li + li {
margin-top: var(--doc-item-spacing, 4px);
}
.docList li::marker {
color: var(--doc-marker-color, inherit);
font-size: var(--doc-marker-size, inherit);
}
.docMinigame {
border: 1px solid var(--doc-accent);
/* Bords carrés, jamais arrondis (retour utilisateur du 24/09/2026 :
"les bord des mini jeu ne doivent plus etre arrondie") — même
règle pour toutes les cartes/boutons/cases du joueur ci-dessous. */
border-radius: 0;
background: rgb(255 95 46 / 8%);
}
.docMinigameBadge {
padding: 18px 20px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.docMinigameLabel {
font-size: 15px;
font-weight: 700;
}
.docMinigamePlaceholder {
font-size: 12.5px;
color: var(--doc-muted);
}
/* ---- Quiz/Association/Memory/Mots mêlés/Scénario : le joueur RÉEL est
désormais VISIBLE en permanence, édition ET Aperçu (retour
utilisateur du 23/09/2026 : "les mini jeux doive etre visible meme
en edition mais jouable dans l'apercus") — seule l'INTERACTIVITÉ
(répondre/glisser/retourner une carte) reste réservée au Mode
Aperçu, via pointer-events ci-dessous (jamais via display:none,
sinon rien ne serait visible en édition). ---- */
/* Le joueur remplit TOUTE la hauteur/largeur disponible (voir
[class$="Player"] plus haut, flex:1 dans .docMinigame:only-child) ET
sa CARTE interne (.docQuizCard, .docAssocCard, .docMemoryCardWrap...)
doit elle aussi occuper toute cette place, pas juste une carte
centrée avec une largeur confortable (retour utilisateur du
23/09/2026 : "le mini jeu dois prendre toute la hauteur et la
largeur du document" — une carte à largeur limitée, même centrée,
ne suffisait pas). align-items:stretch (valeur par défaut, posée
explicitement pour être claire) étire la carte sur tout l'axe
croisé ; sa largeur 100% (voir chaque carte plus bas, sans
max-width) fait le reste. */
.docQuizPlayer,
.docAssocPlayer,
.docMemoryPlayer,
.docMotsPlayer,
.docScenarioPlayer {
pointer-events: none;
display: flex;
align-items: stretch;
}
.docEditor3.docEditor3--preview [data-kind="quiz"],
.docEditor3.docEditor3--preview [data-kind="association"],
.docEditor3.docEditor3--preview [data-kind="memory"],
.docEditor3.docEditor3--preview [data-kind="mots"],
.docEditor3.docEditor3--preview [data-kind="scenario"] {
border: none;
background: transparent;
}
.docEditor3.docEditor3--preview .docQuizPlayer,
.docEditor3.docEditor3--preview .docQuizPlayer *,
.docEditor3.docEditor3--preview .docAssocPlayer,
.docEditor3.docEditor3--preview .docAssocPlayer *,
.docEditor3.docEditor3--preview .docMemoryPlayer,
.docEditor3.docEditor3--preview .docMemoryPlayer *,
.docEditor3.docEditor3--preview .docMotsPlayer,
.docEditor3.docEditor3--preview .docMotsPlayer *,
.docEditor3.docEditor3--preview .docScenarioPlayer,
.docEditor3.docEditor3--preview .docScenarioPlayer * {
/* Deux raisons cumulées de désactiver pointer-events ailleurs, toutes
les deux réactivées ICI en Mode Aperçu : (1) .docEditor3--preview
désactive pointer-events sur tout [data-element-id] (voir plus bas)
pour empêcher la sélection/le déplacement pendant l'Aperçu ; (2) le
joueur lui-même est pointer-events:none PAR DÉFAUT (voir plus haut)
pour rester visible mais non "jouable" hors Aperçu (retour
utilisateur du 23/09/2026). Cette règle est donc le SEUL endroit où
un mini-jeu redevient réellement cliquable/glissable. */
pointer-events: auto;
}
.docQuizCard,
.docQuizResultCard {
/* Toute la hauteur/largeur du DOCUMENT (retour utilisateur du
23/09/2026), pas juste une carte confortable centrée : la carte
elle-même (fond/bordure) couvre désormais toute la page ;
display:flex + justify-content:flex-start ALIGNE SON CONTENU EN
HAUT de cette grande surface (retour utilisateur du 23/09/2026 :
"le contenu aligner en haut", pas centré verticalement). */
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
background: linear-gradient(180deg, var(--doc-card), var(--doc-bg-2));
border: 1px solid var(--doc-border);
border-radius: 0;
padding: 1.8rem;
}
.docQuizResultCard {
text-align: center;
}
.docQuizKicker {
font-size: 12px;
font-weight: 700;
letter-spacing: 0.04em;
color: var(--doc-accent);
margin-bottom: 6px;
}
.docQuizPlayerTitle {
font-size: 1.15rem;
font-weight: 700;
margin: 0 0 20px;
}
.docQuizProgressTrack {
height: 4px;
background: var(--doc-border);
border-radius: 2px;
overflow: hidden;
margin-bottom: 10px;
}
.docQuizProgressFill {
height: 100%;
width: 0%;
background: linear-gradient(120deg, var(--doc-accent), var(--doc-accent-2));
transition: width 0.2s ease;
}
.docQuizMeta {
display: flex;
justify-content: space-between;
font-size: 13px;
color: var(--doc-muted);
font-weight: 600;
margin-bottom: 20px;
}
.docQuizQuestionText {
font-size: 17px;
font-weight: 700;
line-height: 1.3;
margin: 0 0 18px;
}
.docQuizOptions {
display: flex;
flex-direction: column;
gap: 10px;
}
.docQuizOption {
text-align: left;
padding: 13px 15px;
border: 1px solid var(--doc-border);
background: var(--doc-bg-2);
color: var(--doc-text);
border-radius: 0;
font-size: 15px;
font-weight: 500;
cursor: pointer;
display: flex;
align-items: center;
gap: 12px;
transition: border-color 0.15s ease;
}
.docQuizOption:hover:not(:disabled) {
border-color: var(--doc-accent);
}
.docQuizOptionLetter {
flex-shrink: 0;
width: 24px;
height: 24px;
border-radius: 50%;
border: 1px solid var(--doc-border);
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 12.5px;
color: var(--doc-muted);
}
.docQuizOption.is-correct {
border-color: var(--doc-quiz-success-border);
background: var(--doc-quiz-success-bg);
}
.docQuizOption.is-correct .docQuizOptionLetter {
border-color: var(--doc-quiz-success-border);
background: var(--doc-quiz-success-border);
color: #0b0e14;
}
.docQuizOption.is-wrong {
border-color: var(--doc-quiz-danger-border);
background: var(--doc-quiz-danger-bg);
}
.docQuizOption.is-wrong .docQuizOptionLetter {
border-color: var(--doc-accent);
background: var(--doc-accent);
color: #0b0e14;
}
.docQuizOption:disabled:not(.is-correct, .is-wrong) {
opacity: 0.45;
}
.docQuizFeedback {
display: none;
margin-top: 16px;
padding: 13px 15px;
border-radius: 0;
font-size: 14px;
line-height: 1.5;
}
.docQuizFeedback.is-visible {
display: block;
}
.docQuizFeedback.is-ok {
background: var(--doc-quiz-success-bg);
color: var(--doc-quiz-success-text);
border: 1px solid var(--doc-quiz-success-border);
}
.docQuizFeedback.is-ko {
background: var(--doc-quiz-danger-bg);
color: var(--doc-quiz-danger-text);
border: 1px solid var(--doc-quiz-danger-border);
}
.docQuizNextBar {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
.docQuizNextBtn {
padding: 0.75rem 1.6rem;
border-radius: 0;
border: none;
background: linear-gradient(120deg, var(--doc-accent), var(--doc-accent-2));
color: #14100c;
font-weight: 700;
font-size: 14px;
cursor: pointer;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease;
}
.docQuizNextBtn.is-visible {
opacity: 1;
pointer-events: auto;
}
.docQuizResultBig {
font-weight: 800;
font-size: 44px;
background: linear-gradient(120deg, var(--doc-accent), var(--doc-accent-2));
background-clip: text;
color: transparent;
line-height: 1;
}
.docQuizResultSub {
margin-top: 6px;
margin-bottom: 18px;
font-size: 13px;
color: var(--doc-muted);
font-weight: 600;
}
.docQuizRestartBtn {
padding: 0.7rem 1.5rem;
border-radius: 0;
background: transparent;
border: 1px solid var(--doc-border);
color: var(--doc-text);
font-weight: 600;
font-size: 14px;
cursor: pointer;
}
/* ---- Association : plateau de glisser-déposer (voir docs/plan/PLAN.md,
"relier visuellement deux éléments qui vont ensemble"). Réutilise
.docQuizFeedback/.docQuizKicker (même rôle visuel) plutôt que de
dupliquer ces règles. ---- */
.docAssocCard {
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
background: linear-gradient(180deg, var(--doc-card), var(--doc-bg-2));
border: 1px solid var(--doc-border);
border-radius: 0;
padding: 1.8rem;
}
.docAssocTitle {
font-size: 1.15rem;
font-weight: 700;
margin: 0 0 14px;
}
.docAssocMeta {
font-size: 13px;
color: var(--doc-muted);
font-weight: 600;
margin-bottom: 16px;
}
.docAssocBoard {
display: flex;
gap: 24px;
}
.docAssocColumn {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 10px;
}
.docAssocItem,
.docAssocSlot {
padding: 13px 15px;
border: 1px solid var(--doc-border);
background: var(--doc-bg-2);
color: var(--doc-text);
border-radius: 0;
font-size: 14px;
font-weight: 500;
transition: border-color 0.15s ease, opacity 0.15s ease;
}
.docAssocItem {
cursor: grab;
}
.docAssocItem:hover,
.docAssocSlot:hover {
border-color: var(--doc-accent);
}
.docAssocItem.is-selected {
border-color: var(--doc-accent);
box-shadow: var(--doc-glow);
}
.docAssocItem.is-matched,
.docAssocSlot.is-matched {
border-color: var(--doc-quiz-success-border);
background: var(--doc-quiz-success-bg);
color: var(--doc-quiz-success-text);
cursor: default;
opacity: 0.85;
}
.docAssocItem.is-wrong,
.docAssocSlot.is-wrong {
border-color: var(--doc-quiz-danger-border);
background: var(--doc-quiz-danger-bg);
}
/* ---- Barre de bouton "Recommencer" partagée par tous les mini-jeux SAUF
le Quiz (qui garde son propre écran de résultat séparé) : le plateau de
jeu reste affiché en permanence une fois terminé, seul ce bouton
apparaît/disparaît (voir retour utilisateur du 20/09/2026). ---- */
.docMinigameRestartBar {
display: none;
margin-top: 20px;
text-align: center;
}
.docMinigameRestartBar.is-visible {
display: block;
}
.docMinigameRestartBar button {
padding: 0.7rem 1.5rem;
border-radius: 0;
background: transparent;
border: 1px solid var(--doc-border);
color: var(--doc-text);
font-weight: 600;
font-size: 14px;
cursor: pointer;
}
/* ---- Association : panneau Propriétés — paires reliées visuellement par
un glyphe entre les deux champs (voir forgeDocRenderAssociationPairHtml,
static/document/js/document-editor.js). Empilement VERTICAL (pas côte à
côte) : la colonne du panneau Propriétés est trop étroite pour deux
champs de texte libre côte à côte, voir retour utilisateur du
20/09/2026. ---- */
.docAssocPairRow {
display: flex;
flex-direction: column;
gap: 8px;
border: 1px solid var(--doc-border);
border-radius: 10px;
padding: 12px;
margin-bottom: 10px;
background: var(--doc-card);
}
.docAssocPairInput {
width: 100%;
resize: vertical;
border: 1px solid var(--doc-border);
border-radius: 8px;
padding: 8px 10px;
font-size: 13px;
font-family: inherit;
color: var(--doc-text);
background: var(--doc-bg-2);
}
.docAssocPairLink {
align-self: center;
color: var(--doc-accent);
font-weight: 700;
font-size: 15px;
}
/* ---- Memory : panneau Propriétés — étiquette au-dessus de chaque champ
recto/verso (voir forgeDocRenderMemoryCardHtml). Réutilise
.docAssocPairRow/.docAssocPairInput (même carte, mêmes champs) plutôt
que de dupliquer leurs règles. ---- */
.docMemoryFaceLabel {
font-size: 11.5px;
font-weight: 700;
letter-spacing: 0.03em;
color: var(--doc-muted);
text-transform: uppercase;
margin-top: 4px;
}
/* ---- Memory : plateau de jeu (voir docs/plan/PLAN.md, "retourner des
cartes pour constituer des paires d'images identiques"). Réutilise
.docAssocTitle/.docAssocMeta/.docMinigameRestartBar (mêmes rôles
visuels, voir forgeDocRenderMemoryProps ci-dessus). ---- */
.docMemoryCardWrap {
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
background: linear-gradient(180deg, var(--doc-card), var(--doc-bg-2));
border: 1px solid var(--doc-border);
border-radius: 0;
padding: 1.8rem;
}
.docMemoryGrid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
gap: 12px;
}
.docMemoryCard {
aspect-ratio: 3 / 4;
perspective: 700px;
cursor: pointer;
}
.docMemoryCardInner {
position: relative;
width: 100%;
height: 100%;
transition: transform 0.4s ease;
transform-style: preserve-3d;
}
.docMemoryCard.is-flipped .docMemoryCardInner {
transform: rotateY(180deg);
}
.docMemoryCard.is-matched {
/* Une paire trouvée reste face retournée en PERMANENCE (voir
forgeDocMemoryFlip, static/document/js/document-editor.js — la
classe is-flipped n'est jamais retirée pour une carte appariée).
Pas d'opacité réduite ici : une carte à 55% d'opacité, à côté de
cartes face cachée pleinement opaques, se lisait comme "repartie
face cachée" (retour utilisateur du 20/09/2026) alors qu'elle
restait techniquement retournée — la bordure verte ci-dessous
suffit à signaler l'état "trouvée", sans jamais assombrir le
contenu révélé. */
cursor: default;
}
.docMemoryCardFace {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
padding: 6px;
border-radius: 0;
border: 1px solid var(--doc-border);
background: var(--doc-bg-2);
backface-visibility: hidden;
overflow: hidden;
text-align: center;
font-size: 12px;
color: var(--doc-text);
}
.docMemoryCardFace img {
max-width: 100%;
max-height: 60%;
object-fit: contain;
}
.docMemoryCardFront {
background: linear-gradient(135deg, var(--doc-accent), var(--doc-accent-2));
color: #14100c;
}
.docMemoryCardBackGlyph {
font-size: 26px;
font-weight: 800;
}
.docMemoryCardBack {
transform: rotateY(180deg);
border-color: var(--doc-accent);
}
.docMemoryCard.is-matched .docMemoryCardFace.docMemoryCardBack {
border-color: var(--doc-quiz-success-border);
background: var(--doc-quiz-success-bg);
color: var(--doc-quiz-success-text);
}
/* ---- Mots mêlés : grille interactive (voir docs/plan/PLAN.md, "mots
cachés horizontalement/verticalement/en diagonale"). Réutilise
.docAssocCard/.docAssocTitle/.docAssocMeta/.docMinigameRestartBar
(mêmes rôles visuels que l'Association/Memory ci-dessus). ---- */
.docMotsBoard {
display: flex;
flex-wrap: wrap;
gap: 24px;
align-items: flex-start;
}
.docMotsGrid {
display: grid;
gap: 3px;
width: min(100%, 420px);
user-select: none;
}
.docMotsCell {
aspect-ratio: 1;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid var(--doc-border);
background: var(--doc-bg-2);
color: var(--doc-text);
font-weight: 700;
font-size: 13px;
border-radius: 0;
cursor: pointer;
}
.docMotsCell.is-selecting {
border-color: var(--doc-accent);
background: rgb(255 95 46 / 18%);
}
.docMotsCell.is-found {
border-color: var(--doc-quiz-success-border);
background: var(--doc-quiz-success-bg);
color: var(--doc-quiz-success-text);
cursor: default;
}
.docMotsWordList {
display: flex;
flex-direction: column;
gap: 8px;
min-width: 140px;
}
.docMotsWordChip {
padding: 8px 12px;
border: 1px solid var(--doc-border);
border-radius: 0;
font-size: 13px;
font-weight: 600;
color: var(--doc-text);
background: var(--doc-bg-2);
}
.docMotsWordChip.is-found {
border-color: var(--doc-quiz-success-border);
background: var(--doc-quiz-success-bg);
color: var(--doc-quiz-success-text);
text-decoration: line-through;
}
.docUnknown {
font-size: 12.5px;
color: var(--doc-muted);
border: 1px dashed var(--doc-border);
border-radius: 8px;
padding: 10px;
}
.docZoomPill {
position: absolute;
bottom: 24px;
left: 50%;
transform: translateX(-50%);
background: var(--doc-card);
border: 1px solid var(--doc-border);
border-radius: 999px;
height: 36px;
padding: 0 6px;
display: flex;
align-items: center;
gap: 6px;
box-shadow: 0 2px 6px rgb(0 0 0 / 30%);
}
.docZoomPill button {
width: 26px;
height: 26px;
border-radius: 999px;
border: none;
background: transparent;
color: var(--doc-text);
cursor: pointer;
font-size: 15px;
font-weight: 700;
}
.docZoomPill button:hover {
background: var(--doc-bg-2);
}
.docZoomPill span {
font-size: 12.5px;
font-weight: 700;
color: var(--doc-muted);
width: 40px;
text-align: center;
}
/* ---- Pilule de navigation entre PAGES en Mode Aperçu (retour
utilisateur du 21/09/2026 : "il faut implémenter un système de page",
navigation "page par page avec Suivant/Précédent"). Même position que
.docZoomPill ci-dessus (jamais affichées en même temps : docZoomPill
est masquée en Aperçu, voir plus bas) — .is-visible posée par
forgeDocUpdatePageNavPill (static/document/js/document-editor.js)
uniquement en Mode Aperçu ET s'il y a plus d'une page, jamais un
sélecteur CSS seul (une pilule "Page 1 / 1" n'aurait aucun sens). ---- */
.docPageNavPill {
display: none;
position: absolute;
bottom: 24px;
left: 50%;
transform: translateX(-50%);
background: var(--doc-card);
border: 1px solid var(--doc-border);
border-radius: 999px;
height: 36px;
padding: 0 10px;
align-items: center;
gap: 10px;
box-shadow: 0 2px 6px rgb(0 0 0 / 30%);
}
.docPageNavPill.is-visible {
display: flex;
}
.docPageNavPill button {
width: 26px;
height: 26px;
border-radius: 999px;
border: none;
background: transparent;
color: var(--doc-text);
cursor: pointer;
font-size: 16px;
font-weight: 700;
}
.docPageNavPill button:hover:not(:disabled) {
background: var(--doc-bg-2);
}
.docPageNavPill button:disabled {
opacity: 0.35;
cursor: default;
}
.docPageNavPill span {
font-size: 12.5px;
font-weight: 700;
color: var(--doc-muted);
white-space: nowrap;
}
/* ---- Panneau GAUCHE à ONGLETS (retour utilisateur du 23/09/2026 :
"il dois avoir deux onglets [...] un onglet page [...] un onglet mise
en page avec les fonctionnalité et la disposition actuel sans la
section page") : "Pages" (gestion complète, panneau dédié plein
hauteur) et "Mise en page" (bibliothèque d'éléments, inchangée). ---- */
.docSidebarTabs {
display: flex;
flex-shrink: 0;
border-bottom: 1px solid var(--doc-border);
margin: -20px -16px 0;
padding: 0 8px;
}
.docSidebarTab {
flex: 1;
padding: 10px 8px;
border: none;
/* border-radius:0 obligatoire : le bouton Bulma générique (static/style.css)
pose border-radius:var(--bulma-control-radius) sur TOUT <button>, jamais
annulé par notre seule déclaration border-bottom ci-dessous (la cascade
CSS s'applique PROPRIÉTÉ PAR PROPRIÉTÉ, pas règle par règle) — sans ce
reset, le trait du bas suivait les coins arrondis et remontait sur les
côtés au lieu de rester plat (retour utilisateur du 23/09/2026). */
border-radius: 0;
border-bottom: 2px solid transparent;
background: none;
color: var(--doc-muted);
font-size: 13px;
font-weight: 600;
cursor: pointer;
}
.docSidebarTab:hover {
color: var(--doc-text);
}
.docSidebarTab.is-active {
/* Le plus simple des styles d'onglet Bulma (retour utilisateur du
23/09/2026) : SEUL le border-bottom change de couleur, le texte
reste neutre (pas de changement de couleur sur le libellé). */
border-bottom-color: var(--doc-accent);
}
.docSidebarTabPanel {
display: flex;
flex-direction: column;
gap: 22px;
min-height: 0;
flex: 1;
}
/* L'attribut natif [hidden] a la MÊME spécificité qu'une classe et cette
feuille de style charge APRÈS celle du navigateur : sans cette règle,
`.docSidebarTabPanel { display:flex; }` ci-dessus gagne la cascade et
annule le display:none natif de [hidden] — les deux panneaux
d'onglet restaient visibles empilés en même temps, peu importe
forgeDocSwitchSidebarTab (bug réel constaté le 23/09/2026). */
.docSidebarTabPanel[hidden] {
display: none;
}
#docTabPanelPages {
gap: 8px;
}
/* Bouton d'ajout EN HAUT, toujours visible (retour utilisateur du
23/09/2026 : "le bouton pour ajouter une page dois être en haut
toujours visible") — flex-shrink:0 pour ne jamais être compressé par
.docPageManagerList, seule cette dernière défile. */
.docPageManagerAdd,
.docPageManagerDeleteAll {
flex-shrink: 0;
}
.docPageManagerList {
display: flex;
flex-direction: column;
gap: 4px;
flex: 1;
min-height: 0;
overflow-y: auto;
/* Espace autour des rangées (retour utilisateur du 23/09/2026) — le
padding-right laisse aussi un peu d'air entre le texte et la barre
de défilement fine ci-dessous, jamais collée au bord. */
padding: 2px 6px 2px 2px;
/* Barre de défilement fine et discrète (retour utilisateur du
23/09/2026) — Firefox/Chromium récents (scrollbar-width/-color) ET
WebKit/Blink plus anciens (::-webkit-scrollbar) pour une couverture
large, jamais la barre large par défaut du navigateur. */
scrollbar-width: thin;
scrollbar-color: var(--doc-border) transparent;
}
.docPageManagerList::-webkit-scrollbar {
width: 6px;
}
.docPageManagerList::-webkit-scrollbar-track {
background: transparent;
}
.docPageManagerList::-webkit-scrollbar-thumb {
background: var(--doc-border);
border-radius: 3px;
}
.docPageManagerList::-webkit-scrollbar-thumb:hover {
background: var(--doc-muted);
}
.docPageRow {
display: flex;
align-items: center;
gap: 6px;
padding: 7px 8px;
border: 1px solid var(--doc-border);
border-radius: 8px;
background: var(--doc-card);
cursor: pointer;
}
.docPageRow.is-dragging {
opacity: 0.4;
}
.docPageRow:hover {
border-color: var(--doc-accent);
}
.docPageRow.is-active {
border-color: var(--doc-accent);
box-shadow: 0 0 0 1px var(--doc-accent);
}
/* Cible de dépôt d'un élément du canevas glissé depuis une AUTRE page
(retour utilisateur du 26/09/2026), distincte de .is-dragging
(glisser une PAGE) et de .is-active (page actuellement affichée). */
.docPageRow--dropTarget {
border-color: var(--doc-accent);
border-style: dashed;
box-shadow: 0 0 0 2px var(--doc-accent);
}
.docPageRow:focus-visible {
outline: 2px solid var(--doc-accent);
outline-offset: 1px;
}
.docPageRowHandle {
flex-shrink: 0;
color: var(--doc-muted);
font-size: 13px;
cursor: grab;
}
.docPageRowNumber {
flex-shrink: 0;
width: 20px;
height: 20px;
border-radius: 5px;
background: var(--doc-bg-2);
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 700;
color: var(--doc-muted);
}
.docPageRow.is-active .docPageRowNumber {
color: var(--doc-accent);
}
/* Titre tronqué, qui défile au survol si tronqué (retour utilisateur du
21/09/2026) — .has-overflow n'est posé que si le texte dépasse
réellement (voir forgeDocApplyTitleMarquee), jamais un défilement
systématique qui ferait sauter les titres courts. */
.docPageRowTitle {
flex: 1;
min-width: 0;
overflow: hidden;
white-space: nowrap;
}
.docPageRowTitleText {
display: inline-block;
font-size: 12.5px;
font-weight: 600;
color: var(--doc-muted);
transition: transform 0.6s ease;
}
.docPageRow.is-active .docPageRowTitleText {
color: var(--doc-text);
}
.docPageRowTitle.has-overflow:hover .docPageRowTitleText {
transform: translateX(calc(-1 * var(--marquee-distance, 0px)));
}
.docPageRowTitle[contenteditable="true"] {
outline: none;
cursor: text;
user-select: text;
}
.docPageRowTitle[contenteditable="true"] .docPageRowTitleText {
border-bottom: 1px solid var(--doc-accent);
color: var(--doc-text);
}
.docPageRowActions {
display: flex;
align-items: center;
gap: 2px;
flex-shrink: 0;
}
.docPageRowActions button {
width: 20px;
height: 20px;
border: none;
border-radius: 5px;
background: transparent;
color: var(--doc-muted);
font-size: 11px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.docPageRowActions button:hover:not(:disabled) {
background: var(--doc-bg-2);
color: var(--doc-text);
}
.docPageRowActions button:disabled {
opacity: 0.3;
cursor: default;
}
.docPageRowDelete:hover:not(:disabled) {
color: var(--doc-quiz-danger-border);
}
/* ---- RIGHT SIDEBAR : Propriétés ---- */
.docField {
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 14px;
}
.docFieldRow {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 14px;
}
.docField label,
.docFieldRow .docFieldLabel {
font-size: 13px;
color: var(--doc-muted);
}
.docField input,
.docField textarea,
.docField select {
border: 1px solid var(--doc-border);
border-radius: 8px;
padding: 8px 10px;
font-size: 13px;
color: var(--doc-text);
background: var(--doc-card);
font-family: inherit;
width: 100%;
resize: vertical;
}
.docSwatches {
display: flex;
gap: 8px;
}
.docSwatch {
width: 22px;
height: 22px;
border-radius: 50%;
border: 2px solid transparent;
cursor: pointer;
}
.docSwatch.is-active {
border-color: var(--doc-text);
}
.docSegmented {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.docSegBtn {
padding: 6px 10px;
border-radius: 8px;
border: 1px solid var(--doc-border);
background: var(--doc-card);
font-size: 12.5px;
font-weight: 600;
color: var(--doc-text);
cursor: pointer;
}
.docSegBtn.is-active {
background: rgb(255 95 46 / 12%);
color: var(--doc-accent);
border-color: var(--doc-accent);
}
.docToolbarRow {
display: flex;
gap: 6px;
margin-bottom: 14px;
}
.docFmtBtn {
width: 32px;
height: 32px;
border-radius: 8px;
border: 1px solid var(--doc-border);
background: var(--doc-card);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 14px;
font-weight: 800;
color: var(--doc-text);
}
.docFmtBtn.is-active {
background: rgb(255 95 46 / 12%);
border-color: var(--doc-accent);
color: var(--doc-accent);
}
.docFmtBtn.docFmtBtn--italic {
font-style: italic;
font-weight: 400;
}
.docFmtBtn.docFmtBtn--underline {
text-decoration: underline;
font-weight: 400;
}
.docDeleteBtn {
width: 100%;
height: 38px;
border-radius: 10px;
border: 1px solid var(--status-danger, #f04438);
background: transparent;
color: var(--status-danger, #f04438);
font-weight: 700;
cursor: pointer;
margin-top: 6px;
}
.docDeleteBtn:hover {
background: rgb(240 68 56 / 10%);
}
/* ---- Quiz : liste de questions répétable, chacune avec sa propre liste
de choix répétable (voir forgeDocRenderQuizProps, static/document/js/
document-editor.js) ---- */
.docQuizQuestionsLabel {
margin-top: 10px;
}
.docQuizQuestion {
border: 1px solid var(--doc-border);
border-radius: 10px;
padding: 12px;
margin-bottom: 10px;
background: var(--doc-card);
}
.docQuizQuestionHeader {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.docQuizRemoveBtn {
border: none;
background: transparent;
color: var(--doc-muted);
cursor: pointer;
font-size: 14px;
}
.docQuizRemoveBtn:hover {
color: var(--status-danger, #f04438);
}
.docQuizChoicesList {
display: flex;
flex-direction: column;
gap: 6px;
margin: 8px 0;
}
.docQuizChoiceRow {
display: flex;
align-items: center;
gap: 8px;
}
.docQuizChoiceRow input[type="text"] {
flex: 1;
}
/* ---- Scénario : panneau Propriétés — chaque choix garde sa case "bon
choix"/son texte (.docQuizChoiceRow, réutilisé tel quel) ET, en plus,
sa conséquence en dessous (voir forgeDocRenderScenarioChoiceHtml,
static/document/js/document-editor.js) — un bloc de plus que le Quiz,
d'où ce conteneur dédié. ---- */
.docScenarioChoiceBlock {
display: flex;
flex-direction: column;
gap: 6px;
}
.docQuizAddBtn {
width: 100%;
justify-content: center;
margin-top: 4px;
}
.docEmptyState {
font-size: 13px;
color: var(--doc-muted);
text-align: center;
padding: 30px 10px;
}
/* État "aucune page" du canevas (retour utilisateur du 26/09/2026 :
l'éditeur peut s'ouvrir sans page active) — occupe toute la hauteur
disponible de .docPageContent (flex column, voir plus haut) pour
centrer le message quel que soit vertical_align. */
.docEmptyPageState {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 15px;
}
/* ===== BOTTOM NAV (mobile) ===== */
.docBottomNav {
display: none;
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 58px;
background: var(--doc-bg-2);
border-top: 1px solid var(--doc-border);
z-index: 40;
align-items: stretch;
justify-content: space-around;
}
.docBottomNav button {
flex: 1;
background: transparent;
border: none;
color: var(--doc-muted);
cursor: pointer;
font-size: 12px;
font-weight: 600;
}
.docBottomNav button.is-active {
color: var(--doc-accent);
}
.docSidebarClose {
display: none;
}
/* ===== MODE APERÇU : plein écran RÉEL (Fullscreen API, voir
forgeDocSetPreviewMode), tout le chrome d'édition masqué — y compris
.docTopbar désormais (retour utilisateur du 23/09/2026 : "en mode
apercu en plein ecrant"), avec .docPreviewExitBtn comme seul moyen
de revenir à l'éditeur. ===== */
/* Bug réel constaté le 24/09/2026 (capture à l'appui : bande noire en
bas, la page ne fait pas toute la hauteur) : .docEditor3 s'appuie
sur flex:1 1 auto (voir sa règle de base) pour sa taille, une
propriété qui n'a de sens QUE comme enfant d'un conteneur flex
normal — une fois réellement passé en plein écran (élément peint
dans le "top layer" du navigateur, hors du flux normal), flex-basis
n'a plus de contexte parent et peut se résoudre à la taille du
CONTENU plutôt qu'à tout l'écran. width/height explicites en
secours, qui ne dépendent d'aucun mécanisme de layout implicite. */
.docEditor3:fullscreen,
.docEditor3.docEditor3--preview {
width: 100vw;
height: 100vh;
}
.docEditor3.docEditor3--preview .docTopbar,
.docEditor3.docEditor3--preview .docSidebarLeft,
.docEditor3.docEditor3--preview .docSidebarRight,
.docEditor3.docEditor3--preview .docBottomNav,
.docEditor3.docEditor3--preview .docZoomPill {
display: none;
}
.docEditor3.docEditor3--preview [data-element-id] {
cursor: default;
pointer-events: none;
}
.docEditor3.docEditor3--preview [data-element-id]:hover,
.docEditor3.docEditor3--preview [data-element-id].docSelected {
background: transparent;
outline: none;
}
/* Aucun espace autour du mini-jeu en Aperçu (retour utilisateur du
23/09/2026 : "pourquoi je voi de l'espace autour du composant mini
jeu en apercu ? il doit prendre toute la place") — la page elle-même
abandonne son format A4 fixe pour remplir tout l'écran plein écran,
plutôt qu'un rectangle A4 plus petit flottant avec des bords vides
autour. */
.docEditor3.docEditor3--preview .docCanvasArea {
padding: 0;
}
.docEditor3.docEditor3--preview .docPage {
width: 100%;
height: 100%;
aspect-ratio: unset;
/* Bug réel constaté le 24/09/2026 (capture + diagnostic console à
l'appui : .docEditor3/.docBodyWrap/.docCanvasArea faisaient bien
960px = window.innerHeight, mais .docPage seule ne faisait que
678.78px) — max-height (posé en édition pour plafonner la page au
format A4, voir la règle de base .docPage) reste actif ici et
plafonne la page à cette même hauteur MÊME EN PLEIN ÉCRAN, malgré
height:100% ci-dessus (max-height gagne toujours sur height dans
la résolution CSS). Neutralisé ici pour laisser height:100% agir
réellement. */
max-height: none;
min-height: 0;
border: none;
border-radius: 0;
box-shadow: none;
}
.docPreviewExitBtn {
display: none;
position: absolute;
top: 20px;
right: 20px;
z-index: 50;
align-items: center;
gap: 6px;
height: 36px;
padding: 0 16px;
border: 1px solid var(--doc-border);
border-radius: 10px;
background: var(--doc-bg-2);
color: var(--doc-text);
font-size: 13px;
font-weight: 600;
cursor: pointer;
}
.docPreviewExitBtn:hover {
border-color: var(--doc-accent);
}
.docEditor3.docEditor3--preview .docPreviewExitBtn {
display: flex;
}
/* ---- Modale générique (utilisée aujourd'hui par l'éditeur d'arbre du
mini-jeu Scénario, voir forgeDocOpenScenarioTreeModal) — overlay +
boîte de dialogue centrée, indépendante de tout framework externe
(aucune dépendance partagée avec game_engine, voir CLAUDE.md). ---- */
.docModalBackdrop {
display: none;
position: fixed;
inset: 0;
background: rgb(11 14 20 / 65%);
z-index: 200;
align-items: center;
justify-content: center;
padding: 24px;
color: var(--doc-text);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.docModalBackdrop.is-open {
display: flex;
}
.docModalDialog {
background: var(--doc-bg-2);
border: 1px solid var(--doc-border);
border-radius: 16px;
width: min(640px, 100%);
max-height: min(80vh, 720px);
display: flex;
flex-direction: column;
overflow: hidden;
}
.docModalHeader {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid var(--doc-border);
}
.docModalTitle {
font-size: 15px;
font-weight: 700;
color: var(--doc-text);
}
.docModalCloseBtn {
border: none;
background: transparent;
color: var(--doc-muted);
font-size: 16px;
cursor: pointer;
padding: 4px;
}
.docModalCloseBtn:hover {
color: var(--doc-text);
}
.docModalBody {
padding: 20px;
overflow-y: auto;
}
/* ---- Scénario : panneau Propriétés — une carte par scénario (titre +
nombre de nœuds + bouton d'ouverture de la modale), l'édition de
l'arbre lui-même se fait entièrement dans la modale (voir
forgeDocRenderScenarioTreeModal). ---- */
.docScenarioCard .docBtnSecondary {
width: 100%;
justify-content: center;
}
/* ---- Scénario : éditeur d'arbre en GRAPHE VISUEL (retour utilisateur
du 21/09/2026 : "passons à un véritable graphe visuel", remplace la
première version en liste de nœuds + menus déroulants). Dialogue bien
plus grand que la modale générique par défaut : un graphe a besoin de
place pour être lisible. ---- */
#docScenarioTreeModal .docModalDialog {
width: min(1180px, 95vw);
max-height: min(88vh, 820px);
}
#docScenarioTreeModal .docModalBody {
padding: 0;
overflow: hidden;
display: flex;
flex-direction: column;
min-height: 0;
}
.docScenarioGraphTitleRow {
padding: 16px 20px;
border-bottom: 1px solid var(--doc-border);
}
.docScenarioGraphTitleRow input {
width: 100%;
border: 1px solid var(--doc-border);
border-radius: 8px;
padding: 8px 10px;
font-size: 14px;
font-weight: 700;
color: var(--doc-text);
background: var(--doc-bg-2);
}
.docScenarioGraphToolbar {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 20px;
border-bottom: 1px solid var(--doc-border);
}
.docScenarioGraphHint {
font-size: 12.5px;
color: var(--doc-muted);
}
.docScenarioGraphHint.is-connecting {
color: var(--doc-accent);
font-weight: 600;
}
.docScenarioGraphSplit {
flex: 1 1 auto;
min-height: 0;
display: flex;
}
.docScenarioGraphCanvas {
position: relative;
flex: 1 1 auto;
min-width: 0;
overflow: auto;
background:
radial-gradient(circle, var(--doc-border) 1px, transparent 1px) 0 0 / 24px 24px,
var(--doc-bg-2);
cursor: default;
}
.docScenarioGraphCanvas.is-connecting {
cursor: crosshair;
}
.docScenarioGraphCanvas.is-connecting .docScenarioGraphNode {
cursor: crosshair;
}
.docScenarioGraphInner {
position: relative;
width: 2200px;
height: 1600px;
}
.docScenarioGraphSvg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.docScenarioGraphEdge {
fill: none;
stroke: var(--doc-muted);
stroke-width: 2;
}
.docScenarioGraphEdgeLabel {
position: absolute;
transform: translate(-50%, -50%);
background: var(--doc-bg-2);
border: 1px solid var(--doc-border);
border-radius: 6px;
padding: 2px 7px;
font-size: 11px;
font-weight: 600;
color: var(--doc-muted);
white-space: nowrap;
max-width: 140px;
overflow: hidden;
text-overflow: ellipsis;
}
.docScenarioGraphNode {
position: absolute;
width: 180px;
border: 1px solid var(--doc-border);
border-radius: 10px;
background: var(--doc-card);
padding: 10px 12px;
cursor: grab;
user-select: none;
box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
}
.docScenarioGraphNode:active {
cursor: grabbing;
}
.docScenarioGraphNode.is-root {
border-color: var(--doc-accent);
}
.docScenarioGraphNode.is-selected {
border-color: var(--doc-accent);
box-shadow: 0 0 0 2px var(--doc-accent);
}
.docScenarioGraphNode.is-connect-target:hover {
border-color: var(--doc-quiz-success-border);
box-shadow: 0 0 0 2px var(--doc-quiz-success-border);
}
.docScenarioGraphNodeLabel {
font-size: 10.5px;
font-weight: 700;
letter-spacing: 0.03em;
text-transform: uppercase;
color: var(--doc-accent);
margin-bottom: 4px;
}
.docScenarioGraphNodeText {
font-size: 12.5px;
line-height: 1.35;
color: var(--doc-text);
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 4;
overflow: hidden;
}
.docScenarioGraphInspector {
flex: 0 0 300px;
border-left: 1px solid var(--doc-border);
padding: 16px;
overflow-y: auto;
}
.docScenarioGraphInspectorEmpty {
font-size: 13px;
color: var(--doc-muted);
text-align: center;
padding: 30px 10px;
}
.docScenarioChoiceTargetRow {
display: flex;
align-items: center;
gap: 6px;
margin-top: 4px;
}
.docScenarioChoiceTargetLabel {
flex: 1;
min-width: 0;
font-size: 12px;
color: var(--doc-muted);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.docScenarioChoiceLinkBtn {
border: 1px solid var(--doc-border);
background: transparent;
color: var(--doc-text);
border-radius: 6px;
padding: 3px 8px;
font-size: 11.5px;
cursor: pointer;
white-space: nowrap;
}
.docScenarioChoiceLinkBtn:hover {
border-color: var(--doc-accent);
}
.docScenarioChoiceLinkBtn.is-active {
border-color: var(--doc-accent);
background: var(--doc-accent);
color: #14100c;
}
/* ===== RESPONSIVE (chrome de l'éditeur lui-même, <900px) ===== */
@media (width <= 900px) {
.docCrumbDim {
display: none;
}
.docTopbar {
padding: 0 14px;
}
.docBtnLabel {
display: none;
}
.docBtnSecondary {
padding: 0 10px;
}
.docWidthPicker {
display: none;
}
.docCanvasArea {
padding: 18px 14px 84px;
}
.docPage {
padding: 28px 20px;
}
.docSidebar {
/* position:absolute relative à .docBodyWrap (position:relative, voir
plus haut) plutôt que position:fixed relative au viewport : ce
dernier subirait le même rognage par overflow:hidden que .docEditor3
(voir son commentaire) — inutile de toute façon ici, .docBodyWrap
exclut déjà .docTopbar/.docBottomNav (ses frères, jamais ses
parents), inset:0 suffit sans aucun calcul de hauteur de bandeau. */
position: absolute;
inset: 0;
width: 100%;
z-index: 30;
transition: transform 0.25s ease;
}
.docSidebarLeft {
transform: translateX(-100%);
}
.docSidebarRight {
transform: translateX(100%);
}
.docSidebar.is-open {
transform: translateX(0);
}
.docSidebarClose {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
font-weight: 600;
color: var(--doc-muted);
margin-bottom: 16px;
cursor: pointer;
padding: 0 16px;
}
.docBottomNav {
display: flex;
}
.docAssocBoard {
flex-direction: column;
}
}
/* ---- Modale "Utiliser un modèle" (voir static/document/js/
document-editor.js, forgeDocOpenTemplateModal) : PLEIN ÉCRAN (retour
utilisateur du 24/09/2026 : "la modale dois occuper tous l'écran"),
divisée en deux colonnes STRICTEMENT ÉGALES — à gauche une grille de
cartes (une par thème du catalogue), à droite l'aperçu RÉEL du thème
sélectionné (chargé dans un <iframe> pointant sur
/document/<slug>/theme/<id>/preview, jamais un style recopié à la
main). #docTemplateModal remplace ici .docModalBackdrop/.docModalDialog
par sa propre géométrie plutôt que la modale générique centrée/à
padding fixe (voir .docModalBackdrop/.docModalDialog de base
ci-dessus, toujours utilisées telles quelles par #docScenarioTreeModal). ---- */
#docTemplateModal.docModalBackdrop {
padding: 0;
}
.docModalDialog--wide {
width: 100vw;
height: 100vh;
max-height: 100vh;
border-radius: 0;
}
.docTemplateModalBody {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0;
padding: 0;
overflow: hidden;
min-height: 0;
flex: 1;
}
.docTemplateList {
min-height: 0;
overflow-y: auto;
padding: 24px;
border-right: 1px solid var(--doc-border);
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
align-content: start;
gap: 16px;
}
.docTemplateCard {
display: flex;
flex-direction: column;
gap: 6px;
text-align: left;
border: 1px solid var(--doc-border);
border-radius: 12px;
background: var(--doc-card);
color: var(--doc-text);
padding: 18px;
cursor: pointer;
min-height: 100px;
}
.docTemplateCard.is-active {
border-color: var(--doc-accent);
box-shadow: 0 0 0 1px var(--doc-accent);
}
.docTemplateCardName {
font-size: 15px;
font-weight: 700;
}
.docTemplateCardCategory {
font-size: 12px;
color: var(--doc-muted);
}
.docTemplatePreviewPane {
min-width: 0;
min-height: 0;
display: flex;
flex-direction: column;
background: var(--doc-bg);
}
.docTemplatePreviewFrame {
flex: 1;
min-height: 0;
border: none;
display: none;
}
.docTemplatePreviewFrame.is-visible {
display: block;
}
.docTemplatePreviewEmpty {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
color: var(--doc-muted);
font-size: 13px;
text-align: center;
padding: 24px;
}
/* `display: flex` ci-dessus est une règle AUTEUR, qui bat TOUJOURS le
`display: none` d'origine UA de [hidden] quelle que soit la
spécificité/l'ordre (même bug déjà rencontré pour
.docSidebarTabPanel[hidden]) — sans cet override explicite, l'état
vide et l'iframe restent visibles SIMULTANÉMENT, chacun prenant
flex:1, ce qui coupe l'aperçu en deux au lieu de lui laisser toute la
place (bug réel constaté le 24/09/2026, capture à l'appui). */
.docTemplatePreviewEmpty[hidden] {
display: none;
}
.docTemplatePreviewActions {
flex-shrink: 0;
display: flex;
gap: 10px;
padding: 14px 16px;
border-top: 1px solid var(--doc-border);
}
.docTemplatePreviewActions[hidden] {
display: none;
}
.docTemplatePreviewActions .docBtnSecondary {
flex: 1;
justify-content: center;
}
@media (width <= 720px) {
.docTemplateModalBody {
grid-template-columns: 1fr;
grid-template-rows: 1fr 1fr;
}
.docTemplateList {
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
border-right: none;
border-bottom: 1px solid var(--doc-border);
}
}
/* ---- Éditeur de bordure par côté (panneau Propriétés — voir
static/document/js/document-editor.js, forgeDocRenderBoxFieldsHtml) :
une ligne par côté (style/épaisseur/couleur), réutilisée par tous les
kinds au fil de l'audit du 26/09/2026. ---- */
.docBorderEditor {
display: flex;
flex-direction: column;
gap: 6px;
}
.docBorderSideRow {
display: grid;
grid-template-columns: 48px 1fr 56px 1fr;
gap: 6px;
align-items: center;
}
.docBorderSideLabel {
font-size: 12px;
color: var(--doc-muted);
}
.docBorderStyleSelect {
width: 100%;
min-width: 0;
font-size: 12px;
padding: 5px 6px;
border: 1px solid var(--doc-border);
border-radius: 6px;
background: var(--doc-bg-2);
color: var(--doc-text);
}
.docBorderWidthRange {
width: 100%;
min-width: 0;
}
.docBorderColorInput {
width: 100%;
min-width: 0;
height: 28px;
padding: 2px;
border: 1px solid var(--doc-border);
border-radius: 6px;
background: var(--doc-bg-2);
cursor: pointer;
}
/* ---- Curseurs génériques à case "activer" (voir static/document/js/
document-editor.js, forgeDocRenderToggleSliderHtml) — retour
utilisateur du 26/09/2026 : plus aucune valeur CSS saisie à la main,
uniquement des curseurs/cases/listes/sélecteurs de couleur. ---- */
.docSliderField {
margin-bottom: 10px;
}
.docSliderRow {
display: flex;
align-items: center;
gap: 8px;
}
/* `.docField input` (règle générique plus haut) pose padding/bordure/
fond/width:100% pensés pour un champ texte — remis à plat ici pour
une case à cocher/un curseur natifs, sinon ils héritent cette
apparence de champ texte (bug réel constaté le 26/09/2026, capture à
l'appui : case à cocher démesurée et centrée, curseur poussé hors
panneau). */
.docSliderRow input[type='checkbox'] {
flex-shrink: 0;
width: auto;
padding: 0;
border: none;
background: none;
cursor: pointer;
}
.docSliderRow input[type='range'] {
flex: 1;
min-width: 0;
width: auto;
padding: 0;
border: none;
background: none;
}
.docSliderRow input[type='range']:disabled {
opacity: 0.4;
}
.docSliderValue {
flex-shrink: 0;
min-width: 3em;
text-align: right;
font-size: 11.5px;
font-weight: 600;
color: var(--doc-muted);
font-variant-numeric: tabular-nums;
}
.docColorFieldRow {
display: flex;
align-items: center;
gap: 8px;
}
.docColorFieldRow input[type='color'] {
width: 40px;
height: 28px;
padding: 2px;
border: 1px solid var(--doc-border);
border-radius: 6px;
background: var(--doc-bg-2);
cursor: pointer;
}
#docPropsPanel input[type='color'] {
width: 40px;
height: 28px;
padding: 2px;
border: 1px solid var(--doc-border);
border-radius: 6px;
background: var(--doc-bg-2);
cursor: pointer;
}
/* ---- Image : légende, lien, plein écran (voir document_engine/
rendering/render_document_element.py::_render_image et
static/document/js/document-editor.js::forgeDocOpenImageLightbox). ---- */
.docImageFigure {
margin: 0;
display: flex;
flex-direction: column;
gap: 8px;
}
.docImageCaption {
font-size: 12.5px;
color: var(--doc-muted);
text-align: center;
}
.docImageLink {
display: block;
}
.docImageLightboxTrigger {
display: block;
}
.docEditor3.docEditor3--preview .docImageLightboxTrigger {
cursor: zoom-in;
}
.docImageLightboxOverlay {
display: none;
position: fixed;
inset: 0;
z-index: 300;
background: rgb(11 14 20 / 90%);
align-items: center;
justify-content: center;
padding: 40px;
cursor: zoom-out;
}
.docImageLightboxOverlay.is-open {
display: flex;
}
.docImageLightboxImg {
max-width: 100%;
max-height: 100%;
border-radius: 8px;
box-shadow: 0 20px 60px -20px rgb(0 0 0 / 60%);
}