KyeYongChoo/mcq-quiz-page

À utiliser lorsque l'utilisateur demande un questionnaire à choix multiples ou des questions d'entraînement à partir de diapositives ou de notes, sous forme de page cliquable avec des réponses vertes/rouges, un mode révision, une reprise des erreurs et une exportation PDF ; peut inclure des questions structurées et d'analyse de programme.

Qu'est-ce que mcq-quiz-page ?

Cette compétence permet de générer une page web interactive de quiz à choix multiples, entièrement fonctionnelle, à partir de supports de cours (diapositives, notes, PDF). Elle prend en charge à la fois les QCM classiques (avec retour couleur vert/rouge, explications) et les questions structurées ou d'analyse de programme (réponse libre avec modèle de correction). L'utilisateur peut répondre, marquer ses réponses, et les erreurs sont automatiquement collectées dans un onglet « Révision ». Un onglet « Reprise » affiche uniquement les questions échouées, avec progression et possibilité de refaire les erreurs jusqu'à validation complète. La page conserve l'état des réponses et des brouillons dans le navigateur via localStorage. L'export PDF est possible (nécessite la déclaration de capacité `downloads` par l'agent), et le quiz peut être mis à jour par republishing. La compétence inclut des règles strictes de qualité des questions : distracteurs crédibles, équilibre des réponses (25% A-D), pas de réponse systématiquement plus longue, et explication pour chaque question. Le tout est livré sous forme d'artefact HTML avec un template prêt à l'emploi, qu'il suffit de remplir avec les données JSON des questions. Pour les PDF, la compétence utilise `pdftotext -layout` pour extraire le texte, et pour les questions d'analyse de programme, elle intègre des extraits de code dans des blocs monospace.

Compatible avec✓Claude Code~Codex CLI✓Cursor
Part ofMobile
npx skills add https://github.com/KyeYongChoo/mcq-quiz-page-skill/tree/HEAD/mcq-quiz-page

Installed? Explore more Développement et programmation skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Page de quiz QCM

La conception de la page est terminée. Ne la reconcevez pas. Votre travail consiste à rédiger de bonnes questions, à les insérer dans le modèle ci-dessous sous forme de JSON et à le publier.

