Compare commits

...

33 Commits

Author SHA1 Message Date
f611bdcd7e Recentre les encadres TP1/TP2/TP3 de la slide finale
Passe la marge de .final-grid de 1.5rem 0 a 1.5rem auto pour centrer la
grille des trois travaux pratiques.
2026-07-21 08:22:29 +02:00
f7c846c85c Simplifie les boutons d'action du TP Nuisibles
Slide 1 : retire le bouton "Corriger un rendu", l'ecran de contexte ne
garde que l'identification. Slide 13 : ne conserve que Telecharger le PDF,
Corriger un rendu et Recommencer, aligne sur le style BioCID (PDF plein
ambre, deux boutons contour) et retire le lien biocid-anses.fr.
2026-07-21 08:20:13 +02:00
a74df51201 Migre la navigation du TP Nuisibles dans la bottom bar
Reprend le modele de footer d'Exo_BioCID : tous les boutons d'avancement
et d'aide passent dans la barre du bas, pilotes par un slideConfig
(bouton suivant, libelle, indice par slide). Supprime les boutons
in-slide correspondants, gere la validation du nom de groupe et le
deverrouillage du bouton apres choix du cas, et applique le style
footer-btn (compteur a gauche, actions centrees).
2026-07-21 08:16:05 +02:00
42650ca351 Aligne le TP Nuisibles sur le style des autres exercices
Inverse l'accueil et l'identification (héro en slide 0, contexte et
identification du groupe en slide 1), applique le style BioCID aux deux
premieres slides et supprime le badge d'avancement en haut a droite,
redondant avec la barre de progression.
2026-07-21 08:10:09 +02:00
21a7359057 Retravaille l'exercice FDS : étiquette libre et corrigé déplacé
- Slide 5 (FT) : reformule la question sur le type de produit en « Où se
  trouve la liste des TP (type de produit) ? »
- Slide 7 : l'utilisateur peut désormais cliquer n'importe quelle zone,
  même incorrecte ; le clic est enregistré et l'exercice avance.
- Slide 7 : « Terminer l'exercice » mène directement à la synthèse ; le
  récapitulatif « l'étiquette annotée » quitte le déroulé des slides.
- Déplace ce récapitulatif (étiquette annotée + check-list réglementaire)
  vers corriger-exercice.html, en corrigé de référence du rendu FDS.
2026-07-21 08:01:12 +02:00
6bb7985512 Uniformise l'identification du groupe sur BioCID et TP Nuisibles
Applique le style de carte de référence (Exo_FDS) à ExoBIOCID et au TP
Nuisibles : titre dans la carte, champs nom/date, et sélecteur de
formateur Nathan/Gauthier en boutons remplis à l'état actif, à la place
du champ texte libre (BioCID) et des boutons pilule (TP).
2026-07-21 07:46:15 +02:00
090cbc19a2 Aligne les deux premières slides du TP Nuisibles sur BioCID
Les slides 1 et 2 reprennent la présentation des slides BioCID :
étiquette de section, titre, introduction et encadré d'information.
La slide 1 abandonne le bloc héro au profit de ce gabarit, la slide 2
gagne un encadré rappelant que le cas choisi vaut pour tout le TP.

La règle .info-card, absente de ce fichier, est reprise de BioCID.
Aucun champ ni contenu déplacé, numérotation des slides inchangée.
2026-07-20 21:12:56 +02:00
794ae69fa0 Ajoute une slide d'introduction à l'exercice 1 de BioCID
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.
2026-07-20 21:12:56 +02:00
88b075fb88 Bloque les fichiers ne correspondant pas à l'exercice choisi
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.
2026-07-20 20:51:23 +02:00
b5f2eac842 Sauvegarde les réponses de BioCID et du TP Nuisibles
Les réponses saisies sont désormais enregistrées dans le navigateur au fil
de la frappe, sous une clé propre à chaque exercice. Un rechargement ne les
perd plus.

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

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

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

FDS reste à traiter : ses réponses ne vivent pas dans des champs de
formulaire mais dans l'état des zones de dépôt, des liaisons et de
l'étiquette, ce qui demande une logique de réaffichage dédiée.
2026-07-20 20:44:47 +02:00
a42957ad41 Avertit avant la perte des réponses sur les trois TD
Les réponses saisies dans les exercices ne sont pas enregistrées : un
rechargement ou une navigation les efface sans prévenir.

Ajoute sur BioCID, FDS et TP Nuisibles un indicateur de saisie et un
gestionnaire beforeunload : dès qu'une réponse a été renseignée, le
navigateur demande confirmation avant de recharger ou de quitter la page.
Aucune alerte tant que rien n'a été saisi.

Sur FDS, les parties glisser-déposer, liaisons FT et zones cliquables de
l'étiquette ne produisent pas d'événement input : elles signalent la
saisie explicitement. Même chose pour le choix du cas pratique du TP.

Note : le texte affiché dans la boîte de dialogue est imposé par le
navigateur et n'est pas personnalisable.
2026-07-20 20:36:19 +02:00
421ae4c2bf Unifie la correction des exercices en une page unique
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.
2026-07-20 20:30:16 +02:00
affa042c05 Aligne les slides d'intro et la bottom bar sur le modèle BioCID
- 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
2026-07-19 18:17:56 +02:00
30817c829b Retravaille l'exercice FDS : boutons, étiquette et récapitulatif
- index : emoji de la carte "Corriger un exercice" ✔️
- Boutons de toutes les slides centralisés et uniformisés dans la bottom bar
- Slide 1 : encadré "Durée 30 min" remonté au-dessus des trois parties
- Slides 3 et 5 : suppression du bouton "Vérifier mes réponses" et blocage du passage à la partie suivante tant que tout n'est pas rempli/relié
- Slide 7 : bouton "Terminer l'exercice" (ex "Voir le récapitulatif") réparé, faux pictogrammes remplacés par les vraies images SGH, volet de correction de droite et encadré de résultats supprimés
- Slide 8 : encadrés 💡 retirés, boutons déplacés sous le tableau comparatif, bouton "Retour aux exercices" supprimé
- Confettis déplacés de la slide 6 vers la slide 8
2026-07-19 17:59:02 +02:00
41d8b98f96 Ajuste les slides 1 et 5 de l'exercice BioCID
- 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'
2026-07-19 17:15:34 +02:00
d251782842 Ajoute une page unique de correction des exercices
- Nouvelle page corriger-exercice.html : sélection de l'exercice (BioCID, FDS, TP Nuisibles) + zone de dépôt PDF
- Le PDF déposé est transmis à la page de correction dédiée qui se charge automatiquement (?auto=1)
- Carte 'Corriger un exercice' ajoutée à la section Exercices de l'index
- Slide 6 BioCID : suppression du bouton biocid-anses.fr, alignement des boutons Télécharger / Corriger / Recommencer
- Le bouton 'Corriger un rendu' redirige vers la nouvelle page
2026-07-19 17:08:59 +02:00
66ed3c505f Uniformise les boutons du footer BioCID et ajoute des confettis
- 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)
2026-07-19 16:53:32 +02:00
543bb6cd52 Retravaille le footer de l'exercice BioCID
- Footer agrandi (84px) avec les trois boutons Retour, Voir les indications et Exercice suivant centrés
- Boutons d'indications et de navigation déplacés du corps des slides vers le footer
- Suppression du badge 'Exercice .../4' en haut à droite (doublon)
- Slide 3 : simplification du texte des filtres (par TP)
2026-07-19 16:48:40 +02:00
a2a4fea004 Retravaille les exercices FDS/FT/Étiquettes (slides 1, 3, 5, 7)
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.
2026-07-17 17:50:22 +02:00
d351ee6efb Refonte de la slide 7 FDS en étiquette cliquable (hotspots)
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.
2026-07-16 14:36:09 +02:00
0b4c2244fe Ajoute l'option Absent à la slide 5 (FT)
La question sur la compatibilité des matières se relie désormais à Absent, ajouté comme cinquième section (correspondance 1-à-1).
2026-07-16 09:19:31 +02:00
f720beaf3c Refonte de la slide 5 FT en exercice à relier + boutons Recommencer
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).
2026-07-16 09:15:07 +02:00
2a2ed26563 Ignore le web.config de l'API Memory (contient le mot de passe SQL) 2026-07-16 09:00:16 +02:00
fb5c7e9720 Refonte de la slide 3 FDS en glisser-déposer
- 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
2026-07-16 08:58:34 +02:00
a418558393 Ajoute la correction sur fichier aux exercices BioCID et FDS
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).
2026-07-15 23:07:45 +02:00
c1bd61c10c Refonte des exercices BioCID et FDS en canvas slide par slide
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.
2026-07-15 22:46:04 +02:00
44be41b2b2 Renomme la marque en Gauthier Chombart & Nathan Chauwin et finalise le jeu Memory
Header, footer, titres et balises meta de toutes les pages actives : remplace NextGN Formation par Gauthier Chombart & Nathan Chauwin. Nouveau pied de page uniforme mentionnant les droits reserves et le statut de formateurs independants (dossier archives/ laisse inchange).

Integre aussi la refonte du jeu Memory (CSS/JS/HUD/leaderboard) et ajoute le rapport de veille Certibiocide semaine 28.
2026-07-14 23:02:09 +02:00
9140d2f468 Harmonise le jeu Memory avec le style ambre du site et corrige le titre
Le jeu utilisait le teal comme couleur dominante (titres, bordures,
boutons, cartes) alors que le reste du site n'emploie le teal que
ponctuellement (état de succès) et repose sur l'ambre comme couleur
principale. Remplace toutes les références teal par l'ambre déjà
défini dans header-style.css, et réaffecte le teal à son rôle habituel
de couleur de succès (paire trouvée), à la place du vert générique
utilisé jusqu'ici.

Renomme aussi le titre en "Memory pictogrammes", augmente la marge
sous le header pour éviter tout chevauchement, et ajoute un paramètre
de version aux feuilles de style du jeu pour éviter les soucis de
cache navigateur lors des prochaines mises à jour.
2026-07-07 17:23:57 +02:00
75c5a1ac84 Retire le pictogramme manette de l'en-tête du jeu 2026-07-07 17:14:22 +02:00
1e757ccda8 Corrige le panneau "Vos statistiques" et compacte son affichage
Deux règles CSS différentes partageaient le nom .player-stats (la
ligne de détails sous chaque joueur du classement, et tout le panneau
"Vos statistiques"). Le panneau héritait donc du display:flex prévu
pour la ligne de détails, ce qui empêchait la grille de stats de
prendre toute la largeur et l'affichait en une colonne étroite.

Renomme la ligne de détails en .rank-meta et aligne les 4 encadrés de
stats sur une seule rangée compacte sous le titre.
2026-07-07 17:12:29 +02:00
f7f73591c1 Ajoute la config du serveur de preview local
Permet de lancer le site en local (python3 -m http.server) via l'outil
de preview.
2026-07-07 17:08:08 +02:00
cf69373417 Corrige la superposition header/contenu et harmonise le style du jeu Memory
L'animation d'entrée (translation + rebond infini du badge) pouvait
faire chevaucher le header. Remplacée par une simple apparition sans
déplacement vertical, avec plus de marge sous le header.

Aligne aussi les rayons d'angle, les ombres et les boutons du jeu sur
le langage visuel déjà utilisé ailleurs sur le site (cartes à 12px de
rayon, ombres neutres, titre de section souligné, style de bouton
partagé depuis header-style.css).
2026-07-07 17:06:09 +02:00
2227aa89d5 Refonte visuelle du jeu Memory Pictogrammes GHS
Corrige les pictogrammes cassés (mauvais noms de fichiers dans
utils.js) et le conflit de classe CSS .progress-bar avec le header
qui cassait la barre de progression du jeu. Réorganise l'interface
dans un panneau de jeu unifié, ajoute des icônes aux statistiques
et au sélecteur de difficulté, et rend le dos des cartes plus ludique.
2026-07-07 16:58:40 +02:00
29 changed files with 5094 additions and 2620 deletions

11
.claude/launch.json Normal file
View File

@@ -0,0 +1,11 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "static-site",
"runtimeExecutable": "python3",
"runtimeArgs": ["-m", "http.server", "8123"],
"port": 8123
}
]
}

3
.gitignore vendored
View File

