Retravaille l'exercice FDS : boutons, étiquette et récapitulatif

- index : emoji de la carte "Corriger un exercice" ✔️
- Boutons de toutes les slides centralisés et uniformisés dans la bottom bar
- Slide 1 : encadré "Durée 30 min" remonté au-dessus des trois parties
- Slides 3 et 5 : suppression du bouton "Vérifier mes réponses" et blocage du passage à la partie suivante tant que tout n'est pas rempli/relié
- Slide 7 : bouton "Terminer l'exercice" (ex "Voir le récapitulatif") réparé, faux pictogrammes remplacés par les vraies images SGH, volet de correction de droite et encadré de résultats supprimés
- Slide 8 : encadrés 💡 retirés, boutons déplacés sous le tableau comparatif, bouton "Retour aux exercices" supprimé
- Confettis déplacés de la slide 6 vers la slide 8
This commit is contained in:
2026-07-19 17:59:02 +02:00
parent 41d8b98f96
commit 30817c829b
5 changed files with 193 additions and 96 deletions

BIN
data/ghs05-corrosif.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

BIN
data/ghs07-nocif.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@@ -74,6 +74,32 @@
} }
.back-btn:hover { background: rgba(186,117,23,0.08); } .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 */ /* BUTTONS */
.btn { .btn {
font-family: 'Syne', sans-serif; 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); 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; } .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 { .ghs {
width: 38px; height: 38px; position: relative; width: 38px; height: 38px; position: relative;
border: 2.5px solid #c62828; background: #fff; 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 ---- */ /* ---- SLIDE 7 — encadré de réponse sur le côté de l'étiquette ---- */
#eti-stage { #eti-stage {
max-width: 980px; max-width: 680px;
display: grid; display: flex;
grid-template-columns: minmax(0, 1fr) 300px;
align-items: start;
gap: 16px;
justify-content: center; 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 ---- */ /* ---- 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 { 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 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; } } @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) { @media (max-width: 1000px) {
.recap-layout { flex-direction: column; align-items: center; } .recap-layout { flex-direction: column; align-items: center; }
.recap-layout #recap-stage { flex: none; max-width: 560px; width: 100%; } .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
<!-- Progress bar --> <!-- Progress bar -->
<div class="progress-bar"><div class="progress-fill" id="progressFill"></div></div> <div class="progress-bar"><div class="progress-fill" id="progressFill"></div></div>
<!-- Phase badge -->
<div class="phase-badge" id="phaseBadge" style="display:none"></div>
<!-- Bottom bar --> <!-- Bottom bar -->
<div class="bottom-bar" id="bottomBar" style="display:none;"> <div class="bottom-bar" id="bottomBar" style="display:none;">
<button class="back-btn" id="backBtn" onclick="goBack()" style="display:none;">← Retour</button> <button class="back-btn" id="backBtn" onclick="goBack()" style="display:none;">← Retour</button>
<div class="bar-actions" id="barActions"></div>
<div class="slide-counter" id="slideCounter"></div> <div class="slide-counter" id="slideCounter"></div>
</div> </div>
<!-- Confetti -->
<canvas id="confettiCanvas" style="position:fixed;inset:0;pointer-events:none;z-index:150;display:none;"></canvas>
<!-- ============ SLIDE 0 — ACCUEIL ============ --> <!-- ============ SLIDE 0 — ACCUEIL ============ -->
<div class="slide active fade-in" id="slide-0"> <div class="slide active fade-in" id="slide-0">
<div class="deco-circle" style="width:400px;height:400px;background:var(--amber-light);top:-100px;right:-80px;opacity:0.6;"></div> <div class="deco-circle" style="width:400px;height:400px;background:var(--amber-light);top:-100px;right:-80px;opacity:0.6;"></div>
@@ -726,7 +735,6 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
<div class="hero-label">Certibiocide — Travaux dirigés</div> <div class="hero-label">Certibiocide — Travaux dirigés</div>
<div class="hero-title">FDS, FT<br>& Étiquettes</div> <div class="hero-title">FDS, FT<br>& Étiquettes</div>
<p class="hero-sub">Maîtrisez les trois documents essentiels des produits biocides : fiche de sécurité, fiche technique et étiquetage réglementaire.</p> <p class="hero-sub">Maîtrisez les trois documents essentiels des produits biocides : fiche de sécurité, fiche technique et étiquetage réglementaire.</p>
<button class="btn btn-amber" onclick="goTo(1)">Démarrer le TD →</button>
</div> </div>
</div> </div>
@@ -736,6 +744,10 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
<div class="section-tag">Déroulé du TD</div> <div class="section-tag">Déroulé du TD</div>
<div class="slide-title">Trois documents, trois usages</div> <div class="slide-title">Trois documents, trois usages</div>
<p class="slide-intro">Vous allez apprendre à localiser une information dans chacun des documents de référence d'un produit biocide.</p> <p class="slide-intro">Vous allez apprendre à localiser une information dans chacun des documents de référence d'un produit biocide.</p>
<div class="info-card">
<span>⏱️</span>
<div>Durée ≈ 30 min. Pour chaque question, sélectionnez la <strong>zone du document</strong> où trouver l'information, puis vérifiez votre score.</div>
</div>
<div class="overview-grid"> <div class="overview-grid">
<div class="overview-card"> <div class="overview-card">
<div class="overview-icon">📄</div> <div class="overview-icon">📄</div>
@@ -753,10 +765,6 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
<div class="overview-sub">Interface directe avec l'utilisateur</div> <div class="overview-sub">Interface directe avec l'utilisateur</div>
</div> </div>
</div> </div>
<div class="info-card">
<span>⏱️</span>
<div>Durée ≈ 30 min. Pour chaque question, sélectionnez la <strong>zone du document</strong> où trouver l'information, puis vérifiez votre score.</div>
</div>
<div class="identity-card"> <div class="identity-card">
<div class="identity-title">👥 Identification du groupe</div> <div class="identity-title">👥 Identification du groupe</div>
<div class="identity-fields"> <div class="identity-fields">
@@ -771,7 +779,6 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
</div> </div>
<div class="identity-error" id="idGroupe-error" hidden>⚠️ Renseignez le nom du stagiaire ou du groupe pour continuer.</div> <div class="identity-error" id="idGroupe-error" hidden>⚠️ Renseignez le nom du stagiaire ou du groupe pour continuer.</div>
</div> </div>
<button class="btn btn-amber" onclick="startPart1()" style="margin-top:1.5rem;">Commencer la partie 1 →</button>
</div> </div>
<!-- ============ SLIDE 2 — FDS INTRO ============ --> <!-- ============ SLIDE 2 — FDS INTRO ============ -->
@@ -784,7 +791,6 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
<span>🎯</span> <span>🎯</span>
<div>Pour chaque question, indiquez dans quelle <strong>rubrique (1 à 16)</strong> trouver la réponse — ou « Absent » si l'information n'y figure pas. Les questions sont mélangées à chaque chargement.</div> <div>Pour chaque question, indiquez dans quelle <strong>rubrique (1 à 16)</strong> trouver la réponse — ou « Absent » si l'information n'y figure pas. Les questions sont mélangées à chaque chargement.</div>
</div> </div>
<button class="btn btn-amber" onclick="goTo(3)" style="margin-top:1.5rem;">Répondre aux questions →</button>
</div> </div>
<!-- ============ SLIDE 3 — FDS QUIZ (DRAG & DROP) ============ --> <!-- ============ SLIDE 3 — FDS QUIZ (DRAG & DROP) ============ -->
@@ -796,12 +802,7 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
<div class="quiz-list quiz-grid" id="fds-questions"></div> <div class="quiz-list quiz-grid" id="fds-questions"></div>
<div class="dnd-bank" id="fds-bank-right"></div> <div class="dnd-bank" id="fds-bank-right"></div>
</div> </div>
<button class="verify-btn btn-amber" id="fds-verify" onclick="verifyFDS()" disabled>✓ Vérifier mes réponses</button>
<div id="fds-result" class="result-message"></div> <div id="fds-result" class="result-message"></div>
<div class="actions-row">
<button class="btn btn-outline" onclick="resetFDS()">↺ Recommencer</button>
<button class="btn btn-coral" onclick="goTo(4)">Passer à la partie 2 →</button>
</div>
</div> </div>
<!-- ============ SLIDE 4 — FT INTRO ============ --> <!-- ============ SLIDE 4 — FT INTRO ============ -->
@@ -814,7 +815,6 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
<span>🎯</span> <span>🎯</span>
<div>Indiquez la <strong>section</strong> de la FT où trouver chaque information : Description, Propriétés biocides, Conditionnements, Utilisations — ou « Absent ».</div> <div>Indiquez la <strong>section</strong> de la FT où trouver chaque information : Description, Propriétés biocides, Conditionnements, Utilisations — ou « Absent ».</div>
</div> </div>
<button class="btn btn-coral" onclick="goTo(5)" style="margin-top:1.5rem;">Répondre aux questions →</button>
</div> </div>
<!-- ============ SLIDE 5 — FT : RELIER ============ --> <!-- ============ SLIDE 5 — FT : RELIER ============ -->
@@ -827,12 +827,7 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
<div class="match-col match-left" id="ft-left"></div> <div class="match-col match-left" id="ft-left"></div>
<div class="match-col match-right" id="ft-right"></div> <div class="match-col match-right" id="ft-right"></div>
</div> </div>
<button class="verify-btn btn-coral" id="ft-verify" onclick="verifyFT()" disabled>✓ Vérifier mes réponses</button>
<div id="ft-result" class="result-message"></div> <div id="ft-result" class="result-message"></div>
<div class="actions-row">
<button class="btn btn-outline" onclick="resetFT()">↺ Recommencer</button>
<button class="btn btn-teal" onclick="goTo(6)">Passer à la partie 3 →</button>
</div>
</div> </div>
</div> </div>
@@ -846,7 +841,6 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
<span>🎯</span> <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. Chaque clic ouvre la <strong>justification réglementaire</strong> de la zone.</div>
</div> </div>
<button class="btn btn-teal" onclick="goTo(7)" style="margin-top:1.5rem;">Répondre aux questions →</button>
</div> </div>
<!-- ============ SLIDE 7 — ÉTIQUETTE CLIQUABLE (HOTSPOTS) ============ --> <!-- ============ SLIDE 7 — ÉTIQUETTE CLIQUABLE (HOTSPOTS) ============ -->
@@ -911,9 +905,9 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
<div class="lbl-picto-wrap"> <div class="lbl-picto-wrap">
<span class="hotspot" data-zone="z-picto"> <span class="hotspot" data-zone="z-picto">
<span class="pictos"> <span class="pictos">
<span class="ghs"><span>🐟</span></span> <img class="ghs-img" src="../data/ghs09-environnement.jpg" alt="Pictogramme SGH09 — danger pour le milieu aquatique">
<span class="ghs"><span>🧪</span></span> <img class="ghs-img" src="../data/ghs05-corrosif.jpg" alt="Pictogramme SGH05 — corrosif">
<span class="ghs excl"><span>!</span></span> <img class="ghs-img" src="../data/ghs07-nocif.jpg" alt="Pictogramme SGH07 — nocif / irritant">
</span> </span>
</span> </span>
<span class="hotspot" data-zone="z-mention"><span class="lbl-danger">DANGER</span></span> <span class="hotspot" data-zone="z-mention"><span class="lbl-danger">DANGER</span></span>
@@ -923,21 +917,9 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
<div class="lbl-footer">FHER — 2, rue de Sèze, 75009 Paris — Tél. 01 23 45 67 89</div> <div class="lbl-footer">FHER — 2, rue de Sèze, 75009 Paris — Tél. 01 23 45 67 89</div>
</div> </div>
<div class="eti-side-hint" id="eti-side-hint">👉 Cliquez une zone de l'étiquette : sa justification réglementaire s'affiche ici, à côté du bidon.</div>
<div class="eti-pop" id="eti-pop" hidden>
<button class="pop-close" onclick="closeEtiPop()" aria-label="Fermer">×</button>
<div class="pop-status" id="pop-status"></div>
<div class="pop-title" id="pop-title"></div>
<div class="pop-reg" id="pop-reg"></div>
</div>
</div> </div>
<div id="etiquette-result" class="result-message"></div> <div id="etiquette-result" class="result-message"></div>
<div class="actions-row">
<button class="btn btn-outline" onclick="resetEtiquette()">↺ Recommencer</button>
<button class="btn btn-teal" id="eti-recap-btn" onclick="showEtiRecap()" disabled>Voir le récapitulatif →</button>
</div>
</div> </div>
<!-- ---- PANNEAU B : synthèse / check-list ---- --> <!-- ---- PANNEAU B : synthèse / check-list ---- -->
@@ -950,11 +932,6 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
<div class="eti-stage" id="recap-stage"></div> <div class="eti-stage" id="recap-stage"></div>
<table class="checklist-table" id="checklist-table"></table> <table class="checklist-table" id="checklist-table"></table>
</div> </div>
<div class="actions-row">
<button class="btn btn-outline" onclick="hideEtiRecap()">← Retour à l'exercice</button>
<button class="btn btn-amber" onclick="goTo(8)">Voir la synthèse générale →</button>
</div>
</div> </div>
</div> </div>
@@ -979,19 +956,10 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
<tr><td><strong>Étiquette</strong></td><td>Dangers essentiels, interface rapide</td><td>À chaque prise du produit</td><td>Tous les utilisateurs</td></tr> <tr><td><strong>Étiquette</strong></td><td>Dangers essentiels, interface rapide</td><td>À chaque prise du produit</td><td>Tous les utilisateurs</td></tr>
</tbody> </tbody>
</table> </table>
<ul class="tips-list">
<li><strong>FDS = Sécurité et dangers</strong> → à consulter AVANT manipulation.</li>
<li><strong>FT = Mode d'emploi et applications</strong> → à consulter pour l'utilisation.</li>
<li><strong>Étiquette = Interface rapide</strong> → lecture obligatoire avant ouverture du produit.</li>
<li>Les trois documents sont <strong>complémentaires</strong>, jamais redondants — chacun cible un besoin et un public différents.</li>
</ul>
<div class="actions-row"> <div class="actions-row">
<button class="btn btn-amber" onclick="exporterPDF()">⬇ Télécharger mes réponses (PDF)</button> <button class="btn btn-amber" onclick="exporterPDF()">⬇ Télécharger mes réponses (PDF)</button>
<a href="correction-fds.html" class="btn btn-outline">✔ Corriger un rendu</a> <a href="correction-fds.html" class="btn btn-outline">✔ Corriger un rendu</a>
</div>
<div class="actions-row" style="margin-top:0.5rem;">
<button class="btn btn-outline" onclick="restartAll()">↺ Recommencer</button> <button class="btn btn-outline" onclick="restartAll()">↺ Recommencer</button>
<a href="../index.html#formations" class="btn btn-outline">Retour aux exercices</a>
</div> </div>
</div> </div>
</div> </div>
@@ -1093,6 +1061,7 @@ let etiAnswers = {}; // { zoneCible : zoneCliquée en 1re tentative } — p
let etiFirstTry = {}; // { zoneCible : true si trouvée sans erreur } let etiFirstTry = {}; // { zoneCible : true si trouvée sans erreur }
let etiCurError = false; // une erreur a-t-elle eu lieu sur la question courante 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 etiInit = false; // écouteur de clic posé une seule fois
let etiRecapShown = false;// panneau récapitulatif affiché (slide 7)
function buildEtiquette() { function buildEtiquette() {
etiOrder = shuffleArray(etiZones.map((_, i) => i)); etiOrder = shuffleArray(etiZones.map((_, i) => i));
@@ -1107,9 +1076,10 @@ function buildEtiquette() {
closeEtiPop(); closeEtiPop();
const r = document.getElementById('etiquette-result'); const r = document.getElementById('etiquette-result');
r.className = 'result-message'; r.innerHTML = ''; 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-exercise').style.display = 'flex';
document.getElementById('eti-recap').style.display = 'none'; document.getElementById('eti-recap').style.display = 'none';
if (currentSlide === 7) renderBarActions();
if (!etiInit) { if (!etiInit) {
card.addEventListener('click', onEtiClick); card.addEventListener('click', onEtiClick);
@@ -1136,11 +1106,8 @@ function onEtiClick(e) {
if (!hs) return; if (!hs) return;
const clicked = hs.dataset.zone; const clicked = hs.dataset.zone;
// Exploration libre une fois l'exercice terminé // Exercice terminé : plus d'interaction (les corrections sont dans la correction générale)
if (etiPos >= etiOrder.length) { if (etiPos >= etiOrder.length) return;
openEtiPop(hs, 'info', clicked);
return;
}
const target = etiZones[etiOrder[etiPos]]; const target = etiZones[etiOrder[etiPos]];
if (clicked === target.zone) { if (clicked === target.zone) {
@@ -1148,7 +1115,6 @@ function onEtiClick(e) {
hs.classList.add('hs-correct'); hs.classList.add('hs-correct');
if (etiAnswers[target.zone] === undefined) etiAnswers[target.zone] = clicked; if (etiAnswers[target.zone] === undefined) etiAnswers[target.zone] = clicked;
etiFirstTry[target.zone] = !etiCurError; etiFirstTry[target.zone] = !etiCurError;
openEtiPop(hs, 'ok', clicked);
etiPos++; etiPos++;
etiCurError = false; etiCurError = false;
renderEtiQuestion(); renderEtiQuestion();
@@ -1160,14 +1126,12 @@ function onEtiClick(e) {
hs.classList.remove('hs-wrong'); void hs.offsetWidth; // relance l'animation hs.classList.remove('hs-wrong'); void hs.offsetWidth; // relance l'animation
hs.classList.add('hs-wrong'); hs.classList.add('hs-wrong');
setTimeout(() => hs.classList.remove('hs-wrong'), 700); setTimeout(() => hs.classList.remove('hs-wrong'), 700);
openEtiPop(hs, 'ko', clicked);
} }
} }
function finishEtiquette() { function finishEtiquette() {
const correct = etiZones.filter(z => etiFirstTry[z.zone]).length; const b = document.getElementById('eti-recap-btn');
showScore(document.getElementById('etiquette-result'), correct, etiZones.length); if (b) b.disabled = false;
document.getElementById('eti-recap-btn').disabled = false;
} }
/* Info-bulle réglementaire positionnée près de la zone cliquée */ /* 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-exercise').style.display = 'none';
document.getElementById('eti-recap').style.display = 'flex'; document.getElementById('eti-recap').style.display = 'flex';
etiRecapShown = true;
renderBarActions();
// Animation : l'étiquette glisse à gauche, le tableau apparaît à sa droite // Animation : l'étiquette glisse à gauche, le tableau apparaît à sa droite
const rl = document.getElementById('recap-layout'); const rl = document.getElementById('recap-layout');
@@ -1255,6 +1221,8 @@ function showEtiRecap() {
function hideEtiRecap() { function hideEtiRecap() {
document.getElementById('eti-recap').style.display = 'none'; document.getElementById('eti-recap').style.display = 'none';
document.getElementById('eti-exercise').style.display = 'flex'; document.getElementById('eti-exercise').style.display = 'flex';
etiRecapShown = false;
renderBarActions();
const sa = document.querySelector('#slide-7 .scroll-area'); const sa = document.querySelector('#slide-7 .scroll-area');
if (sa) sa.scrollTop = 0; if (sa) sa.scrollTop = 0;
} }
@@ -1296,7 +1264,7 @@ function buildFtMatch() {
initFtMatch(); initFtMatch();
document.getElementById('ft-svg').innerHTML = ''; document.getElementById('ft-svg').innerHTML = '';
document.getElementById('ft-verify').disabled = true; refreshFtState();
} }
function resetFT() { function resetFT() {
@@ -1354,7 +1322,8 @@ function refreshFtState() {
dot.closest('.match-item').classList.toggle('connected', used); dot.closest('.match-item').classList.toggle('connected', used);
}); });
const all = ftQuestions.every(q => ftConnections[q.id] != null); 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() { function initFtMatch() {
@@ -1474,7 +1443,8 @@ function initFdsDnd() {
function updateFdsGate() { function updateFdsGate() {
const slots = document.querySelectorAll('#slide-3 .drop-slot'); const slots = document.querySelectorAll('#slide-3 .drop-slot');
const all = Array.from(slots).every(s => (s.dataset.value || '') !== ''); 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) { function wireVerifyGate(containerId, btnId) {
@@ -1676,6 +1646,7 @@ function goTo(n) {
const sa = next.querySelector('.scroll-area'); const sa = next.querySelector('.scroll-area');
if (sa) sa.scrollTop = 0; if (sa) sa.scrollTop = 0;
updateUI(); updateUI();
if (n === 8) setTimeout(launchConfetti, 200);
} }
function goBack() { function goBack() {
@@ -1691,20 +1662,73 @@ function goBack() {
updateUI(); 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() { function updateUI() {
document.getElementById('progressFill').style.width = document.getElementById('progressFill').style.width =
currentSlide === 0 ? '0%' : ((currentSlide / totalSlides) * 100) + '%'; currentSlide === 0 ? '0%' : ((currentSlide / totalSlides) * 100) + '%';
document.getElementById('slideCounter').textContent = document.getElementById('slideCounter').textContent =
currentSlide === 0 ? '' : currentSlide + ' / ' + totalSlides; 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'; document.getElementById('backBtn').style.display = history.length > 0 ? 'block' : 'none';
renderBarActions();
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';
} }
function restartAll() { function restartAll() {
@@ -1719,6 +1743,79 @@ function restartAll() {
updateUI(); 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', () => { document.addEventListener('DOMContentLoaded', () => {
generateQuizzes(); generateQuizzes();
updateUI(); updateUI();

View File

@@ -141,7 +141,7 @@
<!-- Correction : page unique pour tous les exercices --> <!-- Correction : page unique pour tous les exercices -->
<div class="formation-card"> <div class="formation-card">
<div class="formation-header"> <div class="formation-header">
<div class="formation-icon">✔️</div> <div class="formation-icon"></div>
<h3 class="formation-title">Corriger un exercice</h3> <h3 class="formation-title">Corriger un exercice</h3>
<p class="formation-subtitle">Correction des rendus</p> <p class="formation-subtitle">Correction des rendus</p>
</div> </div>