Ce que fait la page :

  • Choix multiple : appuyez sur une option et elle devient verte (bonne) ou rouge (mauvaise, avec l'option correcte en vert et une explication affichée). Un petit bouton « Refaire cette question » sous chaque question répondue réinitialise uniquement celle-ci.
  • Questions structurées / d'analyse de programme (type "short") : l'utilisateur saisit une réponse (facultatif), appuie sur Afficher le modèle de réponse, puis se marque lui-même « J'ai compris » ou « J'ai raté ». Les questions ratées sont comptées comme des erreurs. Les extraits de code s'affichent dans un bloc monospace.
  • L'onglet Révision liste uniquement les erreurs avec la bonne réponse / le modèle de réponse juste en dessous. Le mode Révision dispose des fonctions Exporter PDF, Copier en texte et Refaire les erreurs.
  • Refaire les erreurs efface ces questions et ouvre un onglet Reprise qui affiche UNIQUEMENT les erreurs précédentes (pas de défilement dans tout le quiz). Il affiche la progression (fait/total), et lorsque tout est terminé, un résumé avec « Refaire les N toujours fausses » et « Retour à toutes les questions ». L'onglet Reprise reste disponible jusqu'à la réinitialisation complète.
  • La progression, les réponses saisies et la liste de reprise sont conservées dans le navigateur du spectateur.

Étapes

  1. Lisez d'abord le matériel source (diapositives, notes, PDF). Les questions doivent en provenir, pas de mémoire. Pour les PDF, utilisez pdftotext -layout. Si l'utilisateur joint des solutions de tutoriel ou d'examen antérieur, basez à la fois les QCM et les questions structurées/d'analyse de programme sur celles-ci (nouvelles variantes, pas de copies conformes, sauf demande contraire).
  2. Rédigez les questions sous forme de tableau JSON (schéma ci-dessous). Par défaut, 4 options pour les QCM. Si l'utilisateur donne un nombre, utilisez exactement ce nombre. Ajoutez des questions structurées / d'analyse de programme lorsque la source contient des questions à réponse courte ou de code, ou lorsque l'utilisateur les demande (placez-les après les QCM de la même section).
  3. Vérifiez les questions avec les règles d'équilibre ci-dessous, idéalement avec un petit script.
  4. Construisez la page : copiez le modèle dans index.html, remplacez le texte __DATA__ par le JSON (échappez chaque </ sous la forme \<\/), et modifiez uniquement : le <title>, le <h1>, la phrase .sub, et la clé de stockage bps-quiz-v1 (donnez à chaque quiz une nouvelle clé, par ex. quiz-<sujet>-v1, afin que la progression sauvegardée ne se mélange jamais). Exécutez node --check sur le script si possible (extrayez le bloc de script dans un fichier .js). Un test rapide Playwright (répondre, marquer, Révision, Refaire les erreurs) vaut la peine d'être fait.
  5. Publiez en tant qu'artefact HTML fait à la main : appelez d'abord l'artefact list_types (il n'y a pas de type de quiz ; utilisez publish_files avec index.html, un titre réel, l'icône quiz). Appelez ensuite declare_capabilities avec {"downloads": true} en utilisant le numéro de version le plus récent. Le bouton PDF ne fonctionne qu'avec cette capacité. Lors de la mise à jour d'un quiz existant, republiez avec publish_files (mode patch, slug, base_version) ; la déclaration de capacité est conservée. Si vous ne pouvez pas publier d'artefacts, livrez index.html en tant que fichier à la place (le bouton PDF reste alors masqué ; Copier en texte fonctionne toujours).
  6. Répondez en quelques phrases : ce qu'est la page, combien de questions (QCM et structurées) et de quelles sections, et que l'exportation PDF demande au spectateur de confirmer le téléchargement. Ne collez pas les questions dans la discussion.

Schéma JSON (un objet par question, dans l'ordre)

Choix multiple :

{"n": 1, "section": "Cours 4 · Liaison", "q": "Texte de la question ?", "opts": ["Texte A", "Texte B", "Texte C", "Texte D"], "ans": 2, "ex": "Explication en une phrase."}

Question structurée ou analyse de programme (code facultatif, kind facultatif, ex facultatif) :

{"n": 5, "type": "short", "section": "Cours 4 · Liaison", "kind": "Question structurée", "q": "Décrivez ... ?", "model": ["Point 1 du modèle de réponse", "Point 2"], "ex": "Note supplémentaire facultative"}
{"n": 6, "type": "short", "section": "...", "kind": "Analyse de programme", "q": "Qu'est-ce qui ne va pas ici et comment le corrigez-vous ?", "code": "int f(char *s) {\n  char b[8];\n  strcpy(b, s);\n}", "model": ["(a) ...", "(b) ..."]}

kind par défaut à "Réponse courte" ; utilisez "Question structurée" ou "Analyse de programme". Chaque entrée model est une puce ; étiquetez les sous-parties "(a)", "(b)" à l'intérieur des puces. Écrivez code avec des retours à la ligne \n et des indentations de 2 espaces.

Notes facultatives par option (UNIQUEMENT lorsque l'utilisateur demande du texte supplémentaire sous les mauvaises options) : ajoutez "notes": ["", "Pourquoi B est faux", "", "Pourquoi D est faux"] à un QCM, une chaîne par option dans le même ordre, avec "" pour les options qui n'en ont pas besoin (généralement la bonne). Les notes apparaissent sous chaque option après que l'utilisateur a répondu, et dans Copier/PDF. N'ajoutez pas de notes sauf si demandé.

n va de 1 à N sans lacunes sur toutes les questions (la page indexe par n-1). ans est l'index basé sur 0 de l'option correcte. section regroupe les questions sur la page ; utilisez la même chaîne pour les questions d'un groupe et gardez les groupes contigus. Évitez les caractères en dehors du texte latin simple dans la mesure du possible (le PDF utilise une police intégrée ; les symboles inhabituels s'impriment en ?).

Règles de qualité des questions (l'utilisateur y tient)

  • Les distracteurs doivent être convaincants : utilisez des concepts réels du même matériel attachés à la mauvaise question (la propriété d'un terme voisin, un nombre proche, un mécanisme plausible). Pas d'options humoristiques, pas de « toutes les réponses ci-dessus ».
  • Ne laissez pas la bonne réponse être la plus longue. Rédigez l'option correcte et les distracteurs à une longueur et un niveau de détail similaires, et faites parfois d'un distracteur le plus long. Objectif : l'option correcte est strictement la plus longue sur pas plus d'environ 30 % des questions avec des options sous forme de phrases complètes (vérifiez avec un script ; il est facile de dépasser).
  • Répartissez la lettre correcte uniformément entre A et D (environ 25 % chacune) et jamais la même lettre trois fois de suite. Méthode la plus simple : écrivez l'option correcte et trois distracteurs séparément, puis mélangez avec une liste de positions équilibrée dans le code.
  • Testez une idée par question. Gardez le texte de la question autonome.
  • Chaque QCM reçoit une explication dans ex qui explique pourquoi la réponse est correcte (et, lorsque cela est utile, ce que sont réellement les distracteurs).
  • Les modèles de réponses pour les questions structurées/analyse de programme doivent refléter les points clés de la solution source sous forme de puces courtes, afin que l'utilisateur puisse s'évaluer honnêtement.
  • Si une diapositive n'est qu'un diagramme que vous ne pouvez pas lire, dites-le plutôt que d'inventer des détails.

Modèle (index.html)

<!DOCTYPE html>
<html lang="fr"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Quiz sur le sujet</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@500&display=swap">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<style>
:root{--bg:#f5f6f8;--card:#ffffff;--ink:#16202e;--mute:#5b6678;--line:#d5dae3;--accent:#1d4ed8;--accent-soft:#e6ecff;
--ok:#0f7a4d;--ok-soft:#dcf3e6;--ok-line:#52b887;--bad:#b42318;--bad-soft:#fde4e1;--bad-line:#e57a70;
--body:"IBM Plex Sans",system-ui,-apple-system,"Segoe UI",sans-serif;--mono:"IBM Plex Mono",ui-monospace,Menlo,monospace}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--bg:#10151d;--card:#171e29;--ink:#e6eaf1;--mute:#9aa6b8;--line:#2e3849;--accent:#8aa9ff;--accent-soft:#1f2a45;--ok:#6fe0aa;--ok-soft:#12332a;--ok-line:#2f8a63;--bad:#ff9a8f;--bad-soft:#3b1b1a;--bad-line:#a94a42;color-scheme:dark}}
:root[data-theme="dark"]{--bg:#10151d;--card:#171e29;--ink:#e6eaf1;--mute:#9aa6b8;--line:#2e3849;--accent:#8aa9ff;--accent-soft:#1f2a45;--ok:#6fe0aa;--ok-soft:#12332a;--ok-line:#2f8a63;--bad:#ff9a8f;--bad-soft:#3b1b1a;--bad-line:#a94a42;color-scheme:dark}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--ink);font:400 16px/1.55 var(--body)}
main{max-width:760px;margin:0 auto;padding:24px 16px 64px}
h1{font-size:1.7rem;line-height:1.2;margin:0 0 6px;text-wrap:balance}
.sub{color:var(--mute);margin:0 0 14px}
.bar{position:sticky;top:0;z-index:3;background:var(--bg);border-bottom:1px solid var(--line);padding:10px 0;display:flex;flex-wrap:wrap;gap:8px 10px;align-items:center}
.tabs{display:inline-flex;border:1px solid var(--line);border-radius:9px;overflow:hidden;background:var(--card)}
.tabs button{border:0;background:transparent;color:var(--ink);font:500 .88rem var(--body);padding:8px 14px;cursor:pointer}
.tabs button[aria-pressed="true"]{background:var(--accent);color:var(--bg)}
.stats{font-size:.88rem;color:var(--mute);font-variant-numeric:tabular-nums;display:flex;gap:12px;flex-wrap:wrap}
.stats b{color:var(--ink)}
.acts{margin-left:auto;display:flex;gap:8px;flex-wrap:wrap}
.btn{font:500 .85rem var(--body);color:var(--ink);background:var(--card);border:1px solid var(--line);border-radius:8px;padding:7px 12px;cursor:pointer;text-decoration:none;display:inline-flex;gap:8px;align-items:center}
.btn.primary{background:var(--accent);border-color:var(--accent);color:var(--bg)}
.btn:disabled{opacity:.45;cursor:not-allowed}
.btn:not(:disabled):hover{filter:brightness(.95)}
:focus-visible{outline:3px solid var(--accent);outline-offset:2px}
.jump{display:flex;flex-wrap:wrap;gap:8px;margin:14px 0 0}
.jump a{font:500 .85rem var(--body);color:var(--ink);background:var(--card);border:1px solid var(--line);border-radius:8px;padding:7px 11px;text-decoration:none}
.jump a span{color:var(--mute);margin-left:8px;font-variant-numeric:tabular-nums}
h2{font-size:1.08rem;margin:30px 0 14px;padding-bottom:8px;border-bottom:2px solid var(--accent);text-wrap:balance;scroll-margin-top:70px}
.banner{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:14px 16px;margin:18px 0 6px}
.banner p{margin:0}.banner p+p{margin-top:8px;color:var(--mute);font-size:.92rem}
.q{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:16px 16px 14px;margin-bottom:14px;scroll-margin-top:70px}
h3{font-size:1rem;font-weight:600;margin:0 0 12px;display:flex;gap:10px;line-height:1.45}
.num{font:500 .8rem var(--mono);color:var(--accent);background:var(--accent-soft);border-radius:6px;padding:2px 7px;height:fit-content;font-variant-numeric:tabular-nums}
.sec{display:block;font-weight:400;font-size:.78rem;color:var(--mute);margin-bottom:2px}
.opts{display:grid;gap:8px}
.opt{display:flex;gap:10px;align-items:flex-start;text-align:left;width:100%;font:inherit;color:inherit;background:transparent;border:1px solid var(--line);border-radius:8px;padding:10px 12px;cursor:pointer}
.opt b{font:500 .85rem var(--mono);color:var(--mute);min-width:1.2em;padding-top:1px}
.opt:not(:disabled):hover{border-color:var(--accent);background:var(--accent-soft)}
.opt:disabled{cursor:default}
.opt.ok{background:var(--ok-soft);border-color:var(--ok-line)}
.opt.ok b,.opt.ok .tag{color:var(--ok)}
.opt.bad{background:var(--bad-soft);border-color:var(--bad-line)}
.opt.bad b,.opt.bad .tag{color:var(--bad)}
.opt .txt{flex:1}
.tag{font:500 .75rem var(--mono);white-space:nowrap;padding-top:2px}
.fb{margin-top:10px;border-radius:8px;padding:10px 12px;background:var(--accent-soft)}
.fb p{margin:0}.fb .head{font-weight:600}.fb.good .head{color:var(--ok)}.fb.miss .head{color:var(--bad)}
.fb .why{color:var(--mute);font-size:.92rem;margin-top:4px}
.empty{text-align:center;color:var(--mute);padding:36px 12px}
.code{margin:0 0 12px;padding:10px 12px;background:var(--bg);border:1px solid var(--line);border-radius:8px;font:500 .82rem/1.5 var(--mono);overflow-x:auto;white-space:pre;tab-size:2}
textarea.ans{width:100%;font:inherit;color:var(--ink);background:var(--card);border:1px solid var(--line);border-radius:8px;padding:10px 12px;resize:vertical;min-height:84px}
textarea.ans[readonly]{background:var(--bg)}
.row{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-top:10px}
.ask{color:var(--mute);font-size:.9rem}
.model{margin:6px 0 0;padding-left:20px}
.model li{margin:3px 0}
.mark{margin:10px 0 0;font-weight:600;font-size:.92rem}
.mark.good{color:var(--ok)}.mark.miss{color:var(--bad)}
.onote{display:block;margin-top:4px;font-size:.86rem;font-weight:400;color:var(--mute)}
.btn.small{padding:5px 10px;font-size:.8rem;color:var(--mute)}
</style></head><body><main>
<h1>Quiz sur le sujet</h1>
<p class="sub">N questions tirées du matériel de cours. Appuyez sur une option pour répondre. En mode Révision, vous ne voyez que celles que vous avez eues fausses et vous pouvez les exporter en PDF.</p>
<div class="bar" role="toolbar" aria-label="Contrôles du quiz">
  <div class="tabs" role="group" aria-label="Mode">
    <button type="button" id="tab-quiz" aria-pressed="true">Quiz</button>
    <button type="button" id="tab-review" aria-pressed="false">Révision</button>
    <button type="button" id="tab-retry" aria-pressed="false" hidden>Reprise</button>
  </div>
  <div class="stats" id="stats" aria-live="polite"></div>
  <div class="acts" id="acts"></div>
</div>
<div id="view"></div>
</main>
<script>
var DATA=__DATA__;
var L="ABCD", KEY="bps-quiz-v1", DKEY=KEY+"-d", RKEY=KEY+"-r";
var answers={}, drafts={}, retry=[], mode="quiz", dl=null;
try{var s=localStorage.getItem(KEY); if(s) answers=JSON.parse(s)||{};}catch(e){}
try{var s2=localStorage.getItem(DKEY); if(s2) drafts=JSON.parse(s2)||{};}catch(e){}
try{var s3=localStorage.getItem(RKEY); if(s3) retry=JSON.parse(s3)||[];}catch(e){}
function save(){try{localStorage.setItem(KEY,JSON.stringify(answers));}catch(e){}}
function saveD(){try{localStorage.setItem(DKEY,JSON.stringify(drafts));}catch(e){}}
function saveR(){try{localStorage.setItem(RKEY,JSON.stringify(retry));}catch(e){}}
function esc(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");}
function byN(n){return DATA[n-1];}
function isDone(q){var a=answers[q.n]; return q.type==="short" ? (a==="ok"||a==="miss") : a!==undefined;}
function wrongList(){return DATA.filter(function(q){return q.type==="short" ? answers[q.n]==="miss" : (answers[q.n]!==undefined&&answers[q.n]!==q.ans);});}
function doneCount(){return DATA.filter(isDone).length;}

function shortHTML(q,ctx){
  var review=(ctx!=="quiz"), a=answers[q.n], marked=(a==="ok"||a==="miss"), shown=marked||a==="rev";
  var lab=(review?esc(q.section)+" - ":"")+es

…

[contenu tronqué pour la traduction]

Skills associés