Ajoute une page unique de correction des exercices

- Nouvelle page corriger-exercice.html : sélection de l'exercice (BioCID, FDS, TP Nuisibles) + zone de dépôt PDF
- Le PDF déposé est transmis à la page de correction dédiée qui se charge automatiquement (?auto=1)
- Carte 'Corriger un exercice' ajoutée à la section Exercices de l'index
- Slide 6 BioCID : suppression du bouton biocid-anses.fr, alignement des boutons Télécharger / Corriger / Recommencer
- Le bouton 'Corriger un rendu' redirige vers la nouvelle page
This commit is contained in:
2026-07-19 17:08:59 +02:00
parent 66ed3c505f
commit d251782842
6 changed files with 385 additions and 4 deletions

View File

@@ -603,10 +603,7 @@
</ul>
<div class="actions-row">
<button class="btn btn-amber" onclick="exporterPDF()">⬇ Télécharger mes réponses (PDF)</button>
<a href="correction-biocid.html" class="btn btn-outline">✔ Corriger un rendu</a>
</div>
<div class="actions-row" style="margin-top:0.5rem;">
<a href="https://biocid-anses.fr/biocid" target="_blank" class="btn btn-outline">biocid-anses.fr ↗</a>
<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>

View File

@@ -502,6 +502,24 @@ dropzone.addEventListener("drop", e => {
if (file) handleFile(file);
});
document.getElementById("filePicker").addEventListener("change", e => handleFile(e.target.files?.[0]));
// Chargement automatique depuis la page « Corriger un exercice » (?auto=1)
(function autoLoadFromHub() {
const params = new URLSearchParams(location.search);
if (params.get("auto") !== "1") return;
const dataUrl = localStorage.getItem("correctionFile");
const name = localStorage.getItem("correctionFileName") || "rendu.pdf";
localStorage.removeItem("correctionFile");
localStorage.removeItem("correctionFileName");
localStorage.removeItem("correctionExo");
if (!dataUrl) return;
try {
const bin = atob(dataUrl.split(",").pop());
const bytes = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
handleFile(new File([bytes], name, { type: "application/pdf" }));
} catch (e) { console.error(e); }
})();
</script>
</body>
</html>

View File

@@ -599,6 +599,24 @@ dropzone.addEventListener("drop", e => {
if (file) handleFile(file);
});
document.getElementById("filePicker").addEventListener("change", e => handleFile(e.target.files?.[0]));
// Chargement automatique depuis la page « Corriger un exercice » (?auto=1)
(function autoLoadFromHub() {
const params = new URLSearchParams(location.search);
if (params.get("auto") !== "1") return;
const dataUrl = localStorage.getItem("correctionFile");
const name = localStorage.getItem("correctionFileName") || "rendu.pdf";
localStorage.removeItem("correctionFile");
localStorage.removeItem("correctionFileName");
localStorage.removeItem("correctionExo");
if (!dataUrl) return;
try {
const bin = atob(dataUrl.split(",").pop());
const bytes = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
handleFile(new File([bytes], name, { type: "application/pdf" }));
} catch (e) { console.error(e); }
})();
</script>
</body>
</html>

View File

@@ -645,6 +645,24 @@ dropzone.addEventListener("drop", e => {
if (file) handleFile(file);
});
document.getElementById("filePicker").addEventListener("change", e => handleFile(e.target.files?.[0]));
// Chargement automatique depuis la page « Corriger un exercice » (?auto=1)
(function autoLoadFromHub() {
const params = new URLSearchParams(location.search);
if (params.get("auto") !== "1") return;
const dataUrl = localStorage.getItem("correctionFile");
const name = localStorage.getItem("correctionFileName") || "rendu.pdf";
localStorage.removeItem("correctionFile");
localStorage.removeItem("correctionFileName");
localStorage.removeItem("correctionExo");
if (!dataUrl) return;
try {
const bin = atob(dataUrl.split(",").pop());
const bytes = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
handleFile(new File([bytes], name, { type: "application/pdf" }));
} catch (e) { console.error(e); }
})();
</script>
</body>
</html>

View File

