{
  "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.",
    "parties": [
      {
        "id": "evenements",
        "title": "Événements"
      },
      {
        "id": "formulaires",
        "title": "Formulaires HTML"
      },
      {
        "id": "envoi",
        "title": "Envoi et validation des données"
      }
    ],
    "prerequisites": [],
    "references": []
  },
  "questions": [
    {
      "id": "q01",
      "difficulty": 1,
      "skills": [
        "evenement-definition"
      ],
      "title": "Événement",
      "partie": "evenements",
      "statement": "Qu'est-ce qu'un **événement** dans le contexte\nd'une page web ?",
      "options": [
        {
          "text": "Une modification CSS entièrement 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 de l'utilisateur ou du navigateur",
          "correct": true,
          "feedback": "Bonne réponse : les événements rendent la\npage **interactive**. JavaScript permet\nd'y réagir.\n"
        }
      ],
      "explanation": "Les éléments de la page **déclenchent** des\névénements (clic, saisie, soumission) ; les\ngestionnaires associés les **écoutent** et y\nréagissent."
    },
    {
      "id": "q02",
      "difficulty": 1,
      "skills": [
        "click"
      ],
      "title": "Événement de clic",
      "partie": "evenements",
      "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",
      "partie": "formulaires",
      "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",
      "partie": "formulaires",
      "statement": "Quelle balise crée un **champ de saisie** texte ?",
      "options": [
        {
          "text": "`<textbox name=\"champ\">`",
          "correct": false,
          "feedback": "Erreur : balise inexistante.\n"
        },
        {
          "text": "`<text size=\"20\">`",
          "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",
      "partie": "evenements",
      "statement": "Comment créer un **bouton** « Envoyer » qui\nsoumet le formulaire ?",
      "options": [
        {
          "text": "`<button type=\"submit\">Envoyer</button>`",
          "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": "`<button type=\"button\">Envoyer</button>`",
          "correct": false,
          "feedback": "Erreur : `type=\"button\"` crée un bouton\ninerte qui ne soumet **pas** le\nformulaire ; il faut `type=\"submit\"`.\n"
        },
        {
          "text": "`<submit>Envoyer</submit>`",
          "correct": false,
          "feedback": "Erreur : balise inexistante ; `submit`\nest une valeur de l'attribut `type`, pas\nune balise.\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",
      "partie": "formulaires",
      "statement": "Quelle balise crée une **liste déroulante** ?",
      "options": [
        {
          "text": "`<list>`",
          "correct": false,
          "feedback": "Erreur : balise inexistante.\n"
        },
        {
          "text": "`<select>`",
          "correct": true,
          "feedback": "Bonne réponse : `<select>` est le\nconteneur, chaque choix étant une\n`<option value=\"...\">texte</option>`\nplacée à l'intérieur.\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",
      "partie": "formulaires",
      "statement": "À quoi sert la balise `<label>` ?",
      "options": [
        {
          "text": "Associer une étiquette à un champ de formulaire",
          "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 interne du code 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 d'accessibilité : 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",
      "partie": "envoi",
      "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, par exemple pour\nvérifier les champs avant l'envoi.\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 ?",
      "partie": "envoi",
      "statement": "Quelle méthode HTTP utilise-t-on pour\n**envoyer un mot de passe** ?",
      "options": [
        {
          "text": "Aucune des deux, HTTP ne convenant pas aux données sensibles",
          "correct": false,
          "feedback": "Pour transmettre un mot\nde passe, on choisit\nsystématiquement la\nméthode POST (avec HTTPS),\nqui place les données dans\nle corps de la requête\nplutôt que dans l'URL.\n"
        },
        {
          "text": "POST, les données voyageant dans le corps de la requête",
          "correct": true,
          "feedback": "Bonne réponse : avec POST, rien ne\ntransite par l'URL. Pour les données\nsensibles, utiliser POST + HTTPS.\n"
        },
        {
          "text": "GET, parce que la requête est plus rapide à envoyer",
          "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": "GET, à condition de masquer le champ avec `type=\"password\"`",
          "correct": false,
          "feedback": "Erreur : `type=\"password\"` masque seulement les\ncaractères à l'écran. Avec GET, le mot de passe\npartirait quand même dans l'URL, visible dans\nl'historique.\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": 2,
      "skills": [
        "exemple-formulaire"
      ],
      "title": "Formulaire complet",
      "partie": "formulaires",
      "statement": "Lequel de ces fragments est un **formulaire valide** demandant un nom et l'envoyant en\nPOST ?",
      "options": [
        {
          "text": "```html\n<form action=\"/save\" method=\"POST\">\n  <label for=\"nom\">Nom :</label>\n  <input id=\"nom\" type=\"text\">\n  <button type=\"submit\">Envoyer</button>\n</form>\n```\n",
          "correct": false,
          "feedback": "Erreur : il manque l'attribut `name` sur\nle champ. Sans `name`, la donnée saisie\nn'est **pas envoyée** au serveur, même si\nle formulaire se soumet sans erreur\napparente.\n"
        },
        {
          "text": "```html\n<form action=\"/save\">\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": false,
          "feedback": "Erreur : sans attribut `method`, le\nformulaire est envoyé avec la méthode par\ndéfaut, **GET** (données visibles dans\nl'URL), et non en POST comme demandé.\n"
        },
        {
          "text": "```html\n<form action=\"/save\" method=\"POST\">\n  Nom : <input name=\"nom\" type=\"text\">\n  <input type=\"button\" value=\"Envoyer\">\n</form>\n```\n",
          "correct": false,
          "feedback": "Erreur : `type=\"button\"` crée un bouton\ninerte qui ne déclenche **pas** la\nsoumission. Il faut `type=\"submit\"` (ou\nun `<button>` dont le type par défaut est\nsubmit).\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",
      "partie": "evenements",
      "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.attachEvent(\"onclick\", handleClick)`",
          "correct": false,
          "feedback": "Erreur : `attachEvent` est l'ancienne API\npropriétaire d'Internet Explorer,\nabandonnée ; le standard est\n`addEventListener`.\n"
        }
      ],
      "explanation": "Avantage d'`addEventListener` sur\n`btn.onclick = handleClick` : on peut attacher\n**plusieurs** gestionnaires au même événement."
    },
    {
      "id": "q12",
      "difficulty": 2,
      "skills": [
        "valeur-input"
      ],
      "title": "Lire la valeur d'un champ",
      "partie": "formulaires",
      "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",
      "partie": "evenements",
      "statement": "À quoi sert `event.preventDefault()` dans un\ngestionnaire ?",
      "options": [
        {
          "text": "À empêcher tous les événements futurs sur la page",
          "correct": false,
          "feedback": "Erreur : seul le comportement par défaut\nde l'événement **courant** est annulé,\nles événements suivants restent actifs.\n"
        },
        {
          "text": "Empêcher le comportement par défaut du navigateur",
          "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 courante après l'événement",
          "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 les messages affichés dans 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",
      "partie": "envoi",
      "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`",
          "correct": true,
          "feedback": "Bonne réponse : `<input required>` active\nla validation native du navigateur, qui\nempêche la soumission si le champ est\nvide, avec un message standard\nautomatique.\n"
        },
        {
          "text": "Avec l'attribut `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`type=\"email\"`, etc."
    },
    {
      "id": "q15",
      "difficulty": 2,
      "skills": [
        "type-input"
      ],
      "title": "Type d'input adapté",
      "partie": "formulaires",
      "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",
      "partie": "evenements",
      "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.element` n'existe pas.\nL'élément sur lequel l'événement a eu\nlieu s'obtient avec `event.target`.\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": "`event.target` désigne l'élément sur lequel\nl'événement s'est produit : un même\ngestionnaire peut ainsi servir à plusieurs\nboutons et savoir lequel a été cliqué."
    },
    {
      "id": "q17",
      "difficulty": 2,
      "skills": [
        "name-attribute"
      ],
      "title": "Attribut name",
      "partie": "formulaires",
      "statement": "Pourquoi l'attribut `name` est-il important\npour un `<input>` ?",
      "options": [
        {
          "text": "Pour le validateur HTML automatique du navigateur",
          "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": "Il identifie la donnée envoyée au serveur",
          "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",
      "partie": "formulaires",
      "statement": "Quelle balise crée une **zone de texte multiligne** ?",
      "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",
      "partie": "evenements",
      "statement": "Quelle est la différence entre les événements\n`input` et `change` sur un `<input type=\"text\">` ?",
      "options": [
        {
          "text": "`input` à chaque touche, `change` à la fin",
          "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 systématiquement 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",
      "partie": "envoi",
      "statement": "Quand on soumet un formulaire en POST vers\n`/save`, que se passe-t-il **côté navigateur** ?",
      "options": [
        {
          "text": "Le navigateur ouvre carrément 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é tel quel, sans requête réseau",
          "correct": false,
          "feedback": "Pas par défaut : une requête part et la\npage **change** pour afficher la réponse.\n"
        },
        {
          "text": "Une requête POST part vers `/save`, puis la réponse s'affiche",
          "correct": true,
          "feedback": "Bonne réponse : c'est le mécanisme\nfondamental du formulaire. Le navigateur\nenvoie la requête, puis affiche la page\nrenvoyée par le serveur.\n"
        },
        {
          "text": "Rien, tant qu'un script JavaScript n'envoie pas les données",
          "correct": false,
          "feedback": "Erreur : la soumission est native, aucun\nJavaScript n'est nécessaire pour qu'un\nformulaire envoie ses données.\n"
        }
      ],
      "explanation": "Le serveur traite les données reçues\n(calcul, enregistrement) et renvoie une\nnouvelle page, que le navigateur affiche à\nla place du formulaire."
    },
    {
      "id": "q21",
      "difficulty": 3,
      "skills": [
        "validation-cote"
      ],
      "title": "Validation client vs serveur",
      "partie": "envoi",
      "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, pour l'ergonomie et la sécurité",
          "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, car 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": [
        "evenements-souris",
        "event-handler"
      ],
      "title": "Un paragraphe qui réagit au survol",
      "partie": "evenements",
      "statement": "Une page contient `<p id=\"zone\">Survolez-moi</p>` et le script :\n\n```javascript\nconst zone = document.getElementById(\"zone\");\nzone.addEventListener(\"mouseover\", () => {\n  zone.textContent = \"Dedans\";\n});\nzone.addEventListener(\"mouseout\", () => {\n  zone.textContent = \"Dehors\";\n});\n```\n\nL'utilisateur amène le pointeur sur le paragraphe, clique deux\nfois dessus, puis éloigne le pointeur. Qu'affiche alors le\nparagraphe ?",
      "options": [
        {
          "text": "`Dehors`",
          "correct": true,
          "feedback": "Bonne réponse : l'arrivée du pointeur déclenche\n`mouseover` (texte `Dedans`), les clics ne déclenchent\naucun de ces deux gestionnaires, et le départ du\npointeur déclenche `mouseout`, qui écrit `Dehors`.\n"
        },
        {
          "text": "`Dedans`",
          "correct": false,
          "feedback": "Erreur : les clics ne figent pas le texte. Quand le\npointeur quitte le paragraphe, l'événement `mouseout`\nse produit et son gestionnaire écrit `Dehors`.\n"
        },
        {
          "text": "`Survolez-moi`",
          "correct": false,
          "feedback": "Erreur : le texte initial ne revient pas tout seul.\nChaque gestionnaire remplace le contenu du paragraphe,\net le dernier exécuté, celui de `mouseout`, écrit\n`Dehors`.\n"
        },
        {
          "text": "`DedansDehors`",
          "correct": false,
          "feedback": "Erreur : affecter `textContent` remplace tout le texte\ndu paragraphe, sans rien ajouter à la suite. Le dernier\ngestionnaire exécuté ne laisse que `Dehors`.\n"
        }
      ],
      "explanation": "Chaque événement a son gestionnaire : `mouseover` à l'arrivée\ndu pointeur sur l'élément, `mouseout` à son départ, `click` au\nclic. Un clic ne déclenche donc ni `mouseover` ni `mouseout`."
    },
    {
      "id": "q23",
      "difficulty": 3,
      "skills": [
        "debug-form"
      ],
      "title": "Bug formulaire",
      "partie": "formulaires",
      "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>`",
          "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 la méthode 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": [
        "https",
        "post",
        "confidentialite"
      ],
      "title": "Un mot de passe envoyé sans chiffrement",
      "partie": "envoi",
      "statement": "Le formulaire de connexion d'un site commence par :\n\n```html\n<form action=\"http://exemple.fr/connexion\" method=\"post\">\n```\n\nL'adresse commence par `http://` et non par `https://`. Que\nrisque l'utilisateur qui envoie son mot de passe ?",
      "options": [
        {
          "text": "Le mot de passe circule en clair et peut être lu s'il est intercepté",
          "correct": true,
          "feedback": "Bonne réponse : POST place les données dans le corps\nde la requête, mais sans HTTPS ce corps n'est pas\nchiffré. Quiconque intercepte la communication, par\nexemple sur un Wi-Fi public, lit le mot de passe.\n"
        },
        {
          "text": "Aucun risque : la méthode POST chiffre les données envoyées",
          "correct": false,
          "feedback": "Erreur : POST évite seulement que les données\napparaissent dans l'URL. Le chiffrement est assuré\npar HTTPS, pas par le choix de la méthode.\n"
        },
        {
          "text": "Le mot de passe apparaît dans l'URL et dans l'historique du navigateur",
          "correct": false,
          "feedback": "Erreur : c'est le cas avec GET. Ici, `method=\"post\"`\nplace les données dans le corps de la requête, qui\ncircule toutefois en clair.\n"
        },
        {
          "text": "Le serveur refuse la requête, car POST exige une adresse `https://`",
          "correct": false,
          "feedback": "Erreur : rien n'impose HTTPS pour une requête POST.\nLe serveur reçoit bien le mot de passe, mais la\ntransmission n'est pas protégée.\n"
        }
      ],
      "explanation": "Deux choix distincts : GET ou POST décide où voyagent les\nparamètres (URL ou corps de la requête) ; HTTP ou HTTPS\ndécide si la transmission est chiffrée. Un mot de passe\nexige POST **et** HTTPS."
    },
    {
      "id": "q25",
      "difficulty": 3,
      "skills": [
        "synthese"
      ],
      "title": "Synthèse",
      "partie": "envoi",
      "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",
      "partie": "formulaires",
      "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",
      "partie": "envoi",
      "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`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."
    },
    {
      "id": "q28",
      "difficulty": 1,
      "skills": [
        "cookie",
        "client-serveur"
      ],
      "title": "Ce qu'est un cookie",
      "partie": "envoi",
      "statement": "Qu'est-ce qu'un **cookie** ?",
      "options": [
        {
          "text": "Un petit texte déposé par le serveur, gardé par le navigateur et renvoyé à chaque requête vers le site",
          "correct": true,
          "feedback": "C'est le mécanisme qui permet au serveur de\nreconnaître un navigateur d'une requête à\nl'autre.\n"
        },
        {
          "text": "Un programme que le serveur fait exécuter au navigateur à chaque visite pour reconnaître l'ordinateur",
          "correct": false,
          "feedback": "Un cookie ne contient pas de code et ne s'exécute\npas ; c'est une donnée, stockée côté client.\n"
        },
        {
          "text": "Un fichier conservé sur le serveur qui contient l'historique complet de chaque visiteur",
          "correct": false,
          "feedback": "Le cookie est rangé chez le client, dans le\nnavigateur ; le serveur ne garde que ce que le\nnavigateur lui renvoie.\n"
        },
        {
          "text": "Une image gardée dans le cache du navigateur pour accélérer l'affichage des pages déjà vues",
          "correct": false,
          "feedback": "Le cache stocke des ressources de pages ; un\ncookie porte une information d'identification ou\nde préférence, pas une image.\n"
        }
      ],
      "explanation": "Le protocole HTTP ne garde aucun souvenir d'une\nrequête à l'autre. Pour reconnaître un visiteur, le\nserveur lui confie un cookie : le navigateur le\nmémorise et le joint à toutes ses requêtes vers ce\nsite. Connexion maintenue, panier, langue choisie :\ntout cela passe par des cookies."
    },
    {
      "id": "q29",
      "difficulty": 2,
      "skills": [
        "cookie",
        "memorisation-client",
        "session"
      ],
      "title": "Connecté le lendemain sans ressaisir le mot de passe",
      "partie": "envoi",
      "statement": "Sur un site, vous cochez « rester connecté »,\nfermez le navigateur et revenez le lendemain : vous\nêtes toujours identifié. Où l'information a-t-elle\nété conservée ?",
      "options": [
        {
          "text": "Dans une variable JavaScript de la page, qui survit à la fermeture du navigateur",
          "correct": false,
          "feedback": "Les variables d'un script disparaissent dès que\nla page est fermée.\n"
        },
        {
          "text": "Dans un cookie gardé par le navigateur et renvoyé au serveur à la nouvelle visite",
          "correct": true,
          "feedback": "Le serveur y a inscrit un jeton de session ; en\nle recevant, il reconnaît votre compte.\n"
        },
        {
          "text": "Dans le code HTML de la page, modifié par le serveur pour y inscrire votre nom",
          "correct": false,
          "feedback": "Le HTML reçu est reconstruit à chaque requête ; il\nne persiste pas sur votre machine.\n"
        },
        {
          "text": "Nulle part : le serveur reconnaît votre ordinateur grâce à son adresse IP",
          "correct": false,
          "feedback": "L'adresse IP change souvent et est partagée par\nplusieurs appareils d'un même réseau : elle ne\npeut pas identifier un compte.\n"
        }
      ],
      "explanation": "Le navigateur stocke le cookie sur le disque, avec\nune date d'expiration. Tant qu'elle n'est pas\npassée, chaque requête vers le site l'emporte, et le\nserveur retrouve la session associée. Se déconnecter\nrevient à faire effacer ce cookie."
    },
    {
      "id": "q30",
      "difficulty": 2,
      "skills": [
        "cookie",
        "memorisation-client",
        "serveur"
      ],
      "title": "Mémoriser une préférence : chez le client ou sur le serveur",
      "partie": "envoi",
      "statement": "Un site propose un choix de langue. Deux solutions :\n(A) enregistrer la langue dans un cookie ; (B)\nl'enregistrer dans la base de données du serveur,\nassociée au compte de l'utilisateur. Laquelle\nretrouve la langue choisie quand l'utilisateur se\nconnecte depuis un autre ordinateur ?",
      "options": [
        {
          "text": "La solution A seule, car le cookie accompagne l'utilisateur",
          "correct": false,
          "feedback": "Le cookie reste dans le navigateur où il a été\ndéposé ; un autre ordinateur n'en a aucune copie.\n"
        },
        {
          "text": "Les deux, car le navigateur transmet ses cookies au nouvel ordinateur",
          "correct": false,
          "feedback": "Rien ne transfère les cookies d'une machine à\nl'autre ; chaque navigateur a les siens.\n"
        },
        {
          "text": "La solution B seule, car la préférence est stockée côté serveur",
          "correct": true,
          "feedback": "Quelle que soit la machine, une fois connecté,\nl'utilisateur retrouve ce que le serveur a\nmémorisé pour son compte.\n"
        },
        {
          "text": "Aucune des deux : une préférence ne se mémorise que pendant la visite",
          "correct": false,
          "feedback": "Les deux solutions survivent à la fin de la\nvisite ; seule la seconde survit au changement de\nmachine.\n"
        }
      ],
      "explanation": "Ce qui est mémorisé dans le client (cookie, stockage\ndu navigateur) reste sur cette machine. Ce qui est\nmémorisé par le serveur suit le compte. Le choix\ndépend de l'usage : un cookie suffit pour un\nvisiteur anonyme, la base du serveur s'impose pour\nun utilisateur identifié."
    },
    {
      "id": "q31",
      "difficulty": 3,
      "skills": [
        "cookie",
        "http",
        "en-tetes"
      ],
      "title": "Le trajet d'un cookie entre deux requêtes",
      "partie": "envoi",
      "statement": "Première requête vers `boutique.fr` : le serveur\nrépond avec l'en-tête `Set-Cookie: panier=42`.\nQuelques secondes plus tard, le navigateur demande\nune autre page du même site. Que contient cette\ndeuxième requête ?",
      "options": [
        {
          "text": "Un en-tête `Cookie: panier=42`, ajouté automatiquement par le navigateur",
          "correct": true,
          "feedback": "Le navigateur joint à chaque requête vers\n`boutique.fr` les cookies que ce site lui a\nconfiés.\n"
        },
        {
          "text": "Rien de particulier : c'est au serveur de redemander la valeur du panier",
          "correct": false,
          "feedback": "Le serveur n'a aucun moyen de « redemander » :\nHTTP est sans mémoire, c'est le client qui\nrenvoie l'information.\n"
        },
        {
          "text": "Un en-tête `Set-Cookie: panier=42`, renvoyé tel quel au serveur",
          "correct": false,
          "feedback": "`Set-Cookie` est un en-tête de réponse, du serveur\nvers le navigateur ; dans l'autre sens, l'en-tête\ns'appelle `Cookie`.\n"
        },
        {
          "text": "Le cookie `panier=42`, envoyé aussi à tous les autres sites visités ensuite",
          "correct": false,
          "feedback": "Un cookie n'est renvoyé qu'au site qui l'a\ndéposé ; les autres sites ne le voient pas.\n"
        }
      ],
      "explanation": "Deux en-têtes se répondent : `Set-Cookie` dans la\nréponse du serveur dépose la valeur, `Cookie` dans\nles requêtes suivantes du navigateur la renvoie. Le\nserveur retrouve ainsi le panier numéro $42$ sans\nque l'utilisateur ait rien ressaisi."
    },
    {
      "id": "q32",
      "difficulty": 1,
      "skills": [
        "evenement",
        "submit",
        "formulaire"
      ],
      "title": "L'événement déclenché par l'envoi",
      "partie": "evenements",
      "statement": "Quel événement se produit sur un élément `form` lorsque\nl'utilisateur clique sur son bouton d'envoi ou appuie sur Entrée\ndans un champ ?",
      "options": [
        {
          "text": "`submit`",
          "correct": true,
          "feedback": "Bonne réponse : `submit` est émis par le formulaire lui-même,\nquel que soit le moyen de l'envoyer.\n"
        },
        {
          "text": "`click`",
          "correct": false,
          "feedback": "Erreur : `click` est émis par le bouton, pas par le formulaire,\net la touche Entrée ne le déclenche pas.\n"
        },
        {
          "text": "`send`",
          "correct": false,
          "feedback": "Erreur : cet événement n'existe pas.\n"
        },
        {
          "text": "`input`",
          "correct": false,
          "feedback": "Erreur : `input` est émis à chaque modification d'un champ.\n"
        }
      ],
      "explanation": "Écouter `submit` sur le formulaire couvre tous les moyens d'envoi,\nbouton comme touche Entrée."
    },
    {
      "id": "q33",
      "difficulty": 2,
      "skills": [
        "evenement",
        "valeur",
        "moment"
      ],
      "title": "Une valeur lue trop tôt",
      "partie": "evenements",
      "statement": "Ce script affiche toujours une chaîne vide au clic. Pourquoi ?\n```javascript\nconst nom = document.getElementById(\"nom\").value;\ndocument.getElementById(\"btn\").addEventListener(\"click\", function () {\n    alert(\"Bonjour \" + nom);\n});\n```",
      "options": [
        {
          "text": "La valeur est lue au chargement, avant la saisie",
          "correct": true,
          "feedback": "Bonne réponse : `nom` reçoit la valeur du champ au moment où le\nscript s'exécute, c'est-à-dire vide. Lire `.value` à l'intérieur\nde la fonction donne la saisie du moment.\n"
        },
        {
          "text": "`alert` ne peut pas afficher une variable, seulement un texte fixe",
          "correct": false,
          "feedback": "Erreur : `alert` affiche n'importe quelle chaîne.\n"
        },
        {
          "text": "Le champ `nom` n'a pas d'attribut `name`, donc pas de valeur",
          "correct": false,
          "feedback": "Erreur : `name` sert à l'envoi, pas à la lecture de `.value`.\n"
        },
        {
          "text": "Le gestionnaire doit être attaché au champ et non au bouton",
          "correct": false,
          "feedback": "Erreur : le bouton est une cible correcte ; le problème est le\nmoment de la lecture.\n"
        }
      ],
      "explanation": "Une valeur de formulaire change avec l'utilisateur : elle se lit au\nmoment de l'événement, pas une fois pour toutes au chargement."
    },
    {
      "id": "q34",
      "difficulty": 3,
      "skills": [
        "evenement",
        "clavier",
        "key"
      ],
      "title": "Savoir quelle touche a été pressée",
      "partie": "evenements",
      "statement": "Dans un gestionnaire de l'événement `keydown`, comment savoir\nquelle touche l'utilisateur a enfoncée ?",
      "options": [
        {
          "text": "En lisant `event.key`",
          "correct": true,
          "feedback": "Bonne réponse : l'objet événement reçu en paramètre porte la\ntouche dans `key`, par exemple `\"Enter\"` ou `\"a\"`.\n"
        },
        {
          "text": "En lisant `event.value`",
          "correct": false,
          "feedback": "Erreur : `value` est une propriété des champs de saisie, pas de\nl'événement clavier.\n"
        },
        {
          "text": "En comparant le texte du champ avant et après la frappe",
          "correct": false,
          "feedback": "Erreur : c'est possible mais peu fiable, et inutile puisque\nl'événement donne la touche.\n"
        },
        {
          "text": "En appelant `keyboard.last()`",
          "correct": false,
          "feedback": "Erreur : cette fonction n'existe pas.\n"
        }
      ],
      "explanation": "Le paramètre `event` d'un gestionnaire décrit ce qui s'est passé :\ntouche pressée, position de la souris, élément visé."
    },
    {
      "id": "q35",
      "difficulty": 2,
      "skills": [
        "formulaire",
        "placeholder",
        "value"
      ],
      "title": "Texte indicatif ou valeur préremplie",
      "partie": "formulaires",
      "statement": "Quelle différence entre `<input placeholder=\"Votre nom\">` et\n`<input value=\"Votre nom\">` ?",
      "options": [
        {
          "text": "Le premier affiche une indication grisée, le second préremplit le champ",
          "correct": true,
          "feedback": "Bonne réponse : `placeholder` n'est pas une valeur, elle n'est\npas envoyée ; `value` est le contenu réel du champ, envoyé tel\nquel si l'utilisateur ne le change pas.\n"
        },
        {
          "text": "Aucune, les deux attributs étant synonymes et envoyés tous deux",
          "correct": false,
          "feedback": "Erreur : l'un guide, l'autre remplit ; seul `value` est transmis.\n"
        },
        {
          "text": "Le premier est obligatoire, le second facultatif pour le navigateur",
          "correct": false,
          "feedback": "Erreur : les deux sont facultatifs ; l'obligation de saisie vient\nde `required`.\n"
        },
        {
          "text": "Le premier concerne les champs texte, le second les boutons",
          "correct": false,
          "feedback": "Erreur : les deux s'appliquent aux champs de saisie.\n"
        }
      ],
      "explanation": "Un `placeholder` aide l'utilisateur sans rien envoyer ; une `value`\nest une donnée, parfois envoyée par erreur si on l'oublie."
    },
    {
      "id": "q36",
      "difficulty": 3,
      "skills": [
        "formulaire",
        "number",
        "min-max",
        "validation"
      ],
      "title": "Un champ numérique borné",
      "partie": "formulaires",
      "statement": "Que garantit `<input type=\"number\" min=\"0\" max=\"20\" name=\"note\">`\nlors de l'envoi du formulaire ?",
      "options": [
        {
          "text": "Un blocage par le navigateur hors de $[0, 20]$, le serveur devant revérifier",
          "correct": true,
          "feedback": "Bonne réponse : la vérification du navigateur protège contre les\nerreurs de saisie, pas contre une requête fabriquée à la main.\nLe serveur reste responsable de la validation.\n"
        },
        {
          "text": "Le serveur ne peut recevoir que des valeurs entre $0$ et $20$, garanties",
          "correct": false,
          "feedback": "Erreur : une requête envoyée sans passer par le formulaire peut\ncontenir n'importe quoi.\n"
        },
        {
          "text": "La valeur est automatiquement arrondie à l'entier le plus proche",
          "correct": false,
          "feedback": "Erreur : `min` et `max` bornent, ils n'arrondissent pas ; `step`\nrègle les valeurs permises.\n"
        },
        {
          "text": "Rien, ces attributs ne servant qu'à l'affichage du champ",
          "correct": false,
          "feedback": "Erreur : le navigateur les utilise bien pour bloquer l'envoi\nd'une valeur hors bornes.\n"
        }
      ],
      "explanation": "La validation côté client améliore l'expérience ; la validation côté\nserveur garantit la sécurité. Les deux sont nécessaires."
    }
  ]
}