{
  "contract_version": "gab_145_contract_v1",
  "gab_id": "GAB-145",
  "canonical_name": "InteractiveBeforeAfterToggle",
  "module_owner": "EdTechInteractiveLearning",
  "renderer_key": "text_cta",
  "required_fields": [
    "gab_id",
    "toggle_id",
    "before_tab_label",
    "after_tab_label",
    "before_state_label",
    "before_state_content",
    "after_state_label",
    "after_state_content"
  ],
  "optional_fields": [
    "title",
    "slider_label",
    "primary_cta"
  ],
  "field_types": {
    "toggle_id": "string — identifiant unique de l'instance (slug)",
    "before_tab_label": "string — libellé du bouton onglet état initial",
    "after_tab_label": "string — libellé du bouton onglet état final",
    "slider_label": "string — texte indicatif au-dessus du slider",
    "before_state_label": "string — étiquette mono affichée dans le frame état initial",
    "before_state_content": "string — contenu HTML (balises em autorisées) état initial",
    "after_state_label": "string — étiquette mono affichée dans le frame état final",
    "after_state_content": "string — contenu HTML (balises em autorisées) état final",
    "primary_cta": "object{label,action}"
  },
  "constraints": [
    "Exactement 2 états : before et after — pas de troisième état.",
    "Les contenus *_state_content acceptent la balise <em> uniquement (pas de script).",
    "primary_cta : bouton structurel, libellé venant du JSON."
  ],
  "blocked_conditions": [
    "toggle_id absent",
    "before_state_content absent",
    "after_state_content absent",
    "before_tab_label absent",
    "after_tab_label absent"
  ],
  "accessibility": [
    "keyboard_navigable",
    "focus_visible",
    "prefers_reduced_motion",
    "aria_labels_on_tabs",
    "slider_aria_label"
  ],
  "qa_cases": [
    { "case": "instance conforme", "expected": "rendu complet avec onglets et slider fonctionnels, 0 erreur" },
    { "case": "champ requis manquant (ex: before_state_content)", "expected": "BLOCKED listant le champ" },
    { "case": "clic onglet AVANT", "expected": "frame passe en style violet, label = before_state_label, slider à 0" },
    { "case": "clic onglet APRÈS", "expected": "frame passe en style mint, label = after_state_label, slider à 100" },
    { "case": "slider glissé > 50%", "expected": "bascule vers état after, onglet after actif" },
    { "case": "slider glissé < 50%", "expected": "bascule vers état before, onglet before actif" },
    { "case": "instance externe injectée via init(ext)", "expected": "rendu change sans modifier le HTML" },
    { "case": "responsive 375/768/1024", "expected": "aucun débordement horizontal" }
  ],
  "traceability": {
    "derived_from_core_gab": "GAB-145",
    "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). Références hors-lot : use_when 'before/after statique Visual → GAB-088' ; 'progression multi-étapes → GAB-097'."
  }
}
