Ajoute la fonctionnalite quiz autonome/plein ecran a la boite a quiz
Build and deploy / test-python (push) Successful in 12m10s
Build and deploy / test-js (push) Successful in 1m27s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

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:
william
2026-09-13 15:07:23 +02:00
co-authored by Claude Sonnet 5
parent 559331f9cf
commit 7db4803b93
26 changed files with 3173 additions and 60 deletions
+14 -1
View File
@@ -42,6 +42,18 @@
#playFrame.sceneMode{ width:100vw; height:100vh; }
.playScreen{ position:absolute; inset:0; display:none; }
.playScreen.playScene{ inset:auto; top:50%; left:50%; transform:translate(-50%, -50%); background:#1a1d24; }
/* "La notion de caméra disparaît" quand un quiz plein écran existe sur
cet écran (demande explicite, voir screens/payload/full_game_payload.py
::any_quiz_box_fullscreen) — le réglage "Caméra (px)" (scene_width/
scene_height, qui donne à .playScene ci-dessus sa taille ET son
`transform` de centrage) ne s'applique alors plus DU TOUT : cet écran
devient lui-même plein viewport, sans transform (qui casserait
position:fixed pour tout descendant, voir dialogue-box-controller.js
::forgeEscapeCameraForFullscreen — plus nécessaire ici puisque
.playScene n'a elle-même plus de transform dans ce cas). */
.playScreen.playScene.playScene--quizFullscreen{
position:fixed; inset:0; top:0; left:0; width:100vw; height:100vh; transform:none;
}
.playElement{
position:absolute; box-sizing:border-box; font-family:system-ui,sans-serif;
}
@@ -89,7 +101,7 @@
<!-- Un seul moteur d'écran désormais (jeu_2d) : objets déjà entièrement
positionnés en pixels absolus par render_scene_object.py (voir
screens/scenes/) — juste le HTML tel quel. -->
<div class="playScreen playScene" data-screen-id="{{ s.id }}" data-kind="jeu_2d" style="width:{{ s.scene_width }}px; height:{{ s.scene_height }}px;">
<div class="playScreen playScene {{ 'playScene--quizFullscreen' if s.any_quiz_box_fullscreen else '' }}" data-screen-id="{{ s.id }}" data-kind="jeu_2d" style="{{ '' if s.any_quiz_box_fullscreen else 'width:' + s.scene_width|string + 'px; height:' + s.scene_height|string + 'px;' }}">
<!-- .sceneWorld : le "monde" (voir static/style.css et
static/js/play/personnage-controller.js) — taille par défaut 100%
(identique au viewport ci-dessus), agrandie/décalée en JS
@@ -180,6 +192,7 @@
<script src="{{ asset_url('js/play/actions.js') }}"></script>
<script src="{{ asset_url('js/play/triggers.js') }}"></script>
<script src="{{ asset_url('js/play/personnage-controller.js') }}"></script>
<script src="{{ asset_url('js/play/quiz-box-templates.js') }}"></script>
<script src="{{ asset_url('js/play/dialogue-box-controller.js') }}"></script>
<script src="{{ asset_url('js/play/video-overlay.js') }}"></script>
<script src="{{ asset_url('js/play/collision-rules-controller.js') }}"></script>
+110 -4
View File
@@ -274,9 +274,20 @@
<div class="builderTabPanel active" data-tab-panel="screen">
<div class="canvasFrame">
<div class="canvas" id="canvas" style="width:{{ world_width }}px; height:{{ world_height }}px; aspect-ratio:auto; margin:0 auto; position:relative; overflow:hidden; background:#1a1d24;"
<!-- Un quiz plein écran fait disparaître la notion de caméra
(demande explicite) — pas seulement le cadre "🎥 Champ de la
caméra" (déjà masqué plus bas), mais le CANEVAS lui-même :
sans ce cas particulier, il restait dimensionné en pixels
fixes (world_width/world_height, une notion de caméra/scène
nominale) — .canvasElement.isQuizFullscreenPreview a beau
remplir 100% de CE canevas, le canevas restait lui-même de la
taille de la caméra, laissant une bande vide visible autour.
width/height:100% (voir la règle .canvas déjà prévue pour ça
dans static/style.css) fait remplir tout .canvasFrame à la
place, indépendamment de toute taille de scène/caméra. -->
<div class="canvas" id="canvas" style="{{ 'width:100%; height:100%;' if any_quiz_box_fullscreen else 'width:' + world_width|string + 'px; height:' + world_height|string + 'px;' }} aspect-ratio:auto; margin:0 auto; position:relative; overflow:hidden; background:#1a1d24;"
ondragover="onCanvasDragOver(event)" ondrop="onCanvasDrop(event)">
{% if world_width > screen.scene_width or world_height > screen.scene_height %}
{% if not any_quiz_box_fullscreen and (world_width > screen.scene_width or world_height > screen.scene_height) %}
<!-- "🎥 Champ de la caméra" : le monde (ce canevas, voir
world_width/world_height ci-dessus, routes/scenes/
scene_edit_view.py) peut être plus grand que la scène
@@ -286,11 +297,14 @@
controller.js::forgeUpdateSceneCamera), pour que le monde
agrandi reste lisible plutôt qu'une simple étendue grise
sans repère. Purement visuel (pointer-events:none) : ne
gêne jamais la sélection/le glisser d'un objet dessous. -->
gêne jamais la sélection/le glisser d'un objet dessous.
Masqué dès qu'un quiz PLEIN ÉCRAN existe sur cet écran
(demande explicite : "la notion de caméra disparaît") —
il n'a plus aucun sens une fois que ce widget couvre tout. -->
<div class="sceneCameraFrame" style="width:{{ screen.scene_width }}px; height:{{ screen.scene_height }}px;" title="Champ de la caméra en jeu ({{ screen.scene_width }}×{{ screen.scene_height }} px)"></div>
{% endif %}
{% for o in objects %}
<div class="canvasElement {{ 'selected' if o.id == selected_id else '' }}" id="el-{{ o.id }}"
<div class="canvasElement {{ 'selected' if o.id == selected_id else '' }} {{ 'isQuizFullscreenPreview' if o.quiz_fullscreen else '' }}" id="el-{{ o.id }}"
style="position:absolute; left:{{ o.x }}px; top:{{ o.y }}px; width:{{ o.width }}px; height:{{ o.height }}px;"
onmousedown="onSceneObjectMouseDown(event, {{ o.id }})">
<div class="canvasElementInner">{{ o.rendered_html|safe }}</div>
@@ -562,6 +576,98 @@
</details>
{% endif %}
{% if obj.kind == 'quiz_box' %}
<!-- "⚙️ Options du quiz" (demande explicite : "étendre les
fonctionnalités de la boîte à quiz pour les RH qui veulent
juste faire un quiz") — voir screens/rendering/quiz_box_config.py,
routes/scenes/scene_object_quiz_config.py. Plein écran/minuteur,
DISTINCT de "🎨 Style" ci-dessus (couleurs/police, partagé avec
dialogue_box/score_widget). -->
<details class="propGroup" open>
<summary>Options du quiz</summary>
<div class="propGroupBody">
<label class="checkbox is-small">
<input type="checkbox" id="quiz-fullscreen" data-was-checked="{{ '1' if quiz_box_config.fullscreen else '0' }}" {{ 'checked' if quiz_box_config.fullscreen else '' }} onchange="saveQuizBoxConfig({{ obj.id }})">
Plein écran
</label>
<div class="field mt-2">
<label class="label is-small">Minuteur</label>
<div class="control">
<select class="input is-small" id="quiz-timer-mode" onchange="saveQuizBoxConfig({{ obj.id }})">
{% for mode in quiz_box_timer_modes %}
<option value="{{ mode }}" {{ 'selected' if quiz_box_config.timer_mode == mode else '' }}>
{{ {'aucun': 'Aucun', 'question': 'Par question', 'quiz': 'Pour tout le quiz'}[mode] }}
</option>
{% endfor %}
</select>
</div>
</div>
<div class="field">
<label class="label is-small">Durée (secondes)</label>
<div class="control">
<input class="input is-small" type="number" id="quiz-timer-seconds" min="3" max="600" value="{{ quiz_box_config.timer_seconds }}" onchange="saveQuizBoxConfig({{ obj.id }})">
</div>
</div>
<p class="help">Le plein écran et le minuteur ne s'appliquent qu'au jeu (jamais dans cet éditeur).</p>
</div>
</details>
<!-- "🖼️ Modèles" — DEUX catégories bien distinctes (demande
explicite), une SEULE des deux visible/modifiable à la fois
selon "Plein écran" ci-dessus (les deux valeurs restent
conservées indépendamment, voir screens/rendering/
quiz_box_config.py) :
- "boîte de dialogue" (hors plein écran) : variantes de forme/
couleur sur une structure proche de "Classique" ;
- "page de quiz" (plein écran) : thème complet et autonome,
structure HTML propre à CHAQUE modèle (voir
screens/rendering/quiz_box_templates.py,
render_quiz_box_page_manga pour le premier).
Ce <div> caché retient la valeur de CELUI des deux réglages
qui n'a pas de galerie affichée ici, pour que saveQuizBoxConfig
(scene-editor.js) ne l'écrase jamais par erreur en l'absence de
sa grille. -->
<div id="quizBoxTemplateMeta" data-dialog-template="{{ quiz_box_config.dialog_template }}" data-page-template="{{ quiz_box_config.page_template }}" style="display:none;"></div>
{% if quiz_box_config.fullscreen %}
<details class="propGroup" open>
<summary>Modèles — page de quiz</summary>
<div class="propGroupBody">
<p class="help mb-2">Visibles seulement en plein écran — un thème complet et autonome par modèle (police, formes, couleurs et structure propres à chacun).</p>
<div class="quizTemplateGrid" id="quizPageTemplateGrid" data-object-id="{{ obj.id }}">
{% for key in quiz_box_page_templates %}
<button type="button" class="quizTemplateTile {{ 'is-selected' if quiz_box_config.page_template == key else '' }}"
data-template="{{ key }}" onclick="selectQuizBoxPageTemplate({{ obj.id }}, '{{ key }}')">
<span class="quizTemplatePreviewImg">
<img src="{{ url_for('static', filename='quiz-templates/' + key + '.png') }}" alt="Aperçu du modèle « {{ quiz_box_page_template_labels[key] }} »" loading="lazy">
</span>
<span class="quizTemplateName">{{ quiz_box_page_template_labels[key] }}</span>
</button>
{% endfor %}
</div>
</div>
</details>
{% else %}
<details class="propGroup" open>
<summary>Modèles — boîte de dialogue</summary>
<div class="propGroupBody">
<p class="help mb-2">Visibles seulement hors plein écran — un thème complet et autonome par modèle (police, formes, couleurs et structure propres à chacun).</p>
<div class="quizTemplateGrid" id="quizDialogTemplateGrid" data-object-id="{{ obj.id }}">
{% for key in quiz_box_dialog_templates %}
<button type="button" class="quizTemplateTile {{ 'is-selected' if quiz_box_config.dialog_template == key else '' }}"
data-template="{{ key }}" onclick="selectQuizBoxDialogTemplate({{ obj.id }}, '{{ key }}')">
<span class="quizTemplatePreviewImg">
<img src="{{ url_for('static', filename='quiz-templates/' + key + '.png') }}" alt="Aperçu du modèle « {{ quiz_box_dialog_template_labels[key] }} »" loading="lazy">
</span>
<span class="quizTemplateName">{{ quiz_box_dialog_template_labels[key] }}</span>
</button>
{% endfor %}
</div>
</div>
</details>
{% endif %}
{% endif %}
{% if obj.kind not in ('fond', 'dialogue_box', 'quiz_box', 'score_widget') %}
<!-- Boîte de collision (voir screens/rendering/collision_settings.py,
static/js/play/conditions.js::elementsOverlap) : posée AUTOMATIQUEMENT