{
  "chapter": {
    "id": "evenements-formulaires",
    "level": "premiere",
    "theme": "IHM Web",
    "title": "Événements et formulaires",
    "description": "Événements DOM (clic, saisie, soumission), gestion des\névénements en JavaScript, balises de formulaire HTML\n(`<form>`, `<input>`, `<button>`, `<select>`), méthodes\nHTTP GET et POST, validation côté client.",
    "prerequisites": [],
    "references": []
  },
  "questions": [
    {
      "id": "q01",
      "difficulty": 1,
      "skills": [
        "evenement-definition"
      ],
      "title": "Événement",
      "statement": "Qu'est-ce qu'un **événement** dans le contexte\nd'une page web ?",
      "options": [
        {
          "text": "Une modification CSS automatique",
          "correct": false,
          "feedback": "Une modification CSS\nrelève de la mise en\nforme. Un évènement,\nen JavaScript, est un\nsignal émis par le\nnavigateur ou l'utilisateur,\nauquel le code peut\nréagir.\n"
        },
        {
          "text": "Un fichier média",
          "correct": false,
          "feedback": "Un fichier média (image,\nson, vidéo) est une\nressource statique,\ntandis qu'un évènement\nest un signal dynamique\ndéclenché lors de\nl'utilisation de la page.\n"
        },
        {
          "text": "Une erreur de programmation",
          "correct": false,
          "feedback": "Erreur : un bug n'est pas un événement DOM.\n"
        },
        {
          "text": "Une action déclenchée par l'utilisateur (clic, saisie, mouvement souris...) ou par le navigateur (chargement, redimensionnement...)",
          "correct": true,
          "feedback": "Bonne réponse : les événements rendent la\npage **interactive**. JavaScript permet\nd'y réagir.\n"
        }
      ],
      "explanation": "Modèle « publication-souscription » : les\néléments **publient** des événements, les\ngestionnaires les **écoutent** et y réagissent."
    },
    {
      "id": "q02",
      "difficulty": 1,
      "skills": [
        "click"
      ],
      "title": "Événement de clic",
      "statement": "Quel est le nom de l'événement déclenché quand\nl'utilisateur **clique** sur un élément ?",
      "options": [
        {
          "text": "`mouseDown`",
          "correct": false,
          "feedback": "C'est un **autre** événement (pression\ndu bouton, sans relâcher).\n"
        },
        {
          "text": "`onclick`",
          "correct": false,
          "feedback": "C'est l'attribut/propriété d'écoute, pas\nle nom de l'événement.\n"
        },
        {
          "text": "`click`",
          "correct": true,
          "feedback": "Bonne réponse : `click` est le nom\nstandard. Pour l'écouter :\n`el.addEventListener(\"click\", ...)`.\n"
        },
        {
          "text": "`tap`",
          "correct": false,
          "feedback": "C'est un événement tactile, distinct.\n"
        }
      ],
      "explanation": "Événements souris : `click`, `dblclick`,\n`mousedown`, `mouseup`, `mousemove`,\n`mouseenter`, `mouseleave`. Idem au clavier :\n`keydown`, `keyup`, `keypress`."
    },
    {
      "id": "q03",
      "difficulty": 1,
      "skills": [
        "form"
      ],
      "title": "Balise form",
      "statement": "Quelle balise HTML délimite un **formulaire** ?",
      "options": [
        {
          "text": "`<send>`",
          "correct": false,
          "feedback": "Erreur : balise inexistante.\n"
        },
        {
          "text": "`<form>`",
          "correct": true,
          "feedback": "Bonne réponse : `<form>` regroupe les\nchamps et permet de les soumettre\nensemble. Attributs clés : `action`\n(URL), `method` (GET/POST).\n"
        },
        {
          "text": "`<input>`",
          "correct": false,
          "feedback": "Erreur : `<input>` est un **champ** de\nformulaire, pas un conteneur.\n"
        },
        {
          "text": "`<formulaire>`",
          "correct": false,
          "feedback": "Erreur : balise inexistante.\n"
        }
      ],
      "explanation": "Le formulaire envoie ses données vers\n`action` quand l'utilisateur soumet (par\nclic sur `<button type=\"submit\">` ou en\nvalidant via Entrée)."
    },
    {
      "id": "q04",
      "difficulty": 1,
      "skills": [
        "input"
      ],
      "title": "Champ texte",
      "statement": "Quelle balise crée un **champ de saisie** texte ?",
      "options": [
        {
          "text": "`<textbox>`",
          "correct": false,
          "feedback": "Erreur : balise inexistante.\n"
        },
        {
          "text": "`<text />`",
          "correct": false,
          "feedback": "Erreur : balise inexistante.\n"
        },
        {
          "text": "`<input type=\"text\">`",
          "correct": true,
          "feedback": "Bonne réponse : `<input>` est polyvalente,\n`type` détermine son apparence et son\ncomportement.\n"
        },
        {
          "text": "`<entry>`",
          "correct": false,
          "feedback": "Erreur : balise inexistante.\n"
        }
      ],
      "explanation": "Types courants d'`<input>` : `text`,\n`password`, `email`, `number`, `date`,\n`checkbox`, `radio`, `submit`, `file`."
    },
    {
      "id": "q05",
      "difficulty": 1,
      "skills": [
        "bouton"
      ],
      "title": "Bouton",
      "statement": "Comment créer un **bouton** « Envoyer » qui\nsoumet le formulaire ?",
      "options": [
        {
          "text": "`<button type=\"submit\">Envoyer</button>` (ou `<input type=\"submit\" value=\"Envoyer\">`)",
          "correct": true,
          "feedback": "Bonne réponse : `<button>` est plus\nflexible (peut contenir du HTML, des\nicônes). `<input type=\"submit\">` est\nhistorique mais valide.\n"
        },
        {
          "text": "`<a>Envoyer</a>`",
          "correct": false,
          "feedback": "Erreur : `<a>` est un **lien**, pas un\nbouton de soumission.\n"
        },
        {
          "text": "`<click>Envoyer</click>`",
          "correct": false,
          "feedback": "Erreur : balise inexistante.\n"
        },
        {
          "text": "`<send>Envoyer</send>`",
          "correct": false,
          "feedback": "Erreur : balise inexistante.\n"
        }
      ],
      "explanation": "Trois types de bouton : `submit`\n(soumission, par défaut dans `<form>`),\n`reset` (réinitialise les champs), `button`\n(générique, à manipuler en JS)."
    },
    {
      "id": "q06",
      "difficulty": 1,
      "skills": [
        "select"
      ],
      "title": "Liste déroulante",
      "statement": "Quelle balise crée une **liste déroulante** ?",
      "options": [
        {
          "text": "`<list>`",
          "correct": false,
          "feedback": "Erreur : balise inexistante.\n"
        },
        {
          "text": "`<select>` contenant des `<option>`",
          "correct": true,
          "feedback": "Bonne réponse : `<select>` est le\nconteneur, chaque choix est une `<option\nvalue=\"...\">texte</option>`.\n"
        },
        {
          "text": "`<dropdown>`",
          "correct": false,
          "feedback": "Erreur : balise inexistante.\n"
        },
        {
          "text": "`<combo>`",
          "correct": false,
          "feedback": "Erreur : balise inexistante.\n"
        }
      ],
      "explanation": "Variantes : `<datalist>` (suggestions sur un\nchamp texte), `multiple` sur `<select>`\n(sélection multiple)."
    },
    {
      "id": "q07",
      "difficulty": 1,
      "skills": [
        "label"
      ],
      "title": "Étiquette",
      "statement": "À quoi sert la balise `<label>` ?",
      "options": [
        {
          "text": "À associer une étiquette explicative à un champ de formulaire (clic sur l'étiquette = focus sur le champ)",
          "correct": true,
          "feedback": "Bonne réponse : améliore l'**accessibilité**\net l'ergonomie. Lier via\n`<label for=\"nomChamp\">...</label>` et\n`<input id=\"nomChamp\">`.\n"
        },
        {
          "text": "À mettre du texte en gras",
          "correct": false,
          "feedback": "Erreur : c'est `<strong>` ou `<b>`.\n"
        },
        {
          "text": "À créer un titre",
          "correct": false,
          "feedback": "Erreur : ce serait `<h1>`...`<h6>`.\n"
        },
        {
          "text": "À étiqueter une variable JavaScript",
          "correct": false,
          "feedback": "Une variable JavaScript\nn'a pas d'étiquette\naccessible aux lecteurs\nd'écran. La balise\n`<label>` agit sur la\nstructure du formulaire\nHTML, pas sur le code.\n"
        }
      ],
      "explanation": "Bonne pratique RGAA/WCAG : tout champ doit\navoir un `<label>` associé. Sans label, les\nlecteurs d'écran ne peuvent pas annoncer\ncorrectement le champ."
    },
    {
      "id": "q08",
      "difficulty": 1,
      "skills": [
        "submit"
      ],
      "title": "Soumission",
      "statement": "Quel événement se déclenche quand un\n**formulaire est soumis** ?",
      "options": [
        {
          "text": "`send`",
          "correct": false,
          "feedback": "Erreur : événement inexistant.\n"
        },
        {
          "text": "`submit`",
          "correct": true,
          "feedback": "Bonne réponse : événement `submit`\ndéclenché sur le `<form>`. À écouter pour\nintercepter la soumission (validation\ncôté client, AJAX...).\n"
        },
        {
          "text": "`change`",
          "correct": false,
          "feedback": "C'est un autre événement (changement de\nvaleur d'un champ).\n"
        },
        {
          "text": "`click`",
          "correct": false,
          "feedback": "Le clic sur le bouton déclenche click,\nmais l'événement de soumission est\n`submit`.\n"
        }
      ],
      "explanation": "Pour empêcher la soumission par défaut\n(par exemple pour valider en JS) :\n`event.preventDefault()` dans le\ngestionnaire."
    },
    {
      "id": "q09",
      "difficulty": 1,
      "skills": [
        "get-vs-post"
      ],
      "title": "GET ou POST ?",
      "statement": "Quelle méthode HTTP utilise-t-on pour\n**envoyer un mot de passe** ?",
      "options": [
        {
          "text": "Aucune des deux",
          "correct": false,
          "feedback": "Pour transmettre un mot\nde passe, on choisit\nsystématiquement la\nméthode POST, qui place\nles données dans le corps\nde la requête plutôt que\ndans l'URL.\n"
        },
        {
          "text": "POST, parce que les données sont dans le corps de la requête, pas dans l'URL",
          "correct": true,
          "feedback": "Bonne réponse : POST est plus discret.\nPour les données sensibles, utiliser\nPOST + HTTPS.\n"
        },
        {
          "text": "GET, parce que c'est plus rapide",
          "correct": false,
          "feedback": "Erreur : GET met les données dans l'URL,\nce qui est **visible** et logué dans\nl'historique. À éviter pour les données\nsensibles.\n"
        },
        {
          "text": "PUT, c'est obligatoire",
          "correct": false,
          "feedback": "Erreur : PUT a un autre rôle (mise à jour\nd'une ressource REST).\n"
        }
      ],
      "explanation": "Règle pratique : GET pour récupérer des\ndonnées (recherche, lecture), POST pour\nmodifier l'état du serveur (création, envoi\nde formulaire)."
    },
    {
      "id": "q10",
      "difficulty": 1,
      "skills": [
        "exemple-formulaire"
      ],
      "title": "Formulaire complet",
      "statement": "Lequel de ces fragments est un **formulaire\nvalide** demandant un nom et l'envoyant en\nPOST ?",
      "options": [
        {
          "text": "```html\n<input type=\"text\">\n```\n",
          "correct": false,
          "feedback": "Insuffisant : pas de `<form>`, pas de\nbouton.\n"
        },
        {
          "text": "```html\n<form>Nom: <input></form>\n```\n",
          "correct": false,
          "feedback": "Trop minimal : ni `name`, ni méthode, ni\nbouton.\n"
        },
        {
          "text": "```html\n<send name=\"nom\" />\n```\n",
          "correct": false,
          "feedback": "Erreur : balise inexistante.\n"
        },
        {
          "text": "```html\n<form action=\"/save\" method=\"POST\">\n  <label for=\"nom\">Nom :</label>\n  <input id=\"nom\" name=\"nom\" type=\"text\">\n  <button type=\"submit\">Envoyer</button>\n</form>\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : `<form>` avec action et\nmethod, `<label>` lié à `<input>` par\n`for`/`id`, `name` (essentiel pour\nidentifier la donnée envoyée), bouton\nsubmit.\n"
        }
      ],
      "explanation": "Attribut **`name`** essentiel : c'est sous\nce nom que la donnée arrive côté serveur.\nSans `name`, le champ n'est **pas envoyé**."
    },
    {
      "id": "q11",
      "difficulty": 2,
      "skills": [
        "event-handler"
      ],
      "title": "Gestionnaire d'événement",
      "statement": "Quel code attache la fonction `handleClick` au\nclic sur le bouton `btn` ?",
      "options": [
        {
          "text": "`btn.onclick(handleClick)`",
          "correct": false,
          "feedback": "Erreur : `onclick` est une **propriété**,\npas une méthode. Il faut écrire\n`btn.onclick = handleClick`.\n"
        },
        {
          "text": "`btn.click(handleClick)`",
          "correct": false,
          "feedback": "Erreur : `btn.click()` **déclenche** le\nclic, ne l'écoute pas.\n"
        },
        {
          "text": "`btn.addEventListener(\"click\", handleClick)`",
          "correct": true,
          "feedback": "Bonne réponse : forme moderne et\nrecommandée.\n"
        },
        {
          "text": "`btn.handle(\"click\", handleClick)`",
          "correct": false,
          "feedback": "Erreur : méthode inexistante.\n"
        }
      ],
      "explanation": "Avantages d'`addEventListener` sur\n`onclick = ` : possibilité d'attacher\n**plusieurs** gestionnaires, options\navancées (`once`, `capture`, `passive`)."
    },
    {
      "id": "q12",
      "difficulty": 2,
      "skills": [
        "valeur-input"
      ],
      "title": "Lire la valeur d'un champ",
      "statement": "Comment récupérer la valeur tapée dans\n`<input id=\"nom\">` ?",
      "options": [
        {
          "text": "`document.getElementById(\"nom\").text`",
          "correct": false,
          "feedback": "Erreur : c'est `value`, pas `text`.\n"
        },
        {
          "text": "`document.getElementById(\"nom\").innerHTML`",
          "correct": false,
          "feedback": "Erreur : `innerHTML` est pour le contenu\nHTML d'éléments génériques, pas pour les\nchamps de formulaire.\n"
        },
        {
          "text": "`document.getElementById(\"nom\").content`",
          "correct": false,
          "feedback": "Erreur : propriété inexistante.\n"
        },
        {
          "text": "`document.getElementById(\"nom\").value`",
          "correct": true,
          "feedback": "Bonne réponse : `value` est la propriété\nstandard pour les champs de formulaire\n(`<input>`, `<select>`, `<textarea>`).\n"
        }
      ],
      "explanation": "Pour modifier aussi : `el.value =\n\"nouveau\"`. Pour les cases à cocher et les\nboutons radio : `el.checked` (booléen)."
    },
    {
      "id": "q13",
      "difficulty": 2,
      "skills": [
        "preventdefault"
      ],
      "title": "preventDefault",
      "statement": "À quoi sert `event.preventDefault()` dans un\ngestionnaire ?",
      "options": [
        {
          "text": "À empêcher tout événement futur",
          "correct": false,
          "feedback": "Erreur : seulement le comportement par\ndéfaut de l'événement courant.\n"
        },
        {
          "text": "À empêcher le comportement par défaut du navigateur (par exemple, ne pas soumettre le formulaire pour le traiter en JS)",
          "correct": true,
          "feedback": "Bonne réponse : très utile pour\nintercepter une soumission ou un clic\nsur un lien (`<a>`) afin de gérer\nl'action en JavaScript.\n"
        },
        {
          "text": "À fermer la page",
          "correct": false,
          "feedback": "La fermeture d'une page\nse demande via `window.close()`.\nLa méthode `preventDefault`\nne ferme rien : elle annule\nuniquement l'action par\ndéfaut associée à un\névènement.\n"
        },
        {
          "text": "À effacer la console",
          "correct": false,
          "feedback": "L'effacement de la\nconsole se fait avec\n`console.clear()`. La\nméthode `preventDefault`\nn'agit que sur l'évènement\nen cours, pas sur les\noutils du navigateur.\n"
        }
      ],
      "explanation": "Cas typique : valider un formulaire en JS\navant de le soumettre.\n```js\nform.addEventListener(\"submit\", e => {\n  if (!valider()) e.preventDefault();\n});\n```"
    },
    {
      "id": "q14",
      "difficulty": 2,
      "skills": [
        "validation-html5"
      ],
      "title": "Validation HTML5",
      "statement": "Comment indiquer qu'un champ est\n**obligatoire** dans le HTML ?",
      "options": [
        {
          "text": "Avec `mandatory=\"true\"`",
          "correct": false,
          "feedback": "Erreur : attribut inexistant.\n"
        },
        {
          "text": "Avec l'attribut `required` : `<input required>`",
          "correct": true,
          "feedback": "Bonne réponse : validation native du\nnavigateur, qui empêche la soumission si\nle champ est vide. Affichage d'un message\nstandard automatiquement.\n"
        },
        {
          "text": "Avec `must-fill`",
          "correct": false,
          "feedback": "Erreur : attribut inexistant.\n"
        },
        {
          "text": "Il faut une fonction JavaScript",
          "correct": false,
          "feedback": "Possible mais HTML5 offre des\nvalidations natives plus simples.\n"
        }
      ],
      "explanation": "Autres attributs de validation HTML5 :\n`min`, `max`, `minlength`, `maxlength`,\n`pattern` (expression régulière), `type=\"email\"`, etc."
    },
    {
      "id": "q15",
      "difficulty": 2,
      "skills": [
        "type-input"
      ],
      "title": "Type d'input adapté",
      "statement": "Quel **type** d'`<input>` est le plus adapté\npour un champ « Âge » ?",
      "options": [
        {
          "text": "`type=\"number\"`",
          "correct": true,
          "feedback": "Bonne réponse : restreint à des nombres,\naffiche des flèches haut/bas, ouvre un\nclavier numérique sur mobile.\n"
        },
        {
          "text": "`type=\"text\"`",
          "correct": false,
          "feedback": "Possible mais peu pratique. L'utilisateur\npourrait taper du texte.\n"
        },
        {
          "text": "`type=\"email\"`",
          "correct": false,
          "feedback": "Erreur : pour un courriel.\n"
        },
        {
          "text": "`type=\"age\"`",
          "correct": false,
          "feedback": "Erreur : type inexistant.\n"
        }
      ],
      "explanation": "Choisir le bon `type` améliore l'ergonomie\n(clavier mobile adapté), accessibilité et\nvalidation. Ne pas négliger ces\n« petits détails »."
    },
    {
      "id": "q16",
      "difficulty": 2,
      "skills": [
        "event-target"
      ],
      "title": "Cible de l'événement",
      "statement": "Dans un gestionnaire d'événement, comment\naccéder à l'**élément** sur lequel l'événement\na eu lieu ?",
      "options": [
        {
          "text": "`event.dom`",
          "correct": false,
          "feedback": "Erreur : propriété inexistante.\n"
        },
        {
          "text": "`this.element`",
          "correct": false,
          "feedback": "Erreur : `this` peut référencer\nl'élément, mais selon le contexte\n(fonction fléchée vs régulière), il\npeut ne pas marcher.\n"
        },
        {
          "text": "`event.target`",
          "correct": true,
          "feedback": "Bonne réponse : référence l'élément\nayant déclenché l'événement. Permet de\npartager un même gestionnaire entre\nplusieurs éléments.\n"
        },
        {
          "text": "`event.element`",
          "correct": false,
          "feedback": "Erreur : propriété inexistante.\n"
        }
      ],
      "explanation": "Distinction `event.target` (où l'événement\ns'est produit) vs `event.currentTarget`\n(où le gestionnaire est attaché). Pour la\ndélégation d'événements, on utilise\n`target`."
    },
    {
      "id": "q17",
      "difficulty": 2,
      "skills": [
        "name-attribute"
      ],
      "title": "Attribut name",
      "statement": "Pourquoi l'attribut `name` est-il important\npour un `<input>` ?",
      "options": [
        {
          "text": "Pour le validateur HTML",
          "correct": false,
          "feedback": "Indirectement, mais le rôle principal est\nautre.\n"
        },
        {
          "text": "Pour le nom affiché",
          "correct": false,
          "feedback": "Erreur : c'est le rôle de `<label>`.\n"
        },
        {
          "text": "Parce qu'il identifie la donnée lors de l'envoi du formulaire (clé dans la requête HTTP)",
          "correct": true,
          "feedback": "Bonne réponse : sans `name`, le champ\nn'est **pas envoyé** au serveur. Le\nserveur reçoit `nom=Alice&age=17` à\npartir des `name`.\n"
        },
        {
          "text": "Pour la couleur du champ",
          "correct": false,
          "feedback": "Erreur : c'est CSS.\n"
        }
      ],
      "explanation": "Distinction triple : `id` (unique, pour CSS\net JS), `class` (pour CSS et JS, partagé),\n`name` (pour la soumission du formulaire)."
    },
    {
      "id": "q18",
      "difficulty": 2,
      "skills": [
        "textarea"
      ],
      "title": "Zone de texte",
      "statement": "Quelle balise crée une **zone de texte\nmultiligne** ?",
      "options": [
        {
          "text": "`<paragraph contenteditable>`",
          "correct": false,
          "feedback": "Possible mais usage avancé, pas un\nformulaire classique.\n"
        },
        {
          "text": "`<textarea>contenu initial</textarea>`",
          "correct": true,
          "feedback": "Bonne réponse : balise dédiée. Attributs\nutiles : `rows`, `cols`, `placeholder`,\n`maxlength`.\n"
        },
        {
          "text": "`<text>multiligne</text>`",
          "correct": false,
          "feedback": "Erreur : balise inexistante.\n"
        },
        {
          "text": "`<input type=\"text\" multiline>`",
          "correct": false,
          "feedback": "Erreur : `multiline` n'existe pas comme\nattribut de `<input>`.\n"
        }
      ],
      "explanation": "Particularité : `<textarea>` n'est pas\nauto-fermante, et son contenu initial\nest entre les balises (pas un attribut\n`value`)."
    },
    {
      "id": "q19",
      "difficulty": 2,
      "skills": [
        "event-input"
      ],
      "title": "Événement input vs change",
      "statement": "Quelle est la différence entre les événements\n`input` et `change` sur un `<input type=\"text\">` ?",
      "options": [
        {
          "text": "`input` se déclenche à chaque modification (chaque touche), `change` seulement quand le champ perd le focus avec une nouvelle valeur",
          "correct": true,
          "feedback": "Bonne réponse : `input` est plus\nréactif (auto-complétion en temps réel),\n`change` plus économe.\n"
        },
        {
          "text": "`change` plante toujours",
          "correct": false,
          "feedback": "L'évènement `change`\nfonctionne parfaitement\net n'a aucune raison\nstructurelle d'échouer.\nIl sert simplement à\nréagir lorsque la\nnouvelle valeur est\nconfirmée par l'utilisateur.\n"
        },
        {
          "text": "Aucune",
          "correct": false,
          "feedback": "Erreur : différence de fréquence.\n"
        },
        {
          "text": "`input` n'existe pas",
          "correct": false,
          "feedback": "Erreur : événement standard.\n"
        }
      ],
      "explanation": "Choix selon le besoin : aperçu en direct →\n`input` ; validation à la fin → `change` ;\nsoumission complète → `submit` (sur le\nformulaire)."
    },
    {
      "id": "q20",
      "difficulty": 2,
      "skills": [
        "requete-http"
      ],
      "title": "Requête HTTP",
      "statement": "Quand on soumet un formulaire en POST vers\n`/save`, que se passe-t-il **côté\nnavigateur** ?",
      "options": [
        {
          "text": "Le navigateur ouvre un autre site",
          "correct": false,
          "feedback": "Le navigateur ne quitte\npas le domaine courant\nde sa propre initiative.\nIl envoie simplement\nune requête vers l'URL\nindiquée par l'attribut\n`action` du formulaire.\n"
        },
        {
          "text": "Le formulaire reste affiché",
          "correct": false,
          "feedback": "Pas par défaut : la page **change**.\n"
        },
        {
          "text": "Le navigateur envoie une requête HTTP POST vers `/save` avec les données dans le corps, puis affiche la réponse du serveur",
          "correct": true,
          "feedback": "Bonne réponse : c'est le mécanisme\nfondamental du formulaire. Sans\nJavaScript, la page change. Avec JS, on\npeut intercepter pour faire de l'AJAX\n(sans rechargement).\n"
        },
        {
          "text": "Rien",
          "correct": false,
          "feedback": "Erreur : la soumission est l'action\nprincipale.\n"
        }
      ],
      "explanation": "L'AJAX (Asynchronous JavaScript And XML)\npermet d'envoyer la requête en arrière-plan\net de mettre à jour la page sans la\nrecharger. Schéma moderne (`fetch` API)."
    },
    {
      "id": "q21",
      "difficulty": 3,
      "skills": [
        "validation-cote"
      ],
      "title": "Validation client vs serveur",
      "statement": "Faut-il valider les données d'un formulaire\n**côté client** (JavaScript), **côté serveur**\nou les deux ?",
      "options": [
        {
          "text": "Aucune des deux : faire confiance",
          "correct": false,
          "feedback": "Erreur : recette pour les bugs et failles.\n"
        },
        {
          "text": "Les deux : côté client pour l'ergonomie (retour immédiat), côté serveur pour la sécurité (autorité)",
          "correct": true,
          "feedback": "Bonne réponse : règle d'or. Toute\ndonnée venant du client est suspecte\njusqu'à validation par le serveur.\n"
        },
        {
          "text": "Côté serveur uniquement",
          "correct": false,
          "feedback": "Possible mais peu ergonomique :\nl'utilisateur doit attendre la réponse\ndu serveur pour voir une erreur.\n"
        },
        {
          "text": "Côté client uniquement (plus rapide)",
          "correct": false,
          "feedback": "Erreur : la validation client peut être\ncontournée (l'utilisateur peut\ndésactiver JS, modifier le HTML,\nenvoyer une requête falsifiée). Pas de\nsécurité.\n"
        }
      ],
      "explanation": "Mantra de sécurité : « Never trust user\ninput. » La validation côté client améliore\nl'expérience, mais la validation côté\nserveur est **non négociable**."
    },
    {
      "id": "q22",
      "difficulty": 3,
      "skills": [
        "delegation"
      ],
      "title": "Délégation d'événements",
      "statement": "On a une liste de 100 boutons. Quelle est la\n**meilleure** façon d'écouter leurs clics ?",
      "options": [
        {
          "text": "Utiliser une variable globale",
          "correct": false,
          "feedback": "Une variable globale ne\nsert pas à intercepter\ndes évènements. La bonne\napproche reste la\ndélégation : un seul\ngestionnaire posé sur\nle parent commun.\n"
        },
        {
          "text": "Attacher un seul écouteur sur le parent et utiliser `event.target` pour identifier le bouton cliqué (« délégation d'événements »)",
          "correct": true,
          "feedback": "Bonne réponse : schéma recommandé.\nAvantages : un seul gestionnaire,\nfonctionne pour les boutons ajoutés\ndynamiquement après coup.\n"
        },
        {
          "text": "Modifier le HTML pour ajouter `onclick` à chaque bouton",
          "correct": false,
          "feedback": "Erreur : 100 attributs onclick = code\nfragile.\n"
        },
        {
          "text": "Mettre `addEventListener` sur chacun des 100 boutons",
          "correct": false,
          "feedback": "Fonctionne mais coûteux en mémoire et à\nmaintenir, surtout si la liste change\ndynamiquement.\n"
        }
      ],
      "explanation": "Modèle de délégation : « les événements\nremontent (bubble) », donc on peut les\nattraper en haut. Cas typique : liste\nd'éléments dynamiques."
    },
    {
      "id": "q23",
      "difficulty": 3,
      "skills": [
        "debug-form"
      ],
      "title": "Bug formulaire",
      "statement": "Mon formulaire en POST envoie au serveur, mais\nle champ « nom » est **vide** côté serveur.\nQuelle est la cause la plus probable ?",
      "options": [
        {
          "text": "L'attribut `name` manque sur l'`<input>` (donc le champ n'est pas inclus dans la requête)",
          "correct": true,
          "feedback": "Bonne réponse : c'est le piège classique.\n`<input id=\"nom\">` ne suffit pas ; il\nfaut `<input name=\"nom\">` (l'`id` n'est\npas envoyé).\n"
        },
        {
          "text": "Le bouton submit est cassé",
          "correct": false,
          "feedback": "Possible mais peu probable si la requête\narrive au serveur.\n"
        },
        {
          "text": "Le serveur est en panne",
          "correct": false,
          "feedback": "Possible mais cela donnerait une autre\nerreur.\n"
        },
        {
          "text": "Le navigateur n'autorise pas POST",
          "correct": false,
          "feedback": "Erreur : POST est standard.\n"
        }
      ],
      "explanation": "Réflexe de débogage : ouvrir l'onglet\nNetwork des outils développeur, regarder la\nrequête envoyée et son contenu (« Form\nData »)."
    },
    {
      "id": "q24",
      "difficulty": 3,
      "skills": [
        "securite-csrf"
      ],
      "title": "Sécurité d'un formulaire",
      "statement": "Quelle est l'attaque classique consistant à\nfaire **soumettre** un formulaire à un\nutilisateur **à son insu** sur un autre site ?",
      "options": [
        {
          "text": "SQL injection",
          "correct": false,
          "feedback": "Existe, mais concerne les bases de\ndonnées.\n"
        },
        {
          "text": "CSRF (Cross-Site Request Forgery)",
          "correct": true,
          "feedback": "Bonne réponse : un site malveillant\nincite l'utilisateur (déjà authentifié\nailleurs) à faire une requête vers le\nsite légitime, exploitant ses cookies.\nProtection : tokens CSRF, attribut\n`SameSite` sur les cookies.\n"
        },
        {
          "text": "Spam",
          "correct": false,
          "feedback": "Pas le terme technique.\n"
        },
        {
          "text": "XSS (Cross-Site Scripting)",
          "correct": false,
          "feedback": "Existe, mais c'est une attaque par\ninjection de script, différente.\n"
        }
      ],
      "explanation": "Trois grandes attaques web : XSS\n(injection de script), CSRF (requêtes\nforgées), SQLi (injection SQL).\nConnaissance essentielle pour développer\nsereinement."
    },
    {
      "id": "q25",
      "difficulty": 3,
      "skills": [
        "synthese"
      ],
      "title": "Synthèse",
      "statement": "Parmi les affirmations suivantes, laquelle\nest **fausse** ?",
      "options": [
        {
          "text": "La méthode HTTP GET est préférable pour transmettre un mot de passe",
          "correct": true,
          "feedback": "Faux (donc bonne réponse) : GET met les\ndonnées dans l'URL (visibles, loguées).\nPour un mot de passe, utiliser POST sur\nHTTPS.\n"
        },
        {
          "text": "Toute donnée client doit être revalidée côté serveur",
          "correct": false,
          "feedback": "Vrai : règle d'or de sécurité.\n"
        },
        {
          "text": "Un événement DOM peut être écouté avec `addEventListener`",
          "correct": false,
          "feedback": "Cette affirmation est\nexacte : `addEventListener`\nest la méthode standard\npour attacher un\ngestionnaire à un\névènement. Ce n'est\ndonc pas la mauvaise\naffirmation recherchée.\n"
        },
        {
          "text": "L'attribut `name` d'un champ est essentiel pour la soumission au serveur",
          "correct": false,
          "feedback": "Vrai : sans `name`, pas envoyé.\n"
        }
      ],
      "explanation": "Mnémonique sécurité : « Never trust user\ninput + always use HTTPS pour les données\nsensibles »."
    },
    {
      "id": "q26",
      "difficulty": 2,
      "skills": [
        "type-input",
        "semantique"
      ],
      "title": "Type d'input pour une adresse e-mail",
      "statement": "Quel attribut `type` choisir pour un champ destiné à\nla saisie d'une adresse électronique ?",
      "options": [
        {
          "text": "`type=\"contact\"`",
          "correct": false,
          "feedback": "Erreur : `contact` n'est pas un type d'input\nvalide. La liste exhaustive des types est\nfixée par le standard HTML.\n"
        },
        {
          "text": "`type=\"email\"`",
          "correct": true,
          "feedback": "Bonne réponse : ce type indique au navigateur\nqu'il s'agit d'une adresse électronique. Cela\ndéclenche une validation de base (présence\nd'un `@`), affiche un clavier adapté sur\nmobile et améliore l'accessibilité.\n"
        },
        {
          "text": "`type=\"text\"`",
          "correct": false,
          "feedback": "Erreur : un champ texte fonctionne, mais\nn'apporte aucune validation automatique. On\nperd les vérifications de format et le clavier\nspécialisé sur mobile.\n"
        },
        {
          "text": "`type=\"address\"`",
          "correct": false,
          "feedback": "Erreur : aucun type `address` n'est défini par\nle standard HTML. Le type spécifique aux\nadresses électroniques est `email`.\n"
        }
      ],
      "explanation": "Choisir le bon type apporte un triple bénéfice :\nvalidation native du navigateur, clavier adapté\nsur mobile, et meilleure information aux\ntechnologies d'assistance. Quelques types utiles :\n`email`, `tel`, `url`, `number`, `date`, `color`,\n`password`."
    },
    {
      "id": "q27",
      "difficulty": 3,
      "skills": [
        "validation",
        "required",
        "pattern"
      ],
      "title": "Validation côté navigateur",
      "statement": "Quel attribut HTML$5$ rend un champ de formulaire\n**obligatoire**, en empêchant la soumission tant\nqu'il est vide ?",
      "options": [
        {
          "text": "`obligatoire`",
          "correct": false,
          "feedback": "Erreur : les attributs HTML sont en anglais.\nLe bon attribut est `required`.\n"
        },
        {
          "text": "`empty=false`",
          "correct": false,
          "feedback": "Erreur : aucune syntaxe de ce type n'existe en\nHTML. L'attribut `required` se passe de\nvaleur : sa simple présence suffit.\n"
        },
        {
          "text": "`required`",
          "correct": true,
          "feedback": "Bonne réponse : ajouter `required` à un champ\nempêche le navigateur de soumettre le\nformulaire tant qu'il est vide. Un message\nd'erreur s'affiche automatiquement.\n"
        },
        {
          "text": "`mandatory`",
          "correct": false,
          "feedback": "Erreur : aucun attribut `mandatory` n'est\ndéfini dans le standard HTML. C'est `required`\nqui joue ce rôle.\n"
        }
      ],
      "explanation": "Autres attributs de validation native :\n`pattern=\"...\"` (motif d'expression régulière à respecter),\n`minlength`/`maxlength` (longueur de la chaîne),\n`min`/`max` (valeur numérique ou date). La\nvalidation côté navigateur n'exempte pas d'une\nrevalidation côté serveur."
    }
  ]
}