# GAB-281 · WritingLearningIntro — « Contrat de production écrite »

**Archétype / renderer_key :** `text_cta` (cartographie) · **module :** EdTechWritingLearning
**Critère validé :** changer le JSON change le rendu sans modifier le HTML. ✅ check.py 12/12.

## Pack (structure officielle par-GAB)
```
GAB-281/
  renderer.html            ← moteur contrat production écrite (ne pas modifier par instance)
  instance.example.json    ← SOURCE DE VÉRITÉ (contenu réel, à plat)
  schema.contract.json     ← contrat de validation
  README-contract.md       ← ce fichier
```

## Champs requis (instance, à plat)
`gab_id` · `writing_id` · `title` · `writing_goal` · `writing_type` · `expected_output` · `start_cta{label,action}`

Optionnels : `icon`, `writing_type_label`, `subtitle`, `source_metadata{duration_label,level_label,length_label}`, `contract_label`, `contract_clauses[]{key,icon,value,detail}`, `idea_starters_label`, `idea_starters[]`, `privacy_notice{heading,text}`, `reason_block{heading,text}`, `secondary_cta{label,action}`.

## Ce qui vient du JSON vs HTML
- **JSON** : titre de la production, type d'écrit, objectif, consignes, clauses du contrat (6), amorces anti-page-blanche (3), notice vie privée, raison pédagogique, libellé des boutons.
- **HTML** : grille 2 colonnes, blocs couleur, layout boutons, logique BLOCKED, affichage slots.

## Garde-fous (child_safety)
- **Vie privée** : `privacy_notice` affiche explicitement que le brouillon est local et que l'adulte ne lit pas sans consentement élève.
- **Anti-invention** : aucun contenu pédagogique codé en dur dans le HTML ; tout vient de l'instance.
- **BLOCKED** si `writing_id`, `title` ou `start_cta` absents — le moteur affiche un message d'erreur propre.

## QA à vérifier
1. Modifier un `value` de clause → grille reflète le changement sans toucher au HTML (critère d'or).
2. Supprimer `writing_id` → BLOCKED propre affiché.
3. `idea_starters` absent → bloc amorces masqué, pas d'erreur JS.
4. `privacy_notice` absent → bloc privacité masqué, pas d'erreur JS.
5. Responsive 375px → grille passe en 1 colonne.
6. Injecter `ENGINE.init(autreInstance)` → rendu change entièrement.

## Source
`INDEX-300-writinglearning-GAB-281-285-PLAYABLE.html` (stage `data-tpl="281"`, section `<!-- GAB-281 INTRO -->`).