@@ -1,2 +1,5 @@
.DS_Store .DS_Store
# Contient le mot de passe SQL Server — à éditer uniquement sur bsite.net
exercices/memory-pictogrammes/api/web.config

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Index Documentation Technique & Biologie | NextGN Formation</title> <title>Index Documentation Technique & Biologie | Gauthier Chombart & Nathan Chauwin</title>
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap" rel="stylesheet">
<style> <style>
:root { :root {
@@ -506,7 +506,7 @@
<nav class="nav-header"> <nav class="nav-header">
<a href="index.html" class="header-logo"> <a href="index.html" class="header-logo">
<span>NextGN</span> Formation Gauthier Chombart & Nathan Chauwin
</a> </a>
<div class="header-nav"> <div class="header-nav">
<a href="index.html#formations">Exercices interactifs</a> <a href="index.html#formations">Exercices interactifs</a>
@@ -548,7 +548,7 @@
</main> </main>
<footer> <footer>
<p>&copy; 2026 NextGN Formation - Index Documentation Technique & Biologie</p> <p>&copy; 2026 Gauthier Chombart & Nathan Chauwin<br>Tous droits réservés — Formations Certibiocide & Techniques — Formateurs indépendants</p>
<p>Tous droits réservés — Gauthier Chombart & Nathan Chauwin — Formateurs indépendants</p> <p>Tous droits réservés — Gauthier Chombart & Nathan Chauwin — Formateurs indépendants</p>
</footer> </footer>

BIN
data/ghs05-corrosif.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

BIN
data/ghs07-nocif.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -17,7 +17,7 @@
justify-content: center; justify-content: center;
padding: 2rem; padding: 2rem;
padding-top: calc(70px + 2rem); padding-top: calc(70px + 2rem);
padding-bottom: calc(52px + 2rem); padding-bottom: calc(84px + 2rem);
opacity: 0; opacity: 0;
transition: opacity 0.4s ease; transition: opacity 0.4s ease;
overflow-y: auto; overflow-y: auto;
@@ -38,73 +38,67 @@
transition: width 0.4s ease; transition: width 0.4s ease;
} }
/* PHASE INDICATOR */
.phase-badge {
position: fixed;
top: 80px; right: 16px;
font-family: 'Syne', sans-serif;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
padding: 4px 12px;
border-radius: 20px;
z-index: 100;
}
/* BOTTOM BAR */ /* BOTTOM BAR */
.bottom-bar { .bottom-bar {
position: fixed; position: fixed;
bottom: 0; left: 0; right: 0; bottom: 0; left: 0; right: 0;
height: 52px; height: 84px;
background: rgba(250, 238, 218, 0.98); background: rgba(250, 238, 218, 0.98);
border-top: 2px solid var(--amber); border-top: 2px solid var(--amber);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: center;
padding: 0 20px; padding: 0 20px;
z-index: 100; z-index: 100;
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
} }
.bottom-actions {
/* SLIDE NAV */ display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
justify-content: center;
}
.footer-btn {
font-family: 'Syne', sans-serif;
font-size: 13px;
font-weight: 600;
padding: 11px 24px;
border-radius: 40px;
border: 1.5px solid var(--amber);
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 6px;
line-height: 1;
transition: all 0.2s;
}
.footer-btn:hover { transform: translateY(-1px); }
.footer-btn:active { transform: scale(0.98); }
.footer-btn.is-primary { background: var(--amber); color: #fff; }
.footer-btn.is-primary:hover { background: var(--amber-dark); }
.footer-btn.is-secondary { background: transparent; color: var(--amber-dark); }
.footer-btn.is-secondary:hover { background: rgba(186,117,23,0.08); }
.slide-counter { .slide-counter {
position: absolute;
left: 20px;
font-size: 12px; font-size: 12px;
font-family: 'Syne', sans-serif; font-family: 'Syne', sans-serif;
font-weight: 600; font-weight: 600;
color: var(--amber-dark); color: var(--amber-dark);
} }
@media (max-width: 560px) {
/* BACK BUTTON */ .slide-counter { display: none; }
.back-btn {
background: none;
border: 0.5px solid var(--amber);
border-radius: 20px;
padding: 6px 16px;
font-size: 12px;
color: var(--amber-dark);
cursor: pointer;
transition: all 0.2s;
font-family: 'DM Sans', sans-serif;
} }
.back-btn:hover { background: rgba(186,117,23,0.08); }
/* FORMATEUR SELECTOR */ /* FORMATEUR SELECTOR */
.formateur-btn { .formateur-btn {
font-family: 'Syne', sans-serif; flex: 1; font-family: 'DM Sans', sans-serif; font-size: 13.5px;
font-size: 14px; padding: 9px 13px; border: 1px solid var(--border); border-radius: 10px;
font-weight: 600; background: var(--bg); color: var(--text); cursor: pointer; transition: all 0.2s ease;
padding: 10px 24px;
border-radius: 30px;
border: 1.5px solid var(--border);
background: white;
color: var(--text-muted);
cursor: pointer;
transition: all 0.2s;
flex: 1;
} }
.formateur-btn:hover { border-color: var(--amber); color: var(--amber); } .formateur-btn:hover { border-color: var(--amber); }
.formateur-btn.selected { background: var(--amber-light); border-color: var(--amber); color: var(--amber-dark); font-weight: 700; } .formateur-btn.selected { background: var(--amber); border-color: var(--amber); color: #fff; }
/* CARDS & BUTTONS */ /* CARDS & BUTTONS */
.btn { .btn {
@@ -246,40 +240,42 @@
.q-item { flex-direction: column; align-items: stretch; } .q-item { flex-direction: column; align-items: stretch; }
.q-item .q-head { display: flex; align-items: flex-start; gap: 12px; } .q-item .q-head { display: flex; align-items: flex-start; gap: 12px; }
/* IDENTIFICATION SCREEN */ /* IDENTIFICATION SCREEN — carte modèle BioCID / FDS */
.id-form { .identity-card {
background: white;
border: 0.5px solid var(--border);
border-radius: 14px;
padding: 16px 18px;
max-width: 460px;
width: 100%; width: 100%;
max-width: 420px; margin: 1.25rem auto;
display: flex; text-align: left;
flex-direction: column;
gap: 14px;
margin: 1.25rem 0;
} }
.id-field label { .identity-card .identity-title {
display: block;
font-family: 'Syne', sans-serif; font-family: 'Syne', sans-serif;
font-size: 11px; font-size: 12px;
font-weight: 700; font-weight: 700;
letter-spacing: 0.05em; letter-spacing: 0.05em;
text-transform: uppercase; text-transform: uppercase;
color: var(--text-muted); color: var(--amber-dark);
margin-bottom: 6px; margin-bottom: 10px;
text-align: left; display: flex; align-items: center; gap: 6px;
} }
.id-field input { .identity-fields { display: flex; flex-direction: column; gap: 8px; }
width: 100%; .identity-fields input {
font-family: 'DM Sans', sans-serif; font-family: 'DM Sans', sans-serif;
font-size: 15px; font-size: 13.5px;
padding: 12px 16px;
border: 1.5px solid var(--border);
border-radius: 10px;
background: white;
color: var(--text); color: var(--text);
background: var(--bg);
border: 1px solid var(--border);
border-radius: 10px;
padding: 9px 13px;
width: 100%;
box-sizing: border-box;
} }
.id-field input:focus { .identity-fields input:focus { outline: none; border-color: var(--amber); background: white; }
outline: none; .formateur-label { font-size: 11px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-muted); margin: 4px 0 2px; }
border-color: var(--amber); .formateur-row { display: flex; gap: 8px; }
}
/* TABLE FORM CONTROLS */ /* TABLE FORM CONTROLS */
.risk-table input, .risk-table textarea { .risk-table input, .risk-table textarea {
@@ -496,6 +492,19 @@
line-height: 1.6; line-height: 1.6;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
.info-card {
background: var(--amber-light);
border-radius: 12px;
padding: 16px 20px;
max-width: 620px;
width: 100%;
font-size: 14px;
color: var(--amber-dark);
line-height: 1.6;
display: flex;
gap: 10px;
align-items: flex-start;
}
.steps-row { .steps-row {
display: flex; display: flex;
gap: 8px; gap: 8px;
@@ -574,7 +583,7 @@
gap: 12px; gap: 12px;
max-width: 640px; max-width: 640px;
width: 100%; width: 100%;
margin: 1.5rem 0; margin: 1.5rem auto;
} }
.final-card { .final-card {
background: white; background: white;
@@ -606,7 +615,7 @@
/* SCROLLABLE AREA */ /* SCROLLABLE AREA */
.scroll-area { .scroll-area {
overflow-y: auto; overflow-y: auto;
max-height: calc(100vh - 232px); max-height: calc(100vh - 200px);
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -633,7 +642,7 @@
<!-- HEADER WITH NAVIGATION --> <!-- HEADER WITH NAVIGATION -->
<header> <header>
<div class="logo">NextGN Formation</div> <div class="logo">Gauthier Chombart & Nathan Chauwin</div>
<nav> <nav>
<a href="../index.html">Accueil</a> <a href="../index.html">Accueil</a>
<a href="../index.html#formations">Exercices</a> <a href="../index.html#formations">Exercices</a>
@@ -647,13 +656,14 @@
<!-- Progress bar --> <!-- Progress bar -->
<div class="progress-bar"><div class="progress-fill" id="progressFill"></div></div> <div class="progress-bar"><div class="progress-fill" id="progressFill"></div></div>
<!-- Phase badge -->
<div class="phase-badge" id="phaseBadge" style="display:none"></div>
<!-- Bottom bar --> <!-- Bottom bar -->
<div class="bottom-bar" id="bottomBar" style="display:none;"> <div class="bottom-bar" id="bottomBar" style="display:none;">
<button class="back-btn" id="backBtn" onclick="goBack()" style="display:none;">← Retour</button>
<div class="slide-counter" id="slideCounter"></div> <div class="slide-counter" id="slideCounter"></div>
<div class="bottom-actions">
<button class="footer-btn is-secondary" id="backBtn" onclick="goBack()" style="display:none;">← Retour</button>
<button class="footer-btn is-secondary" id="footerHintBtn" onclick="showHintCurrent()" style="display:none;">💡 Besoin d'aide ?</button>
<button class="footer-btn is-primary" id="footerNextBtn" onclick="goNext()" style="display:none;">Étape suivante →</button>
</div>
</div> </div>
<!-- Modal overlay --> <!-- Modal overlay -->
@@ -666,48 +676,40 @@
</div> </div>
</div> </div>
<!-- ======================== SLIDE 0 — IDENTIFICATION ======================== --> <!-- ======================== SLIDE 0 — ACCUEIL ======================== -->
<div class="slide active fade-in" id="slide-0"> <div class="slide active fade-in" id="slide-0">
<div class="deco-circle" style="width:320px;height:320px;background:var(--amber-light);top:-80px;left:-80px;opacity:0.5;"></div> <div class="deco-circle" style="width:400px;height:400px;background:var(--amber-light);top:-100px;right:-80px;opacity:0.6;"></div>
<div style="text-align:center; z-index:1; width:100%;"> <div class="deco-circle" style="width:200px;height:200px;background:var(--amber-light);bottom:60px;left:-60px;opacity:0.5;"></div>
<div class="section-tag" style="background:var(--amber-light);color:var(--amber-dark);">Avant de commencer</div> <div style="text-align:center;">
<div class="slide-title">Identification du groupe</div> <div class="hero-label">Certibiocide — Travaux pratiques</div>
<p class="slide-intro" style="margin:0.5rem auto 0;max-width:420px;">Ces informations permettront au formateur de retrouver vos réponses.</p> <div class="hero-title">Étude de cas<br>Nuisibles</div>
<div class="id-form" style="margin-left:auto;margin-right:auto;"> <p class="hero-sub">Votre mission, si vous l'acceptez : maîtriser l'identification, le traitement et la sécurité face aux nuisibles.</p>
<div class="id-field"> <button class="btn btn-amber" onclick="goTo(1)">Démarrer la mission →</button>
<label for="inputNom">Nom du stagiaire ou du groupe</label>
<input type="text" id="inputNom" placeholder="Ex : Groupe 3 - Dupont / Martin" autocomplete="off">
</div>
<div class="id-field">
<label for="inputDate">Date de la session</label>
<input type="date" id="inputDate">
</div>
<div class="id-field">
<label>Formateur de la session</label>
<div style="display:flex;gap:10px;margin-top:2px;">
<button type="button" class="formateur-btn" id="btnNathan" onclick="selectFormateur('Nathan')">Nathan</button>
<button type="button" class="formateur-btn" id="btnGauthier" onclick="selectFormateur('Gauthier')">Gauthier</button>
</div>
</div>
</div>
<div id="idError" style="color:var(--coral);font-size:13px;margin-bottom:0.5rem;display:none;">Merci de renseigner au moins le nom du groupe.</div>
<div style="display:flex;gap:12px;justify-content:center;flex-wrap:wrap;">
<button class="btn btn-teal" onclick="confirmIdentite()">Continuer →</button>
<a href="correction-tp-nuisibles.html" class="btn btn-outline btn-teal">Corriger</a>
</div>
</div> </div>
</div> </div>
<!-- ======================== SLIDE 1 — ACCUEIL ======================== --> <!-- ======================== SLIDE 1 — CONTEXTE & IDENTIFICATION ======================== -->
<div class="slide" id="slide-1"> <div class="slide" id="slide-1">
<div class="deco-circle" style="width:400px;height:400px;background:var(--amber-light);top:-100px;right:-80px;opacity:0.6;"></div> <div class="deco-circle" style="width:300px;height:300px;background:var(--amber-light);top:-60px;left:-60px;opacity:0.4;"></div>
<div class="deco-circle" style="width:200px;height:200px;background:var(--amber-light);bottom:60px;left:-60px;opacity:0.5;"></div> <div class="section-tag" style="background:var(--amber-light);color:var(--amber-dark);">Avant de commencer</div>
<div style="text-align:center; z-index:1;"> <div class="slide-title">Contexte & accès</div>
<div class="hero-label">Formation</div> <p class="slide-intro">3 travaux pratiques vous attendent : protocole d'intervention, recherche produit sur BioCID, puis analyse de risques. Ces informations permettront au formateur de retrouver vos réponses.</p>
<div class="hero-title">CERTIBIOCIDE</div> <div class="info-card" style="max-width:620px;">
<div style="font-family:'Syne',sans-serif;font-size:clamp(14px,2.5vw,22px);font-weight:600;color:var(--amber-mid);letter-spacing:0.04em;margin-bottom:1.5rem;">TRAVAUX PRATIQUES</div> <span>🎯</span>
<p class="hero-sub">Votre mission, si vous l'acceptez : maîtriser l'identification, le traitement et la sécurité face aux nuisibles.</p> <div>Vous allez définir un <strong>protocole d'intervention</strong>, trouver votre <strong>produit sur BioCID</strong>, puis réaliser l'<strong>analyse de risques</strong>.</div>
<button class="btn btn-teal" onclick="goTo(2)">Démarrer la mission →</button> </div>
<div class="identity-card" style="max-width:620px;margin-top:0.5rem;">
<div class="identity-title">👥 Identification du groupe</div>
<div class="identity-fields">
<input type="text" id="inputNom" placeholder="Nom du groupe / stagiaires (ex : Groupe 3 — Dupont / Martin)" autocomplete="off">
<input type="date" id="inputDate">
<div class="formateur-label">Formateur de la session</div>
<div class="formateur-row">
<button type="button" class="formateur-btn" id="btnNathan" onclick="selectFormateur('Nathan')">Nathan</button>
<button type="button" class="formateur-btn" id="btnGauthier" onclick="selectFormateur('Gauthier')">Gauthier</button>
</div>
</div>
<div id="idError" style="color:var(--coral);font-size:13px;margin-top:8px;display:none;">Merci de renseigner au moins le nom du groupe.</div>
</div> </div>
</div> </div>
@@ -715,8 +717,12 @@
<div class="slide" id="slide-2"> <div class="slide" id="slide-2">
<div class="deco-circle" style="width:300px;height:300px;background:var(--amber-light);top:-60px;left:-60px;opacity:0.4;"></div> <div class="deco-circle" style="width:300px;height:300px;background:var(--amber-light);top:-60px;left:-60px;opacity:0.4;"></div>
<div class="section-tag" style="background:var(--amber-light);color:var(--amber-dark);">Étape 1 / 3</div> <div class="section-tag" style="background:var(--amber-light);color:var(--amber-dark);">Étape 1 / 3</div>
<div class="slide-title">Choisissez votre mission</div> <div class="slide-title">🎯 Choisissez votre mission</div>
<p class="slide-intro">Chaque groupe choisit un cas pratique. Vous travaillerez avec ce scénario tout au long des 3 exercices.</p> <p class="slide-intro">Chaque groupe choisit un cas pratique. Vous travaillerez avec ce scénario tout au long des 3 exercices.</p>
<div class="info-card" style="margin-bottom:1.25rem;">
<span>💡</span>
<div>Le cas retenu conditionne <strong>tous les exercices suivants</strong> et apparaît dans le PDF final. Choisissez celui qui se rapproche le plus de votre pratique.</div>
</div>
<div class="case-grid"> <div class="case-grid">
<div class="case-card" onclick="selectCase('punaises','🛏️','Hôtel — Punaises de lit','Infestation importante dans un hôtel')"> <div class="case-card" onclick="selectCase('punaises','🛏️','Hôtel — Punaises de lit','Infestation importante dans un hôtel')">
<div class="case-icon">🛏️</div> <div class="case-icon">🛏️</div>
@@ -744,9 +750,6 @@
<div class="case-sub">Camping</div> <div class="case-sub">Camping</div>
</div> </div>
</div> </div>
<div class="actions-row">
<button class="btn btn-teal" id="btnConfirmCase" onclick="goTo(3)" style="display:none">Confirmer le choix →</button>
</div>
</div> </div>
<!-- ======================== SLIDE 3 — CONSIGNES ======================== --> <!-- ======================== SLIDE 3 — CONSIGNES ======================== -->
@@ -774,7 +777,6 @@
<span>💡</span> <span>💡</span>
<div>À tout moment, cliquez sur <strong>« Besoin d'aide ? »</strong> pour accéder à un indice. Le formateur corrigera à l'oral en fin de séance.</div> <div>À tout moment, cliquez sur <strong>« Besoin d'aide ? »</strong> pour accéder à un indice. Le formateur corrigera à l'oral en fin de séance.</div>
</div> </div>
<button class="btn btn-teal" onclick="goTo(4)">Commencer le TP 1 →</button>
</div> </div>
<!-- ======================== SLIDE 4 — TP1 INTRO ======================== --> <!-- ======================== SLIDE 4 — TP1 INTRO ======================== -->
@@ -792,7 +794,6 @@
<div class="step-pill">2. Intervention</div> <div class="step-pill">2. Intervention</div>
<div class="step-pill">3. Évaluation</div> <div class="step-pill">3. Évaluation</div>
</div> </div>
<button class="btn btn-amber" onclick="goTo(5)">Commencer →</button>
</div> </div>
<!-- ======================== SLIDE 5 — TP1 PREVENTION ======================== --> <!-- ======================== SLIDE 5 — TP1 PREVENTION ======================== -->
@@ -833,10 +834,6 @@
<textarea class="answer-field" id="ans_prevention_4" placeholder="Votre réponse…" data-question="Éléments observés / preuve de présence du nuisible"></textarea> <textarea class="answer-field" id="ans_prevention_4" placeholder="Votre réponse…" data-question="Éléments observés / preuve de présence du nuisible"></textarea>
</li> </li>
</ul> </ul>
<div class="actions-row">
<button class="hint-btn" onclick="showHint('prevention')">💡 Besoin d'aide ?</button>
<button class="btn btn-amber" onclick="goTo(6)">Étape suivante →</button>
</div>
</div> </div>
</div> </div>
@@ -871,10 +868,6 @@
<textarea class="answer-field" id="ans_intervention_3" placeholder="Votre réponse…" data-question="Type de produit (TP) et mode de traitement"></textarea> <textarea class="answer-field" id="ans_intervention_3" placeholder="Votre réponse…" data-question="Type de produit (TP) et mode de traitement"></textarea>
</li> </li>
</ul> </ul>
<div class="actions-row">
<button class="hint-btn" onclick="showHint('intervention')">💡 Besoin d'aide ?</button>
<button class="btn btn-amber" onclick="goTo(7)">Étape suivante →</button>
</div>
</div> </div>
</div> </div>
@@ -905,10 +898,6 @@
<div style="background:var(--amber-light);border-radius:12px;padding:14px 18px;max-width:680px;width:100%;font-size:13px;color:var(--amber-dark);margin:0.75rem 0;"> <div style="background:var(--amber-light);border-radius:12px;padding:14px 18px;max-width:680px;width:100%;font-size:13px;color:var(--amber-dark);margin:0.75rem 0;">
<strong>TP 1 terminé !</strong> Préparez-vous à identifier le produit adapté à votre cas sur biocid-anses.fr. <strong>TP 1 terminé !</strong> Préparez-vous à identifier le produit adapté à votre cas sur biocid-anses.fr.
</div> </div>
<div class="actions-row">
<button class="hint-btn" onclick="showHint('evaluation')">💡 Besoin d'aide ?</button>
<button class="btn btn-coral" onclick="goTo(8)">Passer au TP 2 →</button>
</div>
</div> </div>
</div> </div>
@@ -930,7 +919,6 @@
</div> </div>
<a href="https://biocid-anses.fr" target="_blank" class="resource-link">Ouvrir le site ↗</a> <a href="https://biocid-anses.fr" target="_blank" class="resource-link">Ouvrir le site ↗</a>
</div> </div>
<button class="btn btn-coral" onclick="goTo(9)">Commencer →</button>
</div> </div>
<!-- ======================== SLIDE 9 — TP2 FDS & AMM ======================== --> <!-- ======================== SLIDE 9 — TP2 FDS & AMM ======================== -->
@@ -971,10 +959,6 @@
<textarea class="answer-field" id="ans_fds_4" placeholder="Votre réponse…" data-question="Risques pour la santé"></textarea> <textarea class="answer-field" id="ans_fds_4" placeholder="Votre réponse…" data-question="Risques pour la santé"></textarea>
</li> </li>
</ul> </ul>
<div class="actions-row">
<button class="hint-btn" onclick="showHint('fds')">💡 Besoin d'aide ?</button>
<button class="btn btn-coral" onclick="goTo(10)">Partie suivante →</button>
</div>
</div> </div>
</div> </div>
@@ -1019,10 +1003,6 @@
<div style="background:var(--amber-light);border-radius:12px;padding:14px 18px;max-width:680px;width:100%;font-size:13px;color:var(--amber-dark);margin:0.75rem 0;"> <div style="background:var(--amber-light);border-radius:12px;padding:14px 18px;max-width:680px;width:100%;font-size:13px;color:var(--amber-dark);margin:0.75rem 0;">
<strong>TP 2 terminé !</strong> Passez maintenant à l'analyse de risques complète. <strong>TP 2 terminé !</strong> Passez maintenant à l'analyse de risques complète.
</div> </div>
<div class="actions-row">
<button class="hint-btn" onclick="showHint('epi')">💡 Besoin d'aide ?</button>
<button class="btn btn-purple" onclick="goTo(11)">Passer au TP 3 →</button>
</div>
</div> </div>
</div> </div>
@@ -1039,9 +1019,6 @@
<div class="pyr-level" style="width:320px;background:#D85A30;">Communication</div> <div class="pyr-level" style="width:320px;background:#D85A30;">Communication</div>
<div class="pyr-level" style="width:360px;background:#BA7517;">EPI (dernier recours)</div> <div class="pyr-level" style="width:360px;background:#BA7517;">EPI (dernier recours)</div>
</div> </div>
<div class="actions-row" style="margin-top:1rem;">
<button class="btn btn-purple" onclick="goTo(12)">Remplir le tableau →</button>
</div>
</div> </div>
<!-- ======================== SLIDE 12 — TP3 TABLEAU ======================== --> <!-- ======================== SLIDE 12 — TP3 TABLEAU ======================== -->
@@ -1110,10 +1087,6 @@
<div style="background:var(--amber-light);border-radius:12px;padding:14px 18px;max-width:780px;width:100%;font-size:13px;color:var(--amber-dark);margin:0.25rem 0;"> <div style="background:var(--amber-light);border-radius:12px;padding:14px 18px;max-width:780px;width:100%;font-size:13px;color:var(--amber-dark);margin:0.25rem 0;">
<strong>TP 3 terminé !</strong> Présentez vos réponses au formateur pour la correction. <strong>TP 3 terminé !</strong> Présentez vos réponses au formateur pour la correction.
</div> </div>
<div class="actions-row">
<button class="hint-btn" onclick="showHint('analyse')">💡 Aide générale</button>
<button class="btn btn-blue" onclick="goTo(13)">Terminer la mission →</button>
</div>
</div> </div>
</div> </div>
@@ -1143,15 +1116,10 @@
<div class="final-card-sub">Analyse de risques</div> <div class="final-card-sub">Analyse de risques</div>
</div> </div>
</div> </div>
<div class="submit-zone"> <div class="actions-row">
<div style="display:flex;gap:10px;justify-content:center;flex-wrap:wrap;"> <button class="btn btn-amber" onclick="exporterPDF()">⬇ Télécharger mes réponses (PDF)</button>
<button class="btn btn-teal" onclick="exporterPDF()">⬇ Télécharger en PDF</button> <a href="corriger-exercice.html?exo=tp" class="btn btn-outline btn-teal">✔ Corriger un rendu</a>
<a href="correction-tp-nuisibles.html" class="btn btn-outline btn-teal">Corriger</a> <button class="btn btn-outline btn-teal" onclick="restartAll()">↺ Recommencer</button>
</div>
</div>
<div style="display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-top:0.75rem;">
<a href="https://biocid-anses.fr" target="_blank" class="btn btn-outline btn-teal" style="font-size:13px;padding:10px 20px;">biocid-anses.fr ↗</a>
<button class="btn btn-teal" onclick="restartAll()">↺ Recommencer depuis le début</button>
</div> </div>
</div> </div>
</div> </div>
@@ -1164,6 +1132,22 @@ const totalSlides = 13;
const selectedCase = { key: '', icon: '', title: '', desc: '' }; const selectedCase = { key: '', icon: '', title: '', desc: '' };
const stagiaire = { nom: '', date: '', formateur: '' }; const stagiaire = { nom: '', date: '', formateur: '' };
// Configuration des boutons du footer par slide (modèle BioCID)
const slideConfig = {
1: { next: 2, nextLabel: 'Continuer →' },
2: { next: 3, nextLabel: 'Confirmer le choix →' },
3: { next: 4, nextLabel: 'Commencer le TP 1 →' },
4: { next: 5, nextLabel: 'Commencer →' },
5: { next: 6, nextLabel: 'Étape suivante →', hint: 'prevention' },
6: { next: 7, nextLabel: 'Étape suivante →', hint: 'intervention' },
7: { next: 8, nextLabel: 'Passer au TP 2 →', hint: 'evaluation' },
8: { next: 9, nextLabel: 'Commencer →' },
9: { next: 10, nextLabel: 'Partie suivante →', hint: 'fds' },
10: { next: 11, nextLabel: 'Passer au TP 3 →', hint: 'epi' },
11: { next: 12, nextLabel: 'Remplir le tableau →' },
12: { next: 13, nextLabel: 'Terminer la mission →', hint: 'analyse' }
};
const hints = { const hints = {
prevention: { prevention: {
icon: '💡', icon: '💡',
@@ -1250,11 +1234,28 @@ function goTo(n) {
currentSlide = n; currentSlide = n;
next.style.display = 'flex'; next.style.display = 'flex';
setTimeout(() => { next.classList.add('active', 'fade-in'); }, 10); setTimeout(() => { next.classList.add('active', 'fade-in'); }, 10);
const sa = next.querySelector('.scroll-area');
if (sa) sa.scrollTop = 0;
if (n === 13) setTimeout(launchConfetti, 200); if (n === 13) setTimeout(launchConfetti, 200);
updateUI(); updateUI();
updateCaseBanners(); updateCaseBanners();
} }
function goNext() {
const cfg = slideConfig[currentSlide];
if (!cfg || cfg.next == null) return;
// Slide 1 : validation du nom du groupe avant de continuer
if (currentSlide === 1) { confirmIdentite(); return; }
// Slide 2 : un cas doit être sélectionné
if (currentSlide === 2 && !selectedCase.key) return;
goTo(cfg.next);
}
function showHintCurrent() {
const cfg = slideConfig[currentSlide];
if (cfg && cfg.hint) showHint(cfg.hint);
}
function goBack() { function goBack() {
if (history.length === 0) return; if (history.length === 0) return;
const prev = history.pop(); const prev = history.pop();
@@ -1279,37 +1280,31 @@ function updateUI() {
if (bottomBar) bottomBar.style.display = currentSlide === 0 ? 'none' : 'flex'; if (bottomBar) bottomBar.style.display = currentSlide === 0 ? 'none' : 'flex';
const backBtn = document.getElementById('backBtn'); const backBtn = document.getElementById('backBtn');
backBtn.style.display = history.length > 0 ? 'block' : 'none'; backBtn.style.display = history.length > 0 ? 'inline-flex' : 'none';
const badge = document.getElementById('phaseBadge'); const cfg = slideConfig[currentSlide] || {};
if (currentSlide === 0 || currentSlide === 1 || currentSlide === 2 || currentSlide === 3 || currentSlide === 13) { const hintBtn = document.getElementById('footerHintBtn');
badge.style.display = 'none'; const nextBtn = document.getElementById('footerNextBtn');
} else if (currentSlide >= 4 && currentSlide <= 7) { hintBtn.style.display = cfg.hint ? 'inline-flex' : 'none';
badge.style.display = 'block'; if (cfg.next != null) {
badge.style.background = 'var(--amber-light)'; // Slide 2 : le bouton n'apparaît qu'une fois le cas choisi
badge.style.color = 'var(--amber-dark)'; const gated = currentSlide === 2 && !selectedCase.key;
badge.textContent = 'TP 1 — Protocole'; nextBtn.style.display = gated ? 'none' : 'inline-flex';
} else if (currentSlide >= 8 && currentSlide <= 10) { nextBtn.textContent = cfg.nextLabel;
badge.style.display = 'block'; } else {
badge.style.background = 'var(--coral-light)'; nextBtn.style.display = 'none';
badge.style.color = 'var(--coral)';
badge.textContent = 'TP 2 — Produit';
} else if (currentSlide >= 11 && currentSlide <= 12) {
badge.style.display = 'block';
badge.style.background = 'var(--amber-light)';
badge.style.color = 'var(--amber-dark)';
badge.textContent = 'TP 3 — Risques';
} }
} }
function selectCase(key, icon, title, desc) { function selectCase(key, icon, title, desc) {
marquerModifie();
selectedCase.key = key; selectedCase.key = key;
selectedCase.icon = icon; selectedCase.icon = icon;
selectedCase.title = title; selectedCase.title = title;
selectedCase.desc = desc; selectedCase.desc = desc;
document.querySelectorAll('.case-card').forEach(c => c.classList.remove('selected')); document.querySelectorAll('.case-card').forEach(c => c.classList.remove('selected'));
event.currentTarget.classList.add('selected'); event.currentTarget.classList.add('selected');
document.getElementById('btnConfirmCase').style.display = 'inline-flex'; updateUI();
} }
function updateCaseBanners() { function updateCaseBanners() {
@@ -1379,7 +1374,7 @@ function confirmIdentite() {
document.getElementById('idError').style.display = 'none'; document.getElementById('idError').style.display = 'none';
stagiaire.nom = nom; stagiaire.nom = nom;
stagiaire.date = date || new Date().toISOString().slice(0, 10); stagiaire.date = date || new Date().toISOString().slice(0, 10);
goTo(1); goTo(2);
} }
/* ======================== TABLEAU TP3 — LIGNES DYNAMIQUES ======================== */ /* ======================== TABLEAU TP3 — LIGNES DYNAMIQUES ======================== */
@@ -1606,6 +1601,156 @@ function escapeHtml(str) {
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
} }
/* ======================== 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; 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 = `
<span class="restore-text">💾 Des réponses enregistrées${libelle} ont été retrouvées sur ce poste.</span>
<span class="restore-actions">
<button type="button" class="primary" id="btnRestaurer">Restaurer</button>
<button type="button" id="btnRepartir">Repartir de zéro</button>
</span>`;
document.body.appendChild(bar);
document.getElementById('btnRestaurer').onclick = () => { appliquerSauvegarde(data); bar.remove(); };
document.getElementById('btnRepartir').onclick = () => { effacerSauvegarde(); bar.remove(); };
}
proposerRestauration();
updateUI(); updateUI();
</script> </script>
</body> </body>

View File

@@ -1,650 +0,0 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Correction TP Nuisibles — Certibiocide</title>
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@600;700;800&family=DM+Sans:ital,wght@0,400;0,500;1,400&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../header-style.css"/>
<style>
body {
padding: calc(36px + 70px) 20px 80px;
}
.wrap { max-width: 820px; margin: 0 auto; }
.syne { font-family: 'Syne', sans-serif; }
.eyebrow {
font-family: 'Syne', sans-serif;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--amber);
margin-bottom: 6px;
}
h1 {
font-family: 'Syne', sans-serif;
font-size: 28px;
font-weight: 800;
letter-spacing: -0.5px;
margin-bottom: 28px;
}
/* DROPZONE */
#dropzone {
background: #fff;
border: 2px dashed rgba(186,117,23,0.35);
border-radius: 16px;
padding: 56px 24px;
text-align: center;
cursor: pointer;
transition: border-color 0.2s, background 0.2s;
}
#dropzone.drag { border-color: var(--amber); background: var(--amber-light); }
#dropzone .icon { font-size: 40px; margin-bottom: 12px; }
#dropzone p.title { font-family: 'Syne', sans-serif; font-weight: 700; font-size: 16px; margin-bottom: 6px; }
#dropzone p.sub { color: var(--text-muted); font-size: 13.5px; margin-bottom: 20px; }
#filePicker { display: none; }
.btn-pick {
font-family: 'Syne', sans-serif;
display: inline-block;
background: var(--amber);
color: #fff;
font-weight: 700;
font-size: 13px;
padding: 10px 26px;
border-radius: 30px;
cursor: pointer;
border: none;
}
#status { margin-top: 16px; font-size: 13px; }
#status.loading { color: var(--amber); }
#status.error { color: var(--coral); }
/* RESULT */
#result { display: none; }
.toprow {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 18px;
}
#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); }
.id-card { display: none; }
/* 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;
}
#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;
}
.id-card .label {
font-family: 'Syne', sans-serif;
font-size: 10.5px;
font-weight: 700;
color: var(--amber-dark);
letter-spacing: 0.05em;
text-transform: uppercase;
}
.id-card .value { font-size: 14.5px; margin-top: 2px; }
.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(--teal); transition: width 0.4s ease; }
.progress-row strong { font-family: 'Syne', sans-serif; color: var(--text); }
/* CARROUSEL DE THEMES */
.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: 420px;
}
.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; }
.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.miss { background: var(--coral-light); color: var(--coral); }
.qcard .qtext { flex: 1; }
.qcard .qlabel { font-weight: 600; font-size: 13.5px; margin-bottom: 4px; }
.qcard .qanswer { font-size: 13.5px; white-space: pre-wrap; }
.qcard .qanswer.empty { color: var(--coral); font-style: italic; }
.table-wrap {
background: #fff;
border: 1px solid var(--border);
border-radius: 12px;
padding: 16px 18px;
font-size: 13px;
margin-bottom: 14px;
}
/* TABLEAU ANALYSE DE RISQUES */
.risk-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.risk-table th {
font-family: 'Syne', sans-serif;
text-align: left;
font-size: 11.5px;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--amber-dark);
background: var(--amber-light);
padding: 10px 12px;
}
.risk-table th:first-child { border-radius: 8px 0 0 8px; }
.risk-table th:last-child { border-radius: 0 8px 8px 0; }
.risk-table td {
padding: 10px 12px;
border-bottom: 1px solid var(--border);
vertical-align: top;
white-space: pre-wrap;
}
.risk-table tr:last-child td { border-bottom: none; }
.meta-line { font-size: 12.5px; color: var(--text-muted); margin-top: 20px; }
.meta-line strong { color: var(--text); }
</style>
</head>
<body>
<header>
<div class="logo">NextGN Formation</div>
<nav>
<a href="../index.html">Accueil</a>
<a href="../index.html#formations">Exercices</a>
<a href="../index.html#documentation">Documentation</a>
<a href="../index.html#rapports">Rapports</a>
<a href="../index.html#about">À propos</a>
<a href="../index.html#contact">Contact</a>
</nav>
</header>
<div class="wrap">
<div id="participantsBanner" style="display:none;"></div>
<div class="eyebrow">Certibiocide — Correction de TP</div>
<h1>TP Nuisibles — Lecture du rendu</h1>
<div id="dropzone">
<div class="icon">📄</div>
<p class="title">Déposez le PDF du stagiaire ici</p>
<p class="sub">ou cliquez pour sélectionner le fichier exporté depuis le TP</p>
<label class="btn-pick">Choisir un fichier
<input type="file" id="filePicker" accept="application/pdf">
</label>
<div id="status"></div>
</div>
<div id="result">
<div class="toprow">
<span id="fileName"></span>
<button class="btn-reset" onclick="resetAll()">↺ Charger un autre PDF</button>
</div>
<div class="id-card" id="idCard"></div>
<div class="progress-row">
<div class="progress-track"><div class="progress-fill" id="progressFill"></div></div>
<span><strong id="progressCount"></strong> questions répondues</span>
</div>
<div class="carousel-wrap">
<div class="carousel-nav">
<button class="nav-btn" id="prevBtn" onclick="goTo(currentTheme - 1)"></button>
<div class="dots" id="dots"></div>
<button class="nav-btn" id="nextBtn" onclick="goTo(currentTheme + 1)"></button>
</div>
<div id="themePanel"></div>
</div>
<h2 class="syne" style="font-size:16px;font-weight:800;margin-bottom:12px;color:var(--amber-dark);">Analyse de risques</h2>
<div class="table-wrap" id="tp3Meta"></div>
<div class="table-wrap" id="tp3Table" style="display:none;"></div>
<p class="meta-line" id="suiviLine"></p>
</div>
</div>
<footer>
<p>&copy; 2026 NextGN Formation - Formations Certibiocide & Techniques</p>
<p style="margin-top: 10px; opacity: 0.6;">Tous droits réservés — Gauthier Chombart & Nathan Chauwin — Formateurs indépendants</p>
</footer>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
<script>
pdfjsLib.GlobalWorkerOptions.workerSrc = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js";
// Thèmes regroupant les sections du TP, avec icône et couleur
const THEMES = [
{
title: "Premier contact & diagnostic",
icon: "🗣️", color: "purple",
items: [
"Questions posées au client lors du premier contact",
"Conseils donnés en premier lieu",
"Actions de prévention menées sur le site",
"Éléments observés / preuve de présence du nuisible",
]
},
{
title: "Solutions envisagées",
icon: "🧰", color: "blue",
items: [
"Solutions alternatives non-biocides",
"Solutions biocides mises en place",
"Type de produit (TP) et mode de traitement",
"Suivi mis en place et durée",
"Gestion des déchets",
]
},
{
title: "Produit & traçabilité",
icon: "🧪", color: "teal",
items: [
"FDS trouvée (éléments principaux)",
"Nom du produit et fabricant/fournisseur",
"Numéro AMM",
]
},
{
title: "Sécurité & santé",
icon: "🛡️", color: "coral",
items: [
"Risques pour la santé",
"EPI à porter",
"Risques environnement / dispersion accidentelle",
"DL50 substance(s) active(s)",
"Gestes de premiers secours",
]
},
];
const ALL_SECTIONS = THEMES.flatMap(t => t.items);
const COLOR_MAP = {
purple: { bg: "var(--purple-light)", fg: "var(--purple)" },
blue: { bg: "var(--blue-light)", fg: "var(--blue)" },
teal: { bg: "var(--teal-light)", fg: "var(--teal)" },
coral: { bg: "var(--coral-light)", fg: "var(--coral)" },
};
function escapeRx(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); }
function escapeHtml(s) {
if (!s) return "";
return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
}
function isEmpty(v) {
if (!v) return true;
const t = v.trim();
return t === "" || /^\(sans réponse\)$/i.test(t);
}
function captureBetween(text, label, nextLabels) {
const startRx = new RegExp(escapeRx(label) + "\\s*:?\\s*", "i");
const m = startRx.exec(text);
if (!m) return "";
const from = m.index + m[0].length;
let to = text.length;
for (const nl of nextLabels) {
const endRx = new RegExp(escapeRx(nl), "i");
const m2 = endRx.exec(text.slice(from));
if (m2 && from + m2.index < to) to = from + m2.index;
}
return text.slice(from, to).trim();
}
function parsePdfText(raw) {
let clean = raw.replace(/\r/g, "").replace(/[ \t]+\n/g, "\n").replace(/\s+/g, " ").trim();
// Retire les répétitions d'en-tête de page (ex: "17/06/2026 15:42 Résultats TP — ... about:blank 1/2")
clean = clean.replace(/\d{1,2}\/\d{1,2}\/\d{4}\s+\d{1,2}:\d{2}\s+Résultats TP[^]*?about:blank\s*\d\/\d/gi, " ");
const meta = {
nom: captureBetween(clean, "Groupe / Stagiaire", ["Date de session"]),
date_session: captureBetween(clean, "Date de session", ["Formateur"]),
formateur: captureBetween(clean, "Formateur", ["Cas pratique"]),
cas: captureBetween(clean, "Cas pratique", ["Réponses TP1"]),
};
// Sépare "Groupe N" du nom des participants (ex: "Groupe 1 Romain Kevin Valere Greg P")
const groupMatch = /^Groupe\s*(\d+)\s*(.*)$/i.exec(meta.nom.trim());
if (groupMatch) {
meta.groupe = "Groupe " + groupMatch[1];
meta.participants = groupMatch[2].trim();
} else {
meta.groupe = "";
meta.participants = meta.nom.trim();
}
const startIdx = clean.search(/Réponses TP1\s*&?\s*TP2/i);
const endIdx = clean.search(/TP3\s*[—-]\s*Analyse de risques/i);
const block = startIdx >= 0 ? clean.slice(startIdx, endIdx >= 0 ? endIdx : clean.length) : clean;
const answers = {};
for (let i = 0; i < ALL_SECTIONS.length; i++) {
const label = ALL_SECTIONS[i];
const nextLabel = ALL_SECTIONS[i + 1];
answers[label] = captureBetween(block, label, nextLabel ? [nextLabel] : []);
}
const tp3Zone = endIdx >= 0 ? clean.slice(endIdx) : "";
const tp3 = {
intitule: captureBetween(tp3Zone, "Intitulé", ["Description"]),
description: captureBetween(tp3Zone, "Description", ["Lieu"]),
lieu: captureBetween(tp3Zone, "Lieu", ["Début prévu le"]),
debut: captureBetween(tp3Zone, "Début prévu le", ["Étapes"]),
};
// Tableau de risques : on découpe en s'appuyant sur les intitulés d'étapes connus du TP
const KNOWN_STEPS = [
"remplir la poudreuse",
"sécurisation du site",
"application traitements",
"rangement du matériel. purger la perche",
"retrait du nid",
"gestion des déchets. élimination par des filiaires spé.",
];
let riskRows = [];
const tableMatch = /Étapes\s*Risques\s*Mesures de prévention([\s\S]*?)(Réalisée par|$)/i.exec(tp3Zone);
if (tableMatch) {
let raw = tableMatch[1].trim();
if (raw) {
// Repère les positions de chaque étape connue dans le texte aplati
const positions = [];
KNOWN_STEPS.forEach(step => {
const idx = raw.toLowerCase().indexOf(step.toLowerCase());
if (idx >= 0) positions.push({ step, idx });
});
positions.sort((a, b) => a.idx - b.idx);
if (positions.length > 0) {
for (let i = 0; i < positions.length; i++) {
const from = positions[i].idx + positions[i].step.length;
const to = i + 1 < positions.length ? positions[i + 1].idx : raw.length;
let rest = raw.slice(from, to).trim();
// rest contient "risque... mesure..." -> on coupe sur les mots clés de mesures (gants/masques, barrieres, etc.)
const measureRx = /(gants\s*\/\s*masques\s*\/\s*lunettes[^.]*|barrieres\.?\s*epi)/i;
const mm = measureRx.exec(rest);
let risque = rest, mesure = "";
if (mm) {
risque = rest.slice(0, mm.index).trim();
mesure = rest.slice(mm.index).trim();
}
risque = risque.replace(/^[\s\-.]+/, "").trim();
riskRows.push({ etape: positions[i].step, risque: risque || "—", mesure: mesure || "—" });
}
} else {
riskRows.push({ etape: raw, risque: "", mesure: "" });
}
}
}
const mReal = clean.match(/Réalisée par\s*:\s*([^—\n]*)/i);
const mVerif = clean.match(/Vérifiée par\s*:\s*([^—\n]*)/i);
const mMaj = clean.match(/Mise à jour par\s*:\s*([^—\n]*)/i);
return {
meta, answers, tp3, riskRows,
suivi: {
realise: mReal ? mReal[1].trim() : "",
verifie: mVerif ? mVerif[1].trim() : "",
maj: mMaj ? mMaj[1].trim() : "",
}
};
}
let currentThemeData = null;
let currentTheme = 0;
function renderThemePanel() {
const theme = THEMES[currentTheme];
const data = currentThemeData;
const colors = COLOR_MAP[theme.color];
const themeAnswered = theme.items.filter(s => !isEmpty(data.answers[s])).length;
document.getElementById("themePanel").innerHTML = `
<div class="theme-panel">
<div class="theme-head">
<div class="theme-icon" style="background:${colors.bg};color:${colors.fg};">${theme.icon}</div>
<div class="theme-title">${theme.title}</div>
<div class="theme-count">${themeAnswered}/${theme.items.length}</div>
</div>
<div class="cards">
${theme.items.map(label => {
const value = data.answers[label] || "";
const empty = isEmpty(value);
return `
<div class="qcard">
<div class="badge ${empty ? 'miss' : 'ok'}">${empty ? '!' : '✓'}</div>
<div class="qtext">
<div class="qlabel">${escapeHtml(label)}</div>
<div class="qanswer ${empty ? 'empty' : ''}">${empty ? 'Sans réponse' : escapeHtml(value)}</div>
</div>
</div>
`;
}).join("")}
</div>
</div>
`;
document.getElementById("prevBtn").disabled = currentTheme === 0;
document.getElementById("nextBtn").disabled = currentTheme === THEMES.length - 1;
document.querySelectorAll(".dot").forEach((d, i) => d.classList.toggle("active", i === currentTheme));
}
function goTo(idx) {
if (idx < 0 || idx >= THEMES.length) return;
currentTheme = idx;
renderThemePanel();
}
function renderResult(data) {
currentThemeData = data;
currentTheme = 0;
const banner = document.getElementById("participantsBanner");
if (data.meta.participants) {
banner.style.display = "flex";
banner.innerHTML = `
<div class="pb-icon">👥</div>
<div>
<div class="pb-group">${escapeHtml(data.meta.groupe) || "Participants"}</div>
<div class="pb-names">${escapeHtml(data.meta.participants)}</div>
</div>
`;
} else {
banner.style.display = "none";
}
document.getElementById("idCard").innerHTML = `
<div><div class="label">Groupe / Stagiaire</div><div class="value">${escapeHtml(data.meta.nom) || "—"}</div></div>
<div><div class="label">Date de session</div><div class="value">${escapeHtml(data.meta.date_session) || "—"}</div></div>
<div><div class="label">Formateur</div><div class="value">${escapeHtml(data.meta.formateur) || "—"}</div></div>
<div><div class="label">Cas pratique</div><div class="value">${escapeHtml(data.meta.cas) || "—"}</div></div>
`;
const answeredCount = ALL_SECTIONS.filter(s => !isEmpty(data.answers[s])).length;
document.getElementById("progressFill").style.width = (answeredCount / ALL_SECTIONS.length * 100) + "%";
document.getElementById("progressCount").textContent = `${answeredCount} / ${ALL_SECTIONS.length}`;
document.getElementById("dots").innerHTML = THEMES.map((_, i) =>
`<div class="dot${i === 0 ? ' active' : ''}" onclick="goTo(${i})"></div>`
).join("");
renderThemePanel();
document.getElementById("tp3Meta").innerHTML = `
<p style="margin-bottom:6px;"><strong>Intitulé :</strong> ${escapeHtml(data.tp3.intitule) || '<em style="color:var(--coral)">—</em>'}</p>
<p style="margin-bottom:6px;"><strong>Description :</strong> ${escapeHtml(data.tp3.description) || '<em style="color:var(--coral)">—</em>'}</p>
<p style="margin-bottom:6px;"><strong>Lieu :</strong> ${escapeHtml(data.tp3.lieu) || '<em style="color:var(--coral)">—</em>'}</p>
<p><strong>Début prévu le :</strong> ${escapeHtml(data.tp3.debut) || '<em style="color:var(--coral)">—</em>'}</p>
`;
const tp3TableEl = document.getElementById("tp3Table");
if (data.riskRows && data.riskRows.length > 0) {
tp3TableEl.style.display = "block";
tp3TableEl.innerHTML = `
<table class="risk-table">
<thead><tr><th>Étapes</th><th>Risques</th><th>Mesures de prévention</th></tr></thead>
<tbody>
${data.riskRows.map(r => `
<tr>
<td>${escapeHtml(r.etape) || "—"}</td>
<td>${escapeHtml(r.risque) || "—"}</td>
<td>${escapeHtml(r.mesure) || "—"}</td>
</tr>
`).join("")}
</tbody>
</table>
`;
} else {
tp3TableEl.style.display = "none";
}
document.getElementById("suiviLine").innerHTML =
`Réalisée par : <strong>${escapeHtml(data.suivi.realise) || "—"}</strong> — Vérifiée par : <strong>${escapeHtml(data.suivi.verifie) || "—"}</strong> — Mise à jour par : <strong>${escapeHtml(data.suivi.maj) || "—"}</strong>`;
document.getElementById("dropzone").style.display = "none";
document.getElementById("result").style.display = "block";
}
async function handleFile(file) {
if (!file) return;
const statusEl = document.getElementById("status");
statusEl.className = "loading";
statusEl.textContent = "Lecture du PDF en cours…";
document.getElementById("fileName").textContent = file.name;
try {
const arrayBuffer = await file.arrayBuffer();
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
let fullText = "";
for (let p = 1; p <= pdf.numPages; p++) {
const page = await pdf.getPage(p);
const content = await page.getTextContent();
fullText += content.items.map(it => it.str).join(" ") + "\n";
}
const data = parsePdfText(fullText);
renderResult(data);
statusEl.textContent = "";
} catch (e) {
console.error(e);
statusEl.className = "error";
statusEl.textContent = "Impossible de lire ce PDF. Vérifiez qu'il s'agit bien d'un export du TP.";
}
}
function resetAll() {
document.getElementById("result").style.display = "none";
document.getElementById("dropzone").style.display = "block";
document.getElementById("participantsBanner").style.display = "none";
document.getElementById("status").textContent = "";
document.getElementById("filePicker").value = "";
document.getElementById("tp3Table").style.display = "none";
}
const dropzone = document.getElementById("dropzone");
dropzone.addEventListener("dragover", e => { e.preventDefault(); dropzone.classList.add("drag"); });
dropzone.addEventListener("dragleave", () => dropzone.classList.remove("drag"));
dropzone.addEventListener("drop", e => {
e.preventDefault();
dropzone.classList.remove("drag");
const file = e.dataTransfer.files?.[0];
if (file) handleFile(file);
});
document.getElementById("filePicker").addEventListener("change", e => handleFile(e.target.files?.[0]));
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -44,15 +44,15 @@
@keyframes matchSuccess { @keyframes matchSuccess {
0% { 0% {
transform: scale(1); transform: scale(1);
filter: drop-shadow(0 0 0px rgba(76, 175, 80, 0.6)); filter: drop-shadow(0 0 0px rgba(29, 158, 117, 0.6));
} }
50% { 50% {
transform: scale(1.15) rotate(5deg); transform: scale(1.15) rotate(5deg);
filter: drop-shadow(0 0 12px rgba(76, 175, 80, 0.8)); filter: drop-shadow(0 0 12px rgba(29, 158, 117, 0.8));
} }
100% { 100% {
transform: scale(1); transform: scale(1);
filter: drop-shadow(0 0 0px rgba(76, 175, 80, 0)); filter: drop-shadow(0 0 0px rgba(29, 158, 117, 0));
} }
} }
@@ -101,14 +101,14 @@
/* Pulse Progress Bar */ /* Pulse Progress Bar */
@keyframes progressPulse { @keyframes progressPulse {
0%, 100% { 0%, 100% {
box-shadow: 0 0 8px rgba(29, 158, 117, 0.4); box-shadow: 0 0 8px rgba(186, 117, 23, 0.4);
} }
50% { 50% {
box-shadow: 0 0 24px rgba(29, 158, 117, 0.8); box-shadow: 0 0 24px rgba(186, 117, 23, 0.8);
} }
} }
.progress-bar.pulse { .game-progress-fill.pulse {
animation: progressPulse 0.6s ease-out; animation: progressPulse 0.6s ease-out;
} }
@@ -189,7 +189,7 @@
@keyframes statUpdate { @keyframes statUpdate {
0% { 0% {
transform: scale(0.8); transform: scale(0.8);
background: rgba(29, 158, 117, 0.2); background: rgba(186, 117, 23, 0.2);
} }
100% { 100% {
transform: scale(1); transform: scale(1);
@@ -255,10 +255,10 @@
/* Card glow hover */ /* Card glow hover */
@keyframes glowCard { @keyframes glowCard {
0% { 0% {
box-shadow: 0 0 0px rgba(29, 158, 117, 0); box-shadow: 0 0 0px rgba(186, 117, 23, 0);
} }
100% { 100% {
box-shadow: 0 0 16px rgba(29, 158, 117, 0.3); box-shadow: 0 0 16px rgba(186, 117, 23, 0.3);
} }
} }
@@ -281,10 +281,10 @@
/* Pulse button */ /* Pulse button */
@keyframes buttonPulse { @keyframes buttonPulse {
0%, 100% { 0%, 100% {
box-shadow: 0 0 0px rgba(29, 158, 117, 0.7); box-shadow: 0 0 0px rgba(186, 117, 23, 0.7);
} }
50% { 50% {
box-shadow: 0 0 16px rgba(29, 158, 117, 0.4); box-shadow: 0 0 16px rgba(186, 117, 23, 0.4);
} }
} }

View File

@@ -7,15 +7,14 @@
============================================ */ ============================================ */
.game-hud { .game-hud {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); grid-template-columns: repeat(4, minmax(120px, 1fr));
gap: 16px; gap: 16px;
margin-bottom: 32px; flex: 1;
animation: fadeInUp 0.6s ease-out 0.4s both;
} }
.stat-box { .stat-box {
background: linear-gradient(135deg, var(--teal-light), var(--white)); background: linear-gradient(135deg, var(--amber-light), var(--white));
border: 2px solid var(--teal-mid); border: 2px solid var(--amber-mid);
border-radius: 12px; border-radius: 12px;
padding: 20px; padding: 20px;
text-align: center; text-align: center;
@@ -38,7 +37,7 @@
.stat-box:hover { .stat-box:hover {
transform: translateY(-4px); transform: translateY(-4px);
box-shadow: var(--shadow-lg); box-shadow: var(--shadow-lg);
border-color: var(--teal-dark); border-color: var(--amber-dark);
} }
.stat-box:hover::before { .stat-box:hover::before {
@@ -57,7 +56,7 @@
.stat-box .value { .stat-box .value {
font-size: 32px; font-size: 32px;
font-weight: 800; font-weight: 800;
background: linear-gradient(135deg, var(--teal-mid), var(--teal-dark)); background: linear-gradient(135deg, var(--amber-mid), var(--amber-dark));
-webkit-background-clip: text; -webkit-background-clip: text;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
background-clip: text; background-clip: text;
@@ -73,7 +72,7 @@
PROGRESS BAR (Paires trouvées) PROGRESS BAR (Paires trouvées)
============================================ */ ============================================ */
.progress-container { .progress-container {
background: var(--teal-light); background: var(--amber-light);
border-radius: 12px; border-radius: 12px;
overflow: hidden; overflow: hidden;
height: 12px; height: 12px;
@@ -82,14 +81,14 @@
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-sm);
} }
.progress-bar { .game-progress-fill {
height: 100%; height: 100%;
background: linear-gradient(90deg, var(--teal-mid), var(--teal-dark)); background: linear-gradient(90deg, var(--amber-mid), var(--amber-dark));
width: 0%; width: 0%;
transition: width 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); transition: width 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
} }
.progress-container.pulse .progress-bar { .progress-container.pulse .game-progress-fill {
animation: progressPulse 0.6s ease-out; animation: progressPulse 0.6s ease-out;
} }
@@ -148,15 +147,15 @@
} }
.modal-content { .modal-content {
background: linear-gradient(135deg, var(--white), var(--teal-light)); background: linear-gradient(135deg, var(--white), var(--amber-light));
border-radius: 24px; border-radius: 12px;
padding: 48px; padding: 48px;
max-width: 500px; max-width: 500px;
width: 100%; width: 100%;
text-align: center; text-align: center;
animation: slideUp 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); animation: slideUp 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
border: 2px solid var(--teal-mid); border: 2px solid var(--amber-mid);
box-shadow: 0 32px 64px rgba(15, 110, 86, 0.3); box-shadow: 0 32px 64px rgba(186, 117, 23, 0.3);
position: relative; position: relative;
overflow: hidden; overflow: hidden;
} }
@@ -177,7 +176,7 @@
font-family: 'Syne', sans-serif; font-family: 'Syne', sans-serif;
font-size: 42px; font-size: 42px;
font-weight: 800; font-weight: 800;
color: var(--teal); color: var(--amber);
margin: 32px 0 12px; margin: 32px 0 12px;
position: relative; position: relative;
z-index: 1; z-index: 1;
@@ -186,7 +185,7 @@
.modal-content .score-display { .modal-content .score-display {
font-size: 64px; font-size: 64px;
font-weight: 800; font-weight: 800;
background: linear-gradient(135deg, var(--teal-mid), var(--teal-dark)); background: linear-gradient(135deg, var(--amber-mid), var(--amber-dark));
-webkit-background-clip: text; -webkit-background-clip: text;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
background-clip: text; background-clip: text;
@@ -203,10 +202,10 @@
} }
.stats-summary .stat { .stats-summary .stat {
background: var(--teal-light); background: var(--amber-light);
padding: 12px; padding: 12px;
border-radius: 8px; border-radius: 8px;
border: 1px solid var(--teal-mid); border: 1px solid var(--amber-mid);
animation: popIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); animation: popIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
} }
@@ -217,7 +216,7 @@
.stats-summary .stat-value { .stats-summary .stat-value {
font-weight: 700; font-weight: 700;
color: var(--teal-dark); color: var(--amber-dark);
font-size: 16px; font-size: 16px;
font-family: 'Syne', sans-serif; font-family: 'Syne', sans-serif;
} }
@@ -230,6 +229,41 @@
margin-top: 4px; margin-top: 4px;
} }
/* ============================================
PLAYER NAME MODAL
============================================ */
.name-modal-content h2 {
margin-top: 32px;
}
.name-modal-text {
color: var(--text-muted);
font-size: 14px;
line-height: 1.5;
margin-bottom: 24px;
position: relative;
z-index: 1;
}
.name-input {
width: 100%;
padding: 14px 16px;
font-size: 16px;
font-family: 'DM Sans', sans-serif;
border: 2px solid var(--amber-mid);
border-radius: 8px;
background: var(--white);
color: var(--text);
text-align: center;
box-sizing: border-box;
}
.name-input:focus {
outline: none;
border-color: var(--amber-dark);
box-shadow: 0 0 0 3px rgba(186, 117, 23, 0.15);
}
/* ============================================ /* ============================================
ACHIEVEMENTS ACHIEVEMENTS
============================================ */ ============================================ */
@@ -289,52 +323,6 @@
min-width: 140px; min-width: 140px;
} }
/* ============================================
PAUSE MODAL
============================================ */
.pause-modal {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 1999;
backdrop-filter: blur(5px);
}
.pause-content {
background: var(--white);
border-radius: 16px;
padding: 40px;
text-align: center;
border: 2px solid var(--teal-mid);
animation: slideUp 0.4s ease-out;
}
.pause-content h2 {
font-family: 'Syne', sans-serif;
font-size: 32px;
color: var(--teal);
margin-bottom: 24px;
}
.pause-content .pause-timer {
font-size: 48px;
font-family: 'Syne', sans-serif;
font-weight: 800;
color: var(--teal-dark);
margin-bottom: 24px;
animation: timerTick 0.5s ease-out;
}
.pause-actions {
display: flex;
gap: 12px;
justify-content: center;
flex-wrap: wrap;
}
/* ============================================ /* ============================================
RESPONSIVE DESIGN RESPONSIVE DESIGN
============================================ */ ============================================ */

View File

@@ -2,18 +2,25 @@
LEADERBOARD & PLAYER STATS LEADERBOARD & PLAYER STATS
============================================ */ ============================================ */
.leaderboard-section { .leaderboard-panel {
margin-top: 80px; animation: fadeInUp 0.6s ease-out 0.6s both;
text-align: center;
}
.stats-panel {
animation: fadeInUp 0.6s ease-out 0.6s both; animation: fadeInUp 0.6s ease-out 0.6s both;
} }
.leaderboard-section h2 { .leaderboard-panel h2 {
font-family: 'Syne', sans-serif; font-family: 'Syne', sans-serif;
font-size: clamp(24px, 5vw, 32px); font-size: clamp(20px, 4vw, 26px);
font-weight: 800; font-weight: 800;
color: var(--teal); letter-spacing: -0.5px;
margin-bottom: 24px; color: var(--text);
text-align: center; margin: 0 auto 24px;
padding-bottom: 12px;
border-bottom: 3px solid var(--amber-mid);
display: inline-block;
} }
/* ============================================ /* ============================================
@@ -30,7 +37,7 @@
.tab-btn { .tab-btn {
padding: 10px 20px; padding: 10px 20px;
background: var(--white); background: var(--white);
border: 2px solid var(--teal-light); border: 2px solid var(--amber-light);
border-radius: 8px; border-radius: 8px;
font-family: 'Syne', sans-serif; font-family: 'Syne', sans-serif;
font-size: 12px; font-size: 12px;
@@ -43,13 +50,13 @@
} }
.tab-btn:hover { .tab-btn:hover {
border-color: var(--teal-mid); border-color: var(--amber-mid);
color: var(--teal-mid); color: var(--amber-mid);
} }
.tab-btn.active { .tab-btn.active {
background: linear-gradient(135deg, var(--teal-mid), var(--teal-dark)); background: linear-gradient(135deg, var(--amber-mid), var(--amber-dark));
border-color: var(--teal-dark); border-color: var(--amber-dark);
color: var(--white); color: var(--white);
box-shadow: var(--shadow-md); box-shadow: var(--shadow-md);
} }
@@ -58,12 +65,13 @@
LEADERBOARD LIST LEADERBOARD LIST
============================================ */ ============================================ */
.leaderboard { .leaderboard {
background: linear-gradient(135deg, var(--teal-light), var(--white)); background: linear-gradient(135deg, var(--amber-light), var(--white));
border-radius: 16px; border-radius: 12px;
padding: 24px; padding: 24px;
border: 2px solid var(--teal-mid); border: 2px solid var(--amber-mid);
margin-bottom: 48px; margin-bottom: 48px;
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-sm);
text-align: left;
} }
.leaderboard-list { .leaderboard-list {
@@ -73,19 +81,25 @@
} }
.rank-item { .rank-item {
display: grid; display: flex;
grid-template-columns: 50px 1fr auto auto; flex-direction: column;
align-items: center; gap: 8px;
gap: 16px;
background: var(--white); background: var(--white);
padding: 16px; padding: 14px;
border-radius: 8px; border-radius: 8px;
transition: all 0.3s ease; transition: all 0.3s ease;
border-left: 4px solid var(--teal-light); border-left: 4px solid var(--amber-light);
position: relative; position: relative;
overflow: hidden; overflow: hidden;
} }
.rank-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.rank-item::before { .rank-item::before {
content: ''; content: '';
position: absolute; position: absolute;
@@ -93,14 +107,14 @@
left: -100%; left: -100%;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(29, 158, 117, 0.1), transparent); background: linear-gradient(90deg, transparent, rgba(186, 117, 23, 0.1), transparent);
transition: left 0.5s ease; transition: left 0.5s ease;
} }
.rank-item:hover { .rank-item:hover {
transform: translateX(8px); transform: translateX(8px);
box-shadow: 0 4px 12px rgba(15, 110, 86, 0.15); box-shadow: 0 4px 12px rgba(186, 117, 23, 0.15);
border-left-color: var(--teal-mid); border-left-color: var(--amber-mid);
} }
.rank-item:hover::before { .rank-item:hover::before {
@@ -111,35 +125,21 @@
.rank-number { .rank-number {
font-weight: 800; font-weight: 800;
font-size: 18px; font-size: 18px;
color: var(--teal-mid); color: var(--amber-mid);
text-align: center; text-align: center;
font-family: 'Syne', sans-serif; font-family: 'Syne', sans-serif;
} }
.rank-number.medal-1 { .rank-number.medal-1,
font-size: 24px; .rank-number.medal-2,
} .rank-number.medal-3 {
font-size: 22px;
.rank-number.medal-1::before {
content: '🥇';
display: block;
}
.rank-number.medal-2::before {
content: '🥈';
display: block;
font-size: 20px;
}
.rank-number.medal-3::before {
content: '🥉';
display: block;
font-size: 20px;
} }
/* Infos joueur */ /* Infos joueur */
.rank-info { .rank-info {
flex: 1; flex: 1;
min-width: 0;
} }
.player-name { .player-name {
@@ -147,17 +147,20 @@
color: var(--text); color: var(--text);
margin-bottom: 4px; margin-bottom: 4px;
font-size: 14px; font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
.player-stats { .rank-meta {
font-size: 12px; font-size: 11px;
color: var(--text-muted); color: var(--text-muted);
display: flex; display: flex;
gap: 12px; gap: 10px;
flex-wrap: wrap; flex-wrap: wrap;
} }
.player-stats span { .rank-meta span {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 4px; gap: 4px;
@@ -166,39 +169,11 @@
/* Score et difficulté */ /* Score et difficulté */
.rank-score { .rank-score {
font-weight: 700; font-weight: 700;
color: var(--teal-dark); color: var(--amber-dark);
font-size: 18px; font-size: 16px;
font-family: 'Syne', sans-serif; font-family: 'Syne', sans-serif;
min-width: 60px;
text-align: right; text-align: right;
} flex-shrink: 0;
.rank-difficulty {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
padding: 4px 8px;
border-radius: 4px;
background: var(--teal-light);
color: var(--teal-dark);
min-width: 60px;
text-align: center;
}
.rank-difficulty.easy {
background: #C8E6C9;
color: #2E7D32;
}
.rank-difficulty.normal {
background: var(--teal-light);
color: var(--teal-dark);
}
.rank-difficulty.hard {
background: #FFCCBC;
color: #D84315;
} }
/* Message si pas de scores */ /* Message si pas de scores */
@@ -227,32 +202,32 @@
PLAYER STATS SECTION PLAYER STATS SECTION
============================================ */ ============================================ */
.player-stats { .player-stats {
background: linear-gradient(135deg, rgba(15, 110, 86, 0.05), transparent); background: linear-gradient(135deg, rgba(186, 117, 23, 0.05), transparent);
border-radius: 16px; border-radius: 12px;
padding: 32px; padding: 24px;
border: 2px solid var(--teal-light); border: 2px solid var(--amber-light);
} }
.player-stats h3 { .player-stats h3 {
font-family: 'Syne', sans-serif; font-family: 'Syne', sans-serif;
font-size: 24px; font-size: 18px;
font-weight: 700; font-weight: 700;
color: var(--teal); color: var(--amber);
margin-bottom: 24px; margin-bottom: 16px;
text-align: center; text-align: center;
} }
.stats-grid { .stats-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); grid-template-columns: repeat(2, 1fr);
gap: 16px; gap: 12px;
} }
.stat-card { .stat-card {
background: var(--white); background: var(--white);
border: 2px solid var(--teal-light); border: 2px solid var(--amber-light);
border-radius: 12px; border-radius: 10px;
padding: 20px; padding: 14px 8px;
text-align: center; text-align: center;
transition: all 0.3s ease; transition: all 0.3s ease;
display: flex; display: flex;
@@ -263,27 +238,27 @@
.stat-card:hover { .stat-card:hover {
transform: translateY(-4px); transform: translateY(-4px);
box-shadow: var(--shadow-md); box-shadow: var(--shadow-md);
border-color: var(--teal-mid); border-color: var(--amber-mid);
} }
.stat-icon { .stat-icon {
font-size: 32px; font-size: 22px;
margin-bottom: 12px; margin-bottom: 6px;
} }
.stat-label { .stat-label {
font-size: 11px; font-size: 10px;
font-weight: 600; font-weight: 600;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.05em; letter-spacing: 0.05em;
color: var(--text-muted); color: var(--text-muted);
margin-bottom: 8px; margin-bottom: 4px;
} }
.stat-value { .stat-value {
font-size: 28px; font-size: 20px;
font-weight: 800; font-weight: 800;
background: linear-gradient(135deg, var(--teal-mid), var(--teal-dark)); background: linear-gradient(135deg, var(--amber-mid), var(--amber-dark));
-webkit-background-clip: text; -webkit-background-clip: text;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
background-clip: text; background-clip: text;
@@ -291,7 +266,7 @@
} }
.stat-card.high-value .stat-value { .stat-card.high-value .stat-value {
font-size: 32px; font-size: 22px;
animation: popIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); animation: popIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
} }
@@ -324,28 +299,19 @@
RESPONSIVE DESIGN RESPONSIVE DESIGN
============================================ */ ============================================ */
@media (max-width: 768px) { @media (max-width: 768px) {
.leaderboard-section h2 { .leaderboard-panel h2 {
font-size: 24px; font-size: 24px;
} }
.tabs { .tabs {
gap: 6px; gap: 6px;
} }
.tab-btn { .tab-btn {
padding: 8px 16px; padding: 8px 16px;
font-size: 11px; font-size: 11px;
} }
.rank-item {
grid-template-columns: 40px 1fr auto;
gap: 12px;
}
.rank-difficulty {
display: none;
}
.rank-number { .rank-number {
font-size: 16px; font-size: 16px;
} }
@@ -355,29 +321,29 @@
} }
.player-stats { .player-stats {
padding: 24px; padding: 20px;
} }
.player-stats h3 { .player-stats h3 {
font-size: 20px; font-size: 18px;
} }
.stats-grid { .stats-grid {
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
gap: 12px; gap: 10px;
} }
.stat-card { .stat-card {
padding: 16px; padding: 14px 10px;
} }
.stat-icon { .stat-icon {
font-size: 28px;
margin-bottom: 8px;
}
.stat-value {
font-size: 24px; font-size: 24px;
margin-bottom: 6px;
}
.stat-value {
font-size: 22px;
} }
} }
@@ -397,17 +363,10 @@
} }
.rank-item { .rank-item {
grid-template-columns: 40px 1fr; gap: 8px;
gap: 10px;
padding: 12px; padding: 12px;
} }
.rank-score {
grid-column: 1 / -1;
text-align: left;
margin-top: 8px;
}
.rank-number { .rank-number {
font-size: 14px; font-size: 14px;
} }
@@ -419,24 +378,25 @@
.player-stats { .player-stats {
padding: 16px; padding: 16px;
} }
.player-stats h3 { .player-stats h3 {
font-size: 16px;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
gap: 8px;
}
.stat-card {
padding: 12px 8px;
}
.stat-icon {
font-size: 22px;
}
.stat-value {
font-size: 18px; font-size: 18px;
} }
.stats-grid {
grid-template-columns: 1fr;
}
.stat-card {
padding: 12px;
}
.stat-icon {
font-size: 24px;
}
.stat-value {
font-size: 20px;
}
} }

View File

@@ -5,15 +5,15 @@
:root { :root {
/* Gamification Colors */ /* Gamification Colors */
--success: #4CAF50; --success: var(--teal-mid);
--warning: #FF9800; --warning: #FF9800;
--info: #2196F3; --info: #2196F3;
--accent: #FFD700; --accent: #FFD700;
/* Shadows */ /* Shadows (alignées sur le langage visuel du site) */
--shadow-sm: 0 4px 8px rgba(15, 110, 86, 0.1); --shadow-sm: 0 4px 12px rgba(0, 0, 0, 0.05);
--shadow-md: 0 8px 16px rgba(15, 110, 86, 0.15); --shadow-md: 0 12px 32px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 16px 32px rgba(15, 110, 86, 0.25); --shadow-lg: 0 20px 50px rgba(0, 0, 0, 0.15);
} }
/* ============================================ /* ============================================
@@ -21,12 +21,12 @@
============================================ */ ============================================ */
main { main {
margin-top: 70px; margin-top: 70px;
padding: 40px; padding: 96px 40px 40px;
min-height: calc(100vh - 70px); min-height: calc(100vh - 70px);
} }
.game-container { .game-container {
max-width: 1200px; max-width: 1440px;
margin: 0 auto; margin: 0 auto;
} }
@@ -34,15 +34,16 @@ main {
INTRO SECTION INTRO SECTION
============================================ */ ============================================ */
.intro-section { .intro-section {
margin-bottom: 60px; margin-bottom: 48px;
animation: fadeInDown 0.6s ease-out; animation: fadeIn 0.6s ease-out;
text-align: center;
} }
.intro-section h1 { .intro-section h1 {
font-family: 'Syne', sans-serif; font-family: 'Syne', sans-serif;
font-size: clamp(28px, 5vw, 48px); font-size: clamp(28px, 5vw, 44px);
font-weight: 800; font-weight: 800;
color: var(--teal); color: var(--amber);
margin-bottom: 8px; margin-bottom: 8px;
text-align: center; text-align: center;
} }
@@ -56,11 +57,10 @@ main {
} }
.intro-box { .intro-box {
background: linear-gradient(135deg, var(--teal-light), var(--white)); background: linear-gradient(135deg, var(--amber-light), var(--white));
border-left: 4px solid var(--teal-mid); border-left: 4px solid var(--amber-mid);
border-radius: 8px; border-radius: 8px;
padding: 20px; padding: 20px;
margin-bottom: 32px;
font-size: 0.95em; font-size: 0.95em;
color: var(--text-muted); color: var(--text-muted);
line-height: 1.6; line-height: 1.6;
@@ -70,93 +70,148 @@ main {
text-align: center; text-align: center;
} }
/* ============================================
TOP BAR (niveau + stats en temps réel)
============================================ */
.game-top-bar {
max-width: 1440px;
margin: 0 auto 32px;
background: var(--white);
border: 1px solid var(--border);
border-radius: 12px;
padding: 24px clamp(20px, 3vw, 36px);
box-shadow: var(--shadow-sm);
display: flex;
align-items: center;
justify-content: space-between;
gap: clamp(20px, 3vw, 40px);
animation: fadeInUp 0.6s ease-out 0.2s both;
}
/* ============================================
GAME LAYOUT (classement / jeu / stats)
============================================ */
.game-layout {
display: grid;
grid-template-columns: minmax(0, 320px) minmax(0, 760px) minmax(0, 320px);
grid-template-areas: "leaderboard game stats";
justify-content: center;
align-items: start;
gap: 32px;
margin-bottom: 80px;
}
.leaderboard-panel {
grid-area: leaderboard;
}
.stats-panel {
grid-area: stats;
}
/* ============================================
GAME SECTION / PANEL
============================================ */
.game-section {
grid-area: game;
animation: fadeInUp 0.6s ease-out 0.3s both;
}
.game-panel {
background: var(--white);
border: 1px solid var(--border);
border-radius: 12px;
padding: clamp(20px, 4vw, 32px);
box-shadow: var(--shadow-sm);
}
/* ============================================ /* ============================================
DIFFICULTY SELECTOR DIFFICULTY SELECTOR
============================================ */ ============================================ */
.difficulty-selector { .difficulty-selector {
margin-bottom: 48px; text-align: left;
text-align: center; flex-shrink: 0;
animation: fadeInUp 0.6s ease-out 0.2s both;
} }
.difficulty-selector label { .difficulty-selector label {
display: block; display: block;
font-size: 14px; font-size: 12px;
font-weight: 600; font-weight: 700;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.08em; letter-spacing: 0.1em;
color: var(--text-muted); color: var(--text-muted);
margin-bottom: 16px; margin-bottom: 14px;
} }
.btn-group { .btn-group {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); grid-template-columns: repeat(3, minmax(100px, 1fr));
gap: 12px; gap: 12px;
max-width: 500px;
margin: 0 auto;
} }
.btn-difficulty { .btn-difficulty {
background: var(--white); background: var(--amber-light);
border: 2px solid var(--teal-light); background: linear-gradient(135deg, rgba(250, 238, 218, 0.5), transparent);
border-radius: 8px; border: 2px solid transparent;
padding: 16px 12px; border-radius: 12px;
padding: 14px 12px;
font-family: 'Syne', sans-serif; font-family: 'Syne', sans-serif;
cursor: pointer; cursor: pointer;
transition: all 0.3s ease; transition: all 0.3s ease;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: 4px; gap: 2px;
} }
.btn-difficulty:hover { .btn-difficulty:hover {
border-color: var(--teal-mid); border-color: var(--amber-mid);
transform: translateY(-4px); transform: translateY(-3px);
box-shadow: var(--shadow-md); box-shadow: var(--shadow-md);
} }
.btn-difficulty.active { .btn-difficulty.active {
background: linear-gradient(135deg, var(--teal-light), var(--white)); background: linear-gradient(135deg, var(--amber-mid), var(--amber-dark));
border-color: var(--teal-mid); border-color: var(--amber-dark);
color: var(--teal); box-shadow: var(--shadow-md);
}
.btn-difficulty.active .btn-label,
.btn-difficulty.active .btn-desc {
color: var(--white);
}
.btn-icon {
font-size: 20px;
line-height: 1;
margin-bottom: 2px;
} }
.btn-label { .btn-label {
font-size: 14px; font-size: 14px;
font-weight: 700; font-weight: 700;
color: var(--teal); color: var(--amber);
} }
.btn-desc { .btn-desc {
font-size: 12px; font-size: 11px;
font-family: 'DM Sans', sans-serif; font-family: 'DM Sans', sans-serif;
color: var(--text-muted); color: var(--text-muted);
font-weight: 400; font-weight: 400;
} }
/* ============================================
GAME SECTION
============================================ */
.game-section {
margin-bottom: 80px;
animation: fadeInUp 0.6s ease-out 0.3s both;
}
/* ============================================ /* ============================================
GAME GRID & CARDS GAME GRID & CARDS
============================================ */ ============================================ */
.game-grid { .game-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(clamp(70px, 15vw, 140px), 1fr)); grid-template-columns: repeat(auto-fit, minmax(clamp(70px, 15vw, 110px), 1fr));
gap: clamp(12px, 2vw, 20px); gap: clamp(10px, 2vw, 16px);
max-width: 900px; margin: 0 auto 32px;
margin: 0 auto 40px;
padding: 20px; padding: 20px;
background: linear-gradient(135deg, rgba(225, 245, 238, 0.4), transparent); background: linear-gradient(135deg, rgba(250, 238, 218, 0.4), transparent);
border-radius: 16px; border-radius: 12px;
border: 1px solid var(--teal-light); border: 1px solid var(--amber-light);
} }
/* MEMORY CARD STYLES */ /* MEMORY CARD STYLES */
@@ -188,16 +243,19 @@ main {
} }
.card-face.front { .card-face.front {
background: linear-gradient(135deg, var(--teal-light), var(--teal-light)); background:
border: 2px solid var(--teal-mid); radial-gradient(circle at 30% 25%, rgba(255, 255, 255, 0.6), transparent 60%),
color: var(--teal-dark); linear-gradient(135deg, var(--amber-mid), var(--amber-dark));
border: 2px solid var(--amber-dark);
color: var(--white);
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-sm);
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
} }
.card-face.back { .card-face.back {
transform: rotateY(180deg); transform: rotateY(180deg);
background: linear-gradient(135deg, var(--white), var(--teal-light)); background: linear-gradient(135deg, var(--white), var(--amber-light));
border: 2px solid var(--teal-mid); border: 2px solid var(--amber-mid);
flex-direction: column; flex-direction: column;
gap: 8px; gap: 8px;
padding: 8px; padding: 8px;
@@ -246,7 +304,7 @@ main {
} }
.memory-card.matched .card-face.back { .memory-card.matched .card-face.back {
background: linear-gradient(135deg, var(--white), rgba(76, 175, 80, 0.2)); background: linear-gradient(135deg, var(--white), rgba(29, 158, 117, 0.2));
border-color: var(--success); border-color: var(--success);
} }
@@ -271,26 +329,16 @@ footer {
} }
/* ============================================ /* ============================================
BUTTONS BUTTONS (métriques héritées du style général du site)
============================================ */ ============================================ */
.btn { .btn {
font-family: 'Syne', sans-serif;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
padding: 12px 24px;
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
} }
.btn-primary { .btn-primary {
background: linear-gradient(135deg, var(--teal-mid), var(--teal-dark)); background: linear-gradient(135deg, var(--amber-mid), var(--amber-dark));
color: var(--white); color: var(--white);
} }
@@ -301,27 +349,55 @@ footer {
.btn-secondary { .btn-secondary {
background: var(--white); background: var(--white);
border: 2px solid var(--teal-mid); border: 2px solid var(--amber-mid);
color: var(--teal-mid); color: var(--amber-mid);
} }
.btn-secondary:hover { .btn-secondary:hover {
background: var(--teal-light); background: var(--amber-light);
border-color: var(--teal-dark); border-color: var(--amber-dark);
} }
/* ============================================ /* ============================================
RESPONSIVE DESIGN RESPONSIVE DESIGN
============================================ */ ============================================ */
@media (max-width: 1100px) {
.game-layout {
grid-template-columns: 1fr;
grid-template-areas:
"game"
"leaderboard"
"stats";
gap: 40px;
}
}
@media (max-width: 900px) {
.game-top-bar {
flex-direction: column;
align-items: stretch;
gap: 24px;
}
.difficulty-selector {
text-align: center;
}
.btn-group {
max-width: 520px;
margin: 0 auto;
}
}
@media (max-width: 768px) { @media (max-width: 768px) {
main { main {
padding: 20px; padding: 56px 20px 20px;
} }
.intro-section h1 { .intro-section h1 {
font-size: 28px; font-size: 28px;
} }
.game-grid { .game-grid {
gap: 12px; gap: 12px;
padding: 16px; padding: 16px;
@@ -345,9 +421,9 @@ footer {
@media (max-width: 480px) { @media (max-width: 480px) {
main { main {
padding: 16px; padding: 40px 16px 16px;
} }
.intro-section h1 { .intro-section h1 {
font-size: 24px; font-size: 24px;
} }

View File

@@ -175,7 +175,6 @@ class GameSessionState {
this.moves = 0; this.moves = 0;
this.score = 0; this.score = 0;
this.matched = 0; this.matched = 0;
this.isPaused = false;
this.gameActive = false; this.gameActive = false;
} }
@@ -190,7 +189,6 @@ class GameSessionState {
this.score = 0; this.score = 0;
this.matched = 0; this.matched = 0;
this.gameActive = true; this.gameActive = true;
this.isPaused = false;
} }
/** /**

View File

@@ -192,17 +192,18 @@ class LeaderboardManager {
const medal = index < 3 ? `class="rank-number medal-${index + 1}"` : 'class="rank-number"'; const medal = index < 3 ? `class="rank-number medal-${index + 1}"` : 'class="rank-number"';
rankItem.innerHTML = ` rankItem.innerHTML = `
<div ${medal}>${rankNumber}</div> <div class="rank-top">
<div ${medal}>${rankNumber}</div>
<div class="rank-score">${formatNumber(score.score)}</div>
</div>
<div class="rank-info"> <div class="rank-info">
<div class="player-name">${this.escapeHTML(score.playerName)}</div> <div class="player-name">${this.escapeHTML(score.playerName)}</div>
<div class="player-stats"> <div class="rank-meta">
<span>⏱ ${formatTime(score.timeSeconds)}</span> <span>⏱ ${formatTime(score.timeSeconds)}</span>
<span>🎯 ${score.moves} mouvements</span> <span>🎯 ${score.moves}</span>
${score.isPerfect ? '<span>✨ Parfait</span>' : ''} ${score.isPerfect ? '<span>✨</span>' : ''}
</div> </div>
</div> </div>
<div class="rank-score">${formatNumber(score.score)}</div>
<div class="rank-difficulty ${score.difficulty}">${DIFFICULTY_MODES[score.difficulty].name}</div>
`; `;
leaderboardList.appendChild(rankItem); leaderboardList.appendChild(rankItem);

View File

@@ -14,7 +14,6 @@ class MemoryGameEngine {
moves: 0, moves: 0,
score: 0, score: 0,
isProcessing: false, isProcessing: false,
isPaused: false,
gameStarted: false gameStarted: false
}; };
this.startTime = null; this.startTime = null;
@@ -128,8 +127,7 @@ class MemoryGameEngine {
if (this.gameState.flipped.length === 2) return; if (this.gameState.flipped.length === 2) return;
if (this.cards[cardIndex].isMatched) return; if (this.cards[cardIndex].isMatched) return;
if (this.gameState.flipped.includes(cardIndex)) return; if (this.gameState.flipped.includes(cardIndex)) return;
if (this.gameState.isPaused) return;
// Retourner la carte // Retourner la carte
this.flipCard(cardIndex); this.flipCard(cardIndex);
this.gameState.flipped.push(cardIndex); this.gameState.flipped.push(cardIndex);
@@ -260,9 +258,7 @@ class MemoryGameEngine {
this.startTime = Date.now(); this.startTime = Date.now();
this.timerInterval = setInterval(() => { this.timerInterval = setInterval(() => {
if (!this.gameState.isPaused) { this.updateTimer();
this.updateTimer();
}
}, 100); }, 100);
this.analytics.trackGameStart(this.difficulty); this.analytics.trackGameStart(this.difficulty);
@@ -429,7 +425,6 @@ class MemoryGameEngine {
moves: 0, moves: 0,
score: 0, score: 0,
isProcessing: false, isProcessing: false,
isPaused: false,
gameStarted: false gameStarted: false
}; };
@@ -444,34 +439,13 @@ class MemoryGameEngine {
document.getElementById('timer-display').style.animation = ''; document.getElementById('timer-display').style.animation = '';
} }
/**
* Pause/Reprise
*/
togglePause() {
if (!this.gameState.gameStarted) return;
this.gameState.isPaused = !this.gameState.isPaused;
const pauseBtn = document.getElementById('pause-btn');
if (this.gameState.isPaused) {
pauseBtn.textContent = '▶ Reprendre';
pauseBtn.classList.add('paused');
} else {
pauseBtn.textContent = '⏸ Pause';
pauseBtn.classList.remove('paused');
}
}
/** /**
* Setup event listeners * Setup event listeners
*/ */
setupEventListeners() { setupEventListeners() {
// Bouton restart // Bouton restart
document.getElementById('restart-btn').addEventListener('click', () => this.restart()); document.getElementById('restart-btn').addEventListener('click', () => this.restart());
// Bouton pause
document.getElementById('pause-btn').addEventListener('click', () => this.togglePause());
// Sélecteur de difficulté // Sélecteur de difficulté
document.querySelectorAll('.btn-difficulty').forEach(btn => { document.querySelectorAll('.btn-difficulty').forEach(btn => {
btn.addEventListener('click', () => { btn.addEventListener('click', () => {
@@ -512,14 +486,51 @@ class MemoryGameEngine {
// ============================================ // ============================================
let game; let game;
/**
* Demander le nom du joueur à la première partie
*/
function setupPlayerNameModal(onReady) {
const nameConfirmed = localStorage.getItem('nextgn_name_confirmed') === 'true';
if (nameConfirmed) {
onReady();
return;
}
const modal = document.getElementById('player-name-modal');
const input = document.getElementById('player-name-input');
const confirmBtn = document.getElementById('player-name-confirm');
modal.classList.remove('hidden');
input.focus();
const confirmName = () => {
const name = input.value.trim();
localStorage.setItem('nextgn_playerName', name || getPlayerName());
localStorage.setItem('nextgn_name_confirmed', 'true');
modal.classList.add('hidden');
onReady();
};
confirmBtn.addEventListener('click', confirmName, { once: true });
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') confirmName();
}, { once: true });
}
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
// Initialiser le jeu
game = new MemoryGameEngine('normal');
// Afficher le leaderboard initial // Afficher le leaderboard initial
LeaderboardManager.updateDisplay('all'); LeaderboardManager.updateDisplay('all');
LeaderboardManager.updatePlayerStats(); LeaderboardManager.updatePlayerStats();
setupPlayerNameModal(() => {
LeaderboardManager.updateDisplay('all');
LeaderboardManager.updatePlayerStats();
});
// Initialiser le jeu
game = new MemoryGameEngine('normal');
// Scroll progress // Scroll progress
window.addEventListener('scroll', () => { window.addEventListener('scroll', () => {
game.updateHUD(); game.updateHUD();

View File

@@ -9,7 +9,7 @@ const PICTOGRAMMES = [
{ {
id: 1, id: 1,
nom: "Gaz comprimé", nom: "Gaz comprimé",
filename: "1-gaz-comprime.png", filename: "sous_pression.jpg",
texte: "Je suis sous pression", texte: "Je suis sous pression",
danger: "Danger physique", danger: "Danger physique",
couleur: "teal" couleur: "teal"
@@ -17,15 +17,15 @@ const PICTOGRAMMES = [
{ {
id: 2, id: 2,
nom: "Exclamation", nom: "Exclamation",
filename: "2-exclamation.png", filename: "nocif_irritant_ozone.jpg",
texte: "Je nuis gravement à la santé", texte: "J'altère la santé ou la couche d'ozone",
danger: "Danger pour la santé", danger: "Danger pour la santé/Environnement",
couleur: "teal" couleur: "teal"
}, },
{ {
id: 3, id: 3,
nom: "Explosion", nom: "Explosion",
filename: "3-explosion.png", filename: "explosif.jpg",
texte: "J'explose", texte: "J'explose",
danger: "Danger physique", danger: "Danger physique",
couleur: "teal" couleur: "teal"
@@ -33,7 +33,7 @@ const PICTOGRAMMES = [
{ {
id: 4, id: 4,
nom: "Crâne", nom: "Crâne",
filename: "4-crane.png", filename: "toxicite_aigue.jpg",
texte: "Je tue", texte: "Je tue",
danger: "Danger physique", danger: "Danger physique",
couleur: "teal" couleur: "teal"
@@ -41,7 +41,7 @@ const PICTOGRAMMES = [
{ {
id: 5, id: 5,
nom: "Flamme", nom: "Flamme",
filename: "5-flamme.png", filename: "inflammable.jpg",
texte: "Je flambe", texte: "Je flambe",
danger: "Danger physique", danger: "Danger physique",
couleur: "teal" couleur: "teal"
@@ -49,7 +49,7 @@ const PICTOGRAMMES = [
{ {
id: 6, id: 6,
nom: "Comburant", nom: "Comburant",
filename: "6-comburant.png", filename: "comburant.jpg",
texte: "Je fais flamber", texte: "Je fais flamber",
danger: "Danger physique", danger: "Danger physique",
couleur: "teal" couleur: "teal"
@@ -57,7 +57,7 @@ const PICTOGRAMMES = [
{ {
id: 7, id: 7,
nom: "Environnement", nom: "Environnement",
filename: "7-environnement.png", filename: "pollution.jpg",
texte: "Je pollue", texte: "Je pollue",
danger: "Danger pour l'environnement", danger: "Danger pour l'environnement",
couleur: "teal" couleur: "teal"
@@ -65,17 +65,17 @@ const PICTOGRAMMES = [
{ {
id: 8, id: 8,
nom: "Corrosion", nom: "Corrosion",
filename: "8-corrosion.png", filename: "corrosif.jpg",
texte: "Je ronge", texte: "Je ronge",
danger: "Danger physique", danger: "Danger physique",
couleur: "teal" couleur: "teal"
}, },
{ {
id: 9, id: 9,
nom: "Santé/Environnement", nom: "Santé (CMR)",
filename: "9-sante-environnement.png", filename: "cmr.jpg",
texte: "J'altère la santé ou la couche d'ozone", texte: "Je nuis gravement à la santé",
danger: "Danger pour la santé/Environnement", danger: "Danger pour la santé",
couleur: "teal" couleur: "teal"
} }
]; ];

View File

@@ -3,20 +3,20 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Memory Pictogrammes GHS - NextGN Formation</title> <title>Memory Pictogrammes GHS - Gauthier Chombart & Nathan Chauwin</title>
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap" rel="stylesheet">
<!-- Styles --> <!-- Styles -->
<link rel="stylesheet" href="../../header-style.css"/> <link rel="stylesheet" href="../../header-style.css"/>
<link rel="stylesheet" href="assets/css/memory-game.css"> <link rel="stylesheet" href="assets/css/memory-game.css?v=3">
<link rel="stylesheet" href="assets/css/hud.css"> <link rel="stylesheet" href="assets/css/hud.css?v=3">
<link rel="stylesheet" href="assets/css/animations.css"> <link rel="stylesheet" href="assets/css/animations.css?v=3">
<link rel="stylesheet" href="assets/css/leaderboard.css"> <link rel="stylesheet" href="assets/css/leaderboard.css?v=3">
</head> </head>
<body> <body>
<!-- HEADER NextGN Formation --> <!-- HEADER Gauthier Chombart & Nathan Chauwin -->
<header> <header>
<div class="logo">NextGN Formation</div> <div class="logo">Gauthier Chombart & Nathan Chauwin</div>
<nav> <nav>
<a href="../../index.html">Accueil</a> <a href="../../index.html">Accueil</a>
<a href="../../index.html#formations">Exercices</a> <a href="../../index.html#formations">Exercices</a>
@@ -36,125 +36,158 @@
<main class="game-container"> <main class="game-container">
<!-- INTRO SECTION --> <!-- INTRO SECTION -->
<section class="intro-section"> <section class="intro-section">
<h1>🎮 Memory Pictogrammes GHS</h1> <h1>Memory pictogrammes</h1>
<p class="subtitle">Maîtrisez les 9 symboles de danger</p> <p class="subtitle">Maîtrisez les 9 symboles de danger</p>
<div class="intro-box"> <div class="intro-box">
<p> <p>
<strong>Objectif :</strong> Retrouvez les paires en associant chaque pictogramme à sa signification. <strong>Objectif :</strong> Retrouvez les paires en associant chaque pictogramme à sa signification.
Plus rapide vous êtes, plus de points vous gagnez! 🏆 Plus rapide vous êtes, plus de points vous gagnez! 🏆
</p> </p>
</div> </div>
</section>
<!-- TOP BAR (niveau + stats en temps réel) -->
<div class="game-top-bar">
<!-- DIFFICULTY SELECTOR --> <!-- DIFFICULTY SELECTOR -->
<div class="difficulty-selector"> <div class="difficulty-selector">
<label>Choisir le niveau :</label> <label>Choisir le niveau</label>
<div class="btn-group"> <div class="btn-group">
<button class="btn btn-difficulty" data-difficulty="easy"> <button class="btn btn-difficulty" data-difficulty="easy">
<span class="btn-icon">🌱</span>
<span class="btn-label">Facile</span> <span class="btn-label">Facile</span>
<span class="btn-desc">4 paires</span> <span class="btn-desc">4 paires</span>
</button> </button>
<button class="btn btn-difficulty active" data-difficulty="normal"> <button class="btn btn-difficulty active" data-difficulty="normal">
<span class="btn-icon">🎯</span>
<span class="btn-label">Normal</span> <span class="btn-label">Normal</span>
<span class="btn-desc">9 paires</span> <span class="btn-desc">9 paires</span>
</button> </button>
<button class="btn btn-difficulty" data-difficulty="hard"> <button class="btn btn-difficulty" data-difficulty="hard">
<span class="btn-icon">🔥</span>
<span class="btn-label">Difficile</span> <span class="btn-label">Difficile</span>
<span class="btn-desc">2 min chrono</span> <span class="btn-desc">2 min chrono</span>
</button> </button>
</div> </div>
</div> </div>
</section>
<!-- GAME SECTION -->
<section class="game-section">
<!-- HUD (Stats en temps réel) --> <!-- HUD (Stats en temps réel) -->
<div class="game-hud"> <div class="game-hud">
<div class="stat-box"> <div class="stat-box">
<div class="label">Score</div> <div class="label">🏆 Score</div>
<div class="value" id="score-display">0</div> <div class="value" id="score-display">0</div>
</div> </div>
<div class="stat-box"> <div class="stat-box">
<div class="label">Mouvements</div> <div class="label">👆 Mouvements</div>
<div class="value" id="moves-display">0</div> <div class="value" id="moves-display">0</div>
</div> </div>
<div class="stat-box"> <div class="stat-box">
<div class="label">Temps</div> <div class="label">Temps</div>
<div class="value" id="timer-display">00:00</div> <div class="value" id="timer-display">00:00</div>
</div> </div>
<div class="stat-box"> <div class="stat-box">
<div class="label">Paires</div> <div class="label">🧩 Paires</div>
<div class="value" id="pairs-display">0/9</div> <div class="value" id="pairs-display">0/9</div>
</div> </div>
</div> </div>
</div>
<!-- PROGRESS BAR --> <!-- GAME LAYOUT (classement / jeu / statistiques) -->
<div class="progress-container"> <div class="game-layout">
<div class="progress-bar" id="progress-bar"></div> <!-- LEADERBOARD PANEL (gauche) -->
</div> <aside class="leaderboard-panel">
<h2>🏆 Classement</h2>
<!-- GAME GRID --> <div class="tabs">
<div class="game-grid" id="game-grid"> <button class="tab-btn active" data-filter="all">Tous</button>
<!-- Cartes générées par JS --> <button class="tab-btn" data-filter="easy">Facile</button>
</div> <button class="tab-btn" data-filter="normal">Normal</button>
<button class="tab-btn" data-filter="hard">Difficile</button>
<!-- CONTROLS -->
<div class="game-controls">
<button class="btn btn-primary" id="restart-btn">
🔄 Recommencer
</button>
<button class="btn btn-secondary" id="pause-btn">
⏸ Pause
</button>
</div>
</section>
<!-- LEADERBOARD SECTION -->
<section class="leaderboard-section">
<h2>🏆 Classement</h2>
<div class="tabs">
<button class="tab-btn active" data-filter="all">Tous</button>
<button class="tab-btn" data-filter="easy">Facile</button>
<button class="tab-btn" data-filter="normal">Normal</button>
<button class="tab-btn" data-filter="hard">Difficile</button>
</div>
<div class="leaderboard">
<div class="leaderboard-list" id="leaderboard-list">
<!-- Rempli par JS -->
</div> </div>
</div>
<!-- PLAYER STATS --> <div class="leaderboard">
<div class="player-stats"> <div class="leaderboard-list" id="leaderboard-list">
<h3>Vos statistiques</h3> <!-- Rempli par JS -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon">🎮</div>
<div class="stat-label">Jeux joués</div>
<div class="stat-value" id="total-games">0</div>
</div>
<div class="stat-card">
<div class="stat-icon"></div>
<div class="stat-label">Meilleur score</div>
<div class="stat-value" id="best-score">0</div>
</div>
<div class="stat-card">
<div class="stat-icon"></div>
<div class="stat-label">Temps record</div>
<div class="stat-value" id="best-time">--</div>
</div>
<div class="stat-card">
<div class="stat-icon">🏅</div>
<div class="stat-label">Taux de réussite</div>
<div class="stat-value" id="win-rate">0%</div>
</div> </div>
</div> </div>
</div> </aside>
</section>
<!-- GAME SECTION (centre) -->
<section class="game-section">
<div class="game-panel">
<!-- PROGRESS BAR -->
<div class="progress-container">
<div class="game-progress-fill" id="progress-bar"></div>
</div>
<!-- GAME GRID -->
<div class="game-grid" id="game-grid">
<!-- Cartes générées par JS -->
</div>
<!-- CONTROLS -->
<div class="game-controls">
<button class="btn btn-primary" id="restart-btn">
🔄 Recommencer
</button>
</div>
</div>
</section>
<!-- PLAYER STATS PANEL (droite) -->
<aside class="stats-panel">
<div class="player-stats">
<h3>Vos statistiques</h3>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon">🎮</div>
<div class="stat-label">Jeux joués</div>
<div class="stat-value" id="total-games">0</div>
</div>
<div class="stat-card">
<div class="stat-icon"></div>
<div class="stat-label">Meilleur score</div>
<div class="stat-value" id="best-score">0</div>
</div>
<div class="stat-card">
<div class="stat-icon"></div>
<div class="stat-label">Temps record</div>
<div class="stat-value" id="best-time">--</div>
</div>
<div class="stat-card">
<div class="stat-icon">🏅</div>
<div class="stat-label">Taux de réussite</div>
<div class="stat-value" id="win-rate">0%</div>
</div>
</div>
</div>
</aside>
</div>
</main> </main>
<!-- PLAYER NAME MODAL (1ère partie) -->
<div class="victory-modal hidden" id="player-name-modal">
<div class="modal-content name-modal-content">
<h2>Bienvenue ! 👋</h2>
<p class="name-modal-text">
Entrez votre nom ou un pseudo pour apparaître dans le classement général.
</p>
<input
type="text"
id="player-name-input"
class="name-input"
maxlength="20"
placeholder="Votre nom ou pseudo"
autocomplete="off"
>
<div class="modal-actions">
<button class="btn btn-primary" id="player-name-confirm">
🎮 Commencer à jouer
</button>
</div>
</div>
</div>
<!-- VICTORY MODAL --> <!-- VICTORY MODAL -->
<div class="victory-modal hidden" id="victory-modal"> <div class="victory-modal hidden" id="victory-modal">
<div class="modal-content"> <div class="modal-content">
@@ -196,7 +229,7 @@
<!-- FOOTER --> <!-- FOOTER -->
<footer> <footer>
<p>&copy; 2024 NextGN Formation - Memory Pictogrammes GHS</p> <p>&copy; 2026 Gauthier Chombart & Nathan Chauwin<br>Tous droits réservés — Formations Certibiocide & Techniques — Formateurs indépendants</p>
<p style="margin-top: 10px; opacity: 0.6;">Tous droits réservés - Formateurs indépendants</p> <p style="margin-top: 10px; opacity: 0.6;">Tous droits réservés - Formateurs indépendants</p>
</footer> </footer>

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NextGN Formation - Formations Certibiocide & Techniques</title> <title>Gauthier Chombart & Nathan Chauwin - Formations Certibiocide & Techniques</title>
<!-- SEO de base --> <!-- SEO de base -->
<meta name="description" content="Formations Certibiocide pratiques et interactives par des formateurs indépendants experts. Exercices, documentation technique sur les nuisibles et rapports de veille réglementaire."> <meta name="description" content="Formations Certibiocide pratiques et interactives par des formateurs indépendants experts. Exercices, documentation technique sur les nuisibles et rapports de veille réglementaire.">
@@ -15,13 +15,13 @@
<!-- Open Graph (partage sur les réseaux sociaux et LinkedIn) --> <!-- Open Graph (partage sur les réseaux sociaux et LinkedIn) -->
<meta property="og:type" content="website"> <meta property="og:type" content="website">
<meta property="og:url" content="https://nextgnformation.bsite.net/"> <meta property="og:url" content="https://nextgnformation.bsite.net/">
<meta property="og:title" content="NextGN Formation - Formations Certibiocide & Techniques"> <meta property="og:title" content="Gauthier Chombart & Nathan Chauwin - Formations Certibiocide & Techniques">
<meta property="og:description" content="Formations Certibiocide pratiques et interactives par des formateurs indépendants experts. Exercices, documentation technique sur les nuisibles et rapports de veille réglementaire."> <meta property="og:description" content="Formations Certibiocide pratiques et interactives par des formateurs indépendants experts. Exercices, documentation technique sur les nuisibles et rapports de veille réglementaire.">
<meta property="og:locale" content="fr_FR"> <meta property="og:locale" content="fr_FR">
<!-- Twitter Card --> <!-- Twitter Card -->
<meta name="twitter:card" content="summary"> <meta name="twitter:card" content="summary">
<meta name="twitter:title" content="NextGN Formation - Formations Certibiocide & Techniques"> <meta name="twitter:title" content="Gauthier Chombart & Nathan Chauwin - Formations Certibiocide & Techniques">
<meta name="twitter:description" content="Formations Certibiocide pratiques et interactives par des formateurs indépendants experts."> <meta name="twitter:description" content="Formations Certibiocide pratiques et interactives par des formateurs indépendants experts.">
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap" rel="stylesheet">
@@ -29,7 +29,7 @@
</head> </head>
<body> <body>
<header> <header>
<div class="logo">NextGN Formation</div> <div class="logo">Gauthier Chombart & Nathan Chauwin</div>
<nav> <nav>
<a href="#formations">Exercices interactifs</a> <a href="#formations">Exercices interactifs</a>
<a href="#documentation">Documentation</a> <a href="#documentation">Documentation</a>
@@ -103,7 +103,7 @@
<div class="formation-card"> <div class="formation-card">
<div class="formation-header"> <div class="formation-header">
<div class="formation-icon">📋</div> <div class="formation-icon">📋</div>
<h3 class="formation-title">FDS & Étiquettes</h3> <h3 class="formation-title">FDS, FT & Étiquettes</h3>
<p class="formation-subtitle">Aller plus loin</p> <p class="formation-subtitle">Aller plus loin</p>
</div> </div>
<div class="formation-body"> <div class="formation-body">
@@ -137,6 +137,19 @@
<a href="exercices/memory-pictogrammes/memory.html" class="formation-link">Jouer</a> <a href="exercices/memory-pictogrammes/memory.html" class="formation-link">Jouer</a>
</div> </div>
</div> </div>
<!-- Correction : page unique pour tous les exercices -->
<div class="formation-card">
<div class="formation-header">
<div class="formation-icon"></div>
<h3 class="formation-title">Corriger un exercice</h3>
<p class="formation-subtitle">Correction des rendus</p>
</div>
<div class="formation-body">
<p class="formation-description">Déposez le PDF exporté par un groupe et obtenez la lecture de son rendu. Page unique de correction pour l'ensemble des exercices.</p>
<a href="exercices/corriger-exercice.html" class="formation-link">Corriger</a>
</div>
</div>
</div> </div>
</section> </section>
@@ -314,7 +327,7 @@
</main> </main>
<footer> <footer>
<p>&copy; 2026 NextGN Formation - Formations Certibiocide & Techniques</p> <p>&copy; 2026 Gauthier Chombart & Nathan Chauwin<br>Tous droits réservés — Formations Certibiocide & Techniques — Formateurs indépendants</p>
<p style="margin-top: 10px; opacity: 0.6;">Tous droits réservés — Gauthier Chombart & Nathan Chauwin — Formateurs indépendants</p> <p style="margin-top: 10px; opacity: 0.6;">Tous droits réservés — Gauthier Chombart & Nathan Chauwin — Formateurs indépendants</p>
</footer> </footer>

View File

@@ -419,7 +419,7 @@
<!-- HEADER --> <!-- HEADER -->
<header> <header>
<div class="logo">NextGN Formation</div> <div class="logo">Gauthier Chombart & Nathan Chauwin</div>
<nav> <nav>
<a href="#formations">Exercices interactifs</a> <a href="#formations">Exercices interactifs</a>
<a href="#documentation">Documentation</a> <a href="#documentation">Documentation</a>
@@ -799,7 +799,7 @@
<!-- FOOTER --> <!-- FOOTER -->
<footer> <footer>
<p>&copy; 2024 NextGN Formation - Rapport Veille Semaine 20</p> <p>&copy; 2026 Gauthier Chombart & Nathan Chauwin<br>Tous droits réservés — Formations Certibiocide & Techniques — Formateurs indépendants</p>
<p>Tous droits réservés - Formateur indépendant spécialisé en Certibiocide</p> <p>Tous droits réservés - Formateur indépendant spécialisé en Certibiocide</p>
</footer> </footer>

View File

@@ -419,7 +419,7 @@
<!-- HEADER --> <!-- HEADER -->
<header> <header>
<div class="logo">NextGN Formation</div> <div class="logo">Gauthier Chombart & Nathan Chauwin</div>
<nav> <nav>
<a href="#formations">Exercices interactifs</a> <a href="#formations">Exercices interactifs</a>
<a href="#documentation">Documentation</a> <a href="#documentation">Documentation</a>
@@ -799,7 +799,7 @@
<!-- FOOTER --> <!-- FOOTER -->
<footer> <footer>
<p>&copy; 2024 NextGN Formation - Rapport Veille Semaine 21</p> <p>&copy; 2026 Gauthier Chombart & Nathan Chauwin<br>Tous droits réservés — Formations Certibiocide & Techniques — Formateurs indépendants</p>
<p>Tous droits réservés - Formateur indépendant spécialisé en Certibiocide</p> <p>Tous droits réservés - Formateur indépendant spécialisé en Certibiocide</p>
</footer> </footer>

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Veille Certibiocide — Semaine 23 | NextGN Formation</title> <title>Veille Certibiocide — Semaine 23 | Gauthier Chombart & Nathan Chauwin</title>
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap" rel="stylesheet">
<style> <style>
:root { :root {
@@ -355,7 +355,7 @@
<!-- NAV --> <!-- NAV -->
<nav class="nav-header"> <nav class="nav-header">
<a href="index.html" class="header-logo"><span>NextGN</span> Formation</a> <a href="index.html" class="header-logo">Gauthier Chombart & Nathan Chauwin</a>
<nav class="header-nav"> <nav class="header-nav">
<a href="index.html#formations">Exercices interactifs</a> <a href="index.html#formations">Exercices interactifs</a>
<a href="index.html#about">À propos</a> <a href="index.html#about">À propos</a>
@@ -800,7 +800,7 @@
</main> </main>
<footer> <footer>
<p>&copy; 2026 NextGN Formation — Veille Certibiocide Semaine 23</p> <p>&copy; 2026 Gauthier Chombart & Nathan Chauwin<br>Tous droits réservés — Formations Certibiocide & Techniques — Formateurs indépendants</p>
<p style="margin-top:10px; opacity:0.6;">Tous droits réservés — Formateurs indépendants</p> <p style="margin-top:10px; opacity:0.6;">Tous droits réservés — Formateurs indépendants</p>
</footer> </footer>

View File

@@ -356,7 +356,7 @@
<!-- Navigation --> <!-- Navigation -->
<nav class="nav-header"> <nav class="nav-header">
<a href="index.html" class="header-logo"><span>NextGN</span> Formation</a> <a href="index.html" class="header-logo">Gauthier Chombart & Nathan Chauwin</a>
<nav class="header-nav" id="mainNav"> <nav class="header-nav" id="mainNav">
<a href="index.html#formations">Exercices interactifs</a> <a href="index.html#formations">Exercices interactifs</a>
<a href="index.html#about">À propos</a> <a href="index.html#about">À propos</a>
@@ -790,7 +790,7 @@
</div><!-- /container --> </div><!-- /container -->
<footer> <footer>
<p>&copy; 2026 NextGN Formation — Veille Certibiocide Semaine 24</p> <p>&copy; 2026 Gauthier Chombart & Nathan Chauwin<br>Tous droits réservés — Formations Certibiocide & Techniques — Formateurs indépendants</p>
<p style="margin-top:10px; opacity:0.6;">Tous droits réservés — Formateurs indépendants</p> <p style="margin-top:10px; opacity:0.6;">Tous droits réservés — Formateurs indépendants</p>
</footer> </footer>

View File

@@ -375,7 +375,7 @@
<!-- NAV HEADER --> <!-- NAV HEADER -->
<nav class="nav-header"> <nav class="nav-header">
<a href="index.html" class="header-logo"><span>NextGN</span> Formation</a> <a href="index.html" class="header-logo">Gauthier Chombart & Nathan Chauwin</a>
<button class="hamburger" id="hamburger" aria-label="Menu"> <button class="hamburger" id="hamburger" aria-label="Menu">
<span></span><span></span><span></span> <span></span><span></span><span></span>
</button> </button>
@@ -704,7 +704,7 @@
<!-- FOOTER --> <!-- FOOTER -->
<footer> <footer>
<p>&copy; 2026 NextGN Formation - Veille Certibiocide Semaine 25</p> <p>&copy; 2026 Gauthier Chombart & Nathan Chauwin<br>Tous droits réservés — Formations Certibiocide & Techniques — Formateurs indépendants</p>
<p>Tous droits réservés - Formateurs indépendants</p> <p>Tous droits réservés - Formateurs indépendants</p>
</footer> </footer>

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Veille Certibiocide — Semaine 26 | NextGN Formation</title> <title>Veille Certibiocide — Semaine 26 | Gauthier Chombart & Nathan Chauwin</title>
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap" rel="stylesheet">
<style> <style>
:root { :root {
@@ -378,7 +378,7 @@
<body> <body>
<nav class="nav-header"> <nav class="nav-header">
<a href="index.html" class="header-logo"><span>NextGN</span> Formation</a> <a href="index.html" class="header-logo">Gauthier Chombart & Nathan Chauwin</a>
<nav class="header-nav"> <nav class="header-nav">
<a href="index.html#formations">Exercices interactifs</a> <a href="index.html#formations">Exercices interactifs</a>
<a href="index.html#about">À propos</a> <a href="index.html#about">À propos</a>
@@ -393,7 +393,7 @@
<h1>Veille Certibiocide<br>Semaine 26 — 2026</h1> <h1>Veille Certibiocide<br>Semaine 26 — 2026</h1>
<p class="hero-meta"> <p class="hero-meta">
<strong>Semaine du 23 au 29 juin 2026</strong> &nbsp;·&nbsp; <strong>Semaine du 23 au 29 juin 2026</strong> &nbsp;·&nbsp;
Généré le 29 juin 2026 &nbsp;·&nbsp; NextGN Formation Généré le 29 juin 2026 &nbsp;·&nbsp; Gauthier Chombart & Nathan Chauwin
</p> </p>
</div> </div>
@@ -810,7 +810,7 @@
</div> </div>
<footer> <footer>
<p>&copy; 2026 NextGN Formation — Veille Certibiocide Semaine 26</p> <p>&copy; 2026 Gauthier Chombart & Nathan Chauwin<br>Tous droits réservés — Formations Certibiocide & Techniques — Formateurs indépendants</p>
<p>Tous droits réservés — Formateurs indépendants</p> <p>Tous droits réservés — Formateurs indépendants</p>
</footer> </footer>

View File

@@ -0,0 +1,376 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Veille Certibiocide — Semaine 28 (06-12 juillet 2026)</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap" rel="stylesheet">
<style>
:root {
--teal: #0F6E56;
--teal-mid: #1D9E75;
--teal-light: #E1F5EE;
--teal-dark: #085041;
--amber: #BA7517;
--amber-light: #FAEEDA;
--amber-dark: #633806;
--coral: #993C1D;
--coral-mid: #D85A30;
--coral-light: #FAECE7;
--purple: #534AB7;
--purple-light: #EEEDFE;
--purple-dark: #3C3489;
--blue: #185FA5;
--blue-light: #E6F1FB;
--bg: #F7F6F2;
--white: #FFFFFF;
--text: #1a1a18;
--text-muted: #6b6a65;
--border: rgba(0,0,0,0.1);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
padding-top: 70px;
background: var(--bg);
font-family: 'DM Sans', sans-serif;
color: var(--text);
line-height: 1.6;
}
h1, h2, h3 { font-family: 'Syne', sans-serif; }
/* Header */
.nav-header {
position: fixed; top: 0; left: 0; right: 0; height: 70px;
background: rgba(255,255,255,0.95); backdrop-filter: blur(10px);
border-bottom: 1px solid var(--border); z-index: 1000;
display: flex; align-items: center; justify-content: space-between;
padding: 0 32px;
}
.header-logo {
font-family: 'Syne', sans-serif; font-weight: 800; font-size: 20px;
text-decoration: none;
background: linear-gradient(90deg, var(--teal), var(--coral-mid));
-webkit-background-clip: text; background-clip: text; color: transparent;
}
.header-logo span { font-weight: 800; }
.header-nav { display: flex; gap: 28px; }
.header-nav a {
font-family: 'Syne', sans-serif; font-size: 12px; text-transform: uppercase;
letter-spacing: 0.5px; color: var(--text); text-decoration: none;
position: relative; padding-bottom: 4px;
}
.header-nav a::after {
content: ''; position: absolute; left: 0; bottom: 0; width: 0; height: 2px;
background: var(--teal-mid); transition: width 0.2s ease;
}
.header-nav a:hover::after { width: 100%; }
/* Progress bar */
.progress { height: 3px; background: var(--border); position: fixed; top: 70px; left: 0; right: 0; z-index: 999; }
.progress-bar { height: 100%; background: var(--teal-mid); width: 0%; transition: width 0.3s ease; }
.wrap { max-width: 900px; margin: 0 auto; padding: 40px 24px 20px; }
/* Hero */
.hero {
background: var(--teal-light); border: 1px solid var(--teal-mid);
border-radius: 14px; padding: 36px 40px; margin-bottom: 32px;
}
.badge {
display: inline-block; background: var(--teal); color: var(--white);
font-family: 'Syne', sans-serif; font-size: 12px; text-transform: uppercase;
letter-spacing: 0.5px; padding: 6px 14px; border-radius: 20px; margin-bottom: 14px;
}
.hero h1 { font-size: 30px; font-weight: 800; color: var(--teal-dark); margin-bottom: 8px; }
.hero p { color: var(--text-muted); font-size: 14px; }
/* Sommaire */
.sommaire {
position: sticky; top: 90px; background: var(--white); border: 1px solid var(--border);
border-radius: 10px; padding: 18px 22px; margin-bottom: 32px;
box-shadow: 0 2px 8px rgba(0,0,0,0.04); z-index: 10;
}
.sommaire h3 { font-size: 13px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-muted); margin-bottom: 10px; }
.sommaire-links { display: flex; flex-wrap: wrap; gap: 10px 18px; }
.sommaire-links a {
font-size: 13px; color: var(--teal-dark); text-decoration: none; font-weight: 500;
}
.sommaire-links a:hover { text-decoration: underline; }
/* Points d'attention */
.attention {
background: var(--amber-light); border-left: 4px solid var(--amber);
border-radius: 10px; padding: 24px 28px; margin-bottom: 40px;
}
.attention h2 { font-size: 20px; color: var(--amber-dark); margin-bottom: 14px; }
.attention ol { margin-left: 20px; color: var(--text); }
.attention li { margin-bottom: 10px; font-size: 14.5px; }
/* Sections */
.section {
background: var(--white); border-radius: 10px; margin-bottom: 28px;
box-shadow: 0 2px 10px rgba(0,0,0,0.05); overflow: hidden;
animation: fadeIn 0.6s ease-out;
scroll-margin-top: 100px;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.section-header {
background: var(--teal-light); padding: 16px 24px;
font-family: 'Syne', sans-serif; font-weight: 700; font-size: 17px; color: var(--teal-dark);
}
.section-body { padding: 20px 24px; }
.news { padding: 14px 0; border-bottom: 1px solid var(--border); }
.news:last-child { border-bottom: none; }
.news-source {
display: inline-block; background: var(--teal-light); color: var(--teal-dark);
font-family: 'Syne', sans-serif; font-size: 11px; text-transform: uppercase;
letter-spacing: 0.3px; padding: 3px 10px; border-radius: 20px; margin-bottom: 6px;
}
.news p { font-size: 14.5px; margin: 6px 0; }
.news-meta { font-size: 12.5px; color: var(--text-muted); }
.news-meta a { color: var(--teal); text-decoration: none; transition: transform 0.2s ease; display: inline-block; }
.news-meta a:hover { transform: translateY(-1px); text-decoration: underline; }
.note {
background: var(--amber-light); border: 1px solid var(--amber);
color: var(--amber-dark); border-radius: 8px; padding: 10px 14px;
font-size: 13.5px; margin: 10px 0;
}
footer {
background: var(--text); color: var(--white); padding: 40px; font-size: 13px; text-align: center;
margin-top: 40px;
}
footer p:first-child { opacity: 0.9; }
footer p:last-child { margin-top: 10px; opacity: 0.6; }
@media (max-width: 768px) {
.nav-header { flex-direction: column; height: auto; padding: 12px 20px; align-items: flex-start; gap: 8px; }
body { padding-top: 110px; }
.progress { top: 110px; }
.header-nav { gap: 16px; flex-wrap: wrap; }
.hero { padding: 24px 22px; }
.hero h1 { font-size: 22px; }
.sommaire { position: static; }
.section-body, .section-header { padding-left: 18px; padding-right: 18px; }
}
</style>
</head>
<body>
<nav class="nav-header">
<a href="index.html" class="header-logo">Gauthier Chombart & Nathan Chauwin</a>
<nav class="header-nav">
<a href="index.html#formations">Exercices interactifs</a>
<a href="index.html#about">À propos</a>
<a href="index.html#contact">Contact</a>
</nav>
</nav>
<div class="progress"><div class="progress-bar" id="progressBar"></div></div>
<div class="wrap">
<div class="hero">
<span class="badge">Semaine 28</span>
<h1>Veille Certibiocide</h1>
<p>Semaine du 06 au 12 juillet 2026 — Rapport de veille réglementaire et technique</p>
</div>
<div class="sommaire">
<h3>Sommaire</h3>
<div class="sommaire-links">
<a href="#reglementaire">⚖️ Réglementaire</a>
<a href="#formation">🎓 Marché formation</a>
<a href="#nuisibles">🐀 Nuisibles &amp; ESOD</a>
<a href="#desinfection">🧴 Désinfection</a>
<a href="#opportunites">💡 Opportunités</a>
<a href="#innovation">🔬 Innovation</a>
<a href="#europe">🇪🇺 Veille européenne</a>
<a href="#rh">👥 RH &amp; Concurrence</a>
</div>
</div>
<div class="attention">
<h2>📌 Points d'attention</h2>
<ol>
<li><strong>Qualiopi désormais obligatoire pour le FAFCEA</strong> — depuis le 1er juillet 2026, la période transitoire est terminée : seuls les organismes certifiés Qualiopi peuvent être financés pour les formations destinées aux artisans.</li>
<li><strong>Nouveau règlement européen (UE) 2026/1165</strong> — entré en vigueur le 15 juin 2026, il prolonge jusqu'au 31/12/2030 la protection des données de certains dossiers de substances actives biocides : à intégrer dans les modules réglementaires BPR.</li>
<li><strong>Marchés publics 3D/4D actifs</strong> — plusieurs appels d'offres BOAMP en cours (Oise, Fos-sur-Mer, Carrières-sous-Poissy, Le Cannet) : fenêtre à surveiller pour des partenariats avec des prestataires en recherche de formation Certibiocide.</li>
</ol>
</div>
<div class="section" id="reglementaire">
<div class="section-header">⚖️ Réglementaire</div>
<div class="section-body">
<div class="news">
<span class="news-source">ANSES</span>
<p>Autorisation de mise sur le marché du produit biocide ZANZA SPRAY (n° AMM FR-2026-0040, signature du 18/06/2026). Publication au registre le 1er juillet 2026.</p>
<div class="news-meta"><em>01/07/2026</em><a href="https://www.anses.fr/fr/decisions_biocide" target="_blank" rel="noopener">Registre des AMM biocides — ANSES</a></div>
</div>
<div class="news">
<span class="news-source">ANSES</span>
<p>Retrait de plusieurs produits de traitement du bois (XILIX GEL BS, 18V33CP-PE, INDULINE SW-900 IT, TRAITEMENT BOIS EXTERIEUR NUANCE, V33 TRAITEMENT BOIS MULTI USAGES, XILIX 1000, XYL SGR 2002). Décisions signées le 15/06/2026, publiées le 24/06/2026.</p>
<div class="news-meta"><em>24/06/2026</em><a href="https://www.anses.fr/fr/decisions_biocide" target="_blank" rel="noopener">Registre des AMM biocides — ANSES</a></div>
</div>
<div class="news">
<span class="news-source">ANSES</span>
<p>Arrêté du 4 juin 2026 fixant les redevances biocides dues à l'ANSES pour toute demande déposée en France au titre du règlement (UE) 528/2012.</p>
<div class="news-meta"><em>04/06/2026</em><a href="https://www.anses.fr/fr/content/demarches-produits-biocides" target="_blank" rel="noopener">Démarches produits biocides — ANSES</a></div>
</div>
</div>
</div>
<div class="section" id="formation">
<div class="section-header">🎓 Marché formation</div>
<div class="section-body">
<div class="news">
<span class="news-source">FAFCEA</span>
<p>Depuis le 1er juillet 2026, seuls les organismes certifiés Qualiopi sont éligibles aux financements FAFCEA pour les formations destinées aux artisans. La période de transition s'est achevée le 30 juin 2026.</p>
<div class="news-meta"><em>01/07/2026</em><a href="https://csfc-federation.org/blog/qualiopi-devient-obligatoire-pour-les-financements-fafcea-des-2026/" target="_blank" rel="noopener">CSFC-Fédération</a></div>
</div>
<div class="news">
<span class="news-source">France Compétences</span>
<p>La révision du Référentiel National Qualité, socle de Qualiopi, entre dans sa phase finale : consultations en cours avec certificateurs et organismes de formation.</p>
<div class="news-meta"><em>2026</em><a href="https://fresh-management.fr/qualiopi-2026-changements-organisme-formation/" target="_blank" rel="noopener">Fresh Management</a></div>
</div>
<div class="note">⚠️ Aucune donnée chiffrée récente et vérifiée sur le CPF ou les OPCO EP n'a été trouvée cette semaine au-delà des statistiques 2024 déjà connues (~880 000 contrats d'apprentissage financés).</div>
</div>
</div>
<div class="section" id="nuisibles">
<div class="section-header">🐀 Nuisibles &amp; ESOD</div>
<div class="section-body">
<div class="news">
<span class="news-source">Veille sanitaire</span>
<p>Le moustique tigre est désormais implanté durablement dans 83 des 96 départements métropolitains (plus de 86 %), avec une progression des cas de dengue et de chikungunya à transmission locale.</p>
<div class="news-meta"><em>2026</em><a href="https://izipest.com/blog/top-3-especes-envahissantes-france-2026/" target="_blank" rel="noopener">IZIPest</a></div>
</div>
<div class="news">
<span class="news-source">Frelon asiatique</span>
<p>Un plan national de lutte contre le frelon asiatique et les autres espèces invasives a été lancé en mars 2026. Le frelon asiatique contribuerait à environ 20 % de la mortalité des colonies d'abeilles domestiques.</p>
<div class="news-meta"><em>mars 2026</em><a href="https://www.lyoncapitale.fr/actualite/lyon-la-guerre-contre-les-especes-invasives-aura-t-elle-lieu" target="_blank" rel="noopener">Lyon Capitale</a></div>
</div>
<div class="note">⚠️ Aucune donnée récente vérifiée via sources officielles (ANSES, INRAE) sur les rongeurs résistants aux anticoagulants cette semaine.</div>
</div>
</div>
<div class="section" id="desinfection">
<div class="section-header">🧴 Désinfection</div>
<div class="section-body">
<div class="news">
<span class="news-source">Certibiocide</span>
<p>Depuis le 1er janvier 2026, l'obtention du Certibiocide est obligatoire pour tout professionnel utilisant, achetant ou distribuant des produits biocides à usage professionnel. La formation dure 7 heures et le certificat est valable 5 ans.</p>
<div class="news-meta"><em>01/01/2026</em><a href="https://tbcformation.com/certibiocide-2026-nouveautes-reglementation-desinfection/" target="_blank" rel="noopener">TBC Formation</a></div>
</div>
<div class="news">
<span class="news-source">AFNOR</span>
<p>Le cadre normatif reste structuré autour de NF EN 14885 (produits biocides de désinfection), NF EN 14476 (activité virucide) et NF T72-281 (désinfection par voie aérienne). Aucune révision de ces normes n'a été identifiée cette semaine.</p>
<div class="news-meta"><em>2026</em><a href="https://norminfo.afnor.org/norme/NF%20U43-500/bonnes-pratiques-dapplication-des-produits-phytosanitaires-et-biocides-maitrise-des-applications-de-produits-phytosanitaires/79450" target="_blank" rel="noopener">AFNOR Norm'Info</a></div>
</div>
<div class="note">⚠️ Aucun nouveau produit homologué spécifique signalé cette semaine par les sources consultées.</div>
</div>
</div>
<div class="section" id="opportunites">
<div class="section-header">💡 Opportunités</div>
<div class="section-body">
<div class="news">
<span class="news-source">BOAMP — Oise</span>
<p>Marché de missions de dératisation, désinsectisation, désinfection et traitement spécifique sur les sites et le réseau routier du département de l'Oise. Notification de l'accord-cadre prévue en juillet 2026.</p>
<div class="news-meta"><em>juillet 2026</em><a href="https://www.francemarches.com/appel-offre/3boamp2616972-2026-missions-deratisation-desinsectisation" target="_blank" rel="noopener">France Marchés</a></div>
</div>
<div class="news">
<span class="news-source">BOAMP — Fos-sur-Mer</span>
<p>Marché de prestations de lutte contre les nuisibles sur la voirie et les bâtiments communaux, réparti en 2 lots. Date limite de remise des offres : 2 juillet 2026.</p>
<div class="news-meta"><em>02/07/2026</em><a href="https://www.francemarches.com/appel-offre/3boamp2651311-2026-prestations-lutte-contre" target="_blank" rel="noopener">France Marchés</a></div>
</div>
<div class="news">
<span class="news-source">BOAMP — Carrières-sous-Poissy</span>
<p>Marché de prestations anti-nuisibles 4D (dératisation, désinfection, désinsectisation, dépigeonnisation) actuellement en consultation.</p>
<div class="news-meta"><em>2026</em><a href="https://www.francemarches.com/appel-offre/3boamp2632494-2026-prestation-anti-nuisibles" target="_blank" rel="noopener">France Marchés</a></div>
</div>
</div>
</div>
<div class="section" id="innovation">
<div class="section-header">🔬 Innovation</div>
<div class="section-body">
<div class="news">
<span class="news-source">LIFE BioPest</span>
<p>Le projet européen LIFE BioPest (2024-2027) finance le développement de bioinsecticides à base de champignons entomopathogènes destinés à remplacer les pyréthrinoïdes en zone urbaine.</p>
<div class="news-meta"><em>2024-2027</em><a href="https://www.application-iot.fr/industrie-4-0/les-pieges-connectes-revolutionnent-la-gestion-des-nuisibles-dans-lindustrie-alimentaire/" target="_blank" rel="noopener">Application IoT</a></div>
</div>
<div class="news">
<span class="news-source">Groupe Berkem</span>
<p>Le piège connecté Novaterm® combine intelligence artificielle et superactivateur végétal pour la lutte antiparasitaire.</p>
<div class="news-meta"><em>2026</em><a href="https://www.hamelin.info/actus/deratisation/lutte-anti-nuisible-et-revolution-numerique/" target="_blank" rel="noopener">Hamelin</a></div>
</div>
<div class="news">
<span class="news-source">Anticimex</span>
<p>Déploiement de pièges connectés couplés à des plateformes de gestion de données (IA, IoT) dans l'industrie agroalimentaire, avec alertes en temps réel et anticipation des pics saisonniers.</p>
<div class="news-meta"><em>2026</em><a href="https://www.anticimex.fr/blog/lutte-nuisibles-connectee-ia-iot-data/" target="_blank" rel="noopener">Anticimex</a></div>
</div>
</div>
</div>
<div class="section" id="europe">
<div class="section-header">🇪🇺 Veille européenne</div>
<div class="section-body">
<div class="news">
<span class="news-source">Commission européenne</span>
<p>Le règlement (UE) 2026/1165, entré en vigueur le 15 juin 2026, modifie le BPR et prolonge la protection des données de certains dossiers de substances actives jusqu'au 31 décembre 2030, afin de compenser les retards du programme d'examen.</p>
<div class="news-meta"><em>15/06/2026</em><a href="https://www.cehtra.com/post/amendment-bpr-for-data-protection-extension-enters-into-force" target="_blank" rel="noopener">Cehtra</a></div>
</div>
<div class="news">
<span class="news-source">ECHA</span>
<p>Publication de mises à jour réglementaires concernant plusieurs substances actives biocides clés : cyperméthrine, DBNPA, certains libérateurs de formaldéhyde et éthanol.</p>
<div class="news-meta"><em>2026</em><a href="https://en.reach24h.com/news/industry-news/disinfectant-biocide/eu-brp-key-biocidal-active-substances-update" target="_blank" rel="noopener">REACH24H</a></div>
</div>
<div class="news">
<span class="news-source">Commission européenne</span>
<p>Une consultation publique européenne est ouverte pour évaluer les 12 années de mise en œuvre du règlement biocides (BPR), en vue d'une possible révision future.</p>
<div class="news-meta"><em>2026</em><a href="https://en.reach24h.com/news/industry-news/disinfectant-biocide/eu-bpr-evaluation-public-consultation" target="_blank" rel="noopener">REACH24H</a></div>
</div>
</div>
</div>
<div class="section" id="rh">
<div class="section-header">👥 RH &amp; Concurrence</div>
<div class="section-body">
<div class="news">
<span class="news-source">CS3D</span>
<p>Le CQP Technicien applicateur en maîtrise des nuisibles est inscrit au RNCP au niveau 3 (équivalent CAP). Le parcours dure 3 mois, incluant un stage de 3 semaines en entreprise et le Certibiocide.</p>
<div class="news-meta"><em>2026</em><a href="https://www.paritarisme-emploi-formation.fr/le-cqp-maitrise-des-nuisibles-professionnaliser" target="_blank" rel="noopener">Paritarisme Emploi Formation</a></div>
</div>
<div class="news">
<span class="news-source">Secteur 3D</span>
<p>Plus de 1 200 entreprises actives en France pour un chiffre d'affaires supérieur à 500 millions d'euros. Le secteur recrute en continu et propose fréquemment un CDI à l'issue du stage.</p>
<div class="news-meta"><em>2026</em><a href="https://www.hamelin.info/metier/environnement-de-travail/cqp-maitrise-des-nuisibles/" target="_blank" rel="noopener">Hamelin</a></div>
</div>
<div class="news">
<span class="news-source">Marché de l'emploi</span>
<p>Des offres de technicien 3D Certibiocide sont actives, notamment en Île-de-France (environ 1 752 € brut en CDD).</p>
<div class="news-meta"><em>11/06/2026</em><a href="https://fr.indeed.com/q-deratisation-3d-emplois.html" target="_blank" rel="noopener">Indeed</a></div>
</div>
</div>
</div>
</div>
<footer>
<p>&copy; 2026 Gauthier Chombart & Nathan Chauwin<br>Tous droits réservés — Formations Certibiocide & Techniques — Formateurs indépendants</p>
<p>Tous droits réservés - Formateurs indépendants</p>
</footer>
<script>
window.addEventListener('scroll', () => {
const scrolled = (window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100;
document.getElementById('progressBar').style.width = scrolled + '%';
});
</script>
</body>
</html>

View File

@@ -353,7 +353,7 @@
<!-- HEADER --> <!-- HEADER -->
<header> <header>
<div class="logo">NextGN Formation</div> <div class="logo">Gauthier Chombart & Nathan Chauwin</div>
<nav> <nav>
<a href="../../index.html">Exercices interactifs</a> <a href="../../index.html">Exercices interactifs</a>
<a href="../../index.html#documentation">Documentation</a> <a href="../../index.html#documentation">Documentation</a>
@@ -503,7 +503,7 @@
<!-- FOOTER --> <!-- FOOTER -->
<footer> <footer>
<p>&copy; 2026 NextGN Formation - Formations Certibiocide & Techniques</p> <p>&copy; 2026 Gauthier Chombart & Nathan Chauwin<br>Tous droits réservés — Formations Certibiocide & Techniques — Formateurs indépendants</p>
<p style="margin-top: 10px; opacity: 0.6;">Tous droits réservés — Gauthier Chombart & Nathan Chauwin — Formateurs indépendants</p> <p style="margin-top: 10px; opacity: 0.6;">Tous droits réservés — Gauthier Chombart & Nathan Chauwin — Formateurs indépendants</p>
</footer> </footer>