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.
-
Démarrer le TD →
@@ -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
⚠️ Renseignez le nom du stagiaire ou du groupe pour continuer.
- Commencer la partie 1 →
@@ -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.
- Répondre aux questions →
@@ -796,12 +802,7 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
- ✓ Vérifier mes réponses
-
- ↺ Recommencer
- Passer à la partie 2 →
-
@@ -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 ».
- Répondre aux questions →
@@ -827,12 +827,7 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
- ✓ Vérifier mes réponses
-
- ↺ Recommencer
- Passer à la partie 3 →
-
@@ -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.
- Répondre aux questions →
@@ -911,9 +905,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.
-
-
-
- ↺ Recommencer
- Voir le récapitulatif →
-
@@ -950,11 +932,6 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
-
-
- ← Retour à l'exercice
- Voir la synthèse générale →
-
@@ -979,19 +956,10 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
Étiquette Dangers essentiels, interface rapide À chaque prise du produit Tous les utilisateurs
-
- FDS = Sécurité et dangers → à consulter AVANT manipulation.
- FT = Mode d'emploi et applications → à consulter pour l'utilisation.
- Étiquette = Interface rapide → lecture obligatoire avant ouverture du produit.
- Les trois documents sont complémentaires , jamais redondants — chacun cible un besoin et un public différents.
-
-
@@ -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 @@