From d351ee6efb7c26b8a4dc22b350be60e0a7af82a4 Mon Sep 17 00:00:00 2001 From: "gauthier.chombart" Date: Thu, 16 Jul 2026 14:36:09 +0200 Subject: [PATCH] =?UTF-8?q?Refonte=20de=20la=20slide=207=20FDS=20en=20?= =?UTF-8?q?=C3=A9tiquette=20cliquable=20(hotspots)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remplace les menus déroulants de la partie Étiquette par une étiquette DÉS'INFECT reconstituée (Annexe I du guide FHER) avec 9 zones cliquables : info-bulle de justification réglementaire au clic, états visuels correct/incorrect, mise en situation professionnelle en introduction, et écran de synthèse sur le modèle de la check-list FHER p.15 (étiquette annotée + tableau). Export PDF recâblé sur les nouvelles zones. --- exercices/Exo_FDS.html | 532 +++++++++++++++++++++++++++++++++++------ 1 file changed, 461 insertions(+), 71 deletions(-) diff --git a/exercices/Exo_FDS.html b/exercices/Exo_FDS.html index 1273cdb..f881311 100644 --- a/exercices/Exo_FDS.html +++ b/exercices/Exo_FDS.html @@ -502,6 +502,139 @@ select:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px .answer-row { flex-wrap: wrap; } select { width: 100%; min-width: 0; } } + +/* ============ SLIDE 7 — ÉTIQUETTE CLIQUABLE (HOTSPOTS) ============ */ +#slide-7 { justify-content: flex-start; } +#slide-7 .scroll-area { gap: 0.75rem; } + +/* Mise en situation */ +.situation-box { + display: flex; gap: 12px; align-items: flex-start; + max-width: 680px; width: 100%; + background: var(--teal-light); + border: 1px solid var(--teal-mid); + border-left: 4px solid var(--teal-mid); + border-radius: 12px; + padding: 12px 16px; + color: var(--teal-dark); + font-size: 13.5px; line-height: 1.55; + text-align: left; +} +.situation-box .sit-ico { font-size: 22px; line-height: 1.1; flex-shrink: 0; } +.situation-box strong { color: var(--teal-dark); } + +/* Barre de consigne / question courante */ +.eti-prompt { + display: flex; align-items: center; gap: 12px; + flex-wrap: wrap; justify-content: center; + max-width: 680px; width: 100%; + background: #fff; border: 1px solid var(--border); border-radius: 12px; + padding: 10px 16px; text-align: center; +} +.eti-progress { + font-family: 'Syne', sans-serif; font-weight: 800; font-size: 11px; + letter-spacing: 0.04em; text-transform: uppercase; + color: var(--teal-dark); background: var(--teal-light); + padding: 4px 10px; border-radius: 20px; white-space: nowrap; +} +.eti-question { font-size: 14.5px; font-weight: 600; color: var(--text); } + +/* Scène de l'étiquette + popover */ +.eti-stage { position: relative; width: 100%; max-width: 680px; display: flex; justify-content: center; } + +/* Étiquette reconstituée (DÉS'INFECT — Annexe I FHER) */ +.label-card { + position: relative; + width: 100%; max-width: 640px; + background: #fff; color: #1a1a18; + border: 1px solid #d9d2c4; border-radius: 6px; + padding: 16px 18px 0; overflow: hidden; + font-family: 'DM Sans', sans-serif; font-size: 10.5px; line-height: 1.42; + box-shadow: 0 8px 30px rgba(0,0,0,0.10); +} +.lbl-top { display: flex; align-items: center; gap: 12px; border-bottom: 2px solid #1a1a18; padding-bottom: 6px; margin-bottom: 10px; } +.lbl-flag { font-size: 20px; } +.lbl-title { font-family: 'Syne', sans-serif; font-weight: 800; font-size: clamp(24px, 5vw, 40px); letter-spacing: -0.01em; margin: 0; } +.lbl-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; } +.lbl-h { font-family: 'Syne', sans-serif; font-weight: 700; font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.02em; margin-bottom: 2px; } +.lbl-block { margin-bottom: 9px; } +.lbl-small { color: #555; font-size: 9.5px; line-height: 1.6; margin-top: 4px; } +.lbl-bottom { display: grid; grid-template-columns: auto 1fr auto; align-items: end; gap: 14px; margin-top: 4px; padding: 10px 0; } +.lbl-net { display: flex; flex-direction: column; gap: 2px; } +.lbl-net span { font-size: 10px; } +.lbl-net strong { font-family: 'Syne', sans-serif; font-size: 22px; line-height: 1; } +.lbl-barcode { + height: 42px; align-self: end; min-width: 90px; + background: repeating-linear-gradient(90deg, #1a1a18 0 2px, #fff 2px 4px, #1a1a18 4px 5px, #fff 5px 9px); +} +.lbl-picto-wrap { display: flex; align-items: center; gap: 8px; } +.pictos { display: flex; gap: 6px; } +.ghs { + width: 38px; height: 38px; position: relative; + border: 2.5px solid #c62828; background: #fff; + transform: rotate(45deg); + display: flex; align-items: center; justify-content: center; flex-shrink: 0; +} +.ghs span { transform: rotate(-45deg); font-size: 17px; line-height: 1; } +.ghs.excl span { font-family: 'Syne', sans-serif; font-weight: 800; font-size: 24px; color: #1a1a18; } +.lbl-danger { font-family: 'Syne', sans-serif; font-weight: 800; font-size: 15px; color: #1a1a18; letter-spacing: 0.02em; } +.lbl-footer { margin: 6px -18px 0; padding: 8px 18px; background: #2b2b28; color: #e8e2d5; text-align: center; font-size: 9px; line-height: 1.5; } + +/* Zones cliquables (hotspots) */ +.hotspot { + position: relative; cursor: pointer; border-radius: 5px; + outline: 1.5px dashed transparent; outline-offset: 1px; + transition: background 0.15s ease, outline-color 0.15s ease; +} +span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: clone; box-decoration-break: clone; } +.label-card:not(.recap) .hotspot:hover { background: rgba(43,122,120,0.10); outline-color: var(--teal-mid); } +.hotspot.hs-correct { background: rgba(46,125,50,0.14); outline: 1.5px solid #2e7d32; } +.hotspot.hs-wrong { background: rgba(198,40,40,0.16); outline: 1.5px solid #c62828; animation: hsShake 0.4s ease; } +@keyframes hsShake { 0%,100%{transform:translateX(0);} 25%{transform:translateX(-3px);} 75%{transform:translateX(3px);} } +.hs-num { + display: inline-flex; align-items: center; justify-content: center; + width: 16px; height: 16px; border-radius: 50%; + background: #2e7d32; color: #fff; + font-family: 'Syne', sans-serif; font-weight: 800; font-size: 9px; + margin-right: 3px; vertical-align: middle; +} + +/* Info-bulle réglementaire */ +.eti-pop { + position: absolute; z-index: 20; max-width: 290px; width: max-content; + background: #fff; border: 1px solid var(--border); border-radius: 10px; + box-shadow: 0 10px 30px rgba(0,0,0,0.18); + padding: 12px 30px 12px 14px; font-size: 12.5px; line-height: 1.5; + text-align: left; +} +.eti-pop[hidden] { display: none; } +.eti-pop .pop-status { font-family: 'Syne', sans-serif; font-weight: 800; font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 4px; display: flex; align-items: center; gap: 6px; } +.eti-pop.ok .pop-status { color: #2e7d32; } +.eti-pop.ko .pop-status { color: #c62828; } +.eti-pop.info .pop-status { color: var(--teal-dark); } +.eti-pop .pop-title { font-weight: 700; color: var(--text); margin-bottom: 3px; } +.eti-pop .pop-reg { color: var(--text-muted); } +.eti-pop .pop-close { position: absolute; top: 5px; right: 8px; cursor: pointer; color: var(--text-muted); font-size: 16px; line-height: 1; border: none; background: none; padding: 2px; } + +/* Check-list de synthèse (modèle FHER p.15) */ +.checklist-table { + width: 100%; max-width: 780px; border-collapse: collapse; + background: #fff; border: 1px solid var(--border); border-radius: 12px; overflow: hidden; + font-size: 12.5px; margin: 0.25rem 0 1rem; +} +.checklist-table th { background: var(--teal-light); color: var(--teal-dark); font-family: 'Syne', sans-serif; font-weight: 700; font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.03em; padding: 10px 12px; text-align: left; border-bottom: 2px solid var(--teal-mid); } +.checklist-table td { padding: 9px 12px; border-bottom: 1px solid var(--border); color: var(--text-muted); vertical-align: top; } +.checklist-table tr:last-child td { border-bottom: none; } +.checklist-table td.cl-num { text-align: center; } +.cl-badge { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; border-radius: 50%; background: #2e7d32; color: #fff; font-family: 'Syne', sans-serif; font-weight: 800; font-size: 10px; } +.checklist-table strong { color: var(--text); } +.cl-check { color: #2e7d32; font-weight: 700; } +.cl-cross { color: #c62828; font-weight: 700; } + +@media (max-width: 700px) { + .lbl-cols { grid-template-columns: 1fr; } + .lbl-title { font-size: 26px; } +} @@ -652,22 +785,117 @@ select:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px

L'étiquette est l'interface directe entre le produit et l'utilisateur. Elle communique rapidement les dangers essentiels, les EPI et les consignes d'utilisation.

🎯 -
Indiquez la zone de l'étiquette où trouver l'information : Identité, Pictogrammes, Mots d'avertissement, Mentions de danger, Conseils de prudence, Contenu net, Numéro d'urgence — ou « Absent ».
+
Sur une étiquette réelle (produit professionnel DÉS'INFECT), cliquez la zone qui répond à chaque question : nom commercial, pictogrammes, mention d'avertissement, phrases H, phrases P (EPI, premiers secours, élimination), contenu net et centre antipoison. Chaque clic ouvre la justification réglementaire de la zone.
- +
- -
Zones de l'étiquette
-
- -
-
- + + +
+ +
Zones de l'étiquette
+ +
+ 💬 +
Mise en situation — Vous intervenez chez un client avec un désinfectant de surfaces à usage exclusivement professionnel. Avant l'application, il vous interroge sur les dangers et les précautions du produit. Bidon en main, retrouvez rapidement sur l'étiquette chaque information demandée.
+
+ +
+ Question 1 / 9 + +
+ +
+
+ +
+ 🇫🇷 + DÉS'INFECT +
+ +
+ +
+
Propriétés :
Désinfecte et désodorise les sanitaires. Produit strictement destiné à un usage professionnel.
+
Mode d'emploi :
Concentré à diluer : 5 % pour une activité fongicide, 1 % pour une activité bactéricide. Pulvériser sur les parois, laisser agir 5 à 15 min, rincer à l'eau claire. Type de formulation : concentré soluble (SL). Utilisable en TP2.
+
UFI : 1234-1234-1234-1234
Date de péremption : MM/AAAA
N° LOT : 123456
+
+ + +
+
Substances actives :
Chlorure de didécyldiméthylammonium (N°CAS 7173-51-5) à 75 g/kg ; acide glycolique (CAS 79-14-1) à 35 g/kg.
+
+
Précautions d'emploi :
+ Produit strictement professionnel (FR) — Lire le mode d'emploi ci-joint avant l'emploi. + Formule déposée au centre anti-poison de Nancy : 03 83 22 50 50. + + H302 Nocif en cas d'ingestion. H314 Provoque de graves brûlures de la peau et des lésions oculaires. H410 Très toxique pour les organismes aquatiques, entraîne des effets néfastes à long terme. + + P280 Porter des gants de protection, des vêtements de protection et un équipement de protection du visage. + + P301+P330+P331 EN CAS D'INGESTION : rincer la bouche, ne pas faire vomir. P305+P351+P338 EN CAS DE CONTACT AVEC LES YEUX : rincer avec précaution à l'eau pendant plusieurs minutes. P310 Appeler immédiatement un CENTRE ANTIPOISON ou un médecin. + + P501 Éliminer le contenu / le récipient dans un point de collecte des déchets spéciaux ou dangereux. +
+
+
+ + +
+ + Volume net :5 L + + +
+ + + 🐟 + 🧪 + ! + + + DANGER +
+
+ + +
+ + +
+ +
+
+ + +
+ + + +
@@ -741,19 +969,41 @@ const ftQuestions = [ ]; const ftOptions = ["Description", "Propriétés biocides", "Conditionnements", "Utilisations"]; -const etiquetteQuestions = [ - { id: 1, text: "Quel est le nom du produit et le nom de l'entreprise ?", answer: "Identité" }, - { id: 2, text: "Quels pictogrammes de danger sont affichés ?", answer: "Pictogrammes" }, - { id: 3, text: "Quels sont les mots d'avertissement (« Attention » ou « Danger ») ?", answer: "Mots d'avertissement" }, - { id: 4, text: "Quelles sont les mentions de danger (H-statements) identifiées ?", answer: "Mentions de danger" }, - { id: 5, text: "Quels équipements de protection individuelle (EPI) sont recommandés ?", answer: "Conseils de prudence" }, - { id: 6, text: "Quels premiers secours sont indiqués ?", answer: "Conseils de prudence" }, - { id: 7, text: "Comment éliminer l'emballage après utilisation ?", answer: "Conseils de prudence" }, - { id: 8, text: "Quel est le contenu net du produit ?", answer: "Contenu net" }, - { id: 9, text: "Y a-t-il un numéro d'appel d'urgence mentionné ?", answer: "Numéro d'urgence" }, - { id: 10, text: "Quelles consignes de stockage sont affichées ?", answer: "Conseils de prudence" } +/* 9 zones réglementaires de l'étiquette (ordre canonique = check-list FHER p.15). + zone : identifiant du hotspot correspondant dans l'étiquette + text : consigne affichée pendant l'exercice + label : intitulé check-list (réutilisé dans la synthèse et le PDF) + reg : justification réglementaire (info-bulle) — Guide FHER, Partie 3, p.10-12 */ +const etiZones = [ + { zone: "z-nom", text: "Retrouvez le nom commercial du produit.", + label: "Nom commercial", + reg: "Obligation CLP : le nom commercial doit être identique à celui figurant sur la FDS (Guide FHER, Partie 3 — I.2)." }, + { zone: "z-picto", text: "Cliquez sur les pictogrammes de danger.", + label: "Pictogrammes de danger", + reg: "CLP : pictogrammes aux dimensions requises, regroupés avec la mention d'avertissement et les phrases H/P, se détachant nettement du fond (Partie 3 — I.5)." }, + { zone: "z-mention", text: "Où figure la mention d'avertissement ?", + label: "Mention d'avertissement", + reg: "CLP : « Danger » ou « Attention », disposée dans l'encart d'étiquetage avec pictogrammes et phrases H/P (Partie 3 — I.5)." }, + { zone: "z-h", text: "Repérez les mentions de danger (phrases H).", + label: "Mentions de danger (phrases H)", + reg: "CLP : les phrases H décrivent la nature du danger ; regroupées dans l'encart d'étiquetage (Partie 3 — I.5)." }, + { zone: "z-p-epi", text: "Où sont indiqués les EPI à porter (phrases P) ?", + label: "Conseils de prudence — EPI", + reg: "CLP : conseils de prudence P (ici P280). Six conseils P au maximum, sauf si la nature et la gravité des dangers l'exigent (CLP Art. 28-3) — Partie 3, I.5." }, + { zone: "z-p-secours", text: "Où trouver les instructions de premiers secours ?", + label: "Premiers secours (phrases P)", + reg: "BPR : indications relatives aux premiers soins (Partie 3 — II.8), traduites par les conseils P 3xx (contact, ingestion, inhalation)." }, + { zone: "z-p-elim", text: "Où sont les consignes d'élimination de l'emballage ?", + label: "Élimination du produit et de l'emballage", + reg: "BPR : instructions pour l'élimination en toute sécurité du produit et de son emballage, le cas échéant avec interdiction de le réutiliser (Partie 3 — II.10). Ici P501." }, + { zone: "z-net", text: "Retrouvez le contenu net du produit.", + label: "Contenu net", + reg: "Quantité nette de produit contenue dans l'emballage — mention générale du pavé d'information produit." }, + { zone: "z-antipoison", text: "Où figurent les coordonnées du centre antipoison ?", + label: "Centre antipoison / n° d'urgence", + reg: "Coordonnées d'urgence complétant les instructions de premiers soins (Partie 3 — II.8) ; les mélanges dangereux sont déclarés aux centres antipoison (annexe VIII du CLP)." } ]; -const etiquetteOptions = ["Identité", "Pictogrammes", "Mots d'avertissement", "Mentions de danger", "Conseils de prudence", "Contenu net", "Numéro d'urgence"]; +function etiZoneById(id) { return etiZones.find(z => z.zone === id); } function shuffleArray(array) { const a = [...array]; @@ -764,17 +1014,6 @@ function shuffleArray(array) { return a; } -function buildRow(qNum, selectId, answerAttr, text, options, badgeStyle) { - let opts = ''; - options.forEach(o => { opts += ``; }); - return ` -
-
Q${qNum}
-
${text}
- -
`; -} - function generateQuizzes() { // FDS — glisser-déposer : banques d'étiquettes + cases de dépôt buildFdsDnd(); @@ -782,17 +1021,183 @@ function generateQuizzes() { // FT — exercice « relier » (questions à gauche, sections à droite) buildFtMatch(); - // Étiquette - const etiOpts = etiquetteOptions.map(o => ({ value: o, label: o })); - etiOpts.push({ value: 'absent', label: 'Absent' }); - const etiC = document.getElementById('etiquette-questions'); - etiC.innerHTML = ''; - etiquetteQuestions.forEach((q, i) => { - etiC.innerHTML += buildRow(i + 1, 'eti' + q.id, q.answer, q.text, etiOpts, 'background:var(--teal-light);color:var(--teal-dark);'); - }); + // Étiquette — exercice à zones cliquables (hotspots) + buildEtiquette(); +} - // Bloque la vérification tant que toutes les questions ne sont pas répondues - wireVerifyGate('etiquette-questions', 'eti-verify'); +/* ---- Étiquette : zones cliquables ---- */ +let etiOrder = []; // ordre aléatoire des questions +let etiPos = 0; // question courante +let etiAnswers = {}; // { zoneCible : zoneCliquée en 1re tentative } — pour le PDF +let etiFirstTry = {}; // { zoneCible : true si trouvée sans erreur } +let etiCurError = false; // une erreur a-t-elle eu lieu sur la question courante +let etiInit = false; // écouteur de clic posé une seule fois + +function buildEtiquette() { + etiOrder = shuffleArray(etiZones.map((_, i) => i)); + etiPos = 0; + etiAnswers = {}; + etiFirstTry = {}; + etiCurError = false; + + const card = document.getElementById('labelCard'); + card.querySelectorAll('.hotspot').forEach(h => h.classList.remove('hs-correct', 'hs-wrong')); + + closeEtiPop(); + const r = document.getElementById('etiquette-result'); + r.className = 'result-message'; r.innerHTML = ''; + document.getElementById('eti-recap-btn').disabled = true; + document.getElementById('eti-exercise').style.display = 'flex'; + document.getElementById('eti-recap').style.display = 'none'; + + if (!etiInit) { + card.addEventListener('click', onEtiClick); + etiInit = true; + } + renderEtiQuestion(); +} + +function renderEtiQuestion() { + const prog = document.getElementById('eti-progress'); + const ques = document.getElementById('eti-question'); + if (etiPos >= etiOrder.length) { + prog.textContent = 'Terminé'; + ques.textContent = '✓ Les 9 zones ont été identifiées.'; + return; + } + const z = etiZones[etiOrder[etiPos]]; + prog.textContent = 'Question ' + (etiPos + 1) + ' / ' + etiZones.length; + ques.textContent = z.text; +} + +function onEtiClick(e) { + const hs = e.target.closest('.hotspot'); + if (!hs) return; + const clicked = hs.dataset.zone; + + // Exploration libre une fois l'exercice terminé + if (etiPos >= etiOrder.length) { + openEtiPop(hs, 'info', clicked); + return; + } + + const target = etiZones[etiOrder[etiPos]]; + if (clicked === target.zone) { + hs.classList.remove('hs-wrong'); + hs.classList.add('hs-correct'); + if (etiAnswers[target.zone] === undefined) etiAnswers[target.zone] = clicked; + etiFirstTry[target.zone] = !etiCurError; + openEtiPop(hs, 'ok', clicked); + etiPos++; + etiCurError = false; + renderEtiQuestion(); + if (etiPos >= etiOrder.length) finishEtiquette(); + } else { + if (etiAnswers[target.zone] === undefined) etiAnswers[target.zone] = clicked; // 1re réponse (erronée) mémorisée + etiCurError = true; + etiFirstTry[target.zone] = false; + hs.classList.remove('hs-wrong'); void hs.offsetWidth; // relance l'animation + hs.classList.add('hs-wrong'); + setTimeout(() => hs.classList.remove('hs-wrong'), 700); + openEtiPop(hs, 'ko', clicked); + } +} + +function finishEtiquette() { + const correct = etiZones.filter(z => etiFirstTry[z.zone]).length; + showScore(document.getElementById('etiquette-result'), correct, etiZones.length); + document.getElementById('eti-recap-btn').disabled = false; +} + +/* Info-bulle réglementaire positionnée près de la zone cliquée */ +function openEtiPop(hs, kind, clickedZone) { + const pop = document.getElementById('eti-pop'); + const stage = document.getElementById('eti-stage'); + const z = etiZoneById(clickedZone); + + const statusEl = document.getElementById('pop-status'); + pop.className = 'eti-pop ' + kind; + if (kind === 'ok') statusEl.innerHTML = '✅ Bonne zone'; + else if (kind === 'ko') statusEl.innerHTML = '❌ Ce n\'est pas la bonne zone'; + else statusEl.innerHTML = '🔎 ' + z.label; + + document.getElementById('pop-title').textContent = z.label; + document.getElementById('pop-reg').textContent = z.reg; + + pop.hidden = false; + // Positionnement relatif à la scène + const sr = stage.getBoundingClientRect(); + const hr = hs.getBoundingClientRect(); + const popW = pop.offsetWidth, popH = pop.offsetHeight; + let left = hr.left - sr.left + hr.width / 2 - popW / 2; + left = Math.max(4, Math.min(left, sr.width - popW - 4)); + let top = hr.bottom - sr.top + 8; + if (top + popH > sr.height) top = hr.top - sr.top - popH - 8; // bascule au-dessus si déborde + if (top < 0) top = 4; + pop.style.left = left + 'px'; + pop.style.top = top + 'px'; +} + +function closeEtiPop() { + const pop = document.getElementById('eti-pop'); + if (pop) pop.hidden = true; +} + +function resetEtiquette() { buildEtiquette(); } + +/* ---- Synthèse : étiquette annotée + check-list ---- */ +function showEtiRecap() { + closeEtiPop(); + const stage = document.getElementById('recap-stage'); + stage.innerHTML = ''; + + // Clone de l'étiquette, toutes zones en « correct » + numéros + const clone = document.getElementById('labelCard').cloneNode(true); + clone.removeAttribute('id'); + clone.classList.add('recap'); + clone.querySelectorAll('.hotspot').forEach(h => { + h.classList.remove('hs-wrong'); + h.classList.add('hs-correct'); + const idx = etiZones.findIndex(z => z.zone === h.dataset.zone); + if (idx >= 0) { + const num = document.createElement('b'); + num.className = 'hs-num'; + num.textContent = idx + 1; + h.insertBefore(num, h.firstChild); + } + }); + stage.appendChild(clone); + + // Table check-list + const tbl = document.getElementById('checklist-table'); + let rows = 'N°Élément d\'étiquetageBase réglementaireStatut'; + etiZones.forEach((z, i) => { + const ok = etiFirstTry[z.zone]; + const answered = etiAnswers[z.zone] !== undefined; + const statut = ok + ? '✓ trouvé' + : (answered ? '✗ à revoir' : ''); + rows += ` + ${i + 1} + ${z.label} + ${z.reg} + ${statut} + `; + }); + rows += ''; + tbl.innerHTML = rows; + + document.getElementById('eti-exercise').style.display = 'none'; + document.getElementById('eti-recap').style.display = 'flex'; + const sa = document.querySelector('#slide-7 .scroll-area'); + if (sa) sa.scrollTop = 0; +} + +function hideEtiRecap() { + document.getElementById('eti-recap').style.display = 'none'; + document.getElementById('eti-exercise').style.display = 'flex'; + const sa = document.querySelector('#slide-7 .scroll-area'); + if (sa) sa.scrollTop = 0; } /* ---- FT : exercice « relier » ---- */ @@ -1024,22 +1429,6 @@ function wireVerifyGate(containerId, btnId) { } /* ======================== VÉRIFICATION ======================== */ -function showInlineResult(selectEl, isCorrect, isUnanswered, correctAnswerText) { - const row = selectEl.closest('.answer-row'); - row.querySelector('.answer-label')?.remove(); - row.querySelector('.answer-icon')?.remove(); - - const label = document.createElement('div'); - label.className = 'answer-label'; - label.textContent = correctAnswerText; - selectEl.before(label); - - const icon = document.createElement('div'); - icon.className = 'answer-icon'; - icon.textContent = isUnanswered ? '❓' : (isCorrect ? '✅' : '❌'); - selectEl.before(icon); -} - function showScore(resultDiv, correct, total) { const p = Math.round((correct / total) * 100); if (p === 100) { @@ -1102,18 +1491,6 @@ function verifyFT() { showScore(document.getElementById('ft-result'), correct, ftQuestions.length); } -function verifyEtiquette() { - let correct = 0; - etiquetteQuestions.forEach(q => { - const select = document.getElementById('eti' + q.id); - const userAnswer = select.value; - const isCorrect = userAnswer === q.answer; - if (isCorrect) correct++; - showInlineResult(select, isCorrect, userAnswer === '', q.answer); - }); - showScore(document.getElementById('etiquette-result'), correct, etiquetteQuestions.length); -} - /* ======================== EXPORT PDF ======================== */ function escapeHtml(str) { if (!str) return ''; @@ -1168,7 +1545,20 @@ function exporterPDF() { const p1 = partHtml('P1', 'Partie 1 — FDS (Fiche de Données de Sécurité)', fdsQuestions, 'fds', fdsAnswerLabel, fdsExpectedLabel, isFdsCorrect); const p2 = partHtml('P2', 'Partie 2 — FT (Fiche Technique)', ftQuestions, 'ft', textAnswerLabel, a => a, isTextCorrect, q => ftConnections[q.id] || ''); - const p3 = partHtml('P3', 'Partie 3 — Étiquette', etiquetteQuestions, 'eti', textAnswerLabel, a => a, isTextCorrect); + + // Partie 3 — Étiquette (zones cliquables) : ordre check-list + const p3rows = etiZones.map((z, i) => { + const given = etiAnswers[z.zone]; + const ok = given === z.zone; + nbTotal++; if (ok) scoreTotal++; + const givenLabel = given === undefined ? '(non répondu)' : (etiZoneById(given) ? etiZoneById(given).label : given); + const mark = given === undefined ? '❓' : (ok ? '✓' : '✗'); + return `
+
[P3-Q${i + 1}] ${escapeHtml(z.text)}
+
Zone désignée : ${escapeHtml(givenLabel)} — Attendu : ${escapeHtml(z.label)} — ${mark}
+
`; + }).join(''); + const p3 = `

Partie 3 — Étiquette (zones réglementaires)

${p3rows}`; const w = window.open('', '_blank'); w.document.write(`