diff --git a/data/ghs05-corrosif.jpg b/data/ghs05-corrosif.jpg new file mode 100644 index 0000000..a62caab Binary files /dev/null and b/data/ghs05-corrosif.jpg differ diff --git a/data/ghs07-nocif.jpg b/data/ghs07-nocif.jpg new file mode 100644 index 0000000..099549e Binary files /dev/null and b/data/ghs07-nocif.jpg differ diff --git a/data/ghs09-environnement.jpg b/data/ghs09-environnement.jpg new file mode 100644 index 0000000..5f310da Binary files /dev/null and b/data/ghs09-environnement.jpg differ diff --git a/exercices/Exo_FDS.html b/exercices/Exo_FDS.html index 862c66b..a7fca5c 100644 --- a/exercices/Exo_FDS.html +++ b/exercices/Exo_FDS.html @@ -74,6 +74,32 @@ } .back-btn:hover { background: rgba(186,117,23,0.08); } +/* Boutons centralisés dans la bottom bar */ +.bar-actions { + position: absolute; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 10px; + align-items: center; + flex-wrap: nowrap; +} +.bar-actions .btn { + padding: 8px 22px; + font-size: 13px; + white-space: nowrap; +} +.bar-actions .btn:disabled { + opacity: 0.45; + cursor: not-allowed; + transform: none; + pointer-events: none; +} +@media (max-width: 700px) { + .bar-actions .btn { padding: 7px 14px; font-size: 12px; } + .bottom-bar .slide-counter { display: none; } +} + /* BUTTONS */ .btn { font-family: 'Syne', sans-serif; @@ -568,7 +594,8 @@ select:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px 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; } +.pictos { display: flex; gap: 6px; align-items: center; } +.ghs-img { width: 42px; height: 42px; object-fit: contain; display: block; } .ghs { width: 38px; height: 38px; position: relative; border: 2.5px solid #c62828; background: #fff; @@ -651,23 +678,10 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl /* ---- SLIDE 7 — encadré de réponse sur le côté de l'étiquette ---- */ #eti-stage { - max-width: 980px; - display: grid; - grid-template-columns: minmax(0, 1fr) 300px; - align-items: start; - gap: 16px; + max-width: 680px; + display: flex; justify-content: center; } -#eti-stage .label-card { grid-column: 1; grid-row: 1; } -#eti-stage .eti-pop, -#eti-stage .eti-side-hint { grid-column: 2; grid-row: 1; align-self: start; } -#eti-stage .eti-pop { position: relative; inset: auto; left: auto; top: auto; max-width: none; width: 100%; } -.eti-side-hint { - font-size: 12.5px; color: var(--teal-dark); line-height: 1.5; - background: var(--teal-light); border: 1px dashed var(--teal-mid); - border-radius: 10px; padding: 12px 14px; -} -.eti-side-hint[hidden] { display: none; } /* ---- 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; } @@ -678,12 +692,6 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl @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: 820px) { - #eti-stage { grid-template-columns: 1fr; max-width: 680px; } - #eti-stage .eti-pop, - #eti-stage .eti-side-hint { grid-column: 1; grid-row: auto; } - #eti-stage .label-card { grid-row: auto; } -} @media (max-width: 1000px) { .recap-layout { flex-direction: column; align-items: center; } .recap-layout #recap-stage { flex: none; max-width: 560px; width: 100%; } @@ -709,15 +717,16 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
- - - + + +
@@ -726,7 +735,6 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
Certibiocide — Travaux dirigés
FDS, FT
& Étiquettes

Maîtrisez les trois documents essentiels des produits biocides : fiche de sécurité, fiche technique et étiquetage réglementaire.

-
@@ -736,6 +744,10 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
Déroulé du TD
Trois documents, trois usages

Vous allez apprendre à localiser une information dans chacun des documents de référence d'un produit biocide.

+
+ ⏱️ +
Durée ≈ 30 min. Pour chaque question, sélectionnez la zone du document où trouver l'information, puis vérifiez votre score.
+
📄
@@ -753,10 +765,6 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
Interface directe avec l'utilisateur
-
- ⏱️ -
Durée ≈ 30 min. Pour chaque question, sélectionnez la zone du document où trouver l'information, puis vérifiez votre score.
-
👥 Identification du groupe
@@ -771,7 +779,6 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
- @@ -784,7 +791,6 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl 🎯
Pour chaque question, indiquez dans quelle rubrique (1 à 16) trouver la réponse — ou « Absent » si l'information n'y figure pas. Les questions sont mélangées à chaque chargement.
- @@ -796,12 +802,7 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
-
-
- - -
@@ -814,7 +815,6 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl 🎯
Indiquez la section de la FT où trouver chaque information : Description, Propriétés biocides, Conditionnements, Utilisations — ou « Absent ».
- @@ -827,12 +827,7 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
-
-
- - -
@@ -846,7 +841,6 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl 🎯
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.
- @@ -911,9 +905,9 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
- 🐟 - 🧪 - ! + Pictogramme SGH09 — danger pour le milieu aquatique + Pictogramme SGH05 — corrosif + Pictogramme SGH07 — nocif / irritant DANGER @@ -923,21 +917,9 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
-
👉 Cliquez une zone de l'étiquette : sa justification réglementaire s'affiche ici, à côté du bidon.
- -
-
- - -
@@ -950,11 +932,6 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
- -
- - -
@@ -979,19 +956,10 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl ÉtiquetteDangers essentiels, interface rapideÀ chaque prise du produitTous les utilisateurs -
✔ Corriger un rendu -
-
- Retour aux exercices
@@ -1093,6 +1061,7 @@ let etiAnswers = {}; // { zoneCible : zoneCliquée en 1re tentative } — p 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)); @@ -1107,9 +1076,10 @@ function buildEtiquette() { closeEtiPop(); const r = document.getElementById('etiquette-result'); r.className = 'result-message'; r.innerHTML = ''; - document.getElementById('eti-recap-btn').disabled = true; + etiRecapShown = false; document.getElementById('eti-exercise').style.display = 'flex'; document.getElementById('eti-recap').style.display = 'none'; + if (currentSlide === 7) renderBarActions(); if (!etiInit) { card.addEventListener('click', onEtiClick); @@ -1136,11 +1106,8 @@ function onEtiClick(e) { if (!hs) return; const clicked = hs.dataset.zone; - // Exploration libre une fois l'exercice terminé - if (etiPos >= etiOrder.length) { - openEtiPop(hs, 'info', clicked); - return; - } + // Exercice terminé : plus d'interaction (les corrections sont dans la correction générale) + if (etiPos >= etiOrder.length) return; const target = etiZones[etiOrder[etiPos]]; if (clicked === target.zone) { @@ -1148,7 +1115,6 @@ function onEtiClick(e) { 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(); @@ -1160,14 +1126,12 @@ function onEtiClick(e) { 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; + const b = document.getElementById('eti-recap-btn'); + if (b) b.disabled = false; } /* Info-bulle réglementaire positionnée près de la zone cliquée */ @@ -1243,6 +1207,8 @@ function showEtiRecap() { 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'); @@ -1255,6 +1221,8 @@ function showEtiRecap() { 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; } @@ -1296,7 +1264,7 @@ function buildFtMatch() { initFtMatch(); document.getElementById('ft-svg').innerHTML = ''; - document.getElementById('ft-verify').disabled = true; + refreshFtState(); } function resetFT() { @@ -1354,7 +1322,8 @@ function refreshFtState() { dot.closest('.match-item').classList.toggle('connected', used); }); const all = ftQuestions.every(q => ftConnections[q.id] != null); - document.getElementById('ft-verify').disabled = !all; + const btn = document.getElementById('ft-next'); + if (btn) btn.disabled = !all; } function initFtMatch() { @@ -1474,7 +1443,8 @@ function initFdsDnd() { function updateFdsGate() { const slots = document.querySelectorAll('#slide-3 .drop-slot'); const all = Array.from(slots).every(s => (s.dataset.value || '') !== ''); - document.getElementById('fds-verify').disabled = !all; + const btn = document.getElementById('fds-next'); + if (btn) btn.disabled = !all; } function wireVerifyGate(containerId, btnId) { @@ -1676,6 +1646,7 @@ function goTo(n) { const sa = next.querySelector('.scroll-area'); if (sa) sa.scrollTop = 0; updateUI(); + if (n === 8) setTimeout(launchConfetti, 200); } function goBack() { @@ -1691,20 +1662,73 @@ function goBack() { updateUI(); } +/* Boutons centralisés dans la bottom bar, un jeu par slide */ +function barBtn(bar, html, cls, fn, opts) { + opts = opts || {}; + const b = document.createElement('button'); + b.className = 'btn ' + cls; + b.innerHTML = html; + if (opts.id) b.id = opts.id; + if (opts.disabled) b.disabled = true; + b.addEventListener('click', fn); + bar.appendChild(b); + return b; +} + +function renderBarActions() { + const bar = document.getElementById('barActions'); + if (!bar) return; + bar.innerHTML = ''; + switch (currentSlide) { + case 0: + barBtn(bar, 'Démarrer le TD →', 'btn-amber', () => goTo(1)); + break; + case 1: + barBtn(bar, 'Commencer la partie 1 →', 'btn-amber', startPart1); + break; + case 2: + barBtn(bar, 'Répondre aux questions →', 'btn-amber', () => goTo(3)); + break; + case 3: + barBtn(bar, '↺ Recommencer', 'btn-outline', resetFDS); + barBtn(bar, 'Passer à la partie 2 →', 'btn-coral', () => goTo(4), { id: 'fds-next' }); + updateFdsGate(); + break; + case 4: + barBtn(bar, 'Répondre aux questions →', 'btn-coral', () => goTo(5)); + break; + case 5: + barBtn(bar, '↺ Recommencer', 'btn-outline', resetFT); + barBtn(bar, 'Passer à la partie 3 →', 'btn-teal', () => goTo(6), { id: 'ft-next' }); + refreshFtState(); + break; + case 6: + barBtn(bar, 'Répondre aux questions →', 'btn-teal', () => goTo(7)); + break; + case 7: + if (etiRecapShown) { + barBtn(bar, '← Retour à l\'exercice', 'btn-outline', hideEtiRecap); + barBtn(bar, 'Voir la synthèse générale →', 'btn-amber', () => goTo(8)); + } else { + barBtn(bar, '↺ Recommencer', 'btn-outline', resetEtiquette); + barBtn(bar, 'Terminer l\'exercice →', 'btn-teal', showEtiRecap, + { 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) + break; + } +} + function updateUI() { document.getElementById('progressFill').style.width = currentSlide === 0 ? '0%' : ((currentSlide / totalSlides) * 100) + '%'; document.getElementById('slideCounter').textContent = currentSlide === 0 ? '' : currentSlide + ' / ' + totalSlides; - document.getElementById('bottomBar').style.display = currentSlide === 0 ? 'none' : 'flex'; + document.getElementById('bottomBar').style.display = 'flex'; document.getElementById('backBtn').style.display = history.length > 0 ? 'block' : 'none'; - - const badge = document.getElementById('phaseBadge'); - const set = (bg, col, txt) => { badge.style.display = 'block'; badge.style.background = bg; badge.style.color = col; badge.textContent = txt; }; - if (currentSlide === 2 || currentSlide === 3) set('var(--amber-light)', 'var(--amber-dark)', 'Partie 1 — FDS'); - else if (currentSlide === 4 || currentSlide === 5) set('var(--coral-light)', 'var(--coral)', 'Partie 2 — FT'); - else if (currentSlide === 6 || currentSlide === 7) set('var(--teal-light)', 'var(--teal-dark)', 'Partie 3 — Étiquette'); - else badge.style.display = 'none'; + renderBarActions(); } function restartAll() { @@ -1719,6 +1743,79 @@ function restartAll() { updateUI(); } +/* ======================== CONFETTIS ======================== */ +function launchConfetti() { + const canvas = document.getElementById('confettiCanvas'); + if (!canvas) return; + const ctx = canvas.getContext('2d'); + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + canvas.style.display = 'block'; + + const colors = ['#BA7517', '#D85A30', '#F5DFB8', '#633806', '#F0A855', '#E8C068', '#FF8C5A']; + const particles = Array.from({ length: 180 }, () => ({ + x: Math.random() * canvas.width, + y: -(Math.random() * canvas.height * 0.5), + w: Math.random() * 10 + 5, + h: Math.random() * 5 + 3, + color: colors[Math.floor(Math.random() * colors.length)], + rotation: Math.random() * Math.PI * 2, + rotSpeed: (Math.random() - 0.5) * 0.18, + vx: (Math.random() - 0.5) * 2.5, + vy: Math.random() * 3 + 2, + opacity: 1, + shape: Math.random() < 0.4 ? 'circle' : 'rect' + })); + + let rafId; + let start = null; + const totalDuration = 3800; + + function draw(ts) { + if (!start) start = ts; + const elapsed = ts - start; + + ctx.clearRect(0, 0, canvas.width, canvas.height); + let active = false; + + particles.forEach(p => { + p.x += p.vx; + p.y += p.vy; + p.vy += 0.04; + p.rotation += p.rotSpeed; + p.opacity = elapsed < totalDuration * 0.55 + ? 1 + : Math.max(0, 1 - (elapsed - totalDuration * 0.55) / (totalDuration * 0.45)); + + if (p.y < canvas.height + 20) active = true; + + ctx.save(); + ctx.translate(p.x, p.y); + ctx.rotate(p.rotation); + ctx.globalAlpha = p.opacity; + ctx.fillStyle = p.color; + if (p.shape === 'circle') { + ctx.beginPath(); + ctx.arc(0, 0, p.w / 2, 0, Math.PI * 2); + ctx.fill(); + } else { + ctx.fillRect(-p.w / 2, -p.h / 2, p.w, p.h); + } + ctx.restore(); + }); + + if (active && elapsed < totalDuration * 1.3) { + rafId = requestAnimationFrame(draw); + } else { + canvas.style.display = 'none'; + ctx.clearRect(0, 0, canvas.width, canvas.height); + } + } + + if (rafId) cancelAnimationFrame(rafId); + rafId = requestAnimationFrame(draw); +} + document.addEventListener('DOMContentLoaded', () => { generateQuizzes(); updateUI(); diff --git a/index.html b/index.html index b72dd84..c9aa315 100644 --- a/index.html +++ b/index.html @@ -141,7 +141,7 @@
-
✔️
+

Corriger un exercice

Correction des rendus