/* 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 (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
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