@@ -0,0 +1,317 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Corriger un exercice — Certibiocide</title>
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@600;700;800&family=DM+Sans:ital,wght@0,400;0,500;1,400&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../header-style.css"/>
<style>
body {
padding: calc(36px + 70px) 20px 80px;
}
.wrap { max-width: 820px; margin: 0 auto; }
.eyebrow {
font-family: 'Syne', sans-serif;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--amber);
margin-bottom: 6px;
}
h1 {
font-family: 'Syne', sans-serif;
font-size: 28px;
font-weight: 800;
letter-spacing: -0.5px;
margin-bottom: 8px;
}
.lead { color: var(--text-muted); font-size: 14.5px; line-height: 1.6; margin-bottom: 28px; max-width: 620px; }
/* STEP LABEL */
.step-label {
font-family: 'Syne', sans-serif;
font-size: 13px;
font-weight: 700;
color: var(--text);
margin: 0 0 14px;
display: flex;
align-items: center;
gap: 10px;
}
.step-num {
width: 24px; height: 24px;
border-radius: 50%;
background: var(--amber);
color: #fff;
font-size: 12px;
display: flex; align-items: center; justify-content: center;
flex-shrink: 0;
}
/* EXERCISE SELECTOR */
.exo-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
gap: 12px;
margin-bottom: 36px;
}
.exo-option { position: relative; }
.exo-option input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.exo-option label {
display: flex;
align-items: center;
gap: 14px;
background: #fff;
border: 1.5px solid var(--border);
border-radius: 14px;
padding: 16px 18px;
cursor: pointer;
transition: border-color 0.15s, box-shadow 0.15s, background 0.15s;
height: 100%;
}
.exo-option label:hover { border-color: var(--amber); }
.exo-option input:checked + label {
border-color: var(--amber);
background: var(--amber-light);
box-shadow: 0 4px 16px rgba(186,117,23,0.12);
}
.exo-option input:focus-visible + label { outline: 2px solid var(--amber); outline-offset: 2px; }
.exo-icon {
width: 44px; height: 44px;
border-radius: 12px;
background: var(--amber-light);
display: flex; align-items: center; justify-content: center;
font-size: 22px;
flex-shrink: 0;
}
.exo-option input:checked + label .exo-icon { background: #fff; }
.exo-text { flex: 1; min-width: 0; }
.exo-name { display: block; font-family: 'Syne', sans-serif; font-weight: 700; font-size: 14.5px; }
.exo-sub { display: block; font-size: 12.5px; color: var(--text-muted); margin-top: 2px; }
.exo-check {
width: 22px; height: 22px;
border-radius: 50%;
border: 1.5px solid var(--border);
flex-shrink: 0;
display: flex; align-items: center; justify-content: center;
color: #fff;
font-size: 12px;
transition: background 0.15s, border-color 0.15s;
}
.exo-option input:checked + label .exo-check {
background: var(--amber);
border-color: var(--amber);
}
.exo-option input:checked + label .exo-check::after { content: '✓'; }
/* DROPZONE */
#dropzone {
background: #fff;
border: 2px dashed rgba(186,117,23,0.35);
border-radius: 16px;
padding: 56px 24px;
text-align: center;
cursor: pointer;
transition: border-color 0.2s, background 0.2s, opacity 0.2s;
}
#dropzone.drag { border-color: var(--amber); background: var(--amber-light); }
#dropzone.disabled { opacity: 0.5; cursor: not-allowed; pointer-events: none; }
#dropzone .icon { font-size: 40px; margin-bottom: 12px; }
#dropzone p.title { font-family: 'Syne', sans-serif; font-weight: 700; font-size: 16px; margin-bottom: 6px; }
#dropzone p.sub { color: var(--text-muted); font-size: 13.5px; margin-bottom: 20px; }
#filePicker { display: none; }
.btn-pick {
font-family: 'Syne', sans-serif;
display: inline-block;
background: var(--amber);
color: #fff;
font-weight: 700;
font-size: 13px;
padding: 10px 26px;
border-radius: 30px;
cursor: pointer;
border: none;
}
#status { margin-top: 16px; font-size: 13px; min-height: 18px; }
#status.hint { color: var(--text-muted); }
#status.loading { color: var(--amber); }
#status.error { color: var(--coral); }
</style>
</head>
<body>
<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>
<div class="wrap">
<div class="eyebrow">Certibiocide — Correction des rendus</div>
<h1>Corriger un exercice</h1>
<p class="lead">Sélectionnez l'exercice concerné, puis déposez le fichier PDF exporté par le groupe. La lecture du rendu s'ouvre automatiquement.</p>
<p class="step-label"><span class="step-num">1</span> Choisissez l'exercice à corriger</p>
<div class="exo-grid" id="exoGrid">
<div class="exo-option">
<input type="radio" name="exo" id="exo-biocid" value="biocid">
<label for="exo-biocid">
<span class="exo-icon">🧪</span>
<span class="exo-text">
<span class="exo-name">Plateforme BioCID</span>
<span class="exo-sub">Recherche de produits biocides</span>
</span>
<span class="exo-check"></span>
</label>
</div>
<div class="exo-option">
<input type="radio" name="exo" id="exo-fds" value="fds">
<label for="exo-fds">
<span class="exo-icon">📋</span>
<span class="exo-text">
<span class="exo-name">FDS, FT &amp; Étiquettes</span>
<span class="exo-sub">Lecture documentaire</span>
</span>
<span class="exo-check"></span>
</label>
</div>
<div class="exo-option">
<input type="radio" name="exo" id="exo-tp" value="tp">
<label for="exo-tp">
<span class="exo-icon">📚</span>
<span class="exo-text">
<span class="exo-name">TP Nuisibles final</span>
<span class="exo-sub">Étude de cas de synthèse</span>
</span>
<span class="exo-check"></span>
</label>
</div>
</div>
<p class="step-label"><span class="step-num">2</span> Déposez le rendu PDF</p>
<div id="dropzone" class="disabled">
<div class="icon">📄</div>
<p class="title">Déposez le PDF du groupe ici</p>
<p class="sub">ou cliquez pour sélectionner le fichier exporté depuis l'exercice</p>
<label class="btn-pick">Choisir un fichier
<input type="file" id="filePicker" accept="application/pdf">
</label>
<div id="status" class="hint">Sélectionnez d'abord un exercice ci-dessus.</div>
</div>
</div>
<footer>
<p>&copy; 2026 Gauthier Chombart &amp; Nathan Chauwin<br>Tous droits réservés — Formations Certibiocide &amp; Techniques — Formateurs indépendants</p>
<p style="margin-top: 10px; opacity: 0.6;">Tous droits réservés — Gauthier Chombart &amp; Nathan Chauwin — Formateurs indépendants</p>
</footer>
<script>
// Chaque exercice pointe vers sa page de correction dédiée (parseur spécifique).
const CORRECTORS = {
biocid: "correction-biocid.html",
fds: "correction-fds.html",
tp: "correction-tp-nuisibles.html",
};
const dropzone = document.getElementById("dropzone");
const status = document.getElementById("status");
const filePicker = document.getElementById("filePicker");
function selectedExo() {
const el = document.querySelector('input[name="exo"]:checked');
return el ? el.value : null;
}
function refreshDropzone() {
const exo = selectedExo();
if (exo) {
dropzone.classList.remove("disabled");
status.className = "hint";
status.textContent = "Prêt à recevoir le PDF.";
} else {
dropzone.classList.add("disabled");
status.className = "hint";
status.textContent = "Sélectionnez d'abord un exercice ci-dessus.";
}
}
// Préselection via ?exo=biocid|fds|tp
(function preselect() {
const params = new URLSearchParams(location.search);
const exo = params.get("exo");
if (exo && CORRECTORS[exo]) {
const input = document.getElementById("exo-" + exo);
if (input) input.checked = true;
}
})();
document.querySelectorAll('input[name="exo"]').forEach(r =>
r.addEventListener("change", refreshDropzone)
);
refreshDropzone();
function handleFile(file) {
const exo = selectedExo();
if (!exo) {
status.className = "error";
status.textContent = "Choisissez un exercice avant de déposer un fichier.";
return;
}
if (!file) return;
if (file.type !== "application/pdf" && !/\.pdf$/i.test(file.name)) {
status.className = "error";
status.textContent = "Le fichier doit être un PDF.";
return;
}
status.className = "loading";
status.textContent = "Ouverture du correcteur…";
const reader = new FileReader();
reader.onload = () => {
try {
// On transmet le PDF à la page de correction dédiée via localStorage
localStorage.setItem("correctionFile", reader.result);
localStorage.setItem("correctionFileName", file.name);
localStorage.setItem("correctionExo", exo);
window.location.href = CORRECTORS[exo] + "?auto=1";
} catch (e) {
console.error(e);
status.className = "error";
status.textContent = "Fichier trop volumineux pour être transmis. Ouvrez la page de correction dédiée.";
}
};
reader.onerror = () => {
status.className = "error";
status.textContent = "Impossible de lire le fichier.";
};
reader.readAsDataURL(file);
}
dropzone.addEventListener("dragover", e => {
if (dropzone.classList.contains("disabled")) return;
e.preventDefault();
dropzone.classList.add("drag");
});
dropzone.addEventListener("dragleave", () => dropzone.classList.remove("drag"));
dropzone.addEventListener("drop", e => {
if (dropzone.classList.contains("disabled")) return;
e.preventDefault();
dropzone.classList.remove("drag");
handleFile(e.dataTransfer.files && e.dataTransfer.files[0]);
});
filePicker.addEventListener("change", e => handleFile(e.target.files && e.target.files[0]));
</script>
</body>
</html>