{
  "contract_version": "gab_166_contract_v1",
  "gab_id": "GAB-166",
  "canonical_name": "AudioIntroPlayer",
  "module_owner": "EdTechAudioLearning",
  "renderer_key": "media_viewer",
  "required_fields": [
    "gab_id",
    "audio_intro_id",
    "title",
    "instruction",
    "audio_src",
    "transcript"
  ],
  "optional_fields": [
    "audio_lang",
    "audio_rate",
    "duration_label",
    "transcript_label",
    "transcript_required",
    "banner_text",
    "accessibility",
    "child_safety"
  ],
  "field_types": {
    "audio_src": "string — URL du fichier audio (mp3/ogg/wav) ou '_TODO:...' si absent",
    "audio_lang": "string BCP-47 (ex: 'fr-FR') — langue pour speech synthesis fallback",
    "audio_rate": "number(0.5..2.0) — vitesse de lecture (défaut 0.95)",
    "duration_label": "string — durée affichée ex: '1:20'",
    "transcript": "string — texte intégral obligatoire (RGPD/accessibilité)",
    "transcript_label": "string — libellé de la section transcript",
    "transcript_required": "boolean — toujours true (doctrine module : transcript TOUJOURS visible)",
    "banner_text": "string — texte informatif au-dessus du player",
    "accessibility": "object{transcript_always_visible:boolean, keyboard_navigable:boolean, prefers_reduced_motion:boolean}",
    "child_safety": "object{no_recording:boolean, no_data_transmission:boolean}"
  },
  "constraints": [
    "transcript est OBLIGATOIRE — interdit sans transcript (doctrine MODULE 9 : TRANSCRIPT TOUJOURS visible/disponible).",
    "audio_src peut être '_TODO:...' si le fichier audio réel n'est pas encore disponible ; le renderer doit afficher un état dégradé gracieux.",
    "Pas d'enregistrement audio, pas de transmission de données utilisateur (RGPD mineurs).",
    "Le lecteur ne doit PAS être utilisé pour un audio noté ou une évaluation officielle (→ GAB-134).",
    "duration_label est indicatif uniquement, jamais calculé dynamiquement depuis audio_src dans ce GAB."
  ],
  "blocked_conditions": [
    "title absent (BLOCKED)",
    "transcript absent ou vide (BLOCKED)",
    "audio_intro_id absent (BLOCKED)"
  ],
  "accessibility": [
    "keyboard_navigable",
    "focus_visible",
    "prefers_reduced_motion",
    "transcript_always_visible",
    "fallback_speech_synthesis"
  ],
  "qa_cases": [
    { "case": "instance conforme", "expected": "rendu complet : titre, banner, player, transcript — 0 erreur" },
    { "case": "transcript absent", "expected": "BLOCKED avec message explicite" },
    { "case": "title absent", "expected": "BLOCKED avec message explicite" },
    { "case": "audio_intro_id absent", "expected": "BLOCKED avec message explicite" },
    { "case": "audio_src = '_TODO:...'", "expected": "player affiché en état dégradé, transcript visible, pas de crash" },
    { "case": "instance externe injectée via init(ext)", "expected": "le rendu change sans modifier le HTML" },
    { "case": "responsive 375/768/1024", "expected": "aucun débordement horizontal, transcript lisible" },
    { "case": "bouton play/pause", "expected": "toggle correct, icône change ▶/⏸, wave anime/s'arrête" }
  ],
  "traceability": {
    "derived_from_core_gab": "GAB-166",
    "note": "Ce schema VALIDE l'instance. Le contrat pédagogique complet (input_contract/validation_logic/feedback_scoring_logic) vit dans le CORE-GAB officiel, pas ici (évite la duplication)."
  }
}
