Files
Site_web/exercices/Exo_BioCID.html
gauthier.chombart b5f2eac842 Sauvegarde les réponses de BioCID et du TP Nuisibles
Les réponses saisies sont désormais enregistrées dans le navigateur au fil
de la frappe, sous une clé propre à chaque exercice. Un rechargement ne les
perd plus.

Au chargement, rien n'est restauré d'office : un bandeau signale les
réponses retrouvées avec leur date et propose « Restaurer » ou « Repartir
de zéro ». Sur un poste de formation partagé, un nouveau groupe ne démarre
donc jamais sur le travail du précédent.

Sont restaurés les champs d'identification, les réponses, la position dans
le diaporama et, pour le TP, le tableau de risques (lignes ajoutées
recréées avant remplissage), le cas pratique choisi et le formateur.

L'avertissement avant rechargement est conservé comme second filet. Les
accès à localStorage sont protégés : en navigation privée ou en file://
sous Safari, la page fonctionne normalement, sans sauvegarde.

FDS reste à traiter : ses réponses ne vivent pas dans des champs de
formulaire mais dans l'état des zones de dépôt, des liaisons et de
l'étiquette, ce qui demande une logique de réaffichage dédiée.
2026-07-20 20:44:47 +02:00

976 lines
35 KiB
HTML

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Exercices Pratiques BioCID — Gauthier Chombart & Nathan Chauwin</title>
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../header-style.css"/>
<style>
/* SLIDES */
.slide {
position: absolute;
inset: 0;
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem;
padding-top: calc(70px + 2rem);
padding-bottom: calc(84px + 2rem);
opacity: 0;
transition: opacity 0.4s ease;
overflow-y: auto;
min-height: calc(100vh - 70px);
}
.slide.active { display: flex; opacity: 1; }
.slide.fade-in { animation: fadeIn 0.5s ease forwards; }
@keyframes fadeIn {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
.progress-fill { background: var(--amber); transition: width 0.4s ease; }
/* BOTTOM BAR */
.bottom-bar {
position: fixed;
bottom: 0; left: 0; right: 0;
height: 84px;
background: rgba(250, 238, 218, 0.98);
border-top: 2px solid var(--amber);
display: flex;
align-items: center;
justify-content: center;
padding: 0 20px;
z-index: 100;
backdrop-filter: blur(10px);
}
.bottom-actions {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
justify-content: center;
}
.footer-btn {
font-family: 'Syne', sans-serif;
font-size: 13px;
font-weight: 600;
padding: 11px 24px;
border-radius: 40px;
border: 1.5px solid var(--amber);
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 6px;
line-height: 1;
transition: all 0.2s;
}
.footer-btn:hover { transform: translateY(-1px); }
.footer-btn:active { transform: scale(0.98); }
.footer-btn.is-primary { background: var(--amber); color: #fff; }
.footer-btn.is-primary:hover { background: var(--amber-dark); }
.footer-btn.is-secondary { background: transparent; color: var(--amber-dark); }
.footer-btn.is-secondary:hover { background: rgba(186,117,23,0.08); }
.slide-counter {
position: absolute;
left: 20px;
font-size: 12px;
font-family: 'Syne', sans-serif;
font-weight: 600;
color: var(--amber-dark);
}
@media (max-width: 560px) {
.slide-counter { display: none; }
}
.back-btn {
background: none;
border: 0.5px solid var(--amber);
border-radius: 20px;
padding: 6px 16px;
font-size: 12px;
color: var(--amber-dark);
cursor: pointer;
transition: all 0.2s;
font-family: 'DM Sans', sans-serif;
}
.back-btn:hover { background: rgba(186,117,23,0.08); }
/* BUTTONS */
.btn {
font-family: 'Syne', sans-serif;
font-size: 14px;
font-weight: 600;
padding: 12px 28px;
border-radius: 40px;
border: none;
cursor: pointer;
transition: all 0.2s;
display: inline-flex;
align-items: center;
gap: 8px;
text-decoration: none;
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: scale(0.98); }
.btn-amber { background: var(--amber); color: white; }
.btn-outline { background: transparent; border: 1.5px solid var(--amber); color: var(--amber-dark); }
.hint-btn {
font-family: 'DM Sans', sans-serif;
font-size: 13px;
background: var(--amber-light);
color: var(--amber-dark);
border: 1px solid rgba(186,117,23,0.2);
border-radius: 20px;
padding: 6px 16px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 6px;
transition: all 0.2s;
}
.hint-btn:hover { background: #F5DFB8; }
/* QUESTIONS / TASKS */
.questions-list {
list-style: none;
width: 100%;
max-width: 680px;
display: flex;
flex-direction: column;
gap: 10px;
padding: 0;
}
.q-item {
background: white;
border: 0.5px solid var(--border);
border-radius: 12px;
padding: 14px 18px;
font-size: 14px;
line-height: 1.5;
display: flex;
flex-direction: column;
align-items: stretch;
gap: 8px;
}
.q-item .q-head { display: flex; align-items: flex-start; gap: 12px; }
.q-num {
font-family: 'Syne', sans-serif;
font-size: 11px;
font-weight: 800;
min-width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-top: 1px;
background: var(--amber-light);
color: var(--amber-dark);
}
.answer-field {
width: 100%;
font-family: 'DM Sans', sans-serif;
font-size: 13.5px;
color: var(--text);
background: var(--bg);
border: 1px solid var(--border);
border-radius: 10px;
padding: 10px 14px;
resize: vertical;
min-height: 56px;
line-height: 1.5;
transition: border-color 0.2s, background 0.2s;
}
.answer-field:focus { outline: none; border-color: var(--amber); background: white; }
/* HERO / TITLES */
.hero-label {
font-family: 'Syne', sans-serif;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--amber);
margin-bottom: 8px;
}
.hero-title {
font-family: 'Syne', sans-serif;
font-size: clamp(34px, 6vw, 64px);
font-weight: 800;
line-height: 1.0;
color: var(--text);
margin-bottom: 12px;
}
.hero-sub {
font-size: 16px;
color: var(--text-muted);
max-width: 460px;
text-align: center;
line-height: 1.6;
margin-bottom: 2rem;
}
.section-tag {
font-family: 'Syne', sans-serif;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
padding: 4px 12px;
border-radius: 20px;
margin-bottom: 10px;
display: inline-block;
background: var(--amber-light);
color: var(--amber-dark);
}
.slide-title {
font-family: 'Syne', sans-serif;
font-size: clamp(22px, 3.5vw, 34px);
font-weight: 800;
line-height: 1.15;
margin-bottom: 0.5rem;
text-align: center;
}
.slide-intro {
font-size: 14px;
color: var(--text-muted);
text-align: center;
max-width: 560px;
line-height: 1.6;
margin-bottom: 1.5rem;
}
.actions-row {
display: flex;
gap: 12px;
align-items: center;
margin-top: 1.5rem;
flex-wrap: wrap;
justify-content: center;
}
.steps-row { display: flex; gap: 8px; margin-bottom: 1.5rem; flex-wrap: wrap; justify-content: center; }
.step-pill {
font-size: 12px; padding: 4px 14px; border-radius: 20px;
border: 1px solid var(--border); color: var(--text-muted); background: white;
}
.step-pill.active-pill { border-color: var(--amber); color: var(--amber-dark); font-weight: 600; }
/* RESOURCE / CARDS */
.resource-card {
background: white;
border: 0.5px solid var(--border);
border-radius: 12px;
padding: 14px 18px;
max-width: 560px;
width: 100%;
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 1rem;
}
.resource-icon { font-size: 24px; }
.resource-body { flex: 1; }
.resource-title { font-size: 13px; font-weight: 500; }
.resource-sub { font-size: 12px; color: var(--text-muted); }
.resource-link { font-size: 12px; color: var(--amber); text-decoration: none; font-weight: 500; }
.resource-link:hover { text-decoration: underline; }
.info-card {
background: var(--amber-light);
border-radius: 12px;
padding: 16px 20px;
max-width: 620px;
width: 100%;
font-size: 14px;
color: var(--amber-dark);
line-height: 1.6;
display: flex;
gap: 10px;
align-items: flex-start;
}
.overview-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 14px;
max-width: 640px;
width: 100%;
margin: 1.5rem 0;
}
.overview-card {
background: white;
border: 0.5px solid var(--border);
border-radius: 14px;
padding: 1.25rem;
text-align: center;
}
.overview-icon { font-size: 28px; margin-bottom: 8px; }
.overview-title { font-family: 'Syne', sans-serif; font-size: 13px; font-weight: 700; margin-bottom: 4px; }
.overview-sub { font-size: 12px; color: var(--text-muted); }
/* TIPS LIST */
.tips-list { list-style: none; padding: 0; max-width: 620px; width: 100%; display: flex; flex-direction: column; gap: 8px; }
.tips-list li {
background: white;
border: 0.5px solid var(--border);
border-radius: 10px;
padding: 12px 16px 12px 40px;
position: relative;
font-size: 13.5px;
line-height: 1.5;
color: var(--text-muted);
}
.tips-list li::before {
content: '✓';
position: absolute;
left: 16px;
color: var(--amber);
font-weight: bold;
}
.tips-list strong { color: var(--text); }
/* MODAL */
.modal-overlay {
position: fixed; inset: 0;
background: rgba(0,0,0,0.45);
display: none; align-items: center; justify-content: center;
z-index: 200; padding: 2rem;
}
.modal-overlay.open { display: flex; }
.modal {
background: white; border-radius: 20px; padding: 2rem;
max-width: 480px; width: 100%; position: relative;
animation: modalIn 0.3s ease;
}
@keyframes modalIn {
from { opacity: 0; transform: scale(0.92) translateY(12px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
.modal-close {
position: absolute; top: 16px; right: 16px;
background: var(--bg); border: none;
width: 28px; height: 28px; border-radius: 50%;
cursor: pointer; font-size: 16px;
display: flex; align-items: center; justify-content: center;
color: var(--text-muted);
}
.modal-icon { font-size: 28px; margin-bottom: 12px; }
.modal-title { font-family: 'Syne', sans-serif; font-size: 16px; font-weight: 700; margin-bottom: 10px; }
.modal-body { font-size: 14px; line-height: 1.7; color: var(--text-muted); }
.modal-body strong { color: var(--text); }
/* SCROLL AREA */
.scroll-area {
overflow-y: auto;
max-height: calc(100vh - 200px);
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
padding: 0.5rem 0 4rem;
}
.scroll-area::-webkit-scrollbar { width: 4px; }
.scroll-area::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
/* IDENTITY CARD */
.identity-card {
background: white;
border: 0.5px solid var(--border);
border-radius: 14px;
padding: 16px 18px;
max-width: 560px;
width: 100%;
margin: 0.5rem 0;
}
.identity-card .identity-title {
font-family: 'Syne', sans-serif;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--amber-dark);
margin-bottom: 10px;
display: flex; align-items: center; gap: 6px;
}
.identity-fields { display: flex; flex-direction: column; gap: 8px; }
.identity-fields input {
font-family: 'DM Sans', sans-serif;
font-size: 13.5px;
color: var(--text);
background: var(--bg);
border: 1px solid var(--border);
border-radius: 10px;
padding: 9px 13px;
width: 100%;
box-sizing: border-box;
}
.identity-fields input:focus { outline: none; border-color: var(--amber); background: white; }
/* DECO */
.deco-circle { position: fixed; border-radius: 50%; pointer-events: none; z-index: 0; }
.slide > * { position: relative; z-index: 1; }
@media (max-width: 640px) {
.overview-grid { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<!-- HEADER -->
<header>
<div class="logo">Gauthier Chombart & Nathan Chauwin</div>
<nav>
<a href="../index.html">Accueil</a>
<a href="../index.html#formations">Exercices</a>
<a href="../index.html#documentation">Documentation</a>
<a href="../index.html#rapports">Rapports</a>
<a href="../index.html#about">À propos</a>
<a href="../index.html#contact">Contact</a>
</nav>
</header>
<!-- Progress bar -->
<div class="progress-bar"><div class="progress-fill" id="progressFill"></div></div>
<!-- Bottom bar -->
<div class="bottom-bar" id="bottomBar" style="display:none;">
<div class="slide-counter" id="slideCounter"></div>
<div class="bottom-actions">
<button class="footer-btn is-secondary" id="backBtn" onclick="goBack()" style="display:none;">← Retour</button>
<button class="footer-btn is-secondary" id="footerHintBtn" onclick="showHintCurrent()" style="display:none;">💡 Voir les indications</button>
<button class="footer-btn is-primary" id="footerNextBtn" onclick="goNext()" style="display:none;">Exercice suivant →</button>
</div>
</div>
<!-- Confetti -->
<canvas id="confettiCanvas" style="position:fixed;inset:0;pointer-events:none;z-index:150;display:none;"></canvas>
<!-- Modal -->
<div class="modal-overlay" id="modalOverlay" onclick="closeModal(event)">
<div class="modal" id="modal">
<button class="modal-close" onclick="closeModal()"></button>
<div class="modal-icon" id="modalIcon"></div>
<div class="modal-title" id="modalTitle"></div>
<div class="modal-body" id="modalBody"></div>
</div>
</div>
<!-- ============ SLIDE 0 — ACCUEIL ============ -->
<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:200px;height:200px;background:var(--amber-light);bottom:60px;left:-60px;opacity:0.5;"></div>
<div style="text-align:center;">
<div class="hero-label">Certibiocide — Travaux pratiques</div>
<div class="hero-title">Plateforme<br>BioCID</div>
<p class="hero-sub">Maîtrisez la plateforme officielle de l'ANSES pour consulter les produits biocides autorisés et déclarés en France.</p>
<button class="btn btn-amber" onclick="goTo(1)">Démarrer l'exercice →</button>
</div>
</div>
<!-- ============ SLIDE 1 — CONTEXTE & ACCÈS ============ -->
<div class="slide" id="slide-1">
<div class="deco-circle" style="width:300px;height:300px;background:var(--amber-light);top:-60px;left:-60px;opacity:0.4;"></div>
<div class="section-tag">Avant de commencer</div>
<div class="slide-title">Contexte & accès</div>
<p class="slide-intro">BioCID est la plateforme officielle de l'ANSES pour consulter tous les produits biocides autorisés et déclarés en France. Cet exercice développe votre autonomie dans la consultation des produits.</p>
<div class="info-card" style="max-width:620px;">
<span>🎯</span>
<div>4 exercices vous attendent : recherche par <strong>nom</strong>, par <strong>type</strong>, par <strong>substance active</strong>, puis un <strong>cas de synthèse</strong>. Durée totale ≈ 30 min.</div>
</div>
<div class="resource-card" style="max-width:620px;margin-top:0.5rem;">
<div class="resource-icon">🌐</div>
<div class="resource-body">
<div class="resource-title">Plateforme BioCID — ANSES</div>
<div class="resource-sub">Cliquez sur « Rechercher un produit » pour accéder à la base consultable</div>
</div>
<a href="https://biocid-anses.fr/biocid" target="_blank" class="resource-link">Ouvrir ↗</a>
</div>
<div class="identity-card" style="max-width:620px;">
<div class="identity-title">👥 Identification du groupe</div>
<div class="identity-fields">
<input type="text" id="idGroupe" placeholder="Nom du groupe / stagiaires (ex : Groupe 3 — Dupont / Martin)" autocomplete="off" oninput="clearGroupeWarning()">
<input type="date" id="idDate">
<input type="text" id="idFormateur" placeholder="Formateur (facultatif)" autocomplete="off">
</div>
<div id="idWarning" style="display:none;margin-top:8px;font-size:12.5px;color:var(--coral);">⚠ Renseignez le nom du groupe pour continuer.</div>
</div>
</div>
<!-- ============ SLIDE 2 — EXERCICE 1 ============ -->
<div class="slide" id="slide-2">
<div class="scroll-area">
<div class="section-tag">Exercice 1 / 4 — 5 min</div>
<div class="slide-title">Recherche par nom de produit</div>
<p class="slide-intro">Trouvez un produit spécifique et relevez ses caractéristiques principales.</p>
<ul class="questions-list">
<li class="q-item">
<div class="q-head"><span class="q-num">1</span><span>Recherchez un produit connu (ex. « Goliath », « Phobi », « Soricide »). Notez son <strong>type de produit</strong>, sa <strong>substance active</strong> et son <strong>fabricant</strong>.</span></div>
<textarea class="answer-field" id="bio_ex1_1" placeholder="Votre réponse…" data-question="Ex1 — Produit : type, substance active, fabricant"></textarea>
</li>
<li class="q-item">
<div class="q-head"><span class="q-num">2</span><span>Vérifiez la <strong>date d'autorisation</strong> du produit.</span></div>
<textarea class="answer-field" id="bio_ex1_2" placeholder="Votre réponse…" data-question="Ex1 — Date d'autorisation"></textarea>
</li>
<li class="q-item">
<div class="q-head"><span class="q-num">3</span><span>Observez les <strong>conditions d'utilisation</strong> mentionnées (usage professionnel / grand public).</span></div>
<textarea class="answer-field" id="bio_ex1_3" placeholder="Votre réponse…" data-question="Ex1 — Conditions d'utilisation"></textarea>
</li>
</ul>
</div>
</div>
<!-- ============ SLIDE 3 — EXERCICE 2 ============ -->
<div class="slide" id="slide-3">
<div class="scroll-area">
<div class="section-tag">Exercice 2 / 4 — 5 min</div>
<div class="slide-title">Recherche par type de produit</div>
<p class="slide-intro">Explorez les catégories disponibles à l'aide des filtres par TP.</p>
<ul class="questions-list">
<li class="q-item">
<div class="q-head"><span class="q-num">1</span><span>Utilisez le filtre « Type de produit » et sélectionnez « Rodenticides (TP14) » ou « Insecticides (TP18) ». Combien de produits sont disponibles ?</span></div>
<textarea class="answer-field" id="bio_ex2_1" placeholder="Votre réponse…" data-question="Ex2 — Nombre de produits dans la catégorie"></textarea>
</li>
<li class="q-item">
<div class="q-head"><span class="q-num">2</span><span>Comparez avec une autre catégorie (ex. Désinfectants TP2 / TP3 / TP4). Que remarquez-vous ?</span></div>
<textarea class="answer-field" id="bio_ex2_2" placeholder="Votre réponse…" data-question="Ex2 — Comparaison entre catégories"></textarea>
</li>
</ul>
</div>
</div>
<!-- ============ SLIDE 4 — EXERCICE 3 ============ -->
<div class="slide" id="slide-4">
<div class="scroll-area">
<div class="section-tag">Exercice 3 / 4 — 5 min</div>
<div class="slide-title">Recherche par substance active</div>
<p class="slide-intro">Trouvez tous les produits partageant une même substance active.</p>
<ul class="questions-list">
<li class="q-item">
<div class="q-head"><span class="q-num">1</span><span>Recherchez une substance active (ex. « Brodifacoum », « Perméthrine ») et listez au minimum <strong>3 produits</strong> différents qui la contiennent.</span></div>
<textarea class="answer-field" id="bio_ex3_1" placeholder="Votre réponse…" data-question="Ex3 — 3 produits contenant la substance"></textarea>
</li>
<li class="q-item">
<div class="q-head"><span class="q-num">2</span><span>Différents fabricants proposent-ils des formulations variées ? Comparez les <strong>concentrations</strong> d'un produit à l'autre.</span></div>
<textarea class="answer-field" id="bio_ex3_2" placeholder="Votre réponse…" data-question="Ex3 — Formulations et concentrations comparées"></textarea>
</li>
</ul>
</div>
</div>
<!-- ============ SLIDE 5 — EXERCICE 4 (CAS DE SYNTHÈSE) ============ -->
<div class="slide" id="slide-5">
<div class="scroll-area">
<div class="section-tag" style="background:var(--coral-light);color:var(--coral);">Exercice 4 / 4 — Cas de synthèse</div>
<div class="slide-title">Mise en situation</div>
<div class="info-card" style="background:var(--coral-light);color:var(--coral);margin-bottom:1rem;">
<span>📞</span>
<div>Un restaurant vous appelle au sujet de votre prestation et souhaite choisir le produit entre plusieurs références. En utilisant la plateforme BioCID, présentez une <strong>recommandation argumentée</strong>.</div>
</div>
<ul class="questions-list">
<li class="q-item">
<div class="q-head"><span class="q-num" style="background:var(--coral-light);color:var(--coral);">1</span><span>Filtrez par type : « Désinfectants (TP2/TP3/TP4) » ou « Lutte contre les nuisibles (TP14/TP18/TP20) ». Identifiez au minimum <strong>3 produits applicables</strong>.</span></div>
<textarea class="answer-field" id="bio_ex4_1" placeholder="Votre réponse…" data-question="Ex4 — 3 produits applicables"></textarea>
</li>
<li class="q-item">
<div class="q-head"><span class="q-num" style="background:var(--coral-light);color:var(--coral);">2</span><span>Pour chacun, relevez : <strong>nom, fabricant, substance active, usage spécifique</strong>.</span></div>
<textarea class="answer-field" id="bio_ex4_2" placeholder="Votre réponse…" data-question="Ex4 — Fiche de chaque produit"></textarea>
</li>
<li class="q-item">
<div class="q-head"><span class="q-num" style="background:var(--coral-light);color:var(--coral);">3</span><span>Proposez un court <strong>argumentaire</strong> pour chaque produit. <em>Bonus :</em> recherchez les FDS associées.</span></div>
<textarea class="answer-field" id="bio_ex4_3" placeholder="Votre réponse…" data-question="Ex4 — Argumentaire (+ bonus FDS)"></textarea>
</li>
</ul>
</div>
</div>
<!-- ============ SLIDE 6 — ASTUCES / FIN ============ -->
<div class="slide" id="slide-6">
<div class="scroll-area">
<div class="deco-circle" style="width:320px;height:320px;background:var(--amber-light);bottom:-80px;right:-60px;opacity:0.5;"></div>
<div style="text-align:center;">
<div style="font-size:44px;margin-bottom:0.5rem;">🎉</div>
<div class="section-tag">Exercice terminé</div>
<div class="slide-title" style="margin-top:0.4rem;">Astuces & bonnes pratiques</div>
</div>
<ul class="tips-list" style="margin-top:1.25rem;">
<li>Combinez plusieurs filtres pour affiner (<strong>type + substance + fabricant</strong>).</li>
<li>Consultez toujours les <strong>fiches de sécurité (FDS)</strong> avant toute recommandation.</li>
<li>Vérifiez les conditions d'utilisation : <strong>usage professionnel vs. domestique</strong>.</li>
<li>BioCID est mis à jour régulièrement (nouvelles autorisations et retraits).</li>
<li>En cas de doute, contactez l'ANSES ou consultez un expert en biocides.</li>
<li>Notez toujours les références exactes pour la traçabilité (<strong>numéro d'AMM</strong>).</li>
</ul>
<div class="actions-row">
<button class="btn btn-amber" onclick="exporterPDF()">⬇ Télécharger mes réponses (PDF)</button>
<a href="corriger-exercice.html?exo=biocid" class="btn btn-outline">✔ Corriger un rendu</a>
<button class="btn btn-outline" onclick="restartAll()">↺ Recommencer</button>
</div>
</div>
</div>
<script>
let currentSlide = 0;
let history = [];
const totalSlides = 6;
// Configuration des boutons du footer par slide
const slideConfig = {
1: { next: 2, nextLabel: "Commencer l'exercice 1 →" },
2: { hint: 'ex1', next: 3, nextLabel: 'Exercice suivant →' },
3: { hint: 'ex2', next: 4, nextLabel: 'Exercice suivant →' },
4: { hint: 'ex3', next: 5, nextLabel: 'Cas de synthèse →' },
5: { next: 6, nextLabel: "Terminer l'exercice →" }
};
const hints = {
ex1: {
icon: '🔎',
title: 'Indications — Recherche par nom',
body: `Cherchez dans la <strong>zone de texte principale</strong>. Les résultats affichent le nom du produit, le fabricant et la substance active.<br><br>C'est généralement le chemin le plus rapide pour localiser un produit connu.`
},
ex2: {
icon: '🗂️',
title: 'Indications — Recherche par type',
body: `Les filtres se trouvent dans un <strong>menu latéral</strong> ou en haut de la page. Les types de produits correspondent aux <strong>codes TP</strong> (Type Product).<br><br>Les rodenticides (TP14) et insecticides (TP18) sont souvent parmi les catégories les plus représentées.`
},
ex3: {
icon: '🧪',
title: 'Indications — Recherche par substance',
body: `Cherchez dans la zone <strong>« Substance active »</strong> si elle existe.<br><br>Une même substance active peut être formulée différemment selon les fabricants (poudre, granulé, liquide, etc.) et à des <strong>concentrations variables</strong>.`
}
};
function goTo(n) {
const cur = document.getElementById('slide-' + currentSlide);
const next = document.getElementById('slide-' + n);
if (!next) return;
history.push(currentSlide);
cur.classList.remove('active', 'fade-in');
cur.style.display = 'none';
currentSlide = n;
next.style.display = 'flex';
setTimeout(() => next.classList.add('active', 'fade-in'), 10);
const sa = next.querySelector('.scroll-area');
if (sa) sa.scrollTop = 0;
updateUI();
if (n === 6) setTimeout(launchConfetti, 200);
}
function goBack() {
if (history.length === 0) return;
const prev = history.pop();
const cur = document.getElementById('slide-' + currentSlide);
const target = document.getElementById('slide-' + prev);
cur.classList.remove('active', 'fade-in');
cur.style.display = 'none';
currentSlide = prev;
target.style.display = 'flex';
setTimeout(() => target.classList.add('active', 'fade-in'), 10);
updateUI();
}
function clearGroupeWarning() {
const g = document.getElementById('idGroupe');
if (g) g.style.borderColor = '';
const w = document.getElementById('idWarning');
if (w) w.style.display = 'none';
}
function goNext() {
const cfg = slideConfig[currentSlide];
if (!cfg || cfg.next == null) return;
// Slide 1 : le nom du groupe est obligatoire pour passer à l'exercice 1
if (currentSlide === 1) {
const g = document.getElementById('idGroupe');
if (!g.value.trim()) {
g.style.borderColor = 'var(--coral)';
document.getElementById('idWarning').style.display = 'block';
g.focus();
return;
}
}
goTo(cfg.next);
}
function showHintCurrent() {
const cfg = slideConfig[currentSlide];
if (cfg && cfg.hint) showHint(cfg.hint);
}
function updateUI() {
document.getElementById('progressFill').style.width =
currentSlide === 0 ? '0%' : ((currentSlide / totalSlides) * 100) + '%';
const counter = document.getElementById('slideCounter');
counter.textContent = currentSlide === 0 ? '' : currentSlide + ' / ' + totalSlides;
document.getElementById('bottomBar').style.display = currentSlide === 0 ? 'none' : 'flex';
document.getElementById('backBtn').style.display = history.length > 0 ? 'inline-flex' : 'none';
const cfg = slideConfig[currentSlide] || {};
const hintBtn = document.getElementById('footerHintBtn');
const nextBtn = document.getElementById('footerNextBtn');
hintBtn.style.display = cfg.hint ? 'inline-flex' : 'none';
if (cfg.next != null) {
nextBtn.style.display = 'inline-flex';
nextBtn.textContent = cfg.nextLabel;
} else {
nextBtn.style.display = 'none';
}
}
function showHint(key) {
const h = hints[key];
if (!h) return;
document.getElementById('modalIcon').textContent = h.icon;
document.getElementById('modalTitle').textContent = h.title;
document.getElementById('modalBody').innerHTML = h.body;
document.getElementById('modalOverlay').classList.add('open');
}
function closeModal(e) {
if (!e || e.target === document.getElementById('modalOverlay') || e.target.classList.contains('modal-close')) {
document.getElementById('modalOverlay').classList.remove('open');
}
}
document.addEventListener('keydown', e => { if (e.key === 'Escape') closeModal(); });
function restartAll() {
history = [];
document.querySelectorAll('.slide').forEach(s => { s.classList.remove('active', 'fade-in'); s.style.display = 'none'; });
currentSlide = 0;
const first = document.getElementById('slide-0');
first.style.display = 'flex';
setTimeout(() => first.classList.add('active', 'fade-in'), 10);
updateUI();
}
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);
}
function escapeHtml(str) {
if (!str) return '';
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
function idVal(id) {
const el = document.getElementById(id);
return el ? el.value.trim() : '';
}
function exporterPDF() {
const groupe = idVal('idGroupe');
const date = idVal('idDate') || new Date().toISOString().slice(0, 10);
const formateur = idVal('idFormateur');
const reponses = [];
document.querySelectorAll('.answer-field').forEach(el => {
reponses.push({ question: el.dataset.question || '', reponse: el.value.trim() });
});
const w = window.open('', '_blank');
const lignes = reponses.map(r =>
`<div style="margin-bottom:14px;"><strong>${escapeHtml(r.question)}</strong><p style="margin:4px 0 0;white-space:pre-wrap;">${escapeHtml(r.reponse) || '<em>(sans réponse)</em>'}</p></div>`
).join('');
w.document.write(`
<html><head><meta charset="utf-8"><title>Exercices BioCID — Réponses (${escapeHtml(groupe) || 'sans nom'})</title>
<style>
body { font-family: Arial, sans-serif; padding: 30px; color: #1a1a18; }
h1 { font-size: 20px; border-bottom: 2px solid #BA7517; padding-bottom: 8px; }
.meta { font-size: 13px; color: #555; margin: 2px 0; }
h2 { font-size: 15px; margin-top: 24px; color: #633806; }
</style></head>
<body>
<h1>Exercices Pratiques BioCID</h1>
<p class="meta"><strong>Groupe / Stagiaire :</strong> ${escapeHtml(groupe) || '—'}</p>
<p class="meta"><strong>Date de session :</strong> ${escapeHtml(date)}</p>
<p class="meta"><strong>Formateur :</strong> ${escapeHtml(formateur) || '—'}</p>
<h2>Réponses aux exercices</h2>
${lignes}
</body></html>`);
w.document.close();
setTimeout(() => w.print(), 300);
}
/* ======================== SAUVEGARDE AUTOMATIQUE ======================== */
/* Les réponses sont enregistrées dans le navigateur au fil de la saisie, puis
proposées à la restauration au rechargement. En file://, localStorage peut être
indisponible (Safari) : dans ce cas la page fonctionne normalement, sans filet. */
const CLE_SAUVEGARDE = 'certibiocide:biocid:v1';
let reponsesModifiees = false;
function marquerModifie() { reponsesModifiees = true; sauvegarder(); }
document.addEventListener('input', marquerModifie, true);
document.addEventListener('change', marquerModifie, true);
// Avertit avant tout rechargement ou départ. Le texte de la boîte est imposé
// par le navigateur, il n'est pas personnalisable.
window.addEventListener('beforeunload', e => {
if (!reponsesModifiees) return;
e.preventDefault();
e.returnValue = '';
});
function etatCourant() {
const champs = {};
document.querySelectorAll('input[id], textarea[id]').forEach(el => {
if (el.value) champs[el.id] = el.value;
});
return { savedAt: new Date().toISOString(), slide: currentSlide, champs };
}
let minuteurSauvegarde = null;
function sauvegarder() {
clearTimeout(minuteurSauvegarde);
minuteurSauvegarde = setTimeout(() => {
try {
localStorage.setItem(CLE_SAUVEGARDE, JSON.stringify(etatCourant()));
} catch (e) { /* navigation privée ou quota : on continue sans sauvegarde */ }
}, 400);
}
function lireSauvegarde() {
try { return JSON.parse(localStorage.getItem(CLE_SAUVEGARDE)); }
catch (e) { return null; }
}
function effacerSauvegarde() {
try { localStorage.removeItem(CLE_SAUVEGARDE); } catch (e) {}
}
function appliquerSauvegarde(data) {
Object.entries(data.champs || {}).forEach(([id, v]) => {
const el = document.getElementById(id);
if (el) el.value = v;
});
reponsesModifiees = true;
if (typeof data.slide === 'number' && data.slide > 0) goTo(data.slide);
updateUI();
}
/* Bandeau de restauration : on ne restaure jamais sans demander, pour qu'un
nouveau groupe n'hérite pas des réponses du précédent sur un poste partagé. */
function injecterStylesBandeau() {
const style = document.createElement('style');
style.textContent = `
.restore-bar {
position: fixed; top: 84px; left: 50%; transform: translateX(-50%);
z-index: 900; max-width: min(680px, calc(100vw - 32px));
display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
background: var(--white); border: 1.5px solid var(--amber);
border-radius: 14px; padding: 14px 18px;
box-shadow: 0 8px 28px rgba(0,0,0,0.12); font-size: 13.5px;
}
.restore-bar .restore-text { flex: 1; min-width: 220px; line-height: 1.5; }
.restore-bar .restore-actions { display: flex; gap: 8px; flex-shrink: 0; }
.restore-bar button {
font-family: 'Syne', sans-serif; font-weight: 700; font-size: 12.5px;
padding: 8px 16px; border-radius: 20px; cursor: pointer;
border: 1px solid var(--border); background: transparent; color: var(--text-muted);
}
.restore-bar button:hover { border-color: var(--amber); color: var(--amber); }
.restore-bar button.primary {
background: var(--amber); border-color: var(--amber); color: #fff;
}
.restore-bar button.primary:hover { background: var(--amber-dark); border-color: var(--amber-dark); color: #fff; }
`;
document.head.appendChild(style);
}
function proposerRestauration() {
const data = lireSauvegarde();
if (!data || !data.champs || Object.keys(data.champs).length === 0) return;
const quand = new Date(data.savedAt);
const libelle = isNaN(quand.getTime()) ? '' :
` du ${quand.toLocaleDateString('fr-FR')} à ${quand.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })}`;
injecterStylesBandeau();
const bar = document.createElement('div');
bar.className = 'restore-bar';
bar.innerHTML = `
<span class="restore-text">💾 Des réponses enregistrées${libelle} ont été retrouvées sur ce poste.</span>
<span class="restore-actions">
<button type="button" class="primary" id="btnRestaurer">Restaurer</button>
<button type="button" id="btnRepartir">Repartir de zéro</button>
</span>`;
document.body.appendChild(bar);
document.getElementById('btnRestaurer').onclick = () => { appliquerSauvegarde(data); bar.remove(); };
document.getElementById('btnRepartir').onclick = () => { effacerSauvegarde(); bar.remove(); };
}
proposerRestauration();
updateUI();
</script>
</body>
</html>