From c1bd61c10c66ad8b9479653b9fc471c14be76976 Mon Sep 17 00:00:00 2001 From: "gauthier.chombart" Date: Wed, 15 Jul 2026 22:46:04 +0200 Subject: [PATCH 01/26] Refonte des exercices BioCID et FDS en canvas slide par slide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reprend le fonctionnement du TP Nuisibles : navigation page par page, barre de progression, barre basse avec retour et compteur, badge de phase et modales d'aide. BioCID passe en 7 slides avec champs de notes et export PDF ; FDS passe en 9 slides (code couleur amber/coral/teal) en conservant la logique de quiz et de score. Style harmonisé avec le reste du site. --- exercices/Exo_BioCID.html | 1151 +++++++++++++++------------ exercices/Exo_FDS.html | 1570 +++++++++++++++++-------------------- 2 files changed, 1376 insertions(+), 1345 deletions(-) diff --git a/exercices/Exo_BioCID.html b/exercices/Exo_BioCID.html index 508697d..99d8dd2 100644 --- a/exercices/Exo_BioCID.html +++ b/exercices/Exo_BioCID.html @@ -1,519 +1,690 @@ - - - Exercices Pratiques BioCID - Gauthier Chombart & Nathan Chauwin - - - +@media (max-width: 640px) { + .overview-grid { grid-template-columns: 1fr; } +} + - -
- - -
- -
-
+ +
+ + +
+ + +
+ + + + + + + + + + + +
+
+
+
+
Certibiocide — Travaux pratiques
+
Plateforme
BioCID
+

Maîtrisez la plateforme officielle de l'ANSES pour consulter les produits biocides autorisés et déclarés en France.

+ +
+
+ + +
+
+ +
Contexte & accès
+

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.

+
+
🌐
+
+
Plateforme BioCID — ANSES
+
Cliquez sur « Rechercher un produit » pour accéder à la base consultable
+ Ouvrir ↗ +
+
+ 🎯 +
4 exercices vous attendent : recherche par nom, par type, par substance active, puis un cas de synthèse. Durée totale ≈ 30 min.
+
+ +
-
- - -
- -
-

Contexte

-
- BioCID est la plateforme officielle de l'ANSES pour consulter tous les produits biocides autorisés et déclarés en France. Cet exercice vous permettra de vous familiariser avec ses différents modes de recherche et de développer une autonomie dans la consultation des produits. -
-
- - -
-

Accès à la Plateforme

-
- -
-
- - -
-

Exercice 1 : Recherche par Nom de Produit

-
-
-
1
- Trouver un produit spécifique -
-
Durée : 5 minutes
-
    -
  • Recherchez un produit connu (ex. « Goliath », « Phobi », « Soricide »)
  • -
  • Notez le type de produit, la substance active, et le fabricant
  • -
  • Vérifiez la date d'autorisation du produit
  • -
  • Observez les conditions d'utilisation mentionnées
  • -
- - -
-
- - -
-

Exercice 2 : Recherche par Type de Produit

-
-
-
2
- Explorer les catégories disponibles -
-
Durée : 5 minutes
-
    -
  • Utilisez le filtre « Type de produit »
  • -
  • Sélectionnez « Rodenticides (TP14) » ou « Insecticides (TP18) »
  • -
  • Comptez combien de produits sont disponibles dans cette catégorie
  • -
  • Comparez avec une autre catégorie (ex. Désinfectants TP2/TP3/TP4)
  • -
- - -
-
- - -
-

Exercice 3 : Recherche par Substance Active

-
-
-
3
- Trouver tous les produits d'une même substance -
-
Durée : 5 minutes
-
    -
  • Recherchez une substance active (ex. « Brodifacoum », « Perméthrine »)
  • -
  • Listez au minimum 3 produits différents contenant cette substance
  • -
  • Observez si différents fabricants proposent des formulations variées
  • -
  • Comparez les concentrations de la substance active d'un produit à l'autre
  • -
- - -
-
- - -
-

Exercice 4 : Cas de Synthèse

-
-
Mise en situation réelle
-
- Un restaurant vous appelle pour vous demander un produit. En utilisant BioCID, vous devez présenter une recommandation argumentée. -
-
    -
  • Filtrez par type : « Désinfectants (TP2/TP3/TP4) » ou « Produits de lutte contre les nuisibles (TP14/TP18/TP20) »
  • -
  • Identifiez au minimum 3 produits applicables
  • -
  • Pour chacun, relevez : nom, fabricant, substance active, usage spécifique
  • -
  • Proposez un court argumentaire pour chaque produit
  • -
  • Bonus : Recherchez les fiches de sécurité (FDS) associées
  • -
-
-
- - -
-
-

💡 Astuces d'Utilisation & Bonnes Pratiques

