{
  "contract_version": "gab_359_contract_v1",
  "gab_id": "GAB-359",
  "canonical_name": "MotivationLearningTeamEncouragement",
  "module_owner": "EdTechMotivationLearning",
  "renderer_key": "text_cta",
  "required_fields": [
    "gab_id",
    "team_encouragement_id",
    "messages",
    "solo_fallback"
  ],
  "optional_fields": [
    "intro_notice",
    "pick_prompt",
    "primary_cta",
    "use_when",
    "do_not_use_when",
    "accessibility",
    "child_safety",
    "_note_dev"
  ],
  "field_types": {
    "team_encouragement_id": "string",
    "intro_notice": "string",
    "pick_prompt": "string",
    "messages": "array<{id:string, emoji:string, tone:enum['ok','bad'], text:string, feedback:string, feedback_kind:enum['ok','bad','note','warn']}>",
    "solo_fallback": "object{emoji:string, label:string, feedback:string, feedback_kind:enum['ok','bad','note','warn']}",
    "primary_cta": "object{label:string, action:string}",
    "use_when": "array<string>",
    "do_not_use_when": "array<string>",
    "accessibility": "object{keyboard_navigable:boolean, focus_visible:boolean, prefers_reduced_motion:boolean}",
    "child_safety": "object{moderation_required:boolean, no_humiliating_ranking:boolean, solo_always_available:boolean}"
  },
  "constraints": [
    "messages doit contenir au moins un item avec tone='ok' (message acceptable) et un avec tone='bad' (exemple rabaissant).",
    "solo_fallback est toujours proposé — l'apprenant peut refuser le mode équipe sans pénalité.",
    "Aucun classement nominatif ou humiliant ne peut apparaître : no_humiliating_ranking est un garde-fou structurel.",
    "primary_cta : bouton structurel HTML, libellé venant du JSON.",
    "Tout message tone='bad' doit avoir feedback_kind='bad' pour signaler le blocage par modération."
  ],
  "blocked_conditions": [
    "gab_id absent (BLOCKED)",
    "team_encouragement_id absent (BLOCKED)",
    "messages vides (BLOCKED)",
    "solo_fallback absent (BLOCKED)"
  ],
  "accessibility": [
    "keyboard_navigable",
    "focus_visible",
    "prefers_reduced_motion",
    "tabindex sur chaque message et option solo"
  ],
  "qa_cases": [
    { "case": "instance conforme", "expected": "rendu complet, 0 erreur, messages cliquables" },
    { "case": "messages:[] vide", "expected": "BLOCKED listant le champ" },
    { "case": "solo_fallback absent", "expected": "BLOCKED listant le champ" },
    { "case": "clic message tone='ok'", "expected": "panel ok, bouton CTA activé" },
    { "case": "clic message tone='bad'", "expected": "panel bad (modération), bouton CTA activé" },
    { "case": "clic solo_fallback", "expected": "panel note (mode solo), CTA désactivé" },
    { "case": "instance externe injectée via ENGINE.init(ext)", "expected": "le rendu change sans modifier le HTML" },
    { "case": "responsive 375/768/1024", "expected": "aucun débordement horizontal" }
  ],
  "traceability": {
    "derived_from_core_gab": "GAB-359",
    "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). Source HTML : INDEX-300-motivationlearning-GAB-356-360-PLAYABLE.html, stage data-tpl='359', handlers d359Pick / d359Solo."
  }
}
