Files
Forge-Engine/static/document/document-editor.css
T
williamandClaude Sonnet 5 ce750ec697 Implemente le mini-jeu Association (glisser-deposer par paires)
document_engine/labels/association_config.py (nouveau) : modele de
donnees, meme convention resolve_X/sanitize_X que quiz_config.py -
DEFAULT_ASSOCIATION_CONFIG, sanitize_association_config (chaque paire
doit avoir ses deux cotes non vides, sinon supprimee silencieusement ;
liste tronquee a MAX_PAIRS=8).

Cote serveur, routes/document/document_element_update.py revalide
desormais aussi l'association avant persistance (meme raisonnement que
pour le quiz). Le rendu (_render_association) affiche un resume reel
(nombre de paires) et, des qu'au moins une paire existe, un plateau de
glisser-deposer REELEMENT interactif en Mode Apercu
(_render_association_player) : les deux colonnes (termes/
correspondances) sont melangees independamment (random.shuffle,
melange d'affichage documente dans CODE_QUALITY.md) puis embarquees en
JSON dans un attribut data-assoc-config.

Cote editeur, le panneau Proprietes d'une association ("relier
visuellement deux champs qui vont ensemble") est une liste de paires
repetable, chaque ligne reliant visuellement un champ "Element" et un
champ "Correspondance" par un glyphe ↔. Le plateau jouable en Apercu
(static/document/js/document-editor.js) supporte deux facons de jouer,
toutes deux reelles : glisser-deposer HTML5 natif, ou cliquer une
carte puis son emplacement (repli pour les appareils sans support
fiable du drag) - bonne association verrouillee en vert, mauvaise
signalee puis reinitialisee, ecran de resultat une fois toutes les
paires associees.

Bug reel trouve ET corrige via simulation DOM complete (glisser-
deposer + clic simules, pas juste un chargement de page) : le
feedback visuel reutilisait la classe CSS du quiz via une
reaffectation de className qui supprimait au passage la classe
d'identite docAssocFeedback, rendant l'element introuvable des le
premier essai de match (aurait plante en usage reel des la premiere
tentative). Corrige en gardant toujours les deux classes ensemble.

Tests : 8 tests purs (tests/document/test_association_config.py, sans
Flask) + 1 test de route verifiant la sanitization a l'ecriture.

SKIP=djlint : backlog H021 pre-existant, aucun template touche ici.
ruff/mypy --strict/vulture/bandit/import-linter/eslint/stylelint tous
verts ; 50 tests document verifies frais. Verification manuelle live
complete : ajout, sanitization sur paire invalide, rendu du plateau,
et simulation DOM du gameplay reel (glisser-deposer correct/incorrect,
clic-selection, progression, ecran de resultat) - script de
diagnostic non conserve dans le depot.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 13:01:21 +02:00

1262 lines
26 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;
}
.docEditor3 {
--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;
/* 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"] {
--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 * {
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;
}
.docShapesGrid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.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--shape {
padding: 10px 8px;
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
}
.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--rectangle::before { content: "▭"; }
.docLibIcon--cercle::before { content: "○"; }
.docLibIcon--triangle::before { content: "△"; }
.docLibIcon--trait::before { content: "―"; }
.docLibIcon--paragraphe::before { content: "¶"; }
.docLibIcon--image::before { content: "🖼"; }
.docLibIcon--bouton::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;
justify-content: center;
position: relative;
}
.docPage {
width: 760px;
max-width: 100%;
height: fit-content;
background: var(--doc-card);
border: 1px solid var(--doc-border);
border-radius: 16px;
box-shadow: 0 1px 3px rgb(0 0 0 / 30%);
padding: 48px 52px;
position: relative;
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, pour que .docSnapGridOverlay,
sa voisine, survive à chaque rafraîchissement). */
.docPageContent {
display: flex;
flex-direction: column;
gap: 18px;
}
.docPage.docPage--fixedWidth {
width: var(--doc-preview-width, 760px);
padding: 28px 20px;
}
.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);
}
.docShape[data-element-id] {
padding: 0;
margin: 0;
border-radius: 0;
}
.docShape[data-element-id].docSelected {
outline: 2px solid var(--doc-accent);
background: transparent;
}
.docText {
outline: none;
}
.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;
}
img.docImage {
max-width: 100%;
border-radius: 10px;
display: block;
}
.docButton {
border: none;
border-radius: 999px;
padding: 10px 22px;
font-weight: 700;
font-size: 14px;
background: var(--doc-accent);
color: #14100c;
cursor: pointer;
}
.docMinigame {
border: 1px solid var(--doc-accent);
border-radius: 12px;
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 : mini-jeux interactifs en Mode Aperçu (voir
docs/plan/maquettes/document-formation-web.html — référence visuelle
du Quiz). Masqués en édition (seul .docMinigameBadge y est visible,
voir plus haut) ; jamais les deux visibles en même temps. ---- */
.docQuizPlayer,
.docAssocPlayer {
display: none;
}
.docEditor3.docEditor3--preview [data-kind="quiz"] .docMinigameBadge,
.docEditor3.docEditor3--preview [data-kind="association"] .docMinigameBadge {
display: none;
}
.docEditor3.docEditor3--preview [data-kind="quiz"] .docQuizPlayer,
.docEditor3.docEditor3--preview [data-kind="association"] .docAssocPlayer {
display: block;
}
.docEditor3.docEditor3--preview [data-kind="quiz"],
.docEditor3.docEditor3--preview [data-kind="association"] {
border: none;
background: transparent;
}
.docEditor3.docEditor3--preview .docQuizPlayer,
.docEditor3.docEditor3--preview .docQuizPlayer *,
.docEditor3.docEditor3--preview .docAssocPlayer,
.docEditor3.docEditor3--preview .docAssocPlayer * {
/* .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 — réactivé ici spécifiquement pour que ces mini-jeux restent
RÉELLEMENT cliquables/glissables. */
pointer-events: auto;
}
.docQuizCard,
.docQuizResultCard {
background: linear-gradient(180deg, var(--doc-card), var(--doc-bg-2));
border: 1px solid var(--doc-border);
border-radius: 16px;
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: 10px;
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: 10px;
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: 10px;
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: 10px;
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,
.docAssocResultCard {
background: linear-gradient(180deg, var(--doc-card), var(--doc-bg-2));
border: 1px solid var(--doc-border);
border-radius: 16px;
padding: 1.8rem;
}
.docAssocResultCard {
text-align: center;
}
.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: 10px;
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);
}
.docAssocRestartBtn {
margin-top: 20px;
padding: 0.7rem 1.5rem;
border-radius: 10px;
background: transparent;
border: 1px solid var(--doc-border);
color: var(--doc-text);
font-weight: 600;
font-size: 14px;
cursor: pointer;
}
.docAssocResultBig {
font-size: 40px;
line-height: 1;
color: var(--doc-quiz-success-border);
}
.docAssocResultSub {
margin-top: 10px;
margin-bottom: 4px;
font-size: 14px;
color: var(--doc-text);
}
/* ---- Association : panneau Propriétés — paires reliées visuellement par
un glyphe entre les deux champs (voir forgeDocRenderAssociationPairHtml,
static/document/js/document-editor.js). ---- */
.docAssocPairRow {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
}
.docAssocPairInput {
flex: 1;
min-width: 0;
}
.docAssocPairLink {
flex-shrink: 0;
color: var(--doc-accent);
font-weight: 700;
}
.docUnknown {
font-size: 12.5px;
color: var(--doc-muted);
border: 1px dashed var(--doc-border);
border-radius: 8px;
padding: 10px;
}
/* ---- grille d'accroche (formes libres) — voir docs/plan/PLAN.md,
"grille d'accroche réelle" : visible en overlay pendant le déplacement
d'une forme (voir .docPage--dragging-shape ci-dessous), jamais
décorative. */
.docSnapGridOverlay {
position: absolute;
inset: 0;
pointer-events: none;
z-index: 1;
opacity: 0;
transition: opacity 0.1s ease;
background-image:
linear-gradient(to right, rgb(255 95 46 / 12%) 1px, transparent 1px),
linear-gradient(to bottom, rgb(255 95 46 / 12%) 1px, transparent 1px);
background-size: var(--doc-snap-size, 8px) var(--doc-snap-size, 8px);
}
.docPage.docPage--dragging-shape .docSnapGridOverlay {
opacity: 1;
}
.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;
}
/* ---- 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;
}
.docQuizAddBtn {
width: 100%;
justify-content: center;
margin-top: 4px;
}
.docEmptyState {
font-size: 13px;
color: var(--doc-muted);
text-align: center;
padding: 30px 10px;
}
/* ===== 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 : masque tout le chrome d'édition ===== */
.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;
}
.docEditor3.docEditor3--preview .docCanvasArea {
padding: 40px;
}
/* ===== 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;
}
}