-
    -
  • Combinez plusieurs filtres pour affiner votre recherche (type + substance + fabricant)
  • -
  • Consultez toujours les fiches de sécurité (FDS) des produits avant recommandation
  • -
  • Vérifiez les conditions d'utilisation : usage professionnel vs. domestique
  • -
  • BioCID est mis à jour régulièrement avec les nouvelles autorisations et retraits
  • -
  • En cas de doute, contactez l'ANSES ou consultez un expert en biocides
  • -
  • Notez toujours les références exactes pour traçabilité (numéro d'AMM)
  • -
-
-
-
- -
-

Durée totale estimée : 30 minutes

-

Pour toute question sur BioCID : support.biocid@anses.fr

-

© 2026 Gauthier Chombart & Nathan Chauwin
Tous droits réservés — Formations Certibiocide & Techniques — Formateurs indépendants

-

Tous droits réservés — Gauthier Chombart & Nathan Chauwin — Formateurs indépendants

-
+ +
+
+ +
Recherche par nom de produit
+

Trouvez un produit spécifique et relevez ses caractéristiques principales.

+
    +
  • +
    1Recherchez un produit connu (ex. « Goliath », « Phobi », « Soricide »). Notez son type de produit, sa substance active et son fabricant.
    + +
  • +
  • +
    2Vérifiez la date d'autorisation du produit.
    + +
  • +
  • +
    3Observez les conditions d'utilisation mentionnées (usage professionnel / grand public).
    + +
  • +
+
+ +
+
+
- + +
+
+ +
Mise en situation réelle
+
+ 📞 +
Un restaurant vous appelle pour une demande de produit. En utilisant BioCID, présentez une recommandation argumentée.
+
+
    +
  • +
    1Filtrez par type : « Désinfectants (TP2/TP3/TP4) » ou « Lutte contre les nuisibles (TP14/TP18/TP20) ». Identifiez au minimum 3 produits applicables.
    + +
  • +
  • +
    2Pour chacun, relevez : nom, fabricant, substance active, usage spécifique.
    + +
  • +
  • +
    3Proposez un court argumentaire pour chaque produit. Bonus : recherchez les FDS associées.
    + +
  • +
+
+ +
+
+
+ + +
+
+
+
+
🎉
+ +
Astuces & bonnes pratiques
+
+
    +
  • Combinez plusieurs filtres pour affiner (type + substance + fabricant).
  • +
  • Consultez toujours les fiches de sécurité (FDS) avant toute recommandation.
  • +
  • Vérifiez les conditions d'utilisation : usage professionnel vs. domestique.
  • +
  • BioCID est mis à jour régulièrement (nouvelles autorisations et retraits).
  • +
  • En cas de doute, contactez l'ANSES ou consultez un expert en biocides.
  • +
  • Notez toujours les références exactes pour la traçabilité (numéro d'AMM).
  • +
+
+ + biocid-anses.fr ↗ +
+
+ +
+
+
+ + diff --git a/exercices/Exo_FDS.html b/exercices/Exo_FDS.html index f4c732c..fbb851d 100644 --- a/exercices/Exo_FDS.html +++ b/exercices/Exo_FDS.html @@ -1,906 +1,766 @@ - - - TD FDS, FT et Étiquettes - - - +@media (max-width: 700px) { + .overview-grid { grid-template-columns: 1fr; } + .answer-row { flex-wrap: wrap; } + select { width: 100%; min-width: 0; } +} + -
- - -
-
-
+ +
+ + +
+ + +
+ + + + + + + + +
+
+
+
+
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.

+ +
+
+ + +
+
+ +
Trois documents, trois usages
+

Vous allez apprendre à localiser une information dans chacun des documents de référence d'un produit biocide.

+
+
+
📄
+
Partie 1 — FDS
+
Fiche de Données de Sécurité — 16 rubriques
- -
- - -
- -
- - - -
- - -
-
-

Partie 1 : Fiche de Données de Sécurité (FDS)

-
- Durée : 10 minutes - La FDS est le document de référence pour connaître les dangers, les EPI, les premiers secours et l'élimination des produits biocides. Elle contient 16 rubriques standards. -
-
- -
-

Questions d'analyse FDS

-

Pour chaque question, sélectionnez la rubrique FDS où trouver la réponse (Rubrique 1 à 16 ou « Absent »).

- -
- -
- - -
-
-
- - -
-
-

Partie 2 : Fiche Technique (FT)

-
- Durée : 10 minutes - La FT complète la FDS en fournissant le mode d'emploi, les utilisations, les conditionnements et les compatibilités matières. C'est le document pratique de l'utilisateur. -
-
- -
-

Questions d'analyse FT

-

Pour chaque question, sélectionnez la section FT où trouver la réponse (Description, Propriétés biocides, Conditionnements, Utilisations ou « Absent »).

- -
-
-
Q1
-
Quel est le nom du produit et qui est le fabricant ?
- -
- -
-
Q2
-
Sur quelles matières ce produit est-il compatible ?
- -
- -
-
Q3
-
Quel est (sont) le(s) type(s) de ce produit ?
- -
- -
-
Q4
-
Quels sont les types de conditionnements disponibles ?
- -
- -
-
Q5
-
Où retrouver les différents modes d'utilisation de ce produit ?
- -
-
- - -
-
-
- - -
-
-

Partie 3 : Étiquetage

-
- Durée : 10 minutes - L'étiquette est l'interface directe entre le produit et l'utilisateur. Elle communique rapidement les dangers essentiels, les EPI, et les consignes d'utilisation. -
-
- -
-

Questions d'analyse d'étiquette

-

Pour chaque question, sélectionnez la zone de l'étiquette où trouver l'information (Identité, Pictogrammes, Mots d'avertissement, Mentions de danger, Conseils de prudence, Contenu net, Numéro d'urgence ou « Absent »).

- -
-
-
Q1
-
Quel est le nom du produit et le nom de l'entreprise ?
- -
- -
-
Q2
-
Quels pictogrammes de danger sont affichés ?
- -
- -
-
Q3
-
Quels sont les mots d'avertissement (« Attention » ou « Danger ») ?
- -
- -
-
Q4
-
Quelles sont les mentions de danger (H-statements) identifiées ?
- -
- -
-
Q5
-
Quels équipements de protection individuelle (EPI) sont recommandés ?
- -
- -
-
Q6
-
Quels premiers secours sont indiqués ?
- -
- -
-
Q7
-
Comment éliminer l'emballage après utilisation ?
- -
- -
-
Q8
-
Quel est le contenu net du produit ?
- -
- -
-
Q9
-
Y a-t-il un numéro d'appel d'urgence mentionné ?
- -
- -
-
Q10
-
Quelles consignes de stockage sont affichées ?
- -
-
- - -
-
-
- - -
-

Synthèse Comparative

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
DocumentUtilité PrincipaleMoment de ConsultationPublic
FDSSécurité, dangers, EPI, premiers secoursAVANT manipulationProfessionnels
FTMode d'emploi, applications, conditionnementsPour l'utilisationUtilisateurs finaux
ÉtiquetteDangers essentiels, interface rapideÀ chaque prise du produitTous les utilisateurs
-
- - -
-
-

💡 Clés de Succès

-
    -
  • 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
  • -
  • Chaque document cible un besoin différent et un public différent
  • -
  • Un professionnel doit maîtriser les trois pour une utilisation sûre et efficace
  • -
-
-
-
- -
-

© 2026 Gauthier Chombart & Nathan Chauwin
Tous droits réservés — Formations Certibiocide & Techniques — Formateurs indépendants

-

Tous droits réservés — Gauthier Chombart & Nathan Chauwin — Formateurs indépendants

-
+
+
📋
+
Partie 2 — FT
+
Fiche Technique — mode d'emploi produit
+
+
🏷️
+
Partie 3 — Étiquette
+
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.
+
+ +
- +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('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'; +} + +function restartAll() { + history = []; + document.querySelectorAll('.slide').forEach(s => { s.classList.remove('active', 'fade-in'); s.style.display = 'none'; }); + document.querySelectorAll('.result-message').forEach(r => { r.className = 'result-message'; }); + generateQuizzes(); + currentSlide = 0; + const first = document.getElementById('slide-0'); + first.style.display = 'flex'; + setTimeout(() => first.classList.add('active', 'fade-in'), 10); + updateUI(); +} + +document.addEventListener('DOMContentLoaded', () => { + generateQuizzes(); + updateUI(); +}); + From a418558393b9543d04cea4f07c295ab24fc1456b Mon Sep 17 00:00:00 2001 From: "gauthier.chombart" Date: Wed, 15 Jul 2026 23:07:45 +0200 Subject: [PATCH 02/26] Ajoute la correction sur fichier aux exercices BioCID et FDS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reprend le fonctionnement du TP Nuisibles (« Corriger » à partir du PDF généré avec les réponses du groupe) pour les deux exercices. - BioCID et FDS : carte « Identification du groupe » (groupe, date, formateur) sur l'écran d'intro, reprise dans l'en-tête du PDF exporté. - FDS : ajout d'un export PDF (inexistant) récapitulant réponse du groupe et réponse attendue, partie par partie, avec le score global. - Bouton « Corriger un rendu » sur l'écran final des deux exercices. - Nouvelle page correction-biocid.html : lecture du PDF, réponses regroupées par exercice avec badge renseigné / sans réponse. - Nouvelle page correction-fds.html : notation automatique du QCM (anneau de réussite, score par partie, ✓/✗/? par question). --- exercices/Exo_BioCID.html | 67 +++- exercices/Exo_FDS.html | 124 ++++++- exercices/correction-biocid.html | 507 ++++++++++++++++++++++++++ exercices/correction-fds.html | 604 +++++++++++++++++++++++++++++++ 4 files changed, 1298 insertions(+), 4 deletions(-) create mode 100644 exercices/correction-biocid.html create mode 100644 exercices/correction-fds.html diff --git a/exercices/Exo_BioCID.html b/exercices/Exo_BioCID.html index 99d8dd2..bf4fba8 100644 --- a/exercices/Exo_BioCID.html +++ b/exercices/Exo_BioCID.html @@ -350,6 +350,40 @@ .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; } @@ -426,6 +460,14 @@ 🎯
4 exercices vous attendent : recherche par nom, par type, par substance active, puis un cas de synthèse. Durée totale ≈ 30 min.
+
+
👥 Identification du groupe
+
+ + + +
+
@@ -550,9 +592,10 @@
- biocid-anses.fr ↗ + ✔ Corriger un rendu
+ biocid-anses.fr ↗
@@ -664,7 +707,16 @@ function escapeHtml(str) { return str.replace(/&/g, '&').replace(//g, '>'); } +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() }); @@ -674,12 +726,21 @@ function exporterPDF() { `
${escapeHtml(r.question)}

${escapeHtml(r.reponse) || '(sans réponse)'}

` ).join(''); w.document.write(` - Exercices BioCID — Réponses + Exercices BioCID — Réponses (${escapeHtml(groupe) || 'sans nom'}) -

Exercices Pratiques BioCID

${lignes}`); + +

Exercices Pratiques BioCID

+

Groupe / Stagiaire : ${escapeHtml(groupe) || '—'}

+

Date de session : ${escapeHtml(date)}

+

Formateur : ${escapeHtml(formateur) || '—'}

+

Réponses aux exercices

+ ${lignes} + `); w.document.close(); setTimeout(() => w.print(), 300); } diff --git a/exercices/Exo_FDS.html b/exercices/Exo_FDS.html index fbb851d..3ab178d 100644 --- a/exercices/Exo_FDS.html +++ b/exercices/Exo_FDS.html @@ -336,6 +336,40 @@ select:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px .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; } @@ -413,6 +447,14 @@ select:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px ⏱️
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
+
+ + + +
+
@@ -523,7 +565,11 @@ select:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px
  • Les trois documents sont complémentaires, jamais redondants — chacun cible un besoin et un public différents.
  • - + + ✔ Corriger un rendu +
    +
    + Retour aux exercices
    @@ -700,6 +746,82 @@ function verifyEtiquette() { showScore(document.getElementById('etiquette-result'), correct, etiquetteQuestions.length); } +/* ======================== EXPORT PDF ======================== */ +function escapeHtml(str) { + if (!str) return ''; + return String(str).replace(/&/g, '&').replace(//g, '>'); +} +function idVal(id) { + const el = document.getElementById(id); + return el ? el.value.trim() : ''; +} + +// Libellé lisible d'une réponse FDS (numéro de rubrique) et de l'attendu +function fdsAnswerLabel(v) { + if (!v) return '(non répondu)'; + if (v === 'absent') return 'Absent'; + return 'Rubrique ' + v; +} +function fdsExpectedLabel(answer) { + const parts = answer.split(','); + if (parts.length > 1) return 'Rubrique ' + parts.join(' ou '); + return parts[0] === 'absent' ? 'Absent' : 'Rubrique ' + parts[0]; +} +function textAnswerLabel(v) { + if (!v) return '(non répondu)'; + return v === 'absent' ? 'Absent' : v; +} + +function isFdsCorrect(q, v) { return v !== '' && q.answer.split(',').includes(v); } +function isTextCorrect(q, v) { return v !== '' && v === q.answer; } + +function exporterPDF() { + const groupe = idVal('idGroupe'); + const date = idVal('idDate') || new Date().toISOString().slice(0, 10); + const formateur = idVal('idFormateur'); + + let scoreTotal = 0, nbTotal = 0; + + function partHtml(prefix, title, questions, selPrefix, answerLabelFn, expectedLabelFn, correctFn) { + const rows = questions.map((q, i) => { + const sel = document.getElementById(selPrefix + q.id); + const v = sel ? sel.value : ''; + const ok = correctFn(q, v); + nbTotal++; if (ok) scoreTotal++; + const mark = v === '' ? '❓' : (ok ? '✓' : '✗'); + return `
    +
    [${prefix}-Q${i + 1}] ${escapeHtml(q.text)}
    +
    Réponse du groupe : ${escapeHtml(answerLabelFn(v))} — Attendu : ${escapeHtml(expectedLabelFn(q.answer))} — ${mark}
    +
    `; + }).join(''); + return `

    ${title}

    ${rows}`; + } + + const p1 = partHtml('P1', 'Partie 1 — FDS (Fiche de Données de Sécurité)', fdsQuestions, 'fds', fdsAnswerLabel, fdsExpectedLabel, isFdsCorrect); + const p2 = partHtml('P2', 'Partie 2 — FT (Fiche Technique)', ftQuestions, 'ft', textAnswerLabel, a => a, isTextCorrect); + const p3 = partHtml('P3', 'Partie 3 — Étiquette', etiquetteQuestions, 'eti', textAnswerLabel, a => a, isTextCorrect); + + const w = window.open('', '_blank'); + w.document.write(` + FDS, FT & Étiquettes — Réponses (${escapeHtml(groupe) || 'sans nom'}) + + +

    TD — FDS, FT & Étiquettes

    +

    Groupe / Stagiaire : ${escapeHtml(groupe) || '—'}

    +

    Date de session : ${escapeHtml(date)}

    +

    Formateur : ${escapeHtml(formateur) || '—'}

    +

    Score global : ${scoreTotal} / ${nbTotal}

    + ${p1}${p2}${p3} + `); + w.document.close(); + setTimeout(() => w.print(), 300); +} + /* ======================== NAVIGATION ======================== */ function goTo(n) { const cur = document.getElementById('slide-' + currentSlide); diff --git a/exercices/correction-biocid.html b/exercices/correction-biocid.html new file mode 100644 index 0000000..a7784af --- /dev/null +++ b/exercices/correction-biocid.html @@ -0,0 +1,507 @@ + + + + + +Correction BioCID — Certibiocide + + + + + +
    + + +
    + +
    + +
    Certibiocide — Correction d'exercice
    +

    Plateforme BioCID — Lecture du rendu

    + +
    +
    📄
    +

    Déposez le PDF du groupe ici

    +

    ou cliquez pour sélectionner le fichier exporté depuis l'exercice BioCID

    + +
    +
    + +
    +
    + + +
    + +
    +
    + questions renseignées +
    + + +
    +
    + + + + + + + diff --git a/exercices/correction-fds.html b/exercices/correction-fds.html new file mode 100644 index 0000000..6003004 --- /dev/null +++ b/exercices/correction-fds.html @@ -0,0 +1,604 @@ + + + + + +Correction FDS, FT & Étiquettes — Certibiocide + + + + + +
    + + +
    + +
    + +
    Certibiocide — Correction de TD
    +

    FDS, FT & Étiquettes — Correction du rendu

    + +
    +
    📄
    +

    Déposez le PDF du groupe ici

    +

    ou cliquez pour sélectionner le fichier exporté depuis le TD FDS, FT & Étiquettes

    + +
    +
    + +
    +
    + + +
    + +
    + + +
    +
    + + + + + + + From fb5c7e9720c57807693a6dc5a534464b03d1885a Mon Sep 17 00:00:00 2001 From: "gauthier.chombart" Date: Thu, 16 Jul 2026 08:58:34 +0200 Subject: [PATCH 03/26] =?UTF-8?q?Refonte=20de=20la=20slide=203=20FDS=20en?= =?UTF-8?q?=20glisser-d=C3=A9poser?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Renomme la carte formation en 'FDS, FT & Étiquettes' - Slide 3 : deux colonnes de huit questions, banques d'étiquettes (rubriques 1 à 16 + Absent) de part et d'autre à glisser-déposer dans chaque case (repli au clic pour le tactile) - Bloque la vérification tant que les seize cases ne sont pas remplies - Retire l'étiquette 'Partie 1/3' et remonte le titre pour éviter le défilement - Ordre d'affichage après correction : réponse, emoji, case --- exercices/Exo_FDS.html | 225 ++++++++++++++++++++++++++++++++++++----- index.html | 2 +- 2 files changed, 202 insertions(+), 25 deletions(-) diff --git a/exercices/Exo_FDS.html b/exercices/Exo_FDS.html index 3ab178d..4926658 100644 --- a/exercices/Exo_FDS.html +++ b/exercices/Exo_FDS.html @@ -196,6 +196,73 @@ /* QUIZ ROWS */ .quiz-list { width: 100%; max-width: 720px; display: flex; flex-direction: column; gap: 12px; } +.quiz-list.quiz-grid { + max-width: 1080px; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; +} +@media (max-width: 900px) { + .quiz-list.quiz-grid { grid-template-columns: 1fr; max-width: 720px; } +} + +/* SLIDE 3 — DRAG & DROP */ +#slide-3 { + justify-content: flex-start; + padding-top: calc(70px + 0.9rem); + padding-bottom: calc(52px + 0.6rem); +} +#slide-3 .slide-title { margin: 0.1rem 0 0.15rem; font-size: clamp(19px, 2.4vw, 26px); } +.dnd-hint { font-size: 12.5px; color: var(--text-muted); text-align: center; margin: 0 0 10px; } +.dnd-layout { + display: grid; + grid-template-columns: auto minmax(0, 1fr) auto; + align-items: start; + gap: 14px; + width: 100%; + max-width: 1200px; + margin-bottom: 0.5rem; +} +#slide-3 .quiz-grid { gap: 8px; } +#slide-3 .verify-btn { padding: 9px 26px; } +#slide-3 .actions-row { margin-top: 0.8rem; } +.dnd-bank { display: grid; grid-template-columns: 1fr; gap: 6px; align-content: start; } +.dnd-tile { + font-family: 'Syne', sans-serif; + font-size: 12px; font-weight: 800; + padding: 8px 10px; min-width: 66px; text-align: center; + border-radius: 8px; + background: var(--amber-light); color: var(--amber-dark); + border: 1px solid var(--amber); + cursor: grab; user-select: none; + transition: transform 0.12s ease, box-shadow 0.12s ease; +} +.dnd-tile:hover { transform: translateY(-1px); box-shadow: 0 4px 10px rgba(186,117,23,0.15); } +.dnd-tile:active { cursor: grabbing; } +.dnd-tile.armed { outline: 2px solid var(--amber-dark); box-shadow: 0 0 0 3px rgba(186,117,23,0.18); } +.dnd-tile.dragging { opacity: 0.4; } +#slide-3 .answer-row { padding: 6px 12px; gap: 10px; } +#slide-3 .question-num { min-width: 26px; height: 26px; font-size: 11px; } +#slide-3 .question-text { font-size: 13px; } +.drop-slot { + min-width: 92px; min-height: 36px; + border: 2px dashed var(--amber); border-radius: 8px; + display: flex; align-items: center; justify-content: center; + font-family: 'Syne', sans-serif; font-size: 12px; font-weight: 800; + color: var(--amber-dark); background: var(--amber-light); + cursor: pointer; flex-shrink: 0; + transition: all 0.15s ease; +} +.drop-slot::before { content: 'Déposez ici'; font-weight: 600; font-size: 11px; opacity: 0.55; } +.drop-slot.filled { border-style: solid; background: white; } +.drop-slot.filled::before { content: none; } +.drop-slot.drag-over { border-color: var(--amber-dark); background: white; transform: scale(1.04); } +.drop-slot.slot-correct { border-color: #2e7d32; color: #2e7d32; } +.drop-slot.slot-wrong { border-color: #c62828; color: #c62828; } +@media (max-width: 900px) { + .dnd-layout { grid-template-columns: 1fr; } + .dnd-bank { grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); } +} .answer-row { background: white; border: 1px solid var(--border); @@ -259,6 +326,7 @@ select:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px transition: all 0.2s ease; } .verify-btn:hover { transform: translateY(-1px); } +.verify-btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; } .result-message { margin-top: 4px; @@ -471,17 +539,19 @@ select:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px - +
    -
    - -
    Où trouver l'information ?
    -
    - -
    -
    - -
    +
    Où trouver l'information ?
    +

    Glissez-déposez la bonne rubrique dans chaque case — ou cliquez une étiquette puis une case.

    +
    +
    +
    +
    +
    + +
    +
    +
    @@ -504,7 +574,7 @@ select:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px
    Sections de la fiche technique
    - +
    @@ -531,7 +601,7 @@ select:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px
    Zones de l'étiquette
    - +
    @@ -644,14 +714,8 @@ function buildRow(qNum, selectId, answerAttr, text, options, badgeStyle) { } function generateQuizzes() { - // FDS — shuffled, 16 rubriques - const rubriqueOpts = Array.from({ length: 16 }, (_, i) => ({ value: String(i + 1), label: 'Rubrique ' + (i + 1) })); - rubriqueOpts.push({ value: 'absent', label: 'Absent' }); - const fdsC = document.getElementById('fds-questions'); - fdsC.innerHTML = ''; - shuffleArray(fdsQuestions).forEach((q, i) => { - fdsC.innerHTML += buildRow(i + 1, 'fds' + q.id, q.answer, q.text, rubriqueOpts, 'background:var(--amber-light);color:var(--amber-dark);'); - }); + // FDS — glisser-déposer : banques d'étiquettes + cases de dépôt + buildFdsDnd(); // FT const ftOpts = ftOptions.map(o => ({ value: o, label: o })); @@ -670,6 +734,100 @@ function generateQuizzes() { etiquetteQuestions.forEach((q, i) => { etiC.innerHTML += buildRow(i + 1, 'eti' + q.id, q.answer, q.text, etiOpts, 'background:var(--teal-light);color:var(--teal-dark);'); }); + + // Bloque la vérification tant que toutes les questions ne sont pas répondues + wireVerifyGate('ft-questions', 'ft-verify'); + wireVerifyGate('etiquette-questions', 'eti-verify'); +} + +/* ---- FDS : glisser-déposer ---- */ +function buildFdsDnd() { + const tile = (v, l) => `
    ${l}
    `; + let left = '', right = ''; + for (let i = 1; i <= 8; i++) left += tile(String(i), 'R' + i); + for (let i = 9; i <= 16; i++) right += tile(String(i), 'R' + i); + right += tile('absent', 'Absent'); + document.getElementById('fds-bank-left').innerHTML = left; + document.getElementById('fds-bank-right').innerHTML = right; + + const fdsC = document.getElementById('fds-questions'); + fdsC.innerHTML = ''; + shuffleArray(fdsQuestions).forEach((q, i) => { + fdsC.innerHTML += ` +
    +
    Q${i + 1}
    +
    ${q.text}
    +
    +
    `; + }); + + initFdsDnd(); +} + +function initFdsDnd() { + const slide = document.getElementById('slide-3'); + const tiles = slide.querySelectorAll('.dnd-tile'); + const slots = slide.querySelectorAll('.drop-slot'); + let armed = null; + + const resetResults = () => { + slide.querySelectorAll('.answer-label, .answer-icon').forEach(e => e.remove()); + slide.querySelectorAll('.drop-slot').forEach(s => s.classList.remove('slot-correct', 'slot-wrong')); + const r = document.getElementById('fds-result'); + r.className = 'result-message'; r.innerHTML = ''; + }; + const fill = (slot, v, l) => { + slot.dataset.value = v; slot.textContent = l; slot.classList.add('filled'); + resetResults(); updateFdsGate(); + }; + const clear = (slot) => { + slot.dataset.value = ''; slot.textContent = ''; slot.classList.remove('filled'); + resetResults(); updateFdsGate(); + }; + + tiles.forEach(t => { + t.addEventListener('dragstart', e => { + e.dataTransfer.setData('text/plain', JSON.stringify({ v: t.dataset.value, l: t.dataset.label })); + t.classList.add('dragging'); + }); + t.addEventListener('dragend', () => t.classList.remove('dragging')); + t.addEventListener('click', () => { + if (armed === t) { t.classList.remove('armed'); armed = null; return; } + if (armed) armed.classList.remove('armed'); + armed = t; t.classList.add('armed'); + }); + }); + + slots.forEach(s => { + s.addEventListener('dragover', e => { e.preventDefault(); s.classList.add('drag-over'); }); + s.addEventListener('dragleave', () => s.classList.remove('drag-over')); + s.addEventListener('drop', e => { + e.preventDefault(); s.classList.remove('drag-over'); + try { const d = JSON.parse(e.dataTransfer.getData('text/plain')); fill(s, d.v, d.l); } catch (_) {} + }); + s.addEventListener('click', () => { + if (armed) { fill(s, armed.dataset.value, armed.dataset.label); armed.classList.remove('armed'); armed = null; } + else if (s.classList.contains('filled')) { clear(s); } + }); + }); + + updateFdsGate(); +} + +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; +} + +function wireVerifyGate(containerId, btnId) { + const container = document.getElementById(containerId); + const btn = document.getElementById(btnId); + if (!container || !btn) return; + const selects = Array.from(container.querySelectorAll('select')); + const update = () => { btn.disabled = !selects.every(s => s.value !== ''); }; + selects.forEach(s => s.addEventListener('change', update)); + update(); } /* ======================== VÉRIFICATION ======================== */ @@ -686,7 +844,7 @@ function showInlineResult(selectEl, isCorrect, isUnanswered, correctAnswerText) const icon = document.createElement('div'); icon.className = 'answer-icon'; icon.textContent = isUnanswered ? '❓' : (isCorrect ? '✅' : '❌'); - selectEl.after(icon); + selectEl.before(icon); } function showScore(resultDiv, correct, total) { @@ -709,19 +867,38 @@ function showScore(resultDiv, correct, total) { function verifyFDS() { let correct = 0; fdsQuestions.forEach(q => { - const select = document.getElementById('fds' + q.id); - const userAnswer = select.value; + const slot = document.getElementById('fds' + q.id); + const userAnswer = slot.dataset.value || ''; const expected = q.answer.split(','); const isCorrect = expected.includes(userAnswer); const labelText = expected.length > 1 ? 'Rubriques ' + expected.join(' et ') : (expected[0] === 'absent' ? 'Absent' : 'Rubrique ' + expected[0]); if (isCorrect) correct++; - showInlineResult(select, isCorrect, userAnswer === '', labelText); + showSlotResult(slot, isCorrect, userAnswer === '', labelText); }); showScore(document.getElementById('fds-result'), correct, fdsQuestions.length); } +function showSlotResult(slot, isCorrect, isUnanswered, correctAnswerText) { + const row = slot.closest('.answer-row'); + row.querySelector('.answer-label')?.remove(); + row.querySelector('.answer-icon')?.remove(); + slot.classList.remove('slot-correct', 'slot-wrong'); + + const label = document.createElement('div'); + label.className = 'answer-label'; + label.textContent = correctAnswerText; + slot.before(label); + + const icon = document.createElement('div'); + icon.className = 'answer-icon'; + icon.textContent = isUnanswered ? '❓' : (isCorrect ? '✅' : '❌'); + slot.before(icon); + + if (!isUnanswered) slot.classList.add(isCorrect ? 'slot-correct' : 'slot-wrong'); +} + function verifyFT() { let correct = 0; ftQuestions.forEach(q => { diff --git a/index.html b/index.html index 03b8511..cfdf9a5 100644 --- a/index.html +++ b/index.html @@ -103,7 +103,7 @@
    📋
    -

    FDS & Étiquettes

    +

    FDS, FT & Étiquettes

    Aller plus loin

    From 2a2ed26563b2ff6716670c33ed61fb356d099b38 Mon Sep 17 00:00:00 2001 From: "gauthier.chombart" Date: Thu, 16 Jul 2026 09:00:16 +0200 Subject: [PATCH 04/26] Ignore le web.config de l'API Memory (contient le mot de passe SQL) --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 9bea433..537b43e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ .DS_Store + +# Contient le mot de passe SQL Server — à éditer uniquement sur bsite.net +exercices/memory-pictogrammes/api/web.config From f720beaf3c584c0c0fcad89238e65a203be9c79e Mon Sep 17 00:00:00 2001 From: "gauthier.chombart" Date: Thu, 16 Jul 2026 09:15:07 +0200 Subject: [PATCH 05/26] =?UTF-8?q?Refonte=20de=20la=20slide=205=20FT=20en?= =?UTF-8?q?=20exercice=20=C3=A0=20relier=20+=20boutons=20Recommencer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La slide 5 (Fiche Technique) devient un exercice de mise en relation : questions à gauche, sections mélangées à droite, reliées au glisser (pointer events + tracé SVG). La vérification colore les liaisons (vert/rouge) et n'est active qu'une fois les 5 questions reliées. L'export PDF lit désormais les liaisons. Ajout d'un bouton Recommencer sur les slides 3 (FDS glisser-déposer) et 5 (FT), qui réinitialise l'exercice concerné (réponses effacées, message et vérification remis à zéro, éléments re-mélangés). --- exercices/Exo_FDS.html | 232 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 212 insertions(+), 20 deletions(-) diff --git a/exercices/Exo_FDS.html b/exercices/Exo_FDS.html index 4926658..4dc4c66 100644 --- a/exercices/Exo_FDS.html +++ b/exercices/Exo_FDS.html @@ -263,6 +263,61 @@ .dnd-layout { grid-template-columns: 1fr; } .dnd-bank { grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); } } +/* SLIDE 5 — RELIER (MATCHING) */ +.match-hint { font-size: 12.5px; color: var(--text-muted); text-align: center; margin: -0.4rem 0 0.1rem; max-width: 640px; line-height: 1.5; } +.match-wrap { + position: relative; + width: 100%; + max-width: 780px; + display: grid; + grid-template-columns: 1fr 1fr; + column-gap: clamp(56px, 12vw, 140px); + align-items: start; +} +.match-svg { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 2; overflow: visible; } +.match-col { display: flex; flex-direction: column; gap: 14px; } +.match-right { justify-content: center; } +.match-item { + position: relative; + background: white; + border: 1px solid var(--border); + border-radius: 12px; + padding: 12px 16px; + font-size: 13.5px; + color: var(--text); + line-height: 1.4; + min-height: 46px; + display: flex; align-items: center; + transition: border-color 0.2s ease, box-shadow 0.2s ease; +} +.match-left .match-item { padding-right: 24px; } +.match-right .match-item { + padding-left: 24px; + justify-content: center; text-align: center; + font-family: 'Syne', sans-serif; font-weight: 800; font-size: 13px; + color: var(--coral); background: var(--coral-light); border-color: var(--coral-mid); +} +.match-item.connected { border-color: var(--coral); box-shadow: 0 4px 12px rgba(153,60,29,0.10); } +.match-dot { + position: absolute; top: 50%; transform: translateY(-50%); + width: 16px; height: 16px; border-radius: 50%; + background: white; border: 2.5px solid var(--coral-mid); + cursor: pointer; z-index: 3; touch-action: none; + transition: transform 0.15s ease, background 0.15s ease; +} +.match-left .match-dot { right: -8px; } +.match-right .match-dot { left: -8px; } +.match-dot:hover { transform: translateY(-50%) scale(1.3); background: var(--coral-mid); } +.match-dot.filled { background: var(--coral-mid); } +.match-dot.active { background: var(--coral); border-color: var(--coral); transform: translateY(-50%) scale(1.3); } +@media (max-width: 700px) { + .match-hint { font-size: 12px; } + .match-wrap { column-gap: 44px; } + .match-item { font-size: 12.5px; padding: 10px 12px; } + .match-left .match-item { padding-right: 20px; } + .match-right .match-item { padding-left: 20px; } +} + .answer-row { background: white; border: 1px solid var(--border); @@ -551,6 +606,7 @@ select:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px
    +
    @@ -568,15 +624,21 @@ select:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px
    - +
    Sections de la fiche technique
    -
    +

    Reliez chaque question à la bonne section : cliquez sur un point à gauche, maintenez, puis glissez jusqu'au point de la section correspondante à droite.

    +
    + +
    +
    +
    +
    @@ -717,14 +779,8 @@ function generateQuizzes() { // FDS — glisser-déposer : banques d'étiquettes + cases de dépôt buildFdsDnd(); - // FT - const ftOpts = ftOptions.map(o => ({ value: o, label: o })); - ftOpts.push({ value: 'absent', label: 'Absent' }); - const ftC = document.getElementById('ft-questions'); - ftC.innerHTML = ''; - ftQuestions.forEach((q, i) => { - ftC.innerHTML += buildRow(i + 1, 'ft' + q.id, q.answer, q.text, ftOpts, 'background:var(--coral-light);color:var(--coral);'); - }); + // FT — exercice « relier » (questions à gauche, sections à droite) + buildFtMatch(); // Étiquette const etiOpts = etiquetteOptions.map(o => ({ value: o, label: o })); @@ -736,10 +792,141 @@ function generateQuizzes() { }); // Bloque la vérification tant que toutes les questions ne sont pas répondues - wireVerifyGate('ft-questions', 'ft-verify'); wireVerifyGate('etiquette-questions', 'eti-verify'); } +/* ---- FT : exercice « relier » ---- */ +const ftConnections = {}; // { qId: valeur de la section reliée } +let ftDrag = null; // { qid } pendant un glissement +let ftVerified = false; +const SVGNS = 'http://www.w3.org/2000/svg'; +const FT_COLORS = { line: '#D85A30', temp: '#993C1D', ok: '#2e7d32', ko: '#c62828' }; + +function qById(id) { return ftQuestions.find(q => String(q.id) === String(id)); } + +function buildFtMatch() { + Object.keys(ftConnections).forEach(k => delete ftConnections[k]); + ftDrag = null; ftVerified = false; + + const leftC = document.getElementById('ft-left'); + const rightC = document.getElementById('ft-right'); + leftC.innerHTML = ''; + rightC.innerHTML = ''; + + ftQuestions.forEach(q => { + const item = document.createElement('div'); + item.className = 'match-item'; + item.dataset.qid = q.id; + item.innerHTML = `${q.text}
    `; + leftC.appendChild(item); + }); + + const sections = shuffleArray([...new Set(ftQuestions.map(q => q.answer))]); + sections.forEach(sec => { + const item = document.createElement('div'); + item.className = 'match-item'; + item.dataset.answer = sec; + item.innerHTML = `
    ${sec}`; + rightC.appendChild(item); + }); + + initFtMatch(); + document.getElementById('ft-svg').innerHTML = ''; + document.getElementById('ft-verify').disabled = true; +} + +function resetFT() { + const r = document.getElementById('ft-result'); + r.className = 'result-message'; r.innerHTML = ''; + buildFtMatch(); +} + +function ftSvgRect() { return document.getElementById('ft-svg').getBoundingClientRect(); } +function ftPointerPos(e) { const r = ftSvgRect(); return { x: e.clientX - r.left, y: e.clientY - r.top }; } +function ftDotCenter(dot) { + const r = dot.getBoundingClientRect(); const s = ftSvgRect(); + return { x: r.left + r.width / 2 - s.left, y: r.top + r.height / 2 - s.top }; +} + +function ftDrawLine(svg, a, b, color, dashed) { + const line = document.createElementNS(SVGNS, 'line'); + line.setAttribute('x1', a.x); line.setAttribute('y1', a.y); + line.setAttribute('x2', b.x); line.setAttribute('y2', b.y); + line.setAttribute('stroke', color); + line.setAttribute('stroke-width', '3'); + line.setAttribute('stroke-linecap', 'round'); + if (dashed) line.setAttribute('stroke-dasharray', '6 5'); + svg.appendChild(line); +} + +function drawFtLines(tempEnd) { + const svg = document.getElementById('ft-svg'); + if (!svg) return; + svg.innerHTML = ''; + Object.entries(ftConnections).forEach(([qid, ans]) => { + const from = document.querySelector('.match-dot[data-side="left"][data-qid="' + qid + '"]'); + const to = document.querySelector('.match-dot[data-side="right"][data-answer="' + CSS.escape(ans) + '"]'); + if (!from || !to) return; + const color = ftVerified ? (ans === qById(qid).answer ? FT_COLORS.ok : FT_COLORS.ko) : FT_COLORS.line; + ftDrawLine(svg, ftDotCenter(from), ftDotCenter(to), color); + }); + if (ftDrag && tempEnd) { + const from = document.querySelector('.match-dot[data-side="left"][data-qid="' + ftDrag.qid + '"]'); + if (from) ftDrawLine(svg, ftDotCenter(from), tempEnd, FT_COLORS.temp, true); + } +} + +function refreshFtState() { + ftQuestions.forEach(q => { + const dot = document.querySelector('.match-dot[data-side="left"][data-qid="' + q.id + '"]'); + if (!dot) return; + const has = ftConnections[q.id] != null; + dot.classList.toggle('filled', has); + dot.closest('.match-item').classList.toggle('connected', has); + }); + document.querySelectorAll('.match-dot[data-side="right"]').forEach(dot => { + const used = Object.values(ftConnections).includes(dot.dataset.answer); + dot.classList.toggle('filled', used); + dot.closest('.match-item').classList.toggle('connected', used); + }); + const all = ftQuestions.every(q => ftConnections[q.id] != null); + document.getElementById('ft-verify').disabled = !all; +} + +function initFtMatch() { + const wrap = document.getElementById('ft-match'); + wrap.querySelectorAll('.match-dot[data-side="left"]').forEach(dot => { + dot.addEventListener('pointerdown', e => { + e.preventDefault(); + const qid = dot.dataset.qid; + delete ftConnections[qid]; // repartir d'une question efface son lien + ftVerified = false; + document.getElementById('ft-result').className = 'result-message'; + ftDrag = { qid }; + dot.classList.add('active'); + refreshFtState(); + drawFtLines(ftPointerPos(e)); + }); + }); +} + +// Gestionnaires globaux (le glissement peut sortir de la zone) +document.addEventListener('pointermove', e => { + if (!ftDrag) return; + drawFtLines(ftPointerPos(e)); +}); +document.addEventListener('pointerup', e => { + if (!ftDrag) return; + const target = document.elementFromPoint(e.clientX, e.clientY); + const answerDot = target && target.closest('.match-dot[data-side="right"]'); + if (answerDot) ftConnections[ftDrag.qid] = answerDot.dataset.answer; + document.querySelectorAll('.match-dot.active').forEach(d => d.classList.remove('active')); + ftDrag = null; + refreshFtState(); + drawFtLines(); +}); +window.addEventListener('resize', () => { if (currentSlide === 5) drawFtLines(); }); + /* ---- FDS : glisser-déposer ---- */ function buildFdsDnd() { const tile = (v, l) => `
    ${l}
    `; @@ -764,6 +951,12 @@ function buildFdsDnd() { initFdsDnd(); } +function resetFDS() { + const r = document.getElementById('fds-result'); + r.className = 'result-message'; r.innerHTML = ''; + buildFdsDnd(); +} + function initFdsDnd() { const slide = document.getElementById('slide-3'); const tiles = slide.querySelectorAll('.dnd-tile'); @@ -900,14 +1093,12 @@ function showSlotResult(slot, isCorrect, isUnanswered, correctAnswerText) { } function verifyFT() { + ftVerified = true; let correct = 0; ftQuestions.forEach(q => { - const select = document.getElementById('ft' + q.id); - const userAnswer = select.value; - const isCorrect = userAnswer === q.answer; - if (isCorrect) correct++; - showInlineResult(select, isCorrect, userAnswer === '', q.answer); + if (ftConnections[q.id] === q.answer) correct++; }); + drawFtLines(); showScore(document.getElementById('ft-result'), correct, ftQuestions.length); } @@ -959,10 +1150,11 @@ function exporterPDF() { let scoreTotal = 0, nbTotal = 0; - function partHtml(prefix, title, questions, selPrefix, answerLabelFn, expectedLabelFn, correctFn) { + function partHtml(prefix, title, questions, selPrefix, answerLabelFn, expectedLabelFn, correctFn, valueFn) { const rows = questions.map((q, i) => { - const sel = document.getElementById(selPrefix + q.id); - const v = sel ? sel.value : ''; + let v; + if (valueFn) { v = valueFn(q); } + else { const sel = document.getElementById(selPrefix + q.id); v = sel ? sel.value : ''; } const ok = correctFn(q, v); nbTotal++; if (ok) scoreTotal++; const mark = v === '' ? '❓' : (ok ? '✓' : '✗'); @@ -975,7 +1167,7 @@ function exporterPDF() { } const p1 = partHtml('P1', 'Partie 1 — FDS (Fiche de Données de Sécurité)', fdsQuestions, 'fds', fdsAnswerLabel, fdsExpectedLabel, isFdsCorrect); - const p2 = partHtml('P2', 'Partie 2 — FT (Fiche Technique)', ftQuestions, 'ft', textAnswerLabel, a => a, isTextCorrect); + const p2 = partHtml('P2', 'Partie 2 — FT (Fiche Technique)', ftQuestions, 'ft', textAnswerLabel, a => a, isTextCorrect, q => ftConnections[q.id] || ''); const p3 = partHtml('P3', 'Partie 3 — Étiquette', etiquetteQuestions, 'eti', textAnswerLabel, a => a, isTextCorrect); const w = window.open('', '_blank'); From 0b4c2244febc89006ddc39d8632cd291fb430381 Mon Sep 17 00:00:00 2001 From: "gauthier.chombart" Date: Thu, 16 Jul 2026 09:19:31 +0200 Subject: [PATCH 06/26] =?UTF-8?q?Ajoute=20l'option=20Absent=20=C3=A0=20la?= =?UTF-8?q?=20slide=205=20(FT)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La question sur la compatibilité des matières se relie désormais à Absent, ajouté comme cinquième section (correspondance 1-à-1). --- exercices/Exo_FDS.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercices/Exo_FDS.html b/exercices/Exo_FDS.html index 4dc4c66..1273cdb 100644 --- a/exercices/Exo_FDS.html +++ b/exercices/Exo_FDS.html @@ -734,7 +734,7 @@ const fdsQuestions = [ const ftQuestions = [ { id: 1, text: "Quel est le nom du produit et qui est le fabricant ?", answer: "Description" }, - { id: 2, text: "Sur quelles matières ce produit est-il compatible ?", answer: "Description" }, + { id: 2, text: "Sur quelles matières ce produit est-il compatible ?", answer: "Absent" }, { id: 3, text: "Quel est (sont) le(s) type(s) de ce produit ?", answer: "Propriétés biocides" }, { id: 4, text: "Quels sont les types de conditionnements disponibles ?", answer: "Conditionnements" }, { id: 5, text: "Où retrouver les différents modes d'utilisation de ce produit ?", answer: "Utilisations" } From d351ee6efb7c26b8a4dc22b350be60e0a7af82a4 Mon Sep 17 00:00:00 2001 From: "gauthier.chombart" Date: Thu, 16 Jul 2026 14:36:09 +0200 Subject: [PATCH 07/26] =?UTF-8?q?Refonte=20de=20la=20slide=207=20FDS=20en?= =?UTF-8?q?=20=C3=A9tiquette=20cliquable=20(hotspots)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remplace les menus déroulants de la partie Étiquette par une étiquette DÉS'INFECT reconstituée (Annexe I du guide FHER) avec 9 zones cliquables : info-bulle de justification réglementaire au clic, états visuels correct/incorrect, mise en situation professionnelle en introduction, et écran de synthèse sur le modèle de la check-list FHER p.15 (étiquette annotée + tableau). Export PDF recâblé sur les nouvelles zones. --- exercices/Exo_FDS.html | 532 +++++++++++++++++++++++++++++++++++------ 1 file changed, 461 insertions(+), 71 deletions(-) diff --git a/exercices/Exo_FDS.html b/exercices/Exo_FDS.html index 1273cdb..f881311 100644 --- a/exercices/Exo_FDS.html +++ b/exercices/Exo_FDS.html @@ -502,6 +502,139 @@ select:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px .answer-row { flex-wrap: wrap; } select { width: 100%; min-width: 0; } } + +/* ============ SLIDE 7 — ÉTIQUETTE CLIQUABLE (HOTSPOTS) ============ */ +#slide-7 { justify-content: flex-start; } +#slide-7 .scroll-area { gap: 0.75rem; } + +/* Mise en situation */ +.situation-box { + display: flex; gap: 12px; align-items: flex-start; + max-width: 680px; width: 100%; + background: var(--teal-light); + border: 1px solid var(--teal-mid); + border-left: 4px solid var(--teal-mid); + border-radius: 12px; + padding: 12px 16px; + color: var(--teal-dark); + font-size: 13.5px; line-height: 1.55; + text-align: left; +} +.situation-box .sit-ico { font-size: 22px; line-height: 1.1; flex-shrink: 0; } +.situation-box strong { color: var(--teal-dark); } + +/* Barre de consigne / question courante */ +.eti-prompt { + display: flex; align-items: center; gap: 12px; + flex-wrap: wrap; justify-content: center; + max-width: 680px; width: 100%; + background: #fff; border: 1px solid var(--border); border-radius: 12px; + padding: 10px 16px; text-align: center; +} +.eti-progress { + font-family: 'Syne', sans-serif; font-weight: 800; font-size: 11px; + letter-spacing: 0.04em; text-transform: uppercase; + color: var(--teal-dark); background: var(--teal-light); + padding: 4px 10px; border-radius: 20px; white-space: nowrap; +} +.eti-question { font-size: 14.5px; font-weight: 600; color: var(--text); } + +/* Scène de l'étiquette + popover */ +.eti-stage { position: relative; width: 100%; max-width: 680px; display: flex; justify-content: center; } + +/* Étiquette reconstituée (DÉS'INFECT — Annexe I FHER) */ +.label-card { + position: relative; + width: 100%; max-width: 640px; + background: #fff; color: #1a1a18; + border: 1px solid #d9d2c4; border-radius: 6px; + padding: 16px 18px 0; overflow: hidden; + font-family: 'DM Sans', sans-serif; font-size: 10.5px; line-height: 1.42; + box-shadow: 0 8px 30px rgba(0,0,0,0.10); +} +.lbl-top { display: flex; align-items: center; gap: 12px; border-bottom: 2px solid #1a1a18; padding-bottom: 6px; margin-bottom: 10px; } +.lbl-flag { font-size: 20px; } +.lbl-title { font-family: 'Syne', sans-serif; font-weight: 800; font-size: clamp(24px, 5vw, 40px); letter-spacing: -0.01em; margin: 0; } +.lbl-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; } +.lbl-h { font-family: 'Syne', sans-serif; font-weight: 700; font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.02em; margin-bottom: 2px; } +.lbl-block { margin-bottom: 9px; } +.lbl-small { color: #555; font-size: 9.5px; line-height: 1.6; margin-top: 4px; } +.lbl-bottom { display: grid; grid-template-columns: auto 1fr auto; align-items: end; gap: 14px; margin-top: 4px; padding: 10px 0; } +.lbl-net { display: flex; flex-direction: column; gap: 2px; } +.lbl-net span { font-size: 10px; } +.lbl-net strong { font-family: 'Syne', sans-serif; font-size: 22px; line-height: 1; } +.lbl-barcode { + height: 42px; align-self: end; min-width: 90px; + 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; } +.ghs { + width: 38px; height: 38px; position: relative; + border: 2.5px solid #c62828; background: #fff; + transform: rotate(45deg); + display: flex; align-items: center; justify-content: center; flex-shrink: 0; +} +.ghs span { transform: rotate(-45deg); font-size: 17px; line-height: 1; } +.ghs.excl span { font-family: 'Syne', sans-serif; font-weight: 800; font-size: 24px; color: #1a1a18; } +.lbl-danger { font-family: 'Syne', sans-serif; font-weight: 800; font-size: 15px; color: #1a1a18; letter-spacing: 0.02em; } +.lbl-footer { margin: 6px -18px 0; padding: 8px 18px; background: #2b2b28; color: #e8e2d5; text-align: center; font-size: 9px; line-height: 1.5; } + +/* Zones cliquables (hotspots) */ +.hotspot { + position: relative; cursor: pointer; border-radius: 5px; + outline: 1.5px dashed transparent; outline-offset: 1px; + transition: background 0.15s ease, outline-color 0.15s ease; +} +span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: clone; box-decoration-break: clone; } +.label-card:not(.recap) .hotspot:hover { background: rgba(43,122,120,0.10); outline-color: var(--teal-mid); } +.hotspot.hs-correct { background: rgba(46,125,50,0.14); outline: 1.5px solid #2e7d32; } +.hotspot.hs-wrong { background: rgba(198,40,40,0.16); outline: 1.5px solid #c62828; animation: hsShake 0.4s ease; } +@keyframes hsShake { 0%,100%{transform:translateX(0);} 25%{transform:translateX(-3px);} 75%{transform:translateX(3px);} } +.hs-num { + display: inline-flex; align-items: center; justify-content: center; + width: 16px; height: 16px; border-radius: 50%; + background: #2e7d32; color: #fff; + font-family: 'Syne', sans-serif; font-weight: 800; font-size: 9px; + margin-right: 3px; vertical-align: middle; +} + +/* Info-bulle réglementaire */ +.eti-pop { + position: absolute; z-index: 20; max-width: 290px; width: max-content; + background: #fff; border: 1px solid var(--border); border-radius: 10px; + box-shadow: 0 10px 30px rgba(0,0,0,0.18); + padding: 12px 30px 12px 14px; font-size: 12.5px; line-height: 1.5; + text-align: left; +} +.eti-pop[hidden] { display: none; } +.eti-pop .pop-status { font-family: 'Syne', sans-serif; font-weight: 800; font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 4px; display: flex; align-items: center; gap: 6px; } +.eti-pop.ok .pop-status { color: #2e7d32; } +.eti-pop.ko .pop-status { color: #c62828; } +.eti-pop.info .pop-status { color: var(--teal-dark); } +.eti-pop .pop-title { font-weight: 700; color: var(--text); margin-bottom: 3px; } +.eti-pop .pop-reg { color: var(--text-muted); } +.eti-pop .pop-close { position: absolute; top: 5px; right: 8px; cursor: pointer; color: var(--text-muted); font-size: 16px; line-height: 1; border: none; background: none; padding: 2px; } + +/* Check-list de synthèse (modèle FHER p.15) */ +.checklist-table { + width: 100%; max-width: 780px; border-collapse: collapse; + background: #fff; border: 1px solid var(--border); border-radius: 12px; overflow: hidden; + font-size: 12.5px; margin: 0.25rem 0 1rem; +} +.checklist-table th { background: var(--teal-light); color: var(--teal-dark); font-family: 'Syne', sans-serif; font-weight: 700; font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.03em; padding: 10px 12px; text-align: left; border-bottom: 2px solid var(--teal-mid); } +.checklist-table td { padding: 9px 12px; border-bottom: 1px solid var(--border); color: var(--text-muted); vertical-align: top; } +.checklist-table tr:last-child td { border-bottom: none; } +.checklist-table td.cl-num { text-align: center; } +.cl-badge { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; border-radius: 50%; background: #2e7d32; color: #fff; font-family: 'Syne', sans-serif; font-weight: 800; font-size: 10px; } +.checklist-table strong { color: var(--text); } +.cl-check { color: #2e7d32; font-weight: 700; } +.cl-cross { color: #c62828; font-weight: 700; } + +@media (max-width: 700px) { + .lbl-cols { grid-template-columns: 1fr; } + .lbl-title { font-size: 26px; } +} @@ -652,22 +785,117 @@ select:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px

    L'étiquette est l'interface directe entre le produit et l'utilisateur. Elle communique rapidement les dangers essentiels, les EPI et les consignes d'utilisation.

    🎯 -
    Indiquez la zone de l'étiquette où trouver l'information : Identité, Pictogrammes, Mots d'avertissement, Mentions de danger, Conseils de prudence, Contenu net, Numéro d'urgence — ou « Absent ».
    +
    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.
    - +
    - -
    Zones de l'étiquette
    -
    - -
    -
    - + + +
    + +
    Zones de l'étiquette
    + +
    + 💬 +
    Mise en situation — Vous intervenez chez un client avec un désinfectant de surfaces à usage exclusivement professionnel. Avant l'application, il vous interroge sur les dangers et les précautions du produit. Bidon en main, retrouvez rapidement sur l'étiquette chaque information demandée.
    +
    + +
    + Question 1 / 9 + +
    + +
    +
    + +
    + 🇫🇷 + DÉS'INFECT +
    + +
    + +
    +
    Propriétés :
    Désinfecte et désodorise les sanitaires. Produit strictement destiné à un usage professionnel.
    +
    Mode d'emploi :
    Concentré à diluer : 5 % pour une activité fongicide, 1 % pour une activité bactéricide. Pulvériser sur les parois, laisser agir 5 à 15 min, rincer à l'eau claire. Type de formulation : concentré soluble (SL). Utilisable en TP2.
    +
    UFI : 1234-1234-1234-1234
    Date de péremption : MM/AAAA
    N° LOT : 123456
    +
    + + +
    +
    Substances actives :
    Chlorure de didécyldiméthylammonium (N°CAS 7173-51-5) à 75 g/kg ; acide glycolique (CAS 79-14-1) à 35 g/kg.
    +
    +
    Précautions d'emploi :
    + Produit strictement professionnel (FR) — Lire le mode d'emploi ci-joint avant l'emploi. + Formule déposée au centre anti-poison de Nancy : 03 83 22 50 50. + + H302 Nocif en cas d'ingestion. H314 Provoque de graves brûlures de la peau et des lésions oculaires. H410 Très toxique pour les organismes aquatiques, entraîne des effets néfastes à long terme. + + P280 Porter des gants de protection, des vêtements de protection et un équipement de protection du visage. + + P301+P330+P331 EN CAS D'INGESTION : rincer la bouche, ne pas faire vomir. P305+P351+P338 EN CAS DE CONTACT AVEC LES YEUX : rincer avec précaution à l'eau pendant plusieurs minutes. P310 Appeler immédiatement un CENTRE ANTIPOISON ou un médecin. + + P501 Éliminer le contenu / le récipient dans un point de collecte des déchets spéciaux ou dangereux. +
    +
    +
    + + +
    + + Volume net :5 L + + +
    + + + 🐟 + 🧪 + ! + + + DANGER +
    +
    + + +
    + + +
    + +
    +
    + + +
    + + + +
    @@ -741,19 +969,41 @@ const ftQuestions = [ ]; const ftOptions = ["Description", "Propriétés biocides", "Conditionnements", "Utilisations"]; -const etiquetteQuestions = [ - { id: 1, text: "Quel est le nom du produit et le nom de l'entreprise ?", answer: "Identité" }, - { id: 2, text: "Quels pictogrammes de danger sont affichés ?", answer: "Pictogrammes" }, - { id: 3, text: "Quels sont les mots d'avertissement (« Attention » ou « Danger ») ?", answer: "Mots d'avertissement" }, - { id: 4, text: "Quelles sont les mentions de danger (H-statements) identifiées ?", answer: "Mentions de danger" }, - { id: 5, text: "Quels équipements de protection individuelle (EPI) sont recommandés ?", answer: "Conseils de prudence" }, - { id: 6, text: "Quels premiers secours sont indiqués ?", answer: "Conseils de prudence" }, - { id: 7, text: "Comment éliminer l'emballage après utilisation ?", answer: "Conseils de prudence" }, - { id: 8, text: "Quel est le contenu net du produit ?", answer: "Contenu net" }, - { id: 9, text: "Y a-t-il un numéro d'appel d'urgence mentionné ?", answer: "Numéro d'urgence" }, - { id: 10, text: "Quelles consignes de stockage sont affichées ?", answer: "Conseils de prudence" } +/* 9 zones réglementaires de l'étiquette (ordre canonique = check-list FHER p.15). + zone : identifiant du hotspot correspondant dans l'étiquette + text : consigne affichée pendant l'exercice + label : intitulé check-list (réutilisé dans la synthèse et le PDF) + reg : justification réglementaire (info-bulle) — Guide FHER, Partie 3, p.10-12 */ +const etiZones = [ + { zone: "z-nom", text: "Retrouvez le nom commercial du produit.", + label: "Nom commercial", + reg: "Obligation CLP : le nom commercial doit être identique à celui figurant sur la FDS (Guide FHER, Partie 3 — I.2)." }, + { zone: "z-picto", text: "Cliquez sur les pictogrammes de danger.", + label: "Pictogrammes de danger", + reg: "CLP : pictogrammes aux dimensions requises, regroupés avec la mention d'avertissement et les phrases H/P, se détachant nettement du fond (Partie 3 — I.5)." }, + { zone: "z-mention", text: "Où figure la mention d'avertissement ?", + label: "Mention d'avertissement", + reg: "CLP : « Danger » ou « Attention », disposée dans l'encart d'étiquetage avec pictogrammes et phrases H/P (Partie 3 — I.5)." }, + { zone: "z-h", text: "Repérez les mentions de danger (phrases H).", + label: "Mentions de danger (phrases H)", + reg: "CLP : les phrases H décrivent la nature du danger ; regroupées dans l'encart d'étiquetage (Partie 3 — I.5)." }, + { zone: "z-p-epi", text: "Où sont indiqués les EPI à porter (phrases P) ?", + label: "Conseils de prudence — EPI", + reg: "CLP : conseils de prudence P (ici P280). Six conseils P au maximum, sauf si la nature et la gravité des dangers l'exigent (CLP Art. 28-3) — Partie 3, I.5." }, + { zone: "z-p-secours", text: "Où trouver les instructions de premiers secours ?", + label: "Premiers secours (phrases P)", + reg: "BPR : indications relatives aux premiers soins (Partie 3 — II.8), traduites par les conseils P 3xx (contact, ingestion, inhalation)." }, + { zone: "z-p-elim", text: "Où sont les consignes d'élimination de l'emballage ?", + label: "Élimination du produit et de l'emballage", + reg: "BPR : instructions pour l'élimination en toute sécurité du produit et de son emballage, le cas échéant avec interdiction de le réutiliser (Partie 3 — II.10). Ici P501." }, + { zone: "z-net", text: "Retrouvez le contenu net du produit.", + label: "Contenu net", + reg: "Quantité nette de produit contenue dans l'emballage — mention générale du pavé d'information produit." }, + { zone: "z-antipoison", text: "Où figurent les coordonnées du centre antipoison ?", + label: "Centre antipoison / n° d'urgence", + reg: "Coordonnées d'urgence complétant les instructions de premiers soins (Partie 3 — II.8) ; les mélanges dangereux sont déclarés aux centres antipoison (annexe VIII du CLP)." } ]; -const etiquetteOptions = ["Identité", "Pictogrammes", "Mots d'avertissement", "Mentions de danger", "Conseils de prudence", "Contenu net", "Numéro d'urgence"]; +function etiZoneById(id) { return etiZones.find(z => z.zone === id); } function shuffleArray(array) { const a = [...array]; @@ -764,17 +1014,6 @@ function shuffleArray(array) { return a; } -function buildRow(qNum, selectId, answerAttr, text, options, badgeStyle) { - let opts = ''; - options.forEach(o => { opts += ``; }); - return ` -
    -
    Q${qNum}
    -
    ${text}
    - -
    `; -} - function generateQuizzes() { // FDS — glisser-déposer : banques d'étiquettes + cases de dépôt buildFdsDnd(); @@ -782,17 +1021,183 @@ function generateQuizzes() { // FT — exercice « relier » (questions à gauche, sections à droite) buildFtMatch(); - // Étiquette - const etiOpts = etiquetteOptions.map(o => ({ value: o, label: o })); - etiOpts.push({ value: 'absent', label: 'Absent' }); - const etiC = document.getElementById('etiquette-questions'); - etiC.innerHTML = ''; - etiquetteQuestions.forEach((q, i) => { - etiC.innerHTML += buildRow(i + 1, 'eti' + q.id, q.answer, q.text, etiOpts, 'background:var(--teal-light);color:var(--teal-dark);'); - }); + // Étiquette — exercice à zones cliquables (hotspots) + buildEtiquette(); +} - // Bloque la vérification tant que toutes les questions ne sont pas répondues - wireVerifyGate('etiquette-questions', 'eti-verify'); +/* ---- Étiquette : zones cliquables ---- */ +let etiOrder = []; // ordre aléatoire des questions +let etiPos = 0; // question courante +let etiAnswers = {}; // { zoneCible : zoneCliquée en 1re tentative } — pour le PDF +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 + +function buildEtiquette() { + etiOrder = shuffleArray(etiZones.map((_, i) => i)); + etiPos = 0; + etiAnswers = {}; + etiFirstTry = {}; + etiCurError = false; + + const card = document.getElementById('labelCard'); + card.querySelectorAll('.hotspot').forEach(h => h.classList.remove('hs-correct', 'hs-wrong')); + + closeEtiPop(); + const r = document.getElementById('etiquette-result'); + r.className = 'result-message'; r.innerHTML = ''; + document.getElementById('eti-recap-btn').disabled = true; + document.getElementById('eti-exercise').style.display = 'flex'; + document.getElementById('eti-recap').style.display = 'none'; + + if (!etiInit) { + card.addEventListener('click', onEtiClick); + etiInit = true; + } + renderEtiQuestion(); +} + +function renderEtiQuestion() { + const prog = document.getElementById('eti-progress'); + const ques = document.getElementById('eti-question'); + if (etiPos >= etiOrder.length) { + prog.textContent = 'Terminé'; + ques.textContent = '✓ Les 9 zones ont été identifiées.'; + return; + } + const z = etiZones[etiOrder[etiPos]]; + prog.textContent = 'Question ' + (etiPos + 1) + ' / ' + etiZones.length; + ques.textContent = z.text; +} + +function onEtiClick(e) { + const hs = e.target.closest('.hotspot'); + if (!hs) return; + const clicked = hs.dataset.zone; + + // Exploration libre une fois l'exercice terminé + if (etiPos >= etiOrder.length) { + openEtiPop(hs, 'info', clicked); + return; + } + + const target = etiZones[etiOrder[etiPos]]; + if (clicked === target.zone) { + hs.classList.remove('hs-wrong'); + 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(); + if (etiPos >= etiOrder.length) finishEtiquette(); + } else { + if (etiAnswers[target.zone] === undefined) etiAnswers[target.zone] = clicked; // 1re réponse (erronée) mémorisée + etiCurError = true; + etiFirstTry[target.zone] = false; + 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; +} + +/* Info-bulle réglementaire positionnée près de la zone cliquée */ +function openEtiPop(hs, kind, clickedZone) { + const pop = document.getElementById('eti-pop'); + const stage = document.getElementById('eti-stage'); + const z = etiZoneById(clickedZone); + + const statusEl = document.getElementById('pop-status'); + pop.className = 'eti-pop ' + kind; + if (kind === 'ok') statusEl.innerHTML = '✅ Bonne zone'; + else if (kind === 'ko') statusEl.innerHTML = '❌ Ce n\'est pas la bonne zone'; + else statusEl.innerHTML = '🔎 ' + z.label; + + document.getElementById('pop-title').textContent = z.label; + document.getElementById('pop-reg').textContent = z.reg; + + pop.hidden = false; + // Positionnement relatif à la scène + const sr = stage.getBoundingClientRect(); + const hr = hs.getBoundingClientRect(); + const popW = pop.offsetWidth, popH = pop.offsetHeight; + let left = hr.left - sr.left + hr.width / 2 - popW / 2; + left = Math.max(4, Math.min(left, sr.width - popW - 4)); + let top = hr.bottom - sr.top + 8; + if (top + popH > sr.height) top = hr.top - sr.top - popH - 8; // bascule au-dessus si déborde + if (top < 0) top = 4; + pop.style.left = left + 'px'; + pop.style.top = top + 'px'; +} + +function closeEtiPop() { + const pop = document.getElementById('eti-pop'); + if (pop) pop.hidden = true; +} + +function resetEtiquette() { buildEtiquette(); } + +/* ---- Synthèse : étiquette annotée + check-list ---- */ +function showEtiRecap() { + closeEtiPop(); + const stage = document.getElementById('recap-stage'); + stage.innerHTML = ''; + + // Clone de l'étiquette, toutes zones en « correct » + numéros + const clone = document.getElementById('labelCard').cloneNode(true); + clone.removeAttribute('id'); + clone.classList.add('recap'); + clone.querySelectorAll('.hotspot').forEach(h => { + h.classList.remove('hs-wrong'); + h.classList.add('hs-correct'); + const idx = etiZones.findIndex(z => z.zone === h.dataset.zone); + if (idx >= 0) { + const num = document.createElement('b'); + num.className = 'hs-num'; + num.textContent = idx + 1; + h.insertBefore(num, h.firstChild); + } + }); + stage.appendChild(clone); + + // Table check-list + const tbl = document.getElementById('checklist-table'); + let rows = 'N°Élément d\'étiquetageBase réglementaireStatut'; + etiZones.forEach((z, i) => { + const ok = etiFirstTry[z.zone]; + const answered = etiAnswers[z.zone] !== undefined; + const statut = ok + ? '✓ trouvé' + : (answered ? '✗ à revoir' : ''); + rows += ` + ${i + 1} + ${z.label} + ${z.reg} + ${statut} + `; + }); + rows += ''; + tbl.innerHTML = rows; + + document.getElementById('eti-exercise').style.display = 'none'; + document.getElementById('eti-recap').style.display = 'flex'; + const sa = document.querySelector('#slide-7 .scroll-area'); + if (sa) sa.scrollTop = 0; +} + +function hideEtiRecap() { + document.getElementById('eti-recap').style.display = 'none'; + document.getElementById('eti-exercise').style.display = 'flex'; + const sa = document.querySelector('#slide-7 .scroll-area'); + if (sa) sa.scrollTop = 0; } /* ---- FT : exercice « relier » ---- */ @@ -1024,22 +1429,6 @@ function wireVerifyGate(containerId, btnId) { } /* ======================== VÉRIFICATION ======================== */ -function showInlineResult(selectEl, isCorrect, isUnanswered, correctAnswerText) { - const row = selectEl.closest('.answer-row'); - row.querySelector('.answer-label')?.remove(); - row.querySelector('.answer-icon')?.remove(); - - const label = document.createElement('div'); - label.className = 'answer-label'; - label.textContent = correctAnswerText; - selectEl.before(label); - - const icon = document.createElement('div'); - icon.className = 'answer-icon'; - icon.textContent = isUnanswered ? '❓' : (isCorrect ? '✅' : '❌'); - selectEl.before(icon); -} - function showScore(resultDiv, correct, total) { const p = Math.round((correct / total) * 100); if (p === 100) { @@ -1102,18 +1491,6 @@ function verifyFT() { showScore(document.getElementById('ft-result'), correct, ftQuestions.length); } -function verifyEtiquette() { - let correct = 0; - etiquetteQuestions.forEach(q => { - const select = document.getElementById('eti' + q.id); - const userAnswer = select.value; - const isCorrect = userAnswer === q.answer; - if (isCorrect) correct++; - showInlineResult(select, isCorrect, userAnswer === '', q.answer); - }); - showScore(document.getElementById('etiquette-result'), correct, etiquetteQuestions.length); -} - /* ======================== EXPORT PDF ======================== */ function escapeHtml(str) { if (!str) return ''; @@ -1168,7 +1545,20 @@ function exporterPDF() { const p1 = partHtml('P1', 'Partie 1 — FDS (Fiche de Données de Sécurité)', fdsQuestions, 'fds', fdsAnswerLabel, fdsExpectedLabel, isFdsCorrect); const p2 = partHtml('P2', 'Partie 2 — FT (Fiche Technique)', ftQuestions, 'ft', textAnswerLabel, a => a, isTextCorrect, q => ftConnections[q.id] || ''); - const p3 = partHtml('P3', 'Partie 3 — Étiquette', etiquetteQuestions, 'eti', textAnswerLabel, a => a, isTextCorrect); + + // Partie 3 — Étiquette (zones cliquables) : ordre check-list + const p3rows = etiZones.map((z, i) => { + const given = etiAnswers[z.zone]; + const ok = given === z.zone; + nbTotal++; if (ok) scoreTotal++; + const givenLabel = given === undefined ? '(non répondu)' : (etiZoneById(given) ? etiZoneById(given).label : given); + const mark = given === undefined ? '❓' : (ok ? '✓' : '✗'); + return `
    +
    [P3-Q${i + 1}] ${escapeHtml(z.text)}
    +
    Zone désignée : ${escapeHtml(givenLabel)} — Attendu : ${escapeHtml(z.label)} — ${mark}
    +
    `; + }).join(''); + const p3 = `

    Partie 3 — Étiquette (zones réglementaires)

    ${p3rows}`; const w = window.open('', '_blank'); w.document.write(` From a2a4fea0047dccb52759b0c6b5a15cac6d3f6988 Mon Sep 17 00:00:00 2001 From: "gauthier.chombart" Date: Fri, 17 Jul 2026 17:50:22 +0200 Subject: [PATCH 08/26] =?UTF-8?q?Retravaille=20les=20exercices=20FDS/FT/?= =?UTF-8?q?=C3=89tiquettes=20(slides=201,=203,=205,=207)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Slide 1 : blocage de la navigation tant que le nom du stagiaire/groupe n'est pas renseigné, et choix du formateur via boutons Nathan/Gauthier. Slide 3 : étiquettes renommées « Rubrique 1…16 » (au lieu de « R… ») et élargies. Slide 5 : suppression du tag « Partie 2/3 — FT ». Slide 7 : suppression du tag « Partie 3/3 — Étiquette » ; l'encadré de réponse s'affiche à côté de l'étiquette (plus en superposition) ; le récapitulatif fait glisser l'étiquette à gauche et affiche le tableau de réponses à sa droite. --- exercices/Exo_FDS.html | 131 +++++++++++++++++++++++++++++++++-------- 1 file changed, 106 insertions(+), 25 deletions(-) diff --git a/exercices/Exo_FDS.html b/exercices/Exo_FDS.html index f881311..862c66b 100644 --- a/exercices/Exo_FDS.html +++ b/exercices/Exo_FDS.html @@ -229,8 +229,8 @@ .dnd-bank { display: grid; grid-template-columns: 1fr; gap: 6px; align-content: start; } .dnd-tile { font-family: 'Syne', sans-serif; - font-size: 12px; font-weight: 800; - padding: 8px 10px; min-width: 66px; text-align: center; + font-size: 12px; font-weight: 800; white-space: nowrap; + padding: 8px 12px; min-width: 92px; text-align: center; border-radius: 8px; background: var(--amber-light); color: var(--amber-dark); border: 1px solid var(--amber); @@ -261,7 +261,7 @@ .drop-slot.slot-wrong { border-color: #c62828; color: #c62828; } @media (max-width: 900px) { .dnd-layout { grid-template-columns: 1fr; } - .dnd-bank { grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); } + .dnd-bank { grid-template-columns: repeat(auto-fill, minmax(92px, 1fr)); } } /* SLIDE 5 — RELIER (MATCHING) */ .match-hint { font-size: 12.5px; color: var(--text-muted); text-align: center; margin: -0.4rem 0 0.1rem; max-width: 640px; line-height: 1.5; } @@ -635,6 +635,60 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl .lbl-cols { grid-template-columns: 1fr; } .lbl-title { font-size: 26px; } } + +/* ---- SLIDE 1 — sélection formateur + garde nom ---- */ +.formateur-label { font-size: 11px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-muted); margin: 4px 0 2px; } +.formateur-row { display: flex; gap: 8px; } +.formateur-btn { + flex: 1; font-family: 'DM Sans', sans-serif; font-size: 13.5px; + padding: 9px 13px; border: 1px solid var(--border); border-radius: 10px; + background: var(--bg); color: var(--text); cursor: pointer; transition: all 0.2s ease; +} +.formateur-btn:hover { border-color: var(--amber); } +.formateur-btn.active { background: var(--amber); border-color: var(--amber); color: #fff; } +.identity-error { color: var(--coral); font-size: 12px; margin-top: 8px; line-height: 1.4; } +.identity-error[hidden] { display: none; } + +/* ---- 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; + 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; } +.recap-layout #recap-stage { flex: 1 1 0; min-width: 0; max-width: 560px; } +.recap-layout .checklist-table { flex: 1 1 0; min-width: 0; margin: 0; } +.recap-layout.animate #recap-stage { animation: recapSlideLeft 0.5s ease both; } +.recap-layout.animate .checklist-table { animation: recapSlideRight 0.5s ease both; } +@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%; } + .recap-layout .checklist-table { flex: none; width: 100%; max-width: 640px; } +} @@ -706,12 +760,18 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
    👥 Identification du groupe
    - + - + +
    Formateur de la session
    +
    + + +
    +
    - +
    @@ -760,7 +820,6 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
    -
    Sections de la fiche technique

    Reliez chaque question à la bonne section : cliquez sur un point à gauche, maintenez, puis glissez jusqu'au point de la section correspondante à droite.

    @@ -796,7 +855,6 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
    -
    Zones de l'étiquette
    @@ -865,6 +923,8 @@ 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.
    + @@ -503,7 +503,7 @@
    Recherche par type de produit
    -

    Explorez les catégories disponibles à l'aide des filtres par code TP (Type Product).

    +

    Explorez les catégories disponibles à l'aide des filtres par TP.

    • 1Utilisez le filtre « Type de produit » et sélectionnez « Rodenticides (TP14) » ou « Insecticides (TP18) ». Combien de produits sont disponibles ?
      @@ -514,10 +514,6 @@
    -
    - - -
    @@ -537,10 +533,6 @@ -
    - - -
    @@ -567,9 +559,6 @@ -
    - -
    @@ -606,6 +595,14 @@ let currentSlide = 0; let history = []; const totalSlides = 6; +// Configuration des boutons du footer par slide +const slideConfig = { + 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: 'Voir les bonnes pratiques →' } +}; + const hints = { ex1: { icon: '🔎', @@ -652,28 +649,33 @@ function goBack() { updateUI(); } +function goNext() { + const cfg = slideConfig[currentSlide]; + if (cfg && cfg.next != null) 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 ? 'block' : 'none'; + document.getElementById('backBtn').style.display = history.length > 0 ? 'inline-flex' : 'none'; - const badge = document.getElementById('phaseBadge'); - if (currentSlide >= 2 && currentSlide <= 5) { - badge.style.display = 'block'; - if (currentSlide === 5) { - badge.style.background = 'var(--coral-light)'; - badge.style.color = 'var(--coral)'; - badge.textContent = 'Cas de synthèse'; - } else { - badge.style.background = 'var(--amber-light)'; - badge.style.color = 'var(--amber-dark)'; - badge.textContent = 'Exercice ' + (currentSlide - 1) + ' / 4'; - } + 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 { - badge.style.display = 'none'; + nextBtn.style.display = 'none'; } } From 66ed3c505fb522e56ccf50e74da3ebae0169baf3 Mon Sep 17 00:00:00 2001 From: "gauthier.chombart" Date: Sun, 19 Jul 2026 16:53:32 +0200 Subject: [PATCH 10/26] Uniformise les boutons du footer BioCID et ajoute des confettis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bouton 'Commencer l'exercice 1' déplacé de la slide 1 vers la bottom bar - Nouveau style .footer-btn uniforme pour les trois boutons (is-primary / is-secondary) - Effet de confettis au canvas déclenché à l'arrivée sur la slide 6 (comme le TP Nuisibles) --- exercices/Exo_BioCID.html | 104 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 100 insertions(+), 4 deletions(-) diff --git a/exercices/Exo_BioCID.html b/exercices/Exo_BioCID.html index 157ec57..edb93f1 100644 --- a/exercices/Exo_BioCID.html +++ b/exercices/Exo_BioCID.html @@ -53,6 +53,26 @@ 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; @@ -418,12 +438,15 @@ + + + -
    @@ -597,6 +619,7 @@ 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 →' }, @@ -634,6 +657,7 @@ function goTo(n) { const sa = next.querySelector('.scroll-area'); if (sa) sa.scrollTop = 0; updateUI(); + if (n === 6) setTimeout(launchConfetti, 200); } function goBack() { @@ -704,6 +728,78 @@ function restartAll() { 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, '&').replace(//g, '>'); From d251782842ea4213db9489c8946c61efac1b2422 Mon Sep 17 00:00:00 2001 From: "gauthier.chombart" Date: Sun, 19 Jul 2026 17:08:59 +0200 Subject: [PATCH 11/26] Ajoute une page unique de correction des exercices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- exercices/Exo_BioCID.html | 5 +- exercices/correction-biocid.html | 18 ++ exercices/correction-fds.html | 18 ++ exercices/correction-tp-nuisibles.html | 18 ++ exercices/corriger-exercice.html | 317 +++++++++++++++++++++++++ index.html | 13 + 6 files changed, 385 insertions(+), 4 deletions(-) create mode 100644 exercices/corriger-exercice.html diff --git a/exercices/Exo_BioCID.html b/exercices/Exo_BioCID.html index edb93f1..4093958 100644 --- a/exercices/Exo_BioCID.html +++ b/exercices/Exo_BioCID.html @@ -603,10 +603,7 @@
    - ✔ Corriger un rendu -
    -
    diff --git a/exercices/correction-biocid.html b/exercices/correction-biocid.html index a7784af..9b3f242 100644 --- a/exercices/correction-biocid.html +++ b/exercices/correction-biocid.html @@ -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); } +})(); diff --git a/exercices/correction-fds.html b/exercices/correction-fds.html index 6003004..68b90c7 100644 --- a/exercices/correction-fds.html +++ b/exercices/correction-fds.html @@ -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); } +})(); diff --git a/exercices/correction-tp-nuisibles.html b/exercices/correction-tp-nuisibles.html index 8355993..1034bd1 100644 --- a/exercices/correction-tp-nuisibles.html +++ b/exercices/correction-tp-nuisibles.html @@ -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); } +})(); diff --git a/exercices/corriger-exercice.html b/exercices/corriger-exercice.html new file mode 100644 index 0000000..cd004d7 --- /dev/null +++ b/exercices/corriger-exercice.html @@ -0,0 +1,317 @@ + + + + + +Corriger un exercice — Certibiocide + + + + + +
    + + +
    + +
    +
    Certibiocide — Correction des rendus
    +

    Corriger un exercice

    +

    Sélectionnez l'exercice concerné, puis déposez le fichier PDF exporté par le groupe. La lecture du rendu s'ouvre automatiquement.

    + +

    1 Choisissez l'exercice à corriger

    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + +

    2 Déposez le rendu PDF

    +
    +
    📄
    +

    Déposez le PDF du groupe ici

    +

    ou cliquez pour sélectionner le fichier exporté depuis l'exercice

    + +
    Sélectionnez d'abord un exercice ci-dessus.
    +
    +
    + +
    +

    © 2026 Gauthier Chombart & Nathan Chauwin
    Tous droits réservés — Formations Certibiocide & Techniques — Formateurs indépendants

    +

    Tous droits réservés — Gauthier Chombart & Nathan Chauwin — Formateurs indépendants

    +
    + + + + diff --git a/index.html b/index.html index cfdf9a5..b72dd84 100644 --- a/index.html +++ b/index.html @@ -137,6 +137,19 @@ Jouer + + +
    +
    +
    ✔️
    +

    Corriger un exercice

    +

    Correction des rendus

    +
    +
    +

    Déposez le PDF exporté par un groupe et obtenez la lecture de son rendu. Page unique de correction pour l'ensemble des exercices.

    + Corriger +
    +
    From 41d8b98f96ba3449d261dd4308ec28c508bba1bc Mon Sep 17 00:00:00 2001 From: "gauthier.chombart" Date: Sun, 19 Jul 2026 17:15:34 +0200 Subject: [PATCH 12/26] Ajuste les slides 1 et 5 de l'exercice BioCID MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Slide 1 : encadrés '4 exercices' et 'Plateforme BioCID' inversés et mis à la même largeur - Slide 1 : passage à l'exercice 1 bloqué tant que le nom du groupe n'est pas renseigné (message d'alerte) - Slide 5 : titre 'Mise en situation réelle' -> 'Mise en situation' - Slide 5 : reformulation de la mise en situation (restaurant / choix entre plusieurs références) - Slide 5 : bouton 'Voir les bonnes pratiques' -> 'Terminer l'exercice' --- exercices/Exo_BioCID.html | 41 ++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/exercices/Exo_BioCID.html b/exercices/Exo_BioCID.html index 4093958..563e1db 100644 --- a/exercices/Exo_BioCID.html +++ b/exercices/Exo_BioCID.html @@ -475,7 +475,11 @@
    Contexte & accès

    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.

    -
    +
    + 🎯 +
    4 exercices vous attendent : recherche par nom, par type, par substance active, puis un cas de synthèse. Durée totale ≈ 30 min.
    +
    +
    🌐
    Plateforme BioCID — ANSES
    @@ -483,17 +487,14 @@
    Ouvrir ↗
    -
    - 🎯 -
    4 exercices vous attendent : recherche par nom, par type, par substance active, puis un cas de synthèse. Durée totale ≈ 30 min.
    -
    -
    +
    👥 Identification du groupe
    - +
    +
    @@ -562,10 +563,10 @@
    -
    Mise en situation réelle
    +
    Mise en situation
    📞 -
    Un restaurant vous appelle pour une demande de produit. En utilisant BioCID, présentez une recommandation argumentée.
    +
    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 recommandation argumentée.
    • @@ -620,7 +621,7 @@ const slideConfig = { 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: 'Voir les bonnes pratiques →' } + 5: { next: 6, nextLabel: "Terminer l'exercice →" } }; const hints = { @@ -670,9 +671,27 @@ function goBack() { 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) goTo(cfg.next); + 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() { From 30817c829be3b8955b47019b48f2750fbf4cef56 Mon Sep 17 00:00:00 2001 From: "gauthier.chombart" Date: Sun, 19 Jul 2026 17:59:02 +0200 Subject: [PATCH 13/26] =?UTF-8?q?Retravaille=20l'exercice=20FDS=20:=20bout?= =?UTF-8?q?ons,=20=C3=A9tiquette=20et=20r=C3=A9capitulatif?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- data/ghs05-corrosif.jpg | Bin 0 -> 7677 bytes data/ghs07-nocif.jpg | Bin 0 -> 6745 bytes data/ghs09-environnement.jpg | Bin 0 -> 7851 bytes exercices/Exo_FDS.html | 287 +++++++++++++++++++++++------------ index.html | 2 +- 5 files changed, 193 insertions(+), 96 deletions(-) create mode 100644 data/ghs05-corrosif.jpg create mode 100644 data/ghs07-nocif.jpg create mode 100644 data/ghs09-environnement.jpg diff --git a/data/ghs05-corrosif.jpg b/data/ghs05-corrosif.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a62caab6735e8f05c44d8fddec6f12e06ff9bf02 GIT binary patch literal 7677 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 literal 0 HcmV?d00001 diff --git a/data/ghs07-nocif.jpg b/data/ghs07-nocif.jpg new file mode 100644 index 0000000000000000000000000000000000000000..099549edbcf4bdf53707c7889b61ffc80f6d05ae GIT binary patch literal 6745 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
      - - - + + +
      @@ -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.

      -
    @@ -736,6 +744,10 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
    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
    -
    @@ -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.
    - @@ -796,12 +802,7 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
    -
    -
    - - -
    @@ -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 ».
    - @@ -827,12 +827,7 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
    -
    -
    - - -
    @@ -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.
    - @@ -911,9 +905,9 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
    - 🐟 - 🧪 - ! + Pictogramme SGH09 — danger pour le milieu aquatique + Pictogramme SGH05 — corrosif + Pictogramme SGH07 — nocif / irritant DANGER @@ -923,21 +917,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.
    - -
    -
    - - -
    @@ -950,11 +932,6 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
    - -
    - - -
    @@ -979,19 +956,10 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl ÉtiquetteDangers essentiels, interface rapideÀ chaque prise du produitTous 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.
    • -
    ✔ Corriger un rendu -
    -
    - Retour aux exercices
    @@ -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 @@
    -
    ✔️
    +

    Corriger un exercice

    Correction des rendus

    From affa042c051e9371a37b53c019fd6ec646c1530c Mon Sep 17 00:00:00 2001 From: "gauthier.chombart" Date: Sun, 19 Jul 2026 18:17:56 +0200 Subject: [PATCH 14/26] =?UTF-8?q?Aligne=20les=20slides=20d'intro=20et=20la?= =?UTF-8?q?=20bottom=20bar=20sur=20le=20mod=C3=A8le=20BioCID?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FDS : bottom bar refaite façon BioCID (84 px, compteur à gauche, boutons pilule centrés en style footer-btn, bouton retour intégré, masquée sur l'accueil) - FDS : bouton "Démarrer le TD" remis dans la slide d'accueil et 2e cercle déco passé en ambre - TP_Nuisibles : formulaire d'identification encadré dans une carte blanche comme la carte identité BioCID --- exercices/Exo_FDS.html | 110 +++++++++++++++++++----------------- exercices/TP_Nuisibles.html | 42 +++++++++----- 2 files changed, 85 insertions(+), 67 deletions(-) diff --git a/exercices/Exo_FDS.html b/exercices/Exo_FDS.html index a7fca5c..8975baa 100644 --- a/exercices/Exo_FDS.html +++ b/exercices/Exo_FDS.html @@ -17,7 +17,7 @@ justify-content: center; padding: 2rem; padding-top: calc(70px + 2rem); - padding-bottom: calc(52px + 2rem); + padding-bottom: calc(84px + 2rem); opacity: 0; transition: opacity 0.4s ease; overflow-y: auto; @@ -46,58 +46,62 @@ z-index: 100; } -/* BOTTOM BAR */ +/* BOTTOM BAR (modèle BioCID) */ .bottom-bar { position: fixed; bottom: 0; left: 0; right: 0; - height: 52px; + height: 84px; background: rgba(250, 238, 218, 0.98); border-top: 2px solid var(--amber); display: flex; align-items: center; - justify-content: space-between; + justify-content: center; padding: 0 20px; z-index: 100; backdrop-filter: blur(10px); } -.slide-counter { font-size: 12px; font-family: 'Syne', sans-serif; font-weight: 600; color: var(--amber-dark); } -.back-btn { - background: none; - border: 0.5px solid var(--amber); - border-radius: 20px; - padding: 6px 16px; +.slide-counter { + position: absolute; + left: 20px; font-size: 12px; + font-family: 'Syne', sans-serif; + font-weight: 600; 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); } - /* 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; + gap: 12px; + flex-wrap: wrap; + justify-content: center; } -.bar-actions .btn { - padding: 8px 22px; +.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; + text-decoration: none; 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; } +.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); } +.footer-btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; pointer-events: none; } +@media (max-width: 560px) { + .slide-counter { display: none; } + .footer-btn { padding: 9px 16px; font-size: 12px; } } /* BUTTONS */ @@ -236,7 +240,7 @@ #slide-3 { justify-content: flex-start; padding-top: calc(70px + 0.9rem); - padding-bottom: calc(52px + 0.6rem); + padding-bottom: calc(84px + 0.6rem); } #slide-3 .slide-title { margin: 0.1rem 0 0.15rem; font-size: clamp(19px, 2.4vw, 26px); } .dnd-hint { font-size: 12.5px; color: var(--text-muted); text-align: center; margin: 0 0 10px; } @@ -719,9 +723,8 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl @@ -730,11 +733,12 @@ 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.

    +
    @@ -1666,7 +1670,7 @@ function goBack() { function barBtn(bar, html, cls, fn, opts) { opts = opts || {}; const b = document.createElement('button'); - b.className = 'btn ' + cls; + b.className = 'footer-btn ' + cls; b.innerHTML = html; if (opts.id) b.id = opts.id; if (opts.disabled) b.disabled = true; @@ -1679,39 +1683,40 @@ function renderBarActions() { const bar = document.getElementById('barActions'); if (!bar) return; bar.innerHTML = ''; + // Bouton retour intégré au groupe centré (sauf sur le récapitulatif, qui a son propre retour) + if (history.length > 0 && !(currentSlide === 7 && etiRecapShown)) { + barBtn(bar, '← Retour', 'is-secondary', goBack); + } 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); + barBtn(bar, 'Commencer la partie 1 →', 'is-primary', startPart1); break; case 2: - barBtn(bar, 'Répondre aux questions →', 'btn-amber', () => goTo(3)); + barBtn(bar, 'Répondre aux questions →', 'is-primary', () => goTo(3)); break; case 3: - barBtn(bar, '↺ Recommencer', 'btn-outline', resetFDS); - barBtn(bar, 'Passer à la partie 2 →', 'btn-coral', () => goTo(4), { id: 'fds-next' }); + barBtn(bar, '↺ Recommencer', 'is-secondary', resetFDS); + barBtn(bar, 'Passer à la partie 2 →', 'is-primary', () => goTo(4), { id: 'fds-next' }); updateFdsGate(); break; case 4: - barBtn(bar, 'Répondre aux questions →', 'btn-coral', () => goTo(5)); + barBtn(bar, 'Répondre aux questions →', 'is-primary', () => goTo(5)); break; case 5: - barBtn(bar, '↺ Recommencer', 'btn-outline', resetFT); - barBtn(bar, 'Passer à la partie 3 →', 'btn-teal', () => goTo(6), { id: 'ft-next' }); + barBtn(bar, '↺ Recommencer', 'is-secondary', resetFT); + barBtn(bar, 'Passer à la partie 3 →', 'is-primary', () => goTo(6), { id: 'ft-next' }); refreshFtState(); break; case 6: - barBtn(bar, 'Répondre aux questions →', 'btn-teal', () => goTo(7)); + barBtn(bar, 'Répondre aux questions →', 'is-primary', () => 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)); + barBtn(bar, '← Retour à l\'exercice', 'is-secondary', hideEtiRecap); + barBtn(bar, 'Voir la synthèse générale →', 'is-primary', () => goTo(8)); } else { - barBtn(bar, '↺ Recommencer', 'btn-outline', resetEtiquette); - barBtn(bar, 'Terminer l\'exercice →', 'btn-teal', showEtiRecap, + barBtn(bar, '↺ Recommencer', 'is-secondary', resetEtiquette); + barBtn(bar, 'Terminer l\'exercice →', 'is-primary', showEtiRecap, { id: 'eti-recap-btn', disabled: etiPos < etiOrder.length }); } break; @@ -1726,8 +1731,7 @@ function updateUI() { currentSlide === 0 ? '0%' : ((currentSlide / totalSlides) * 100) + '%'; document.getElementById('slideCounter').textContent = currentSlide === 0 ? '' : currentSlide + ' / ' + totalSlides; - document.getElementById('bottomBar').style.display = 'flex'; - document.getElementById('backBtn').style.display = history.length > 0 ? 'block' : 'none'; + document.getElementById('bottomBar').style.display = currentSlide === 0 ? 'none' : 'flex'; renderBarActions(); } diff --git a/exercices/TP_Nuisibles.html b/exercices/TP_Nuisibles.html index 52ae58d..f216556 100644 --- a/exercices/TP_Nuisibles.html +++ b/exercices/TP_Nuisibles.html @@ -280,6 +280,18 @@ outline: none; border-color: var(--amber); } +/* Carte d'identité encadrée (modèle BioCID) */ +.id-card { + background: white; + border: 0.5px solid var(--border); + border-radius: 16px; + padding: 20px 22px; + max-width: 460px; + width: 100%; + margin: 1.25rem auto; + box-shadow: 0 8px 30px rgba(0,0,0,0.04); +} +.id-card .id-form { margin: 0; max-width: none; } /* TABLE FORM CONTROLS */ .risk-table input, .risk-table textarea { @@ -673,20 +685,22 @@
    Identification du groupe

    Ces informations permettront au formateur de retrouver vos réponses.

    -
    -
    - - -
    -
    - - -
    -
    - -
    - - +
    +
    +
    + + +
    +
    + + +
    +
    + +
    + + +
    From 421ae4c2bf1562e8b201e233411997f0f20f8b8d Mon Sep 17 00:00:00 2001 From: "gauthier.chombart" Date: Mon, 20 Jul 2026 20:30:16 +0200 Subject: [PATCH 15/26] Unifie la correction des exercices en une page unique MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La page de correction ne réagissait pas au dépôt d'un PDF : elle encodait le fichier en base64 dans localStorage puis redirigeait vers une page dédiée censée le relire. Ce relais échoue en file://, qui est le mode d'ouverture habituel des pages. corriger-exercice.html devient autonome : sélection de l'exercice, dépôt du PDF et affichage des réponses se font sur place, sans localStorage ni redirection. Les trois parseurs (BioCID, FDS, TP Nuisibles) sont regroupés derrière un format commun avec carrousel exercice par exercice, bandeau participants, correction chiffrée pour la partie FDS et annexe TP3. Ajoute un garde-fou signalant un PDF ne correspondant pas à l'exercice sélectionné, repointe les boutons « Corriger » de Exo_FDS et TP_Nuisibles vers la page unifiée, et supprime les trois pages de correction dédiées devenues sans référence. --- exercices/Exo_FDS.html | 2 +- exercices/TP_Nuisibles.html | 4 +- exercices/correction-biocid.html | 525 --------------- exercices/correction-fds.html | 622 ----------------- exercices/correction-tp-nuisibles.html | 668 ------------------- exercices/corriger-exercice.html | 889 ++++++++++++++++++++++--- 6 files changed, 794 insertions(+), 1916 deletions(-) delete mode 100644 exercices/correction-biocid.html delete mode 100644 exercices/correction-fds.html delete mode 100644 exercices/correction-tp-nuisibles.html diff --git a/exercices/Exo_FDS.html b/exercices/Exo_FDS.html index 8975baa..9a336da 100644 --- a/exercices/Exo_FDS.html +++ b/exercices/Exo_FDS.html @@ -962,7 +962,7 @@ span.hotspot { display: inline; padding: 0 2px; -webkit-box-decoration-break: cl
    - ✔ Corriger un rendu + ✔ Corriger un rendu
    diff --git a/exercices/TP_Nuisibles.html b/exercices/TP_Nuisibles.html index f216556..ef4c76d 100644 --- a/exercices/TP_Nuisibles.html +++ b/exercices/TP_Nuisibles.html @@ -707,7 +707,7 @@
    - Corriger + Corriger
    @@ -1160,7 +1160,7 @@
    - Corriger + Corriger
    diff --git a/exercices/correction-biocid.html b/exercices/correction-biocid.html deleted file mode 100644 index 9b3f242..0000000 --- a/exercices/correction-biocid.html +++ /dev/null @@ -1,525 +0,0 @@ - - - - - -Correction BioCID — Certibiocide - - - - - -
    - - -
    - -
    - -
    Certibiocide — Correction d'exercice
    -

    Plateforme BioCID — Lecture du rendu

    - -
    -
    📄
    -

    Déposez le PDF du groupe ici

    -

    ou cliquez pour sélectionner le fichier exporté depuis l'exercice BioCID

    - -
    -
    - -
    -
    - - -
    - -
    -
    - questions renseignées -
    - - -
    -
    - -
    -

    © 2026 Gauthier Chombart & Nathan Chauwin
    Tous droits réservés — Formations Certibiocide & Techniques — Formateurs indépendants

    -

    Tous droits réservés — Gauthier Chombart & Nathan Chauwin — Formateurs indépendants

    -
    - - - - - diff --git a/exercices/correction-fds.html b/exercices/correction-fds.html deleted file mode 100644 index 68b90c7..0000000 --- a/exercices/correction-fds.html +++ /dev/null @@ -1,622 +0,0 @@ - - - - - -Correction FDS, FT & Étiquettes — Certibiocide - - - - - -
    - - -
    - -
    - -
    Certibiocide — Correction de TD
    -

    FDS, FT & Étiquettes — Correction du rendu

    - -
    -
    📄
    -

    Déposez le PDF du groupe ici

    -

    ou cliquez pour sélectionner le fichier exporté depuis le TD FDS, FT & Étiquettes

    - -
    -
    - -
    -
    - - -
    - -
    - - -
    -
    - -
    -

    © 2026 Gauthier Chombart & Nathan Chauwin
    Tous droits réservés — Formations Certibiocide & Techniques — Formateurs indépendants

    -

    Tous droits réservés — Gauthier Chombart & Nathan Chauwin — Formateurs indépendants

    -
    - - - - - diff --git a/exercices/correction-tp-nuisibles.html b/exercices/correction-tp-nuisibles.html deleted file mode 100644 index 1034bd1..0000000 --- a/exercices/correction-tp-nuisibles.html +++ /dev/null @@ -1,668 +0,0 @@ - - - - - -Correction TP Nuisibles — Certibiocide - - - - - -
    - - -
    - -
    - -
    Certibiocide — Correction de TP
    -

    TP Nuisibles — Lecture du rendu

    - -
    -
    📄
    -

    Déposez le PDF du stagiaire ici

    -

    ou cliquez pour sélectionner le fichier exporté depuis le TP

    - -
    -
    - -
    -
    - - -
    - -
    - -
    -
    - questions répondues -
    - - - -

    Analyse de risques

    -
    - -

    -
    -
    - -
    -

    © 2026 Gauthier Chombart & Nathan Chauwin
    Tous droits réservés — Formations Certibiocide & Techniques — Formateurs indépendants

    -

    Tous droits réservés — Gauthier Chombart & Nathan Chauwin — Formateurs indépendants

    -
    - - - - - diff --git a/exercices/corriger-exercice.html b/exercices/corriger-exercice.html index cd004d7..2cff3ad 100644 --- a/exercices/corriger-exercice.html +++ b/exercices/corriger-exercice.html @@ -10,7 +10,7 @@ body { padding: calc(36px + 70px) 20px 80px; } -.wrap { max-width: 820px; margin: 0 auto; } +.wrap { max-width: 860px; margin: 0 auto; } .eyebrow { font-family: 'Syne', sans-serif; font-size: 12px; @@ -142,6 +142,183 @@ h1 { #status.hint { color: var(--text-muted); } #status.loading { color: var(--amber); } #status.error { color: var(--coral); } + +/* ======================= RÉSULTAT ======================= */ +#result { display: none; } +#setup.hidden { display: none; } + +.toprow { + display: flex; + justify-content: space-between; + align-items: center; + gap: 14px; + margin-bottom: 18px; + flex-wrap: wrap; +} +#fileName { font-size: 12.5px; color: var(--text-muted); } +.btn-reset { + font-family: 'Syne', sans-serif; + background: transparent; + border: 1px solid var(--border); + border-radius: 20px; + padding: 6px 16px; + font-size: 12px; + font-weight: 600; + cursor: pointer; + color: var(--text-muted); +} +.btn-reset:hover { border-color: var(--amber); color: var(--amber); } + +/* BANDEAU PARTICIPANTS */ +#participantsBanner { + background: linear-gradient(135deg, var(--amber) 0%, #D8923A 100%); + border-radius: 16px; + padding: 20px 26px; + margin-bottom: 24px; + display: flex; + align-items: center; + gap: 16px; + color: #fff; + flex-wrap: wrap; +} +#participantsBanner .pb-icon { + width: 48px; height: 48px; + border-radius: 12px; + background: rgba(255,255,255,0.2); + display: flex; align-items: center; justify-content: center; + font-size: 24px; + flex-shrink: 0; +} +#participantsBanner .pb-group { + font-family: 'Syne', sans-serif; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.06em; + text-transform: uppercase; + opacity: 0.85; + margin-bottom: 4px; +} +#participantsBanner .pb-names { + font-family: 'Syne', sans-serif; + font-size: 18px; + font-weight: 700; +} +#participantsBanner .pb-meta { + margin-left: auto; + font-size: 12.5px; + text-align: right; + opacity: 0.92; + line-height: 1.5; +} + +.progress-row { display: flex; align-items: center; gap: 10px; margin-bottom: 30px; font-size: 13px; color: var(--text-muted); } +.progress-track { flex: 1; height: 7px; background: rgba(0,0,0,0.08); border-radius: 6px; overflow: hidden; } +.progress-fill { height: 100%; background: var(--amber); transition: width 0.4s ease; } +.progress-row strong { font-family: 'Syne', sans-serif; color: var(--text); } + +/* CARROUSEL */ +.carousel-wrap { margin-bottom: 36px; } +.carousel-nav { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; +} +.nav-btn { + width: 38px; height: 38px; + border-radius: 50%; + border: 1px solid var(--border); + background: #fff; + font-size: 16px; + cursor: pointer; + display: flex; align-items: center; justify-content: center; + transition: background 0.15s, border-color 0.15s; +} +.nav-btn:hover { background: var(--amber-light); border-color: var(--amber); } +.nav-btn:disabled { opacity: 0.3; cursor: default; } +.nav-btn:disabled:hover { background: #fff; border-color: var(--border); } +.dots { display: flex; gap: 7px; } +.dot { width: 8px; height: 8px; border-radius: 50%; background: rgba(0,0,0,0.15); cursor: pointer; transition: background 0.15s, transform 0.15s; } +.dot.active { background: var(--amber); transform: scale(1.3); } + +.theme-panel { + background: #fff; + border: 1px solid var(--border); + border-radius: 18px; + padding: 26px 28px; + min-height: 360px; +} +.theme-head { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 18px; +} +.theme-icon { + width: 44px; height: 44px; + border-radius: 12px; + display: flex; align-items: center; justify-content: center; + font-size: 22px; + flex-shrink: 0; +} +.theme-title { font-family: 'Syne', sans-serif; font-weight: 800; font-size: 17px; } +.theme-count { font-size: 12.5px; color: var(--text-muted); margin-left: auto; white-space: nowrap; } + +.cards { display: flex; flex-direction: column; gap: 10px; } +.qcard { + background: var(--bg); + border: 1px solid var(--border); + border-radius: 12px; + padding: 14px 18px; + display: flex; + gap: 12px; + align-items: flex-start; + transition: box-shadow 0.2s; +} +.qcard:hover { box-shadow: 0 4px 16px rgba(0,0,0,0.06); } +.qcard .badge { + width: 22px; height: 22px; + border-radius: 50%; + display: flex; align-items: center; justify-content: center; + font-size: 12px; + flex-shrink: 0; + margin-top: 1px; +} +.qcard .badge.ok { background: var(--teal-light); color: var(--teal); } +.qcard .badge.ko { background: var(--coral-light); color: var(--coral); } +.qcard .badge.miss { background: rgba(0,0,0,0.06); color: var(--text-muted); } +.qcard .qtext { flex: 1; min-width: 0; } +.qcard .qlabel { font-weight: 600; font-size: 13.5px; margin-bottom: 4px; } +.qcard .qanswer { font-size: 13.5px; white-space: pre-wrap; overflow-wrap: anywhere; } +.qcard .qanswer.empty { color: var(--text-muted); font-style: italic; } +.qcard .qexpected { font-size: 12.5px; color: var(--text-muted); margin-top: 4px; } +.qcard .qexpected strong { color: var(--teal); } + +/* ANNEXES (TP3 …) */ +.annex { + background: #fff; + border: 1px solid var(--border); + border-radius: 18px; + padding: 24px 28px; + margin-bottom: 24px; +} +.annex h2 { + font-family: 'Syne', sans-serif; + font-size: 15px; + font-weight: 800; + margin-bottom: 14px; +} +.annex .kv { font-size: 13.5px; margin-bottom: 6px; } +.annex .kv span { color: var(--text-muted); } +.table-scroll { overflow-x: auto; margin-top: 14px; } +.annex table { width: 100%; border-collapse: collapse; font-size: 13px; min-width: 520px; } +.annex th, .annex td { + border: 1px solid var(--border); + padding: 8px 10px; + text-align: left; + vertical-align: top; +} +.annex th { background: var(--amber-light); font-family: 'Syne', sans-serif; font-size: 12px; } @@ -158,56 +335,85 @@ h1 {
    -
    Certibiocide — Correction des rendus
    -

    Corriger un exercice

    -

    Sélectionnez l'exercice concerné, puis déposez le fichier PDF exporté par le groupe. La lecture du rendu s'ouvre automatiquement.

    +
    +
    Certibiocide — Correction des rendus
    +

    Corriger un exercice

    +

    Sélectionnez l'exercice concerné, puis déposez le fichier PDF exporté par le groupe. Les réponses s'affichent directement ici, exercice par exercice.

    -

    1 Choisissez l'exercice à corriger

    -
    -
    - - +

    1 Choisissez l'exercice à corriger

    +
    +
    + + +
    +
    + + +
    +
    + + +
    -
    - - -
    -
    - -
    -

    2 Déposez le rendu PDF

    -
    -
    📄
    -

    Déposez le PDF du groupe ici

    -

    ou cliquez pour sélectionner le fichier exporté depuis l'exercice

    - -
    Sélectionnez d'abord un exercice ci-dessus.
    +
    + +
    Certibiocide — Correction d'exercice
    +

    + +
    + + +
    + +
    +
    + +
    + + + +
    @@ -216,16 +422,481 @@ h1 {

    Tous droits réservés — Gauthier Chombart & Nathan Chauwin — Formateurs indépendants

    + diff --git a/exercices/Exo_FDS.html b/exercices/Exo_FDS.html index 9a336da..dad8fe6 100644 --- a/exercices/Exo_FDS.html +++ b/exercices/Exo_FDS.html @@ -1113,6 +1113,7 @@ function onEtiClick(e) { // Exercice terminé : plus d'interaction (les corrections sont dans la correction générale) if (etiPos >= etiOrder.length) return; + marquerModifie(); const target = etiZones[etiOrder[etiPos]]; if (clicked === target.zone) { hs.classList.remove('hs-wrong'); @@ -1356,7 +1357,7 @@ document.addEventListener('pointerup', e => { if (!ftDrag) return; const target = document.elementFromPoint(e.clientX, e.clientY); const answerDot = target && target.closest('.match-dot[data-side="right"]'); - if (answerDot) ftConnections[ftDrag.qid] = answerDot.dataset.answer; + if (answerDot) { marquerModifie(); ftConnections[ftDrag.qid] = answerDot.dataset.answer; } document.querySelectorAll('.match-dot.active').forEach(d => d.classList.remove('active')); ftDrag = null; refreshFtState(); @@ -1407,6 +1408,7 @@ function initFdsDnd() { r.className = 'result-message'; r.innerHTML = ''; }; const fill = (slot, v, l) => { + marquerModifie(); slot.dataset.value = v; slot.textContent = l; slot.classList.add('filled'); resetResults(); updateFdsGate(); }; @@ -1820,6 +1822,23 @@ function launchConfetti() { rafId = requestAnimationFrame(draw); } +/* ======================== 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. */ +let reponsesModifiees = false; +function marquerModifie() { reponsesModifiees = true; } + +// Les champs texte déclenchent input/change ; les parties FDS (glisser-déposer), +// FT (liaisons) et Étiquette (zones cliquables) appellent marquerModifie() directement. +document.addEventListener('input', marquerModifie, true); +document.addEventListener('change', marquerModifie, true); + +window.addEventListener('beforeunload', e => { + if (!reponsesModifiees) return; + e.preventDefault(); + e.returnValue = ''; +}); + document.addEventListener('DOMContentLoaded', () => { generateQuizzes(); updateUI(); diff --git a/exercices/TP_Nuisibles.html b/exercices/TP_Nuisibles.html index ef4c76d..dbf1964 100644 --- a/exercices/TP_Nuisibles.html +++ b/exercices/TP_Nuisibles.html @@ -1317,6 +1317,7 @@ function updateUI() { } function selectCase(key, icon, title, desc) { + marquerModifie(); selectedCase.key = key; selectedCase.icon = icon; selectedCase.title = title; @@ -1620,6 +1621,21 @@ function escapeHtml(str) { return str.replace(/&/g, '&').replace(//g, '>'); } +/* ======================== 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. */ +let reponsesModifiees = false; +function marquerModifie() { reponsesModifiees = true; } + +document.addEventListener('input', marquerModifie, true); +document.addEventListener('change', marquerModifie, true); + +window.addEventListener('beforeunload', e => { + if (!reponsesModifiees) return; + e.preventDefault(); + e.returnValue = ''; +}); + updateUI(); From b5f2eac84235e3dff2e7249f6d1beb9b3979d447 Mon Sep 17 00:00:00 2001 From: "gauthier.chombart" Date: Mon, 20 Jul 2026 20:44:47 +0200 Subject: [PATCH 17/26] =?UTF-8?q?Sauvegarde=20les=20r=C3=A9ponses=20de=20B?= =?UTF-8?q?ioCID=20et=20du=20TP=20Nuisibles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- exercices/Exo_BioCID.html | 103 +++++++++++++++++++++++++- exercices/TP_Nuisibles.html | 143 +++++++++++++++++++++++++++++++++++- 2 files changed, 238 insertions(+), 8 deletions(-) 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(); diff --git a/exercices/TP_Nuisibles.html b/exercices/TP_Nuisibles.html index dbf1964..9d82c77 100644 --- a/exercices/TP_Nuisibles.html +++ b/exercices/TP_Nuisibles.html @@ -1621,21 +1621,156 @@ function escapeHtml(str) { return str.replace(/&/g, '&').replace(//g, '>'); } -/* ======================== 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:tp-nuisibles: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; + }); + // Les cellules du tableau TP3 n'ont pas d'id : on les repère par leur position. + const risques = Array.from(document.querySelectorAll('#riskTableBody .risk-row')) + .map(row => Array.from(row.querySelectorAll('.risk-cell')).map(c => c.value)); + + return { + savedAt: new Date().toISOString(), + slide: currentSlide, + champs, + risques, + cas: selectedCase.key || '', + formateur: stagiaire.formateur || '', + nom: stagiaire.nom || '', + date: stagiaire.date || '', + }; +} + +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; + }); + + // Recrée autant de lignes de risques que nécessaire avant de les remplir + const risques = data.risques || []; + const tbody = document.getElementById('riskTableBody'); + while (tbody.querySelectorAll('.risk-row').length < risques.length) addRiskRow(); + tbody.querySelectorAll('.risk-row').forEach((row, i) => { + const valeurs = risques[i] || []; + row.querySelectorAll('.risk-cell').forEach((cell, j) => { cell.value = valeurs[j] || ''; }); + }); + + // Le cas pratique et le formateur passent par leurs boutons, qui remettent + // aussi l'interface (carte sélectionnée, bouton de confirmation) dans le bon état. + if (data.cas) { + const carte = Array.from(document.querySelectorAll('.case-card')) + .find(c => (c.getAttribute('onclick') || '').includes("'" + data.cas + "'")); + if (carte) carte.click(); + } + if (data.formateur) { + const btn = document.getElementById('btn' + data.formateur); + if (btn) btn.click(); + } + stagiaire.nom = data.nom || stagiaire.nom; + stagiaire.date = data.date || stagiaire.date; + + 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) return; + const aDesReponses = (data.champs && Object.keys(data.champs).length > 0) + || (data.risques || []).some(r => r.some(v => v)); + if (!aDesReponses) 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(); From 88b075fb88655e874a656c682a9bb06af0f58fd4 Mon Sep 17 00:00:00 2001 From: "gauthier.chombart" Date: Mon, 20 Jul 2026 20:51:23 +0200 Subject: [PATCH 18/26] =?UTF-8?q?Bloque=20les=20fichiers=20ne=20correspond?= =?UTF-8?q?ant=20pas=20=C3=A0=20l'exercice=20choisi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Chaque exercice dispose désormais d'une signature propre, basée sur l'en-tête du PDF qu'il exporte. La fonction detectExercise identifie l'exercice réellement contenu dans le fichier déposé (signature d'abord, sentinelle en secours). Si le PDF correspond à un autre exercice que celui sélectionné, un message d'incompatibilité nommant les deux exercices s'affiche et la correction n'est pas générée. --- exercices/corriger-exercice.html | 43 +++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/exercices/corriger-exercice.html b/exercices/corriger-exercice.html index 2cff3ad..e919a2a 100644 --- a/exercices/corriger-exercice.html +++ b/exercices/corriger-exercice.html @@ -792,11 +792,38 @@ function parseTp(raw) { /* ======================= REGISTRE DES EXERCICES ======================= */ const EXERCISES = { - biocid: { parse: parseBiocid, sentinel: /Réponses aux exercices|Ex1\s*[—-]/i, name: "BioCID" }, - fds: { parse: parseFds, sentinel: /\[P1-Q1\]|Partie 1\s*[—-]\s*FDS/i, name: "FDS, FT & Étiquettes" }, - tp: { parse: parseTp, sentinel: /Réponses TP1|TP Nuisibles/i, name: "TP Nuisibles" }, + biocid: { + parse: parseBiocid, + // Signature : en-tête propre au PDF exporté par Exo_BioCID.html + signature: /Exercices\s+Pratiques\s+BioCID/i, + sentinel: /Réponses aux exercices|Ex1\s*[—–-]/i, + name: "Plate-forme BioCID", + }, + fds: { + parse: parseFds, + signature: /TD\s*[—–-]\s*FDS,\s*FT/i, + sentinel: /\[P1-Q1\]|Partie 1\s*[—–-]\s*FDS/i, + name: "FDS, FT & Étiquettes", + }, + tp: { + parse: parseTp, + signature: /TP\s+Nuisibles\s*[—–-]\s*Certibiocide/i, + sentinel: /Réponses TP1|TP3\s*[—–-]\s*Analyse de risques/i, + name: "TP Nuisibles", + }, }; +// Identifie l'exercice réellement contenu dans le PDF (signature d'abord, sentinelle en secours). +function detectExercise(text) { + for (const [key, cfg] of Object.entries(EXERCISES)) { + if (cfg.signature.test(text)) return key; + } + for (const [key, cfg] of Object.entries(EXERCISES)) { + if (cfg.sentinel.test(text)) return key; + } + return null; +} + /* ======================= RENDU ======================= */ let currentData = null; @@ -949,7 +976,15 @@ async function handleFile(file) { const text = await readPdfText(file); const config = EXERCISES[exo]; - if (!config.sentinel.test(text)) { + const detected = detectExercise(text); + + if (detected && detected !== exo) { + statusEl.className = "error"; + statusEl.textContent = `Fichier incompatible : ce PDF est un rendu « ${EXERCISES[detected].name} », or vous avez sélectionné « ${config.name} ». Changez d'exercice ou déposez le bon fichier.`; + return; + } + + if (!detected) { statusEl.className = "error"; statusEl.textContent = `Ce PDF ne ressemble pas à un rendu « ${config.name} ». Vérifiez l'exercice sélectionné.`; return; From 794ae69fa09a8dcf8b98a3d9d1c80ea5be1dc801 Mon Sep 17 00:00:00 2001 From: "gauthier.chombart" Date: Mon, 20 Jul 2026 21:12:56 +0200 Subject: [PATCH 19/26] =?UTF-8?q?Ajoute=20une=20slide=20d'introduction=20?= =?UTF-8?q?=C3=A0=20l'exercice=201=20de=20BioCID?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L'exercice 1 s'ouvre désormais sur une slide de présentation calquée sur celles de l'exercice FDS : cercle décoratif, étiquette de section, titre avec emoji, texte d'introduction et encadré d'information. Les slides suivantes sont décalées d'un rang (2 à 6 deviennent 3 à 7), le compteur passe à 7 étapes et les confettis se déclenchent sur la nouvelle slide finale. Les questions et leurs champs sont inchangés. --- exercices/Exo_BioCID.html | 45 ++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/exercices/Exo_BioCID.html b/exercices/Exo_BioCID.html index 0c930f5..2962a2c 100644 --- a/exercices/Exo_BioCID.html +++ b/exercices/Exo_BioCID.html @@ -498,12 +498,22 @@
    - +
    +
    + +
    🔎 Recherche par nom de produit
    +

    La recherche par nom est le chemin le plus direct dans BioCID : elle donne accès à la fiche complète d'un produit déjà identifié — son type, sa substance active, son fabricant et ses conditions d'utilisation.

    +
    + 🎯 +
    Pour chaque question, saisissez votre réponse dans le champ prévu. Vos réponses sont enregistrées automatiquement et reprises dans le PDF final.
    +
    +
    + + +
    -
    Recherche par nom de produit
    -

    Trouvez un produit spécifique et relevez ses caractéristiques principales.

    • 1Recherchez un produit connu (ex. « Goliath », « Phobi », « Soricide »). Notez son type de produit, sa substance active et son fabricant.
      @@ -521,8 +531,8 @@
    - -
    + +
    Recherche par type de produit
    @@ -540,8 +550,8 @@
    - -
    + +
    Recherche par substance active
    @@ -559,8 +569,8 @@
    - -
    + +
    Mise en situation
    @@ -585,8 +595,8 @@
    - -
    + +
    @@ -613,15 +623,16 @@