Ajoute la fonctionnalite quiz autonome/plein ecran a la boite a quiz
Introduit la double categorie de modeles (boite de dialogue / page de quiz plein ecran) avec plein ecran, minuteur, score integre et ecran de resultat pour les modeles page ; ajoute les modeles "Manga" (boite et page) et "Classique" (page), pilotables aussi par l'assistant IA Ruby. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
559331f9cf
commit
7db4803b93
+559
-2
@@ -609,6 +609,24 @@ body.builderBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden; d
|
||||
.iconGallery.personnageGallery{ grid-template-columns:repeat(2, 1fr); gap:10px; max-height:420px; }
|
||||
.personnageThumb{ width:100%; height:100%; object-fit:contain; image-rendering:pixelated; }
|
||||
|
||||
/* "🖼️ Modèles" de la "❓ Boîte à quiz" (dialogue ET page, voir
|
||||
templates/scene_edit.html, screens/rendering/quiz_box_templates.py) —
|
||||
chaque modèle est un thème complet et autonome (structure/police/
|
||||
couleurs propres) : plutôt que reproduire sa structure/CSS en
|
||||
miniature (fragile, vite désynchronisé d'un vrai thème complet), la
|
||||
vignette est une vraie capture d'écran du modèle (static/
|
||||
quiz-templates/<clé>.png, une par modèle). */
|
||||
.quizTemplateGrid{ display:grid; grid-template-columns:repeat(2, 1fr); gap:10px; margin-top:6px; }
|
||||
.quizTemplateTile{
|
||||
display:flex; flex-direction:column; gap:6px; padding:6px; background:var(--panel2);
|
||||
border:2px solid var(--border); border-radius:8px; cursor:pointer;
|
||||
}
|
||||
.quizTemplateTile:hover{ border-color:var(--accent); }
|
||||
.quizTemplateTile.is-selected{ border-color:var(--accent); box-shadow:0 0 0 2px var(--accent) inset; }
|
||||
.quizTemplateName{ font-size:11.5px; color:var(--text-dim); text-align:center; }
|
||||
.quizTemplatePreviewImg{ display:block; width:100%; height:170px; overflow:hidden; border-radius:4px; }
|
||||
.quizTemplatePreviewImg img{ width:100%; height:100%; object-fit:cover; object-position:top; display:block; }
|
||||
|
||||
/* "Mes assets" — sous-sections Images/Sons/Vidéos (demande explicite :
|
||||
"voir, utiliser ou supprimer des son, image et vidéo") : une tuile
|
||||
image garde son bouton de pose (clic/glisser-déposer, voir
|
||||
@@ -795,6 +813,27 @@ body.builderBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden; d
|
||||
}
|
||||
.playScreen .quizBoxWidget{ display:none; }
|
||||
.playScreen .quizBoxWidget.is-active{ display:flex; animation:quizPopIn .3s ease; }
|
||||
/* Un quiz PLEIN ÉCRAN est déplacé en enfant direct de <body> au premier
|
||||
affichage (voir forgeEscapeCameraForFullscreen, dialogue-box-controller.js
|
||||
— ".playScreen.playScene" est transformé/centré, ce qui casserait
|
||||
position:fixed s'il y restait) : plus dans ".playScreen" ensuite, donc
|
||||
plus couvert par les deux règles ci-dessus — cette classe reproduit le
|
||||
même masquage par défaut/affichage sur ".is-active", posée au moment du
|
||||
déplacement. */
|
||||
.quizBoxWidget--detachedForFullscreen{ display:none; }
|
||||
.quizBoxWidget--detachedForFullscreen.is-active{ display:flex; animation:quizPopIn .3s ease; }
|
||||
/* Bandeau partagé header + score/minuteur (demande explicite : "le score
|
||||
doit être à l'intérieur de la boîte à quiz, pas à l'extérieur") — le
|
||||
score (topbar) vit dans un élément FRÈRE du header (voir le
|
||||
commentaire d'en-tête de screens/rendering/quiz_box_templates.py, JS
|
||||
écrase les enfants du header via .textContent), posé ici CÔTE À CÔTE
|
||||
avec lui dans le MÊME bandeau plutôt qu'empilé au-dessus dans une bande
|
||||
à part — un seul fond commun (posé sur CE conteneur, jamais sur le
|
||||
header seul, voir _quiz_header_row_html), pas deux bandes de couleurs
|
||||
différentes. */
|
||||
.quizBoxHeaderRow{ display:flex; align-items:stretch; }
|
||||
.quizBoxHeaderRow .quizBoxHeader{ flex:1 1 auto; min-width:0; }
|
||||
.quizBoxHeaderRow .quizBoxTopBar{ width:auto; flex:0 0 auto; }
|
||||
.quizBoxHeader{ padding:8px 12px; font-weight:700; }
|
||||
.quizBoxBody{ padding:10px 12px; flex:1; overflow-y:auto; display:flex; flex-direction:column; gap:10px; }
|
||||
.quizBoxQuestionText{ margin:0; font-weight:600; }
|
||||
@@ -818,12 +857,530 @@ body.builderBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden; d
|
||||
.quizBoxChoiceBtn.is-wrong{ background:#e0524d !important; border-color:#e0524d !important; animation:quizWrongShake .4s ease; }
|
||||
/* Bouton "Continuer →" (voir forgeQuizBoxAnswer/forgeQuizBoxContinue,
|
||||
RGAA/WCAG 2.2.1) — remplace l'ancienne avancée automatique après un
|
||||
délai fixe : l'apprenant décide lui-même quand passer à la suite. */
|
||||
délai fixe : l'apprenant décide lui-même quand passer à la suite.
|
||||
TOUJOURS présent dès la question (posé désactivé par forgeShowQuizBox,
|
||||
activé par forgeQuizBoxAnswer une fois répondu — demande explicite,
|
||||
comme la maquette manga validée) : ":disabled" générique ici, commun
|
||||
à TOUS les modèles, quelle que soit leur propre couleur d'action. */
|
||||
.quizBoxContinueBtn{
|
||||
margin-top:4px; background:#2f6fed; color:#fff; border:1px solid #2f6fed;
|
||||
border-radius:6px; padding:8px 10px; font:inherit; font-size:13px; font-weight:600; cursor:pointer;
|
||||
}
|
||||
.quizBoxContinueBtn:hover{ background:#255bc4; }
|
||||
.quizBoxContinueBtn:hover:not(:disabled){ background:#255bc4; }
|
||||
.quizBoxContinueBtn:disabled{ opacity:.45; cursor:not-allowed; }
|
||||
|
||||
/* "Plein écran" (demande explicite : quiz RH autonome, voir
|
||||
screens/rendering/quiz_box_config.py) — la boîte à quiz elle-même
|
||||
devient la surface plein écran (pas besoin d'un overlay séparé comme
|
||||
static/js/play/video-overlay.js : son propre fond couvre déjà tout).
|
||||
z-index 1001 : au-dessus de .forgeVideoOverlay (1000) par précaution,
|
||||
au cas où une vidéo serait encore visible. Commun à tous les modèles. */
|
||||
.quizBoxWidget.is-fullscreen{
|
||||
position:fixed !important; inset:0 !important; left:0 !important; top:0 !important;
|
||||
width:100vw !important; height:100vh !important;
|
||||
z-index:1001 !important; display:flex !important; flex-direction:column !important;
|
||||
}
|
||||
/* Demande explicite : "pas de bordure visible, pas de bord arrondi" en
|
||||
plein écran — une bordure/un coin arrondi/une ombre portée n'ont plus
|
||||
de sens une fois que la boîte occupe TOUT l'écran (rien derrière/autour
|
||||
d'elle à en distinguer), quel que soit le modèle. */
|
||||
.quizBoxWidget.is-fullscreen{
|
||||
border-radius:0 !important; box-shadow:none !important; border:none !important;
|
||||
}
|
||||
/* Bug corrigé : "rien ne s'affiche" — align-items:center/justify-content:center
|
||||
posés ici auparavant retiraient l'étirement (stretch) par défaut d'un
|
||||
conteneur flex en colonne, réduisant .quizBoxHeader/.quizBoxBody à la
|
||||
largeur de LEUR PROPRE contenu au lieu de toute la largeur — dans
|
||||
certains navigateurs, combiné à .quizBoxBody (lui-même un conteneur
|
||||
flex en colonne, voir plus haut), ce rétrécissement en cascade
|
||||
ramenait la zone de texte à une largeur quasi nulle, rendant le
|
||||
header/la question/les choix invisibles bien qu'ils soient bien posés
|
||||
dans le DOM avec leur texte. Un simple empilement plein largeur/pleine
|
||||
hauteur (comportement déjà éprouvé hors plein écran) suffit : pas
|
||||
besoin de centrer quoi que ce soit, la boîte occupe déjà tout l'écran.*/
|
||||
.quizBoxWidget.is-fullscreen .quizBoxBody{ min-height:0; }
|
||||
/* Éditeur (voir templates/scene_edit.html) : occuper le VIEWPORT du
|
||||
navigateur (comme au jeu) casserait l'édition — un fond opaque
|
||||
couvrirait aussi le panneau de propriétés. "Plein écran" occupe ici
|
||||
tout le CANEVAS (le "monde" de la scène) à la place, posé sur
|
||||
.canvasElement (voir plus bas, jamais sur .quizBoxWidget lui-même —
|
||||
déjà forcé à 100%/100% de .canvasElement par .canvasElementInner >
|
||||
.quizBoxWidget, voir plus haut dans ce fichier) : suffisant pour que
|
||||
le créateur voie tout de suite l'effet, sans avoir à lancer le jeu.
|
||||
La "notion de caméra disparaît" (demande explicite) : le cadre "🎥
|
||||
Champ de la caméra" est masqué côté serveur dès qu'un quiz plein écran
|
||||
existe sur l'écran (voir routes/scenes/scene_edit_view.py), puisqu'il
|
||||
n'a plus aucun sens une fois que ce widget couvre tout, caméra ou pas.*/
|
||||
.canvasElement.isQuizFullscreenPreview{
|
||||
left:0 !important; top:0 !important; width:100% !important; height:100% !important; z-index:9000 !important;
|
||||
}
|
||||
/* Barre score/minuteur — visible SEULEMENT en plein écran (une boîte à
|
||||
quiz "normale", posée dans le décor, n'a pas besoin d'afficher un
|
||||
score/minuteur en permanence) : masquée par défaut, quel que soit le
|
||||
modèle, pour ne rien changer à l'apparence d'un quiz existant. */
|
||||
.quizBoxTopBar{
|
||||
display:none; width:100%; padding:6px 10px; font-weight:700; font-size:13px;
|
||||
align-items:center; justify-content:space-between; gap:10px; box-sizing:border-box;
|
||||
}
|
||||
.quizBoxWidget.is-fullscreen .quizBoxTopBar{ display:flex; }
|
||||
.quizBoxTimerDisplay:empty{ display:none; }
|
||||
.quizBoxTimerDisplay{
|
||||
font-variant-numeric:tabular-nums; padding:2px 10px; border-radius:999px;
|
||||
background:rgba(255,255,255,.15);
|
||||
}
|
||||
.quizBoxTimerDisplay.is-urgent{ background:#e0524d; color:#fff; }
|
||||
|
||||
/* ---------- Modèle "Manga" (boîte de dialogue) — voir
|
||||
screens/rendering/quiz_box_templates.py::render_quiz_box_manga_dialogue,
|
||||
validé par le créateur via une maquette interactive. Pendant, en
|
||||
petite carte flottante (JAMAIS plein écran), du modèle "page de quiz"
|
||||
"Manga" plus bas — réutilise la structure PARTAGÉE des modèles
|
||||
"boîte" (quizBoxHeaderRow/quizBoxHeader/quizBoxTopBar/quizBoxBody/
|
||||
quizBoxQuestionText/quizBoxChoices/quizBoxChoiceBtn/
|
||||
quizBoxContinueBtn), seule la décoration change. .quizMangaDialogueFrame
|
||||
porte la forme/le fond (jamais le conteneur racine, qui doit rester
|
||||
positionnable/redimensionnable normalement) — même principe que les
|
||||
anciens modèles "Verre dépoli"/"Néon". Police : Montserrat/Poppins
|
||||
(déjà auto-hébergées, voir static/vendor/fonts/fonts.css) — jamais
|
||||
Bangers/Noto Sans JP de la maquette (Google Fonts), même contrainte
|
||||
SCORM hors-ligne déjà rencontrée pour le modèle "page" "Manga". */
|
||||
.quizBoxWidget--mangaDialogue{
|
||||
position:relative; background:#151316; border-radius:0 !important;
|
||||
box-shadow:6px 6px 0 rgba(0,0,0,.4) !important; border:3px solid #000 !important;
|
||||
}
|
||||
.quizBoxWidget--mangaDialogue .quizMangaDialogueFrame{
|
||||
display:flex; flex-direction:column; flex:1; width:100%; height:100%; overflow:hidden;
|
||||
font-family:"Poppins", system-ui, sans-serif; color:#f3ead6; background:#151316;
|
||||
}
|
||||
.quizBoxWidget--mangaDialogue .quizBoxHeaderRow{
|
||||
/* Identité de marque FIXE (rouge), jamais la couleur configurable du
|
||||
panneau "🎨 Style" (header_bg, posée en style INLINE par
|
||||
_quiz_header_row_html — !important nécessaire pour la dominer) —
|
||||
même principe déjà appliqué par l'ancien modèle "Ludique" avant sa
|
||||
suppression, conforme à la maquette validée. */
|
||||
background-color:#d81e3e !important;
|
||||
clip-path:polygon(0 0, 100% 0, 97% 100%, 0 100%); padding:10px 20px 12px 18px; position:relative;
|
||||
background-image:radial-gradient(rgba(255,255,255,.16) 1px, transparent 1.4px); background-size:7px 7px;
|
||||
/* Un `border` classique ne suit pas un clip-path — le liseré noir le
|
||||
long du bord dentelé (demande explicite : "des bords stylisés" sur
|
||||
la maquette) vient d'un drop-shadow qui, lui, épouse la silhouette
|
||||
RÉELLEMENT visible après découpe (contrairement à box-shadow, qui
|
||||
resterait un simple rectangle). */
|
||||
filter:drop-shadow(2px 0 0 #000) drop-shadow(-2px 0 0 #000) drop-shadow(0 2px 0 #000);
|
||||
}
|
||||
.quizBoxWidget--mangaDialogue .quizBoxHeader{
|
||||
font-family:"Montserrat", sans-serif; font-weight:700; font-style:italic; letter-spacing:.02em;
|
||||
text-transform:uppercase; font-size:15px; color:#fff2ec; text-shadow:2px 2px 0 #8f0f24;
|
||||
}
|
||||
/* "Quête : " devant le nom (JS n'écrit QUE le nom brut de l'objet
|
||||
déclencheur dans [data-quiz-role="header"], voir forgeShowQuizBox,
|
||||
dialogue-box-controller.js — contrat partagé par tous les modèles
|
||||
"boîte") — un ::before CSS pur, jamais touché par ce .textContent,
|
||||
pour reproduire le "Quête : X" de la maquette validée. */
|
||||
.quizBoxWidget--mangaDialogue .quizBoxHeader::before{ content:"Quête : "; }
|
||||
.quizBoxWidget--mangaDialogue .quizBoxTopBar{ margin-top:6px; }
|
||||
.quizBoxWidget--mangaDialogue .quizBoxScoreDisplay,
|
||||
.quizBoxWidget--mangaDialogue .quizBoxTimerDisplay{ background:rgba(0,0,0,.28); color:#fff; border-radius:2px; }
|
||||
/* Corps CRÈME (papier), comme la maquette — jamais la couleur
|
||||
configurable du panneau "🎨 Style" (body_bg, posée en style INLINE) :
|
||||
!important nécessaire pour la dominer, même raison que le bandeau
|
||||
ci-dessus. padding-bottom réservé pour ne jamais passer SOUS le
|
||||
bandeau de pied de page toujours visible (voir
|
||||
.quizMangaDialogueFooterBand plus bas).
|
||||
Coin dentelé du bas (demande explicite) SANS clip-path ni décor à
|
||||
part : un `background` (dégradé quasi-plat, juste 3° d'inclinaison,
|
||||
qui BASCULE net de crème à encre) — deux essais précédents (clip-path
|
||||
sur .quizBoxBody, puis un décor z-index:-1 séparé) ont chacun rogné
|
||||
ou fait disparaître le bouton "Continuer →" du moteur partagé
|
||||
(DESCENDANT de cet élément, positionné en absolute par rapport au
|
||||
widget) : un clip-path/overflow sur un ANCÊTRE le rogne TOUJOURS,
|
||||
quel que soit son propre positionnement ; un contexte d'empilement
|
||||
(position+z-index) posé ici l'emprisonne aussi, l'empêchant de
|
||||
ressortir au-dessus du bandeau bas. Un simple `background` ne clippe
|
||||
ni n'empile RIEN — le bouton reste entièrement libre. */
|
||||
.quizBoxWidget--mangaDialogue .quizBoxBody{
|
||||
/* Fond CRÈME plat (demande explicite : coins bas arrondis) — le
|
||||
dégradé diagonal précédent aurait rendu l'arrondi invisible : ses
|
||||
18% du bas étaient DÉJÀ encre, un coin arrondi encre sur un fond
|
||||
encre (le bandeau du bas) ne se voit tout simplement pas, aucun
|
||||
contraste. Coin plat + arrondi net = le seul réglage qui reste
|
||||
visible. */
|
||||
background:#f3ead6 !important;
|
||||
color:#151316; padding:16px 18px 78px; overflow:visible;
|
||||
/* Un `border-radius` seul, SANS toucher à overflow (reste visible),
|
||||
ne clippe RIEN : contrairement à un clip-path ou un
|
||||
overflow:hidden, il ne rogne le bouton "Continuer →" (descendant
|
||||
qui doit rester libre de déborder, voir le commentaire au-dessus)
|
||||
que si overflow est masqué — jamais le cas ici. */
|
||||
border-bottom-left-radius:16px; border-bottom-right-radius:16px;
|
||||
}
|
||||
.quizBoxWidget--mangaDialogue .quizBoxChoices{ overflow:visible; }
|
||||
.quizBoxWidget--mangaDialogue .quizBoxQuestionText{ font-family:"Montserrat", sans-serif; font-weight:700; color:#151316; }
|
||||
.quizBoxWidget--mangaDialogue .quizBoxChoices{ gap:8px; counter-reset:mangaDialogueChoice; }
|
||||
.quizBoxWidget--mangaDialogue .quizBoxChoiceBtn{
|
||||
counter-increment:mangaDialogueChoice; position:relative; background:#fff; color:#151316;
|
||||
border:2px solid #000; border-radius:0; padding:8px 10px 8px 38px; font-family:inherit;
|
||||
}
|
||||
.quizBoxWidget--mangaDialogue .quizBoxChoiceBtn::before{
|
||||
content:counter(mangaDialogueChoice, upper-alpha); position:absolute; left:8px; top:50%; transform:translateY(-50%);
|
||||
width:22px; height:22px; border-radius:50%; background:#151316; color:#f3ead6;
|
||||
display:flex; align-items:center; justify-content:center;
|
||||
font-family:"Montserrat", sans-serif; font-weight:700; font-size:11px;
|
||||
}
|
||||
.quizBoxWidget--mangaDialogue .quizBoxChoiceBtn:hover:not(:disabled){ background:#fff7e6; }
|
||||
/* border-color:#000 !important nécessaire : le contrat partagé
|
||||
(static/style.css, règle générique .quizBoxChoiceBtn.is-correct/
|
||||
is-wrong) pose déjà un liseré vert/rouge "!important" — sans ce
|
||||
repli, il resterait visible par-dessus le fond gold/rouge-foncé
|
||||
propre à ce modèle (demande explicite : "il n'y a pas le liseré
|
||||
vert" sur la maquette — bordure NOIRE partout, jamais colorée). */
|
||||
.quizBoxWidget--mangaDialogue .quizBoxChoiceBtn.is-correct{ background:#e7b923 !important; border-color:#000 !important; color:#151316; }
|
||||
.quizBoxWidget--mangaDialogue .quizBoxChoiceBtn.is-wrong{ background:#8f0f24 !important; border-color:#000 !important; color:#fff; text-decoration:line-through; }
|
||||
.quizBoxWidget--mangaDialogue .quizBoxChoiceBtn.is-correct::after,
|
||||
.quizBoxWidget--mangaDialogue .quizBoxChoiceBtn.is-wrong::after{
|
||||
position:absolute; right:10px; top:50%; transform:translateY(-50%);
|
||||
font-family:"Montserrat", sans-serif; font-weight:700; font-size:10px;
|
||||
padding:2px 6px; border:1.5px solid currentColor; border-radius:3px;
|
||||
}
|
||||
.quizBoxWidget--mangaDialogue .quizBoxChoiceBtn.is-correct::after{ content:"正解"; }
|
||||
.quizBoxWidget--mangaDialogue .quizBoxChoiceBtn.is-wrong::after{ content:"不正解"; }
|
||||
/* Bandeau de pied de page TOUJOURS visible (voir
|
||||
quizMangaDialogueFooterBand, render_quiz_box_manga_dialogue) — comme
|
||||
la maquette validée : une bande sombre distincte, jamais la même
|
||||
couleur que le corps crème. */
|
||||
.quizBoxWidget--mangaDialogue .quizMangaDialogueFooterBand{
|
||||
/* 44px auparavant : trop bas pour le bouton (padding 8px×2 + texte +
|
||||
bordure 2px×2 ≈ 40px) et son décalage bottom:8px — il débordait par
|
||||
le HAUT, à moitié caché sous le panneau crème (demande explicite :
|
||||
"ou dois etre le bouton"). 72px laisse une vraie marge de respir,
|
||||
comme la maquette. */
|
||||
position:absolute; left:0; right:0; bottom:0; height:72px;
|
||||
background:#151316; pointer-events:none;
|
||||
/* Coin haut-gauche dentelé (demande explicite : "les bords du bas
|
||||
doivent être retravaillés") — écho du bandeau titre découpé en
|
||||
haut, plutôt qu'un bas de carte parfaitement rectangulaire. Même
|
||||
technique drop-shadow que le bandeau titre (un `border` classique
|
||||
ne suit pas un clip-path) pour un liseré qui épouse la VRAIE
|
||||
silhouette découpée. */
|
||||
clip-path:polygon(4% 0, 100% 0, 100% 100%, 0 100%, 0 4%);
|
||||
filter:drop-shadow(0 -2px 0 #000) drop-shadow(-2px 0 0 #000) drop-shadow(2px 0 0 #000) drop-shadow(0 2px 0 #000);
|
||||
}
|
||||
/* Le bouton "Continuer →" du moteur partagé atterrit en DERNIER enfant
|
||||
de .quizBoxChoices (jamais dans quizMangaDialogueFooterBand, que le
|
||||
moteur ne remplit jamais) — posé PAR-DESSUS ce bandeau en
|
||||
position:absolute (.quizBoxWidget--mangaDialogue est position:relative,
|
||||
voir plus haut), petit et skewé aligné à droite comme la maquette
|
||||
(sans ce repli, il s'étire à la largeur des choix — .quizBoxChoices
|
||||
est un flex-column, stretch par défaut). */
|
||||
.quizBoxWidget--mangaDialogue .quizBoxContinueBtn{
|
||||
position:absolute; right:20px; bottom:20px; z-index:1; margin:0;
|
||||
background:#d81e3e; border:2px solid #000; color:#fff2ec; border-radius:0;
|
||||
padding:8px 20px; transform:skewX(-6deg); font-family:"Montserrat", sans-serif; font-weight:700;
|
||||
box-shadow:3px 3px 0 #000;
|
||||
}
|
||||
.quizBoxWidget--mangaDialogue .quizBoxContinueBtn:hover:not(:disabled){ background:#8f0f24; }
|
||||
|
||||
/* ---------- Modèle "Manga" — premier "PAGE DE QUIZ" (voir
|
||||
screens/rendering/quiz_box_templates.py::render_quiz_box_page_manga),
|
||||
validé par le créateur via une maquette interactive avant intégration.
|
||||
Thème complet et autonome : structure/police/formes/couleurs propres,
|
||||
AUCUNE règle partagée avec les modèles "boîte de dialogue" ci-dessus
|
||||
(à part .quizBoxChoiceBtn/.quizBoxContinueBtn, injectés par le moteur
|
||||
JS partagé, voir static/js/play/dialogue-box-controller.js).
|
||||
Polices : Montserrat/Poppins (déjà auto-hébergées, voir
|
||||
static/vendor/fonts/fonts.css) plutôt que Bangers/Noto Sans JP de la
|
||||
maquette d'origine — un paquet SCORM exporté tourne SANS accès
|
||||
internet dans le LMS du client, une police chargée depuis Google
|
||||
Fonts y resterait invisible (repli silencieux sur la police système). */
|
||||
.quizBoxWidget--manga{
|
||||
background:#151316; color:#f3ead6;
|
||||
font-family:"Poppins", "Montserrat", system-ui, sans-serif;
|
||||
display:flex; flex-direction:column; align-items:center; gap:18px; padding:20px;
|
||||
overflow-y:auto;
|
||||
background-image:
|
||||
radial-gradient(circle at 14% 8%, rgba(216,30,62,.16), transparent 42%),
|
||||
repeating-linear-gradient(115deg, rgba(255,255,255,.03) 0 2px, transparent 2px 26px);
|
||||
}
|
||||
/* Contenu centré en colonne confortable (demande explicite, comme la
|
||||
maquette validée) plutôt qu'étiré sur toute la largeur de l'écran. */
|
||||
.quizBoxWidget--manga .mangaTopbar,
|
||||
.quizBoxWidget--manga .mangaProgress,
|
||||
.quizBoxWidget--manga .mangaPanelQuestion,
|
||||
.quizBoxWidget--manga .mangaChoices{ width:100%; max-width:860px; flex-shrink:0; }
|
||||
/* Barre d'étapes (voir forgeQuizMangaOnShow, static/js/play/
|
||||
quiz-box-templates.js) — un <i> par question de CE dialogue, remplie
|
||||
EN JEU (le nombre total de questions n'est connu qu'à l'exécution). */
|
||||
.quizBoxWidget--manga .mangaProgress{ display:flex; gap:6px; }
|
||||
.quizBoxWidget--manga .mangaProgress i{ flex:1; height:6px; background:rgba(243,234,214,.18); border-radius:1px; font-style:normal; }
|
||||
.quizBoxWidget--manga .mangaProgress i.is-done{ background:#d81e3e; }
|
||||
.quizBoxWidget--manga .mangaProgress i.is-current{ background:#e7b923; }
|
||||
.quizBoxWidget--manga .mangaTopbar{ display:flex; align-items:stretch; gap:12px; flex-wrap:wrap; }
|
||||
.quizBoxWidget--manga .mangaPlate{
|
||||
background:#f3ead6; color:#151316; border:3px solid #151316; box-shadow:4px 4px 0 #1d1a1f;
|
||||
padding:8px 14px; display:flex; flex-direction:column; justify-content:center; gap:2px; min-width:118px;
|
||||
}
|
||||
.quizBoxWidget--manga .mangaPlateLabel{ font-size:10px; font-weight:600; letter-spacing:.14em; text-transform:uppercase; opacity:.65; }
|
||||
.quizBoxWidget--manga .mangaPlateValue{
|
||||
font-family:"Montserrat", sans-serif; font-weight:700; font-size:22px; line-height:1;
|
||||
letter-spacing:.02em; font-variant-numeric:tabular-nums;
|
||||
}
|
||||
.quizBoxWidget--manga .mangaPlate--score .mangaPlateValue{ color:#8f0f24; }
|
||||
.quizBoxWidget--manga .mangaPlate--timer:has(.quizBoxTimerDisplay.is-urgent){
|
||||
background:#d81e3e; color:#f3ead6; animation:mangaTimerPulse .5s ease infinite;
|
||||
}
|
||||
.quizBoxWidget--manga .mangaPlate--timer:has(.quizBoxTimerDisplay.is-urgent) .mangaPlateValue{ color:#f3ead6; }
|
||||
@keyframes mangaTimerPulse{ 0%,100%{ transform:scale(1); } 50%{ transform:scale(1.05); } }
|
||||
.quizBoxWidget--manga .mangaTitlemark{
|
||||
flex:1 1 200px; min-width:180px; background:#1d1a1f; border:3px solid #151316; outline:3px solid #f3ead6; outline-offset:-8px;
|
||||
display:flex; align-items:center; justify-content:center; padding:10px;
|
||||
background-image:radial-gradient(rgba(243,234,214,.14) 1.3px, transparent 1.5px); background-size:8px 8px;
|
||||
}
|
||||
.quizBoxWidget--manga .mangaTitlemark span{
|
||||
font-family:"Montserrat", sans-serif; font-weight:700; font-style:italic; text-transform:uppercase;
|
||||
font-size:clamp(16px,3vw,24px); letter-spacing:.02em; color:#f3ead6;
|
||||
-webkit-text-stroke:1px #f3ead6; text-shadow:2px 2px 0 #d81e3e; transform:skewX(-8deg); display:inline-block;
|
||||
}
|
||||
.quizBoxWidget--manga .mangaPanelQuestion{
|
||||
background:#f3ead6; color:#151316; border:3px solid #151316; box-shadow:6px 6px 0 #1d1a1f;
|
||||
clip-path:polygon(0 0, 100% 0%, 100% 92%, 97% 100%, 3% 100%, 0 88%);
|
||||
padding:22px 26px 30px; flex:0 0 auto;
|
||||
}
|
||||
.quizBoxWidget--manga .mangaKicker{
|
||||
display:inline-block; font-size:11px; font-weight:700; letter-spacing:.14em; text-transform:uppercase;
|
||||
background:#d81e3e; color:#f3ead6; padding:3px 10px; margin-bottom:12px; transform:rotate(-2deg);
|
||||
}
|
||||
.quizBoxWidget--manga .mangaQuestionText{
|
||||
margin:0; font-family:"Montserrat", sans-serif; font-weight:700; font-style:italic; line-height:1.2;
|
||||
font-size:clamp(19px,2.6vw,28px); letter-spacing:-.01em; text-transform:uppercase; text-wrap:balance;
|
||||
}
|
||||
.quizBoxWidget--manga .mangaChoices{
|
||||
display:grid; grid-template-columns:1fr 1fr; gap:14px; flex:1 1 auto; align-content:start;
|
||||
}
|
||||
@media (max-width:620px){ .quizBoxWidget--manga .mangaChoices{ grid-template-columns:1fr; } }
|
||||
.quizBoxWidget--manga .quizBoxChoiceBtn{
|
||||
counter-increment:mangaChoice; position:relative; display:flex; align-items:center; gap:14px;
|
||||
background:#1d1a1f; color:#f3ead6; border:3px solid #151316; outline:2px solid rgba(243,234,214,.5); outline-offset:-7px;
|
||||
padding:12px 16px 12px 12px; font-family:inherit; font-size:15px; font-weight:500; text-align:left;
|
||||
border-radius:0; box-shadow:none;
|
||||
}
|
||||
.quizBoxWidget--manga .mangaChoices{ counter-reset:mangaChoice; }
|
||||
.quizBoxWidget--manga .quizBoxChoiceBtn::before{
|
||||
content:counter(mangaChoice, upper-alpha); flex:0 0 auto; width:34px; height:34px; border-radius:50%;
|
||||
background:#f3ead6; color:#151316; border:3px solid #151316; display:flex; align-items:center; justify-content:center;
|
||||
font-family:"Montserrat", sans-serif; font-weight:700; font-size:15px;
|
||||
}
|
||||
.quizBoxWidget--manga .quizBoxChoiceBtn:hover:not(:disabled){ background:#26222a; transform:translate(-2px,-2px); }
|
||||
.quizBoxWidget--manga .quizBoxChoiceBtn.is-correct{ background:#e7b923 !important; color:#151316; border-color:#151316 !important; }
|
||||
.quizBoxWidget--manga .quizBoxChoiceBtn.is-correct::before{ background:#151316; color:#e7b923; border-color:#151316; }
|
||||
.quizBoxWidget--manga .quizBoxChoiceBtn.is-wrong{ background:#8f0f24 !important; border-color:#151316 !important; }
|
||||
.quizBoxWidget--manga .quizBoxChoiceBtn.is-correct::after,
|
||||
.quizBoxWidget--manga .quizBoxChoiceBtn.is-wrong::after{
|
||||
position:absolute; top:50%; right:14px; transform:translateY(-50%) rotate(-8deg);
|
||||
font-family:"Montserrat", sans-serif; font-weight:700; font-size:13px; letter-spacing:.06em;
|
||||
padding:2px 8px; border:2px solid currentColor; border-radius:4px;
|
||||
}
|
||||
.quizBoxWidget--manga .quizBoxChoiceBtn.is-correct::after{ content:"正解"; color:#151316; }
|
||||
.quizBoxWidget--manga .quizBoxChoiceBtn.is-wrong::after{ content:"不正解"; color:#f3ead6; }
|
||||
/* Une réponse fausse est barrée (demande explicite, comme la maquette) —
|
||||
jamais le badge lettré (::before), qui reste un simple repère A/B/C/D. */
|
||||
.quizBoxWidget--manga .quizBoxChoiceBtn.is-wrong{ text-decoration:line-through; text-decoration-thickness:2px; }
|
||||
.quizBoxWidget--manga .quizBoxChoiceBtn.is-wrong::before{ text-decoration:none; }
|
||||
/* Posé DANS la grille des choix (voir forgeQuizBoxAnswer, moteur partagé)
|
||||
mais sur sa propre ligne, aligné à droite en petit pavé (comme la
|
||||
maquette) plutôt qu'étiré à la largeur d'une case de la grille — et
|
||||
relabellisé en "Suite ▶" (texte "Continuer →" du moteur partagé
|
||||
conservé pour les lecteurs d'écran, juste masqué visuellement). */
|
||||
.quizBoxWidget--manga .quizBoxContinueBtn{
|
||||
grid-column:1 / -1; justify-self:end; width:max-content;
|
||||
font-family:"Montserrat", sans-serif; font-weight:700; letter-spacing:.03em; font-size:0;
|
||||
background:#d81e3e; color:#f3ead6; border:3px solid #151316; box-shadow:3px 3px 0 #1d1a1f;
|
||||
padding:9px 22px; transform:skewX(-6deg); border-radius:0;
|
||||
}
|
||||
.quizBoxWidget--manga .quizBoxContinueBtn::after{ content:"Suite ▶"; font-size:14px; }
|
||||
.quizBoxWidget--manga .quizBoxContinueBtn:hover:not(:disabled){ background:#8f0f24; }
|
||||
|
||||
/* Écran de résultat de fin de quiz (voir forgeQuizMangaOnQuizComplete,
|
||||
static/js/play/quiz-box-templates.js) — demande explicite : "il faut
|
||||
aussi adapter le modele manga pour avoir sa page de résultat", même
|
||||
mécanique que "classique" mais dans le thème manga propre à ce
|
||||
modèle. Pas de conteneur "content" à part ici : c'est la RACINE
|
||||
(.quizBoxWidget--manga) qui bascule en "is-showing-result" — le
|
||||
bandeau topbar/score et la barre d'étapes (déjà remplie à 100%,
|
||||
voir le JS) restent visibles, seuls le panneau question et les choix
|
||||
disparaissent. */
|
||||
.quizBoxWidget--manga.is-showing-result > .mangaPanelQuestion,
|
||||
.quizBoxWidget--manga.is-showing-result > .mangaChoices{ display:none; }
|
||||
.mangaResultZone{
|
||||
display:none; flex-direction:column; align-items:center; justify-content:center;
|
||||
width:100%; max-width:860px; text-align:center; flex:1 1 auto;
|
||||
}
|
||||
.quizBoxWidget--manga.is-showing-result .mangaResultZone{ display:flex; }
|
||||
.mangaResultCard{
|
||||
background:#f3ead6; color:#151316; border:3px solid #151316; box-shadow:6px 6px 0 #1d1a1f;
|
||||
clip-path:polygon(0 0, 100% 0%, 100% 92%, 97% 100%, 3% 100%, 0 88%);
|
||||
padding:32px 30px 40px; width:100%;
|
||||
}
|
||||
.mangaResultTitle{
|
||||
font-family:"Montserrat", sans-serif; font-weight:700; font-style:italic; text-transform:uppercase;
|
||||
font-size:clamp(26px,4vw,38px); text-shadow:3px 3px 0 #d81e3e; transform:skewX(-4deg);
|
||||
display:inline-block; margin:0 0 10px;
|
||||
}
|
||||
.mangaResultScore{ font-family:"Montserrat", sans-serif; font-weight:700; font-size:19px; color:#8f0f24; margin:0 0 18px; }
|
||||
.mangaResultBadge{
|
||||
display:inline-block; font-family:"Montserrat", sans-serif; font-weight:700; font-size:22px;
|
||||
letter-spacing:.05em; padding:6px 22px; border:3px solid #151316; margin-bottom:24px; transform:rotate(-2deg);
|
||||
}
|
||||
.mangaResultBadge.pass{ background:#e7b923; }
|
||||
.mangaResultBadge.fail{ background:#8f0f24; color:#f3ead6; }
|
||||
.mangaQuitBtn{
|
||||
font-family:"Montserrat", sans-serif; font-weight:700; letter-spacing:.03em; cursor:pointer;
|
||||
background:#151316; color:#f3ead6; border:3px solid #151316; box-shadow:3px 3px 0 #d81e3e;
|
||||
padding:10px 26px; transform:skewX(-6deg); border-radius:0;
|
||||
}
|
||||
.mangaQuitBtn:hover{ background:#26222a; }
|
||||
|
||||
/* "Classique" (modèle "page de quiz" DE BASE) — maquette interactive
|
||||
validée par le créateur (formation sécurité incendie comme exemple) :
|
||||
couleurs VOLONTAIREMENT identiques à celles par défaut du modèle
|
||||
"boîte de dialogue" de base (#2f6fed/#1a1d24/#11141b, voir
|
||||
screens/rendering/dialogue_box_style.py::DEFAULT_DIALOGUE_BOX_STYLE)
|
||||
plutôt qu'une palette propre — contrairement à "manga". */
|
||||
/* Police : "Poppins" (déjà auto-hébergée pour "manga", voir
|
||||
static/vendor/fonts/fonts.css) — jamais "IBM Plex Sans/Mono" de la
|
||||
maquette (Google Fonts), qui casserait l'export SCORM hors-ligne, même
|
||||
contrainte déjà rencontrée pour "manga". */
|
||||
.quizBoxWidget--classique{
|
||||
position:relative; display:flex; flex-direction:column; font-family:"Poppins", system-ui, sans-serif;
|
||||
}
|
||||
.classiqueHeaderBar{
|
||||
display:flex; align-items:center; justify-content:space-between; gap:14px; flex-wrap:wrap;
|
||||
padding:14px 20px; color:#fff;
|
||||
}
|
||||
.classiqueHeaderTitleWrap{ display:flex; flex-direction:column; gap:1px; min-width:0; }
|
||||
.classiqueHeaderEyebrow{ font-size:10px; font-weight:600; letter-spacing:.09em; text-transform:uppercase; color:rgba(255,255,255,.7); }
|
||||
.classiqueHeaderTitle{ font-size:16px; font-weight:700; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||||
.classiqueHeaderChips{ display:flex; gap:8px; flex-shrink:0; }
|
||||
.classiqueChip{
|
||||
display:flex; flex-direction:column; align-items:center; justify-content:center;
|
||||
background:rgba(255,255,255,.14); border-radius:8px; padding:4px 12px; min-width:56px;
|
||||
}
|
||||
.classiqueChipLabel{ font-size:9px; font-weight:600; letter-spacing:.08em; text-transform:uppercase; color:rgba(255,255,255,.65); }
|
||||
.classiqueChipValue{
|
||||
font-family:"Roboto", monospace; font-size:13.5px; font-weight:600; color:#fff;
|
||||
font-variant-numeric:tabular-nums;
|
||||
}
|
||||
/* Bug identifié (demande répétée : "le mot aucun a une background
|
||||
color... un bord arrondi") : [data-quiz-role="timer"] porte AUSSI la
|
||||
classe partagée .quizBoxTimerDisplay (voir plus haut dans ce fichier —
|
||||
nécessaire pour le mécanisme commun ":empty"/".is-urgent") qui pose
|
||||
SA PROPRE pastille (fond + border-radius:999px) — DEUX pastilles
|
||||
s'empilaient donc : celle, correcte et voulue, du conteneur
|
||||
".classiqueChip" (même fond que "Score"), et celle, parasite, de
|
||||
".quizBoxTimerDisplay" collée UNIQUEMENT autour du mot "Aucun"/du
|
||||
temps restant. On neutralise ICI cette seconde pastille (le
|
||||
conteneur ".classiqueChip" reste seul responsable du fond, TOUJOURS
|
||||
visible, identique que pour "Score", avec ou sans minuteur réglé). */
|
||||
.classiqueChipValue.quizBoxTimerDisplay{ background:none; padding:0; border-radius:0; }
|
||||
.classiqueChip--timer:has(.quizBoxTimerDisplay.is-urgent){ background:rgba(224,82,77,.35); }
|
||||
.classiqueChip--timer:has(.quizBoxTimerDisplay.is-urgent) .classiqueChipValue,
|
||||
.classiqueChip--timer:has(.quizBoxTimerDisplay.is-urgent) .classiqueChipLabel{ color:#ffb3b0; }
|
||||
|
||||
.classiqueBody{ flex:1; display:flex; flex-direction:column; align-items:center; padding:24px 20px 96px; overflow-y:auto; }
|
||||
.classiqueContent{ width:100%; max-width:640px; display:flex; flex-direction:column; flex:1; }
|
||||
|
||||
/* Barre d'étapes — voir forgeQuizClassiqueOnShow (static/js/play/
|
||||
quiz-box-templates.js), même mécanique que "manga". */
|
||||
.classiqueProgress{ display:flex; gap:6px; margin-bottom:24px; }
|
||||
.classiqueProgress i{ flex:1; height:4px; border-radius:2px; background:rgba(255,255,255,.14); font-style:normal; }
|
||||
.classiqueProgress i.is-done{ background:#2ecc71; }
|
||||
.classiqueProgress i.is-current{ background:#fff; }
|
||||
|
||||
.classiqueKicker{
|
||||
display:inline-block; align-self:flex-start; background:rgba(255,255,255,.14); border-radius:999px;
|
||||
padding:4px 12px; font-size:11.5px; font-weight:600; color:rgba(255,255,255,.75); margin-bottom:16px;
|
||||
}
|
||||
.classiqueQuestionText{ margin:0 0 22px; font-weight:600; line-height:1.35; font-size:clamp(18px,2.6vw,22px); text-wrap:balance; }
|
||||
|
||||
.classiqueChoices{ display:flex; flex-direction:column; gap:10px; }
|
||||
.classiqueChoices .quizBoxChoiceBtn{
|
||||
display:flex; align-items:center; gap:14px; border-radius:8px; padding:13px 16px;
|
||||
}
|
||||
.classiqueChoices .quizBoxChoiceBtn.is-correct{ color:#08210f; }
|
||||
.classiqueChoices .quizBoxChoiceBtn.is-wrong{ text-decoration:line-through; opacity:.85; }
|
||||
/* Bandeau de pied de page TOUJOURS visible (voir classiqueFooterBand,
|
||||
render_quiz_box_page_classique) — couleur configurable "🎨 Style"
|
||||
(footer_bg, voir --classique-footer-bg posée sur la racine du widget)
|
||||
plutôt qu'une couleur figée en dur, demande explicite : "il manque le
|
||||
footer foncé avec le bouton" (comme la maquette validée). */
|
||||
.classiqueFooterBand{
|
||||
position:absolute; left:0; right:0; bottom:0; height:66px;
|
||||
background:var(--classique-footer-bg, #11141b); border-top:1px solid rgba(255,255,255,.08);
|
||||
pointer-events:none;
|
||||
}
|
||||
/* Le bouton "Continuer →" du moteur partagé atterrit en DERNIER enfant
|
||||
de .classiqueChoices (jamais dans classiqueFooterBand, que le moteur
|
||||
ne remplit jamais) — posé PAR-DESSUS ce bandeau en position:absolute
|
||||
(.quizBoxWidget--classique est position:relative, voir plus haut).
|
||||
Demande explicite : aligné à DROITE — dans la même colonne centrée de
|
||||
640px que le corps (.classiqueContent) : `max(20px, calc(50vw - 320px))`
|
||||
recalcule le bord DROIT de CETTE colonne (640px = 2×320px, 20px = le
|
||||
padding latéral de .classiqueBody) plutôt que le bord droit de TOUT le
|
||||
widget. 50vw est fiable ici : ce modèle n'existe qu'en plein écran, où
|
||||
`.is-fullscreen` force déjà `width:100vw`. */
|
||||
.classiqueChoices .quizBoxContinueBtn{
|
||||
position:absolute; right:max(20px, calc(50vw - 320px)); bottom:14px; z-index:1; margin:0;
|
||||
background:#2f6fed; border:1px solid #2f6fed; border-radius:7px; padding:10px 22px; font-weight:600;
|
||||
}
|
||||
.classiqueChoices .quizBoxContinueBtn:hover:not(:disabled){ background:#255bc4; }
|
||||
|
||||
/* Encart d'explication après réponse — RÉSERVÉ pour une fonctionnalité
|
||||
FUTURE non encore implémentée (voir le commentaire dans
|
||||
render_quiz_box_page_classique, screens/rendering/quiz_box_templates.py)
|
||||
: reste toujours vide aujourd'hui, donc toujours caché — seul :not(:empty)
|
||||
le fera apparaître le jour où un vrai contenu y sera écrit. */
|
||||
.classiqueExplain:not(:empty){
|
||||
margin-top:14px; padding:14px 16px; border-radius:8px; background:rgba(255,255,255,.06);
|
||||
border:1px solid rgba(255,255,255,.14); font-size:13.5px; color:rgba(255,255,255,.75); line-height:1.5;
|
||||
}
|
||||
|
||||
/* Écran de résultat de fin de quiz (voir forgeQuizClassiqueOnQuizComplete/
|
||||
forgeQuizResultRestart, static/js/play/quiz-box-templates.js et
|
||||
dialogue-box-controller.js) — demande explicite : "la page de resultat
|
||||
est cassee... elle affiche un conteneur qui reaffiche le contenu du
|
||||
quiz au lieu du resultat" (le vrai moteur n'avait jusqu'ici AUCUN écran
|
||||
de fin, juste la maquette artifact). ".classiqueContent.is-showing-
|
||||
result" masque tout le reste (progression/question/choix/explication)
|
||||
pour ne laisser QUE ce bloc visible, sans reconstruire le DOM du
|
||||
widget (le contrat data-quiz-role="header/question/choices/score/
|
||||
timer" reste intact pour un "Recommencer" qui relance normalement). */
|
||||
.classiqueContent.is-showing-result > *{ display:none; }
|
||||
.classiqueContent.is-showing-result .classiqueResultZone{ display:flex; }
|
||||
.classiqueResultZone{
|
||||
display:none; flex:1; flex-direction:column; align-items:center; justify-content:center;
|
||||
text-align:center; padding:20px 0;
|
||||
}
|
||||
.classiqueResultRing{
|
||||
width:112px; height:112px; margin:0 0 20px; border-radius:50%;
|
||||
display:flex; align-items:center; justify-content:center; position:relative;
|
||||
background:conic-gradient(#2f6fed calc(var(--pct, 0) * 1%), rgba(255,255,255,.14) 0);
|
||||
}
|
||||
.classiqueResultRing::before{
|
||||
content:""; position:absolute; inset:8px; border-radius:50%; background:var(--classique-body-bg, #1a1d24);
|
||||
}
|
||||
.classiqueResultRing span{ position:relative; font-weight:700; font-size:22px; font-variant-numeric:tabular-nums; }
|
||||
.classiqueResultTitle{ font-size:19px; font-weight:700; margin:0 0 6px; }
|
||||
.classiqueResultSub{ font-size:13.5px; color:rgba(255,255,255,.62); margin:0 0 22px; }
|
||||
.classiqueResultBadge{
|
||||
display:inline-flex; align-items:center; gap:6px; padding:6px 16px; border-radius:999px;
|
||||
font-size:12.5px; font-weight:700; margin-bottom:26px;
|
||||
}
|
||||
.classiqueResultBadge.pass{ background:rgba(46,204,113,.16); color:#2ecc71; }
|
||||
.classiqueResultBadge.fail{ background:rgba(224,82,77,.16); color:#e0524d; }
|
||||
.classiqueQuitBtn{
|
||||
font:inherit; font-weight:600; font-size:13.5px; color:#fff; background:transparent;
|
||||
border:1px solid rgba(255,255,255,.3); border-radius:9px; padding:10px 20px; cursor:pointer;
|
||||
}
|
||||
.classiqueQuitBtn:hover{ border-color:#2f6fed; background:rgba(255,255,255,.08); }
|
||||
|
||||
/* Widget "🏆 Score" — TOUJOURS visible une fois posé (pas de collision/
|
||||
déclencheur, contrairement aux deux widgets ci-dessus), voir
|
||||
|
||||
Reference in New Issue
Block a user