diff --git a/exercices/Exo_BioCID.html b/exercices/Exo_BioCID.html
index b6da2a2..0c930f5 100644
--- a/exercices/Exo_BioCID.html
+++ b/exercices/Exo_BioCID.html
@@ -859,21 +859,116 @@ function exporterPDF() {
setTimeout(() => w.print(), 300);
}
-/* ======================== PROTECTION CONTRE LA PERTE DES RÉPONSES ======================== */
-/* Les réponses ne sont pas enregistrées : on avertit avant tout rechargement ou départ.
- Le texte de la boîte est imposé par le navigateur, il n'est pas personnalisable. */
+/* ======================== 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; }
+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 = `
+ 💾 Des réponses enregistrées${libelle} ont été retrouvées sur ce poste.
+
+
+
+ `;
+ document.body.appendChild(bar);
+
+ document.getElementById('btnRestaurer').onclick = () => { appliquerSauvegarde(data); bar.remove(); };
+ document.getElementById('btnRepartir').onclick = () => { effacerSauvegarde(); bar.remove(); };
+}
+
+proposerRestauration();
+
updateUI();