Enrichit le reporting SCORM/xAPI et met en conformité RGAA le player
SCORM/xAPI : - Ajoute l'export SCORM 2004 (3rd/4th edition) au choix, en plus du 1.2 par défaut : sépare completion_status/success_status (un échec reste "completed" au lieu de retomber à tort en "incomplete" comme force la 1.2), remonte aussi cmi.interactions.n.* par question répondue. - Fournit cmi.core.score.min/max (1.2 et 2004), calculé depuis les récompenses de quiz du jeu, pour que le LMS affiche un vrai pourcentage au lieu du score brut à tort étiqueté "%". - Libellés de verbes xAPI en français en plus de l'anglais. Accessibilité (RGAA/WCAG 2.1 AA) sur le player : - Navigation clavier des objets de scène "au clic"/"au survol" (tabindex, role, Entrée/Espace, focus/blur). - alt sur les images (nom auteur ou décoratif), aria-hidden sur les icônes seules, role="dialog"/aria-live sur les boîtes de dialogue/quiz. - Landmark <main> + titre de page, respect de prefers-reduced-motion. - Le quiz n'avance plus automatiquement après un délai fixe : bouton "Continuer →" explicite (RGAA 2.2.1). - Avertissement de contraste dans l'éditeur de style de dialogue. - Déclaration d'accessibilité téléchargeable depuis la modale d'export.
This commit is contained in:
@@ -58,7 +58,12 @@ def render_scene_object(obj):
|
||||
f"font-weight:{style['font_weight']}; color:{html_lib.escape(style['text_color'])};"
|
||||
)
|
||||
return (
|
||||
f'<div class="dialogueBoxWidget" data-object-id="{obj["id"]}" data-element-id="{obj["id"]}" style="{box_style}">'
|
||||
# role="dialog"/aria-live (RGAA/WCAG 4.1.3) : le texte de
|
||||
# dialogue change dynamiquement (innerHTML/textContent, voir
|
||||
# static/js/play/dialogue-box-controller.js) sans jamais
|
||||
# recharger la page — sans annonce explicite, un lecteur
|
||||
# d'écran ne détecte aucun changement.
|
||||
f'<div class="dialogueBoxWidget" role="dialog" aria-live="polite" aria-atomic="true" data-object-id="{obj["id"]}" data-element-id="{obj["id"]}" style="{box_style}">'
|
||||
f'<div class="dialogueBoxHeader" data-dialogue-role="header" style="background:{html_lib.escape(style["header_bg"])}">Nom du personnage</div>'
|
||||
f'<div class="dialogueBoxBody" data-dialogue-role="body" style="background:{html_lib.escape(style["body_bg"])}">Le texte du dialogue s\'affiche ici.</div>'
|
||||
f'<div class="dialogueBoxFooter" data-dialogue-role="footer" style="background:{html_lib.escape(style["footer_bg"])}">'
|
||||
@@ -83,7 +88,7 @@ def render_scene_object(obj):
|
||||
f"font-weight:{style['font_weight']}; color:{html_lib.escape(style['text_color'])};"
|
||||
)
|
||||
return (
|
||||
f'<div class="quizBoxWidget" data-object-id="{obj["id"]}" data-element-id="{obj["id"]}" style="{box_style}">'
|
||||
f'<div class="quizBoxWidget" role="dialog" aria-live="polite" aria-atomic="true" data-object-id="{obj["id"]}" data-element-id="{obj["id"]}" style="{box_style}">'
|
||||
f'<div class="quizBoxHeader" data-quiz-role="header" style="background:{html_lib.escape(style["header_bg"])}">Quête : Titre de la quête</div>'
|
||||
f'<div class="quizBoxBody" data-quiz-role="body" style="background:{html_lib.escape(style["body_bg"])}">'
|
||||
f'<p class="quizBoxQuestionText" data-quiz-role="question">La question s\'affiche ici.</p>'
|
||||
@@ -115,8 +120,14 @@ def render_scene_object(obj):
|
||||
if obj["kind"] == "personnage":
|
||||
src = resolve_personnage_idle_frame(obj)
|
||||
src_attr = f' src="{html_lib.escape(src)}"' if src else ""
|
||||
# alt (RGAA/WCAG 1.1.1) : le nom donné par l'auteur ("ℹ️
|
||||
# Informations", voir scene_object_names.py) s'il existe — c'est
|
||||
# déjà ce qu'un lecteur d'écran devrait annoncer ("qui parle" dans
|
||||
# un dialogue de quête réutilise le même nom). alt="" (décoratif)
|
||||
# sinon, jamais le nom de fichier brut par défaut du navigateur.
|
||||
alt_attr = f' alt="{html_lib.escape(obj.get("name") or "")}"'
|
||||
return (f'<img class="sceneObjectSprite" data-object-id="{obj["id"]}" data-element-id="{obj["id"]}" '
|
||||
f'data-anim-target="{obj["id"]}" data-personnage="1" style="{style}"{src_attr}>')
|
||||
f'data-anim-target="{obj["id"]}" data-personnage="1" style="{style}"{src_attr}{alt_attr}>')
|
||||
src = (obj.get("attributes") or {}).get("src", "")
|
||||
src_attr = f' src="{html_lib.escape(src)}"' if src else ""
|
||||
# "fond" (voir add_scene_object.py) : même image fixe que "decor",
|
||||
@@ -126,5 +137,9 @@ def render_scene_object(obj):
|
||||
# étendue sur tout le "monde" (voir personnage-controller.js, la
|
||||
# caméra qui la suit en défilant).
|
||||
css_class = "sceneObjectSprite sceneBackground" if obj["kind"] == "fond" else "sceneObjectSprite"
|
||||
# "fond" est TOUJOURS décoratif (un décor de scène entier, jamais une
|
||||
# information à transmettre) — un "decor" nommé par l'auteur utilise
|
||||
# ce nom, sinon alt="" plutôt qu'aucun attribut du tout.
|
||||
alt_attr = ' alt=""' if obj["kind"] == "fond" else f' alt="{html_lib.escape(obj.get("name") or "")}"'
|
||||
return (f'<img class="{css_class}" data-object-id="{obj["id"]}" data-element-id="{obj["id"]}" '
|
||||
f'data-anim-target="{obj["id"]}" style="{style}"{src_attr}>')
|
||||
f'data-anim-target="{obj["id"]}" style="{style}"{src_attr}{alt_attr}>')
|
||||
|
||||
Reference in New Issue
Block a user