Retravaille l'exercice FDS : étiquette libre et corrigé déplacé
- Slide 5 (FT) : reformule la question sur le type de produit en « Où se trouve la liste des TP (type de produit) ? » - Slide 7 : l'utilisateur peut désormais cliquer n'importe quelle zone, même incorrecte ; le clic est enregistré et l'exercice avance. - Slide 7 : « Terminer l'exercice » mène directement à la synthèse ; le récapitulatif « l'étiquette annotée » quitte le déroulé des slides. - Déplace ce récapitulatif (étiquette annotée + check-list réglementaire) vers corriger-exercice.html, en corrigé de référence du rendu FDS.
This commit is contained in:
@@ -647,21 +647,6 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
|
||||
.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; }
|
||||
@@ -687,20 +672,6 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* ---- SLIDE 7 — récapitulatif : étiquette à gauche, tableau à droite ---- */
|
||||
.recap-layout { display: flex; gap: 20px; align-items: flex-start; justify-content: center; width: 100%; max-width: 1120px; }
|
||||
.recap-layout #recap-stage { flex: 1 1 0; min-width: 0; max-width: 560px; }
|
||||
.recap-layout .checklist-table { flex: 1 1 0; min-width: 0; margin: 0; }
|
||||
.recap-layout.animate #recap-stage { animation: recapSlideLeft 0.5s ease both; }
|
||||
.recap-layout.animate .checklist-table { animation: recapSlideRight 0.5s ease both; }
|
||||
@keyframes recapSlideLeft { from { opacity: 0; transform: translateX(-40px); } to { opacity: 1; transform: none; } }
|
||||
@keyframes recapSlideRight { from { opacity: 0; transform: translateX(40px); } to { opacity: 1; transform: none; } }
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.recap-layout { flex-direction: column; align-items: center; }
|
||||
.recap-layout #recap-stage { flex: none; max-width: 560px; width: 100%; }
|
||||
.recap-layout .checklist-table { flex: none; width: 100%; max-width: 640px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -843,7 +814,7 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
|
||||
<p class="slide-intro">L'étiquette est l'interface directe entre le produit et l'utilisateur. Elle communique rapidement les <strong>dangers essentiels</strong>, les <strong>EPI</strong> et les <strong>consignes d'utilisation</strong>.</p>
|
||||
<div class="info-card" style="background:var(--teal-light);color:var(--teal-dark);">
|
||||
<span>🎯</span>
|
||||
<div>Sur une <strong>étiquette réelle</strong> (produit professionnel DÉS'INFECT), <strong>cliquez la zone</strong> 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 <strong>justification réglementaire</strong> de la zone.</div>
|
||||
<div>Sur une <strong>étiquette réelle</strong> (produit professionnel DÉS'INFECT), <strong>cliquez la zone</strong> qui répond à chaque question : nom commercial, pictogrammes, mention d'avertissement, phrases H, phrases P (EPI, premiers secours, élimination), contenu net et centre antipoison. La <strong>justification réglementaire</strong> de chaque zone est fournie sur la page de correction de l'exercice.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -926,18 +897,6 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
|
||||
<div id="etiquette-result" class="result-message"></div>
|
||||
</div>
|
||||
|
||||
<!-- ---- PANNEAU B : synthèse / check-list ---- -->
|
||||
<div id="eti-recap" hidden style="width:100%;display:flex;flex-direction:column;align-items:center;gap:0.75rem;">
|
||||
<div class="section-tag" style="background:var(--teal-light);color:var(--teal-dark);">Check-list d'étiquetage</div>
|
||||
<div class="slide-title">Récapitulatif — l'étiquette annotée</div>
|
||||
<p class="slide-intro">Les 9 zones réglementaires et leurs bonnes réponses, présentées comme la <strong>check-list officielle d'étiquetage des produits biocides</strong> (Guide FHER, p. 15) — un outil réutilisable en conditions réelles.</p>
|
||||
|
||||
<div class="recap-layout" id="recap-layout">
|
||||
<div class="eti-stage" id="recap-stage"></div>
|
||||
<table class="checklist-table" id="checklist-table"></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -996,7 +955,7 @@ const fdsQuestions = [
|
||||
const ftQuestions = [
|
||||
{ id: 1, text: "Quel est le nom du produit et qui est le fabricant ?", answer: "Description" },
|
||||
{ id: 2, text: "Sur quelles matières ce produit est-il compatible ?", answer: "Absent" },
|
||||
{ id: 3, text: "Quel est (sont) le(s) type(s) de ce produit ?", answer: "Propriétés biocides" },
|
||||
{ id: 3, text: "Où se trouve la liste des TP (type de produit) ?", answer: "Propriétés biocides" },
|
||||
{ id: 4, text: "Quels sont les types de conditionnements disponibles ?", answer: "Conditionnements" },
|
||||
{ id: 5, text: "Où retrouver les différents modes d'utilisation de ce produit ?", answer: "Utilisations" }
|
||||
];
|
||||
@@ -1063,16 +1022,13 @@ 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
|
||||
let etiRecapShown = false;// panneau récapitulatif affiché (slide 7)
|
||||
|
||||
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'));
|
||||
@@ -1080,9 +1036,6 @@ function buildEtiquette() {
|
||||
closeEtiPop();
|
||||
const r = document.getElementById('etiquette-result');
|
||||
r.className = 'result-message'; r.innerHTML = '';
|
||||
etiRecapShown = false;
|
||||
document.getElementById('eti-exercise').style.display = 'flex';
|
||||
document.getElementById('eti-recap').style.display = 'none';
|
||||
if (currentSlide === 7) renderBarActions();
|
||||
|
||||
if (!etiInit) {
|
||||
@@ -1110,28 +1063,20 @@ function onEtiClick(e) {
|
||||
if (!hs) return;
|
||||
const clicked = hs.dataset.zone;
|
||||
|
||||
// Exercice terminé : plus d'interaction (les corrections sont dans la correction générale)
|
||||
// Exercice terminé : plus d'interaction (la correction est sur la page de correction).
|
||||
if (etiPos >= etiOrder.length) return;
|
||||
|
||||
marquerModifie();
|
||||
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;
|
||||
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);
|
||||
}
|
||||
|
||||
// La zone cliquée — bonne ou mauvaise — est enregistrée comme réponse, puis on passe
|
||||
// à la question suivante. Aucun retour de justesse n'est affiché ici : la correction
|
||||
// (étiquette annotée + check-list) est présentée sur la page de correction de l'exercice.
|
||||
etiAnswers[target.zone] = clicked;
|
||||
etiFirstTry[target.zone] = (clicked === target.zone);
|
||||
etiPos++;
|
||||
renderEtiQuestion();
|
||||
if (etiPos >= etiOrder.length) finishEtiquette();
|
||||
}
|
||||
|
||||
function finishEtiquette() {
|
||||
@@ -1168,70 +1113,6 @@ function closeEtiPop() {
|
||||
|
||||
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 = '<thead><tr><th style="width:34px;">N°</th><th>Élément d\'étiquetage</th><th>Base réglementaire</th><th style="width:70px;">Statut</th></tr></thead><tbody>';
|
||||
etiZones.forEach((z, i) => {
|
||||
const ok = etiFirstTry[z.zone];
|
||||
const answered = etiAnswers[z.zone] !== undefined;
|
||||
const statut = ok
|
||||
? '<span class="cl-check">✓ trouvé</span>'
|
||||
: (answered ? '<span class="cl-cross">✗ à revoir</span>' : '<span class="cl-cross">—</span>');
|
||||
rows += `<tr>
|
||||
<td class="cl-num"><span class="cl-badge">${i + 1}</span></td>
|
||||
<td><strong>${z.label}</strong></td>
|
||||
<td>${z.reg}</td>
|
||||
<td>${statut}</td>
|
||||
</tr>`;
|
||||
});
|
||||
rows += '</tbody>';
|
||||
tbl.innerHTML = rows;
|
||||
|
||||
document.getElementById('eti-exercise').style.display = 'none';
|
||||
document.getElementById('eti-recap').style.display = 'flex';
|
||||
etiRecapShown = true;
|
||||
renderBarActions();
|
||||
|
||||
// Animation : l'étiquette glisse à gauche, le tableau apparaît à sa droite
|
||||
const rl = document.getElementById('recap-layout');
|
||||
if (rl) { rl.classList.remove('animate'); void rl.offsetWidth; rl.classList.add('animate'); }
|
||||
|
||||
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';
|
||||
etiRecapShown = false;
|
||||
renderBarActions();
|
||||
const sa = document.querySelector('#slide-7 .scroll-area');
|
||||
if (sa) sa.scrollTop = 0;
|
||||
}
|
||||
|
||||
/* ---- FT : exercice « relier » ---- */
|
||||
const ftConnections = {}; // { qId: valeur de la section reliée }
|
||||
let ftDrag = null; // { qid } pendant un glissement
|
||||
@@ -1685,8 +1566,8 @@ function renderBarActions() {
|
||||
const bar = document.getElementById('barActions');
|
||||
if (!bar) return;
|
||||
bar.innerHTML = '';
|
||||
// Bouton retour intégré au groupe centré (sauf sur le récapitulatif, qui a son propre retour)
|
||||
if (history.length > 0 && !(currentSlide === 7 && etiRecapShown)) {
|
||||
// Bouton retour intégré au groupe centré
|
||||
if (history.length > 0) {
|
||||
barBtn(bar, '← Retour', 'is-secondary', goBack);
|
||||
}
|
||||
switch (currentSlide) {
|
||||
@@ -1713,14 +1594,9 @@ function renderBarActions() {
|
||||
barBtn(bar, 'Répondre aux questions →', 'is-primary', () => goTo(7));
|
||||
break;
|
||||
case 7:
|
||||
if (etiRecapShown) {
|
||||
barBtn(bar, '← Retour à l\'exercice', 'is-secondary', hideEtiRecap);
|
||||
barBtn(bar, 'Voir la synthèse générale →', 'is-primary', () => goTo(8));
|
||||
} else {
|
||||
barBtn(bar, '↺ Recommencer', 'is-secondary', resetEtiquette);
|
||||
barBtn(bar, 'Terminer l\'exercice →', 'is-primary', showEtiRecap,
|
||||
{ id: 'eti-recap-btn', disabled: etiPos < etiOrder.length });
|
||||
}
|
||||
barBtn(bar, '↺ Recommencer', 'is-secondary', resetEtiquette);
|
||||
barBtn(bar, 'Terminer l\'exercice →', 'is-primary', () => goTo(8),
|
||||
{ id: 'eti-recap-btn', disabled: etiPos < etiOrder.length });
|
||||
break;
|
||||
case 8:
|
||||
// Boutons placés sous le tableau comparatif, dans la slide (pas dans la bottom bar)
|
||||
|
||||
Reference in New Issue
Block a user