Fix accès au canevas masqué + quiz : header quête, réponse fausse n'avance jamais bloquée, animé
- Bug corrigé : les panneaux flottants "🧩 Objets"/"⚙️ Propriétés" sont en position:fixed (hors du flux flex de .builder3) — ils flottaient PAR-DESSUS le canevas sans jamais réduire sa largeur, rendant sa partie droite/gauche inaccessible au clic/glisser tant qu'un panneau restait ouvert. .builderCanvasArea réserve maintenant leur largeur (marge) dès qu'un panneau est ouvert (:has()). - Boîte à quiz : header = "Quête : <titre>" (au lieu du texte de la question), corps = la question ET ses choix ensemble. - Mauvaise réponse : ne bloque plus JAMAIS la progression (bug signalé : "je suis obligé de bien répondre sinon j'avance pas") — surligne la bonne réponse en vert (le choix cliqué en rouge s'il était faux) puis avance automatiquement après un court délai, sans jamais octroyer de points. Un second clic pendant la révélation est ignoré. - Animations : la boîte à quiz rejoue une entrée (pop-in) à CHAQUE nouvelle question, la bonne réponse pulse en vert, une mauvaise réponse "secoue" en rouge. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
b5546df044
commit
d705f58c4a
@@ -121,7 +121,7 @@ function forgeShowCurrentConversationLine(dialogueObjectId) {
|
||||
var widget = forgeDialogueBoxWidgetById(dialogueObjectId);
|
||||
if (line.type === 'question') {
|
||||
if (widget) widget.classList.remove('is-active');
|
||||
forgeShowQuizBox(dialogueObjectId, line);
|
||||
forgeShowQuizBox(dialogueObjectId, forgeQuestById(state.questId), line);
|
||||
return;
|
||||
}
|
||||
forgeHideQuizBoxOnCurrentScreen();
|
||||
@@ -189,43 +189,76 @@ function forgeDialogueBoxAdvance(objectId) {
|
||||
forgeAdvanceConversation(objectId);
|
||||
}
|
||||
|
||||
// Combien de temps la bonne/mauvaise réponse reste affichée (surlignée,
|
||||
// voir forgeQuizBoxAnswer) avant d'avancer automatiquement à la ligne
|
||||
// suivante — assez long pour LIRE la bonne réponse en cas d'erreur, plus
|
||||
// court si la réponse était déjà correcte (voir static/style.css,
|
||||
// @keyframes quizPopIn/quizWrongShake pour l'animation elle-même).
|
||||
var FORGE_QUIZ_REVEAL_DELAY_CORRECT = 700;
|
||||
var FORGE_QUIZ_REVEAL_DELAY_WRONG = 1400;
|
||||
|
||||
// Affiche une question ("❓ Question") dans la boîte à quiz posée sur
|
||||
// l'écran — si l'auteur n'en a posé aucune, la question est ignorée
|
||||
// (avance directement) plutôt que de bloquer la conversation pour
|
||||
// toujours en attendant une réponse qu'aucun widget ne peut recueillir.
|
||||
function forgeShowQuizBox(dialogueObjectId, line) {
|
||||
// l'écran — header = "Quête : <titre>", corps = la question + ses choix
|
||||
// (voir demande utilisateur) — si l'auteur n'a posé aucune boîte à quiz,
|
||||
// la question est ignorée (avance directement) plutôt que de bloquer la
|
||||
// conversation pour toujours en attendant une réponse qu'aucun widget ne
|
||||
// peut recueillir.
|
||||
function forgeShowQuizBox(dialogueObjectId, quest, line) {
|
||||
var widget = forgeQuizBoxWidgetOnCurrentScreen();
|
||||
if (!widget) { forgeAdvanceConversation(dialogueObjectId); return; }
|
||||
var quizObjectId = widget.getAttribute('data-object-id');
|
||||
forgeQuizBoxState[quizObjectId] = {
|
||||
dialogueObjectId: dialogueObjectId, correctIndex: line.correct_index, rewardAmount: line.reward_amount,
|
||||
dialogueObjectId: dialogueObjectId, correctIndex: line.correct_index, rewardAmount: line.reward_amount, resolved: false,
|
||||
};
|
||||
var header = widget.querySelector('[data-quiz-role="header"]');
|
||||
var questionEl = widget.querySelector('[data-quiz-role="question"]');
|
||||
var choicesEl = widget.querySelector('[data-quiz-role="choices"]');
|
||||
if (header) header.textContent = line.text;
|
||||
if (header) header.textContent = 'Quête : ' + (quest ? quest.title : '');
|
||||
if (questionEl) questionEl.textContent = line.text;
|
||||
if (choicesEl) {
|
||||
choicesEl.innerHTML = line.choices.map(function (choice, i) {
|
||||
return '<button type="button" class="quizBoxChoiceBtn" onclick="forgeQuizBoxAnswer(' + quizObjectId + ', ' + i + ')">' + forgeEscapeHtml(choice) + '</button>';
|
||||
}).join('');
|
||||
}
|
||||
// Rejoue l'animation d'entrée (@keyframes quizPopIn) même si la boîte
|
||||
// était déjà "is-active" juste avant (2 questions à la suite) : sans
|
||||
// ce retrait/reflow/réajout, une classe déjà posée ne redéclenche pas
|
||||
// son animation CSS.
|
||||
widget.classList.remove('is-active');
|
||||
void widget.offsetWidth;
|
||||
widget.classList.add('is-active');
|
||||
}
|
||||
|
||||
// Appelée par le clic sur un choix (voir render_scene_object.py côté
|
||||
// éditeur — les vrais choix sont injectés EN JEU par forgeShowQuizBox
|
||||
// ci-dessus, jamais rendus côté serveur). Mauvaise réponse : rien, la
|
||||
// question reste affichée. Bonne réponse : crédite le score, masque la
|
||||
// boîte, avance la conversation.
|
||||
// Appelée par le clic sur un choix (les boutons sont injectés EN JEU par
|
||||
// forgeShowQuizBox ci-dessus, jamais rendus côté serveur — voir
|
||||
// render_scene_object.py pour le seul aperçu statique de l'éditeur).
|
||||
// TOUJOURS crédite le score si la réponse est bonne PUIS avance à la
|
||||
// ligne suivante, QUE la réponse soit bonne ou mauvaise (demande
|
||||
// explicite : une mauvaise réponse ne doit jamais bloquer la
|
||||
// progression) — surligne la bonne réponse en vert (et le choix cliqué
|
||||
// en rouge s'il était faux) le temps de la voir avant d'avancer. Un
|
||||
// second clic pendant cet affichage est ignoré (`resolved`).
|
||||
function forgeQuizBoxAnswer(quizObjectId, choiceIndex) {
|
||||
var state = forgeQuizBoxState[quizObjectId];
|
||||
if (!state) return;
|
||||
if (choiceIndex !== state.correctIndex) return;
|
||||
forgeAddQuizScore(state.rewardAmount);
|
||||
if (!state || state.resolved) return;
|
||||
state.resolved = true;
|
||||
var widget = forgeQuizBoxWidgetById(quizObjectId);
|
||||
if (widget) widget.classList.remove('is-active');
|
||||
var correct = choiceIndex === state.correctIndex;
|
||||
if (widget) {
|
||||
var buttons = widget.querySelectorAll('.quizBoxChoiceBtn');
|
||||
for (var i = 0; i < buttons.length; i++) {
|
||||
buttons[i].disabled = true;
|
||||
if (i === state.correctIndex) buttons[i].classList.add('is-correct');
|
||||
else if (i === choiceIndex) buttons[i].classList.add('is-wrong');
|
||||
}
|
||||
}
|
||||
if (correct) forgeAddQuizScore(state.rewardAmount);
|
||||
var dialogueObjectId = state.dialogueObjectId;
|
||||
delete forgeQuizBoxState[quizObjectId];
|
||||
forgeAdvanceConversation(dialogueObjectId);
|
||||
setTimeout(function () {
|
||||
if (widget) widget.classList.remove('is-active');
|
||||
delete forgeQuizBoxState[quizObjectId];
|
||||
forgeAdvanceConversation(dialogueObjectId);
|
||||
}, correct ? FORGE_QUIZ_REVEAL_DELAY_CORRECT : FORGE_QUIZ_REVEAL_DELAY_WRONG);
|
||||
}
|
||||
|
||||
// ---------- Offre de quête (Accepter/Refuser) ----------
|
||||
|
||||
Reference in New Issue
Block a user