Déplacement de fichiers et cohérence des header d'exercices
@@ -1,519 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Exercices Pratiques BioCID - NextGN Formation</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="../header-style.css"/>
|
||||
<style>
|
||||
body {
|
||||
padding-top: 70px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
background: var(--white);
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, var(--amber-light) 0%, rgba(186, 117, 23, 0.05) 100%);
|
||||
color: var(--text);
|
||||
padding: 50px 40px;
|
||||
text-align: center;
|
||||
border-bottom: 3px solid var(--amber);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: 2.2em;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.5px;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
font-size: 1em;
|
||||
opacity: 0.8;
|
||||
font-weight: 400;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 50px 40px;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 40px;
|
||||
animation: fadeIn 0.6s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: 'Syne', sans-serif;
|
||||
color: var(--text);
|
||||
font-size: 1.6em;
|
||||
margin-bottom: 15px;
|
||||
border-bottom: 3px solid var(--amber);
|
||||
padding-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
h2::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--amber);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: 1.2em;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
color: var(--text);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.intro {
|
||||
background: var(--amber-light);
|
||||
padding: 20px;
|
||||
border-left: 4px solid var(--amber);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 0.95em;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.intro a {
|
||||
color: var(--amber);
|
||||
text-decoration: none;
|
||||
font-weight: 700;
|
||||
border-bottom: 2px solid var(--amber);
|
||||
}
|
||||
|
||||
.intro a:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.access ul,
|
||||
.exercise ul,
|
||||
.tips ul {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.access li,
|
||||
.exercise li,
|
||||
.tips li {
|
||||
padding-left: 30px;
|
||||
margin-bottom: 12px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.access li::before,
|
||||
.exercise li::before,
|
||||
.tips li::before {
|
||||
content: '▸';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--amber);
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.access a {
|
||||
color: var(--amber);
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
border-bottom: 2px solid var(--amber);
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.access a:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.exercise {
|
||||
background: var(--white);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 25px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.exercise-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
font-size: 1.1em;
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.exercise-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: var(--amber);
|
||||
color: var(--white);
|
||||
border-radius: 50%;
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-weight: 800;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.exercise-desc {
|
||||
font-size: 0.9em;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 15px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.exercise ul {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.exercise li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
padding: 10px 24px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
background: var(--amber);
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: var(--amber-dark);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 16px rgba(186, 117, 23, 0.25);
|
||||
}
|
||||
|
||||
.feedback {
|
||||
display: none;
|
||||
background: var(--amber-light);
|
||||
border-left: 4px solid var(--amber);
|
||||
padding: 15px;
|
||||
margin-top: 15px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.95em;
|
||||
color: var(--text-muted);
|
||||
animation: slideDown 0.3s ease;
|
||||
}
|
||||
|
||||
.feedback.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.case-study {
|
||||
background: var(--amber-light);
|
||||
border-left: 4px solid var(--amber);
|
||||
border-radius: 8px;
|
||||
padding: 25px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.case-study-title {
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: 1.1em;
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.case-study-desc {
|
||||
font-size: 0.95em;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 15px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.case-study ul {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.case-study li {
|
||||
padding-left: 30px;
|
||||
margin-bottom: 12px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.case-study li::before {
|
||||
content: '✓';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--amber);
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.tips {
|
||||
background: var(--amber-light);
|
||||
border-left: 4px solid var(--amber);
|
||||
border-radius: 8px;
|
||||
padding: 25px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tips h3 {
|
||||
margin-top: 0;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: 8px 0;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.page-header h1 {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.exercise {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.case-study {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.tips {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- HEADER NAVIGATION -->
|
||||
<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>
|
||||
|
||||
<!-- PROGRESS BAR -->
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="progressFill"></div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="page-header">
|
||||
<h1>🎯 Exercices Pratiques BioCID</h1>
|
||||
<p>Maîtriser la plateforme de recherche de produits biocides</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- SECTION CONTEXTE -->
|
||||
<div class="section">
|
||||
<h2>Contexte</h2>
|
||||
<div class="intro">
|
||||
BioCID est la plateforme officielle de l'ANSES pour consulter tous les produits biocides autorisés et déclarés en France. Cet exercice vous permettra de vous familiariser avec ses différents modes de recherche et de développer une autonomie dans la consultation des produits.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ACCÈS À LA PLATEFORME -->
|
||||
<div class="section">
|
||||
<h2>Accès à la Plateforme</h2>
|
||||
<div class="access">
|
||||
<ul>
|
||||
<li>Rendez-vous sur : <a href="https://biocid-anses.fr/biocid" target="_blank">https://biocid-anses.fr/biocid</a></li>
|
||||
<li>Cliquez sur « Rechercher un produit »</li>
|
||||
<li>Vous accédez à la base de données consultable</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EXERCICE 1 -->
|
||||
<div class="section">
|
||||
<h2>Exercice 1 : Recherche par Nom de Produit</h2>
|
||||
<div class="exercise">
|
||||
<div class="exercise-title">
|
||||
<div class="exercise-icon">1</div>
|
||||
Trouver un produit spécifique
|
||||
</div>
|
||||
<div class="exercise-desc">Durée : 5 minutes</div>
|
||||
<ul>
|
||||
<li>Recherchez un produit connu (ex. « Goliath », « Phobi », « Soricide »)</li>
|
||||
<li>Notez le type de produit, la substance active, et le fabricant</li>
|
||||
<li>Vérifiez la date d'autorisation du produit</li>
|
||||
<li>Observez les conditions d'utilisation mentionnées</li>
|
||||
</ul>
|
||||
<button onclick="toggleFeedback(1)">📝 Voir les indications</button>
|
||||
<div class="feedback" id="feedback1">
|
||||
<strong>Indications :</strong> Cherchez dans la zone de texte principale. Les résultats affichent le nom du produit, le fabricant et la substance active. C'est généralement le chemin le plus rapide pour localiser un produit connu.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EXERCICE 2 -->
|
||||
<div class="section">
|
||||
<h2>Exercice 2 : Recherche par Type de Produit</h2>
|
||||
<div class="exercise">
|
||||
<div class="exercise-title">
|
||||
<div class="exercise-icon">2</div>
|
||||
Explorer les catégories disponibles
|
||||
</div>
|
||||
<div class="exercise-desc">Durée : 5 minutes</div>
|
||||
<ul>
|
||||
<li>Utilisez le filtre « Type de produit »</li>
|
||||
<li>Sélectionnez « Rodenticides (TP14) » ou « Insecticides (TP18) »</li>
|
||||
<li>Comptez combien de produits sont disponibles dans cette catégorie</li>
|
||||
<li>Comparez avec une autre catégorie (ex. Désinfectants TP2/TP3/TP4)</li>
|
||||
</ul>
|
||||
<button onclick="toggleFeedback(2)">📝 Voir les indications</button>
|
||||
<div class="feedback" id="feedback2">
|
||||
<strong>Indications :</strong> Les filtres se trouvent généralement dans un menu latéral ou en haut de la page. Les types de produits correspondent aux codes TP (Type Product). Les rodenticides et insecticides sont souvent parmi les plus représentés.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EXERCICE 3 -->
|
||||
<div class="section">
|
||||
<h2>Exercice 3 : Recherche par Substance Active</h2>
|
||||
<div class="exercise">
|
||||
<div class="exercise-title">
|
||||
<div class="exercise-icon">3</div>
|
||||
Trouver tous les produits d'une même substance
|
||||
</div>
|
||||
<div class="exercise-desc">Durée : 5 minutes</div>
|
||||
<ul>
|
||||
<li>Recherchez une substance active (ex. « Brodifacoum », « Perméthrine »)</li>
|
||||
<li>Listez au minimum 3 produits différents contenant cette substance</li>
|
||||
<li>Observez si différents fabricants proposent des formulations variées</li>
|
||||
<li>Comparez les concentrations de la substance active d'un produit à l'autre</li>
|
||||
</ul>
|
||||
<button onclick="toggleFeedback(3)">📝 Voir les indications</button>
|
||||
<div class="feedback" id="feedback3">
|
||||
<strong>Indications :</strong> Cherchez dans la zone « Substance active » si elle existe. Une même substance active peut être formée différemment selon les fabricants (poudre, granulé, liquid, etc.).
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EXERCICE 4 : CAS DE SYNTHÈSE -->
|
||||
<div class="section">
|
||||
<h2>Exercice 4 : Cas de Synthèse</h2>
|
||||
<div class="case-study">
|
||||
<div class="case-study-title">Mise en situation réelle</div>
|
||||
<div class="case-study-desc">
|
||||
Un restaurant vous appelle pour vous demander un produit. En utilisant BioCID, vous devez présenter une recommandation argumentée.
|
||||
</div>
|
||||
<ul>
|
||||
<li>Filtrez par type : « Désinfectants (TP2/TP3/TP4) » ou « Produits de lutte contre les nuisibles (TP14/TP18/TP20) »</li>
|
||||
<li>Identifiez au minimum 3 produits applicables</li>
|
||||
<li>Pour chacun, relevez : nom, fabricant, substance active, usage spécifique</li>
|
||||
<li>Proposez un court argumentaire pour chaque produit</li>
|
||||
<li><strong>Bonus :</strong> Recherchez les fiches de sécurité (FDS) associées</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ASTUCES ET BONNES PRATIQUES -->
|
||||
<div class="section">
|
||||
<div class="tips">
|
||||
<h3>💡 Astuces d'Utilisation & Bonnes Pratiques</h3>
|
||||
<ul>
|
||||
<li>Combinez plusieurs filtres pour affiner votre recherche (type + substance + fabricant)</li>
|
||||
<li>Consultez toujours les fiches de sécurité (FDS) des produits avant recommandation</li>
|
||||
<li>Vérifiez les conditions d'utilisation : usage professionnel vs. domestique</li>
|
||||
<li>BioCID est mis à jour régulièrement avec les nouvelles autorisations et retraits</li>
|
||||
<li>En cas de doute, contactez l'ANSES ou consultez un expert en biocides</li>
|
||||
<li>Notez toujours les références exactes pour traçabilité (numéro d'AMM)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p><strong>Durée totale estimée :</strong> 30 minutes</p>
|
||||
<p style="margin-top: 15px;">Pour toute question sur BioCID : <strong>support.biocid@anses.fr</strong></p>
|
||||
<p style="margin-top: 20px; opacity: 0.7; font-size: 0.9em;">© 2026 NextGN Formation - Exercices BioCID</p>
|
||||
<p style="opacity: 0.6; font-size: 0.85em;">Tous droits réservés — Gauthier Chombart & Nathan Chauwin — Formateurs indépendants</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function toggleFeedback(exerciseNum) {
|
||||
const feedbackId = 'feedback' + exerciseNum;
|
||||
const element = document.getElementById(feedbackId);
|
||||
element.classList.toggle('show');
|
||||
}
|
||||
|
||||
// Mise à jour de la barre de progression au scroll
|
||||
window.addEventListener('scroll', () => {
|
||||
const scrollTop = window.scrollY;
|
||||
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
|
||||
const scrollPercent = docHeight > 0 ? (scrollTop / docHeight) * 100 : 0;
|
||||
document.getElementById('progressFill').style.width = scrollPercent + '%';
|
||||
});
|
||||
|
||||
// Animation au chargement
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const sections = document.querySelectorAll('.section');
|
||||
sections.forEach((section, index) => {
|
||||
section.style.animationDelay = (index * 0.1) + 's';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,517 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Exercices Pratiques BioCID - NextGN Formation</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="../header-style.css"/>
|
||||
<style>
|
||||
body {
|
||||
padding-top: 70px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
background: var(--white);
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, var(--amber-light) 0%, rgba(186, 117, 23, 0.05) 100%);
|
||||
color: var(--text);
|
||||
padding: 50px 40px;
|
||||
text-align: center;
|
||||
border-bottom: 3px solid var(--amber);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: 2.2em;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.5px;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
font-size: 1em;
|
||||
opacity: 0.8;
|
||||
font-weight: 400;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 50px 40px;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 40px;
|
||||
animation: fadeIn 0.6s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: 'Syne', sans-serif;
|
||||
color: var(--text);
|
||||
font-size: 1.6em;
|
||||
margin-bottom: 15px;
|
||||
border-bottom: 3px solid var(--amber);
|
||||
padding-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
h2::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--amber);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: 1.2em;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
color: var(--text);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.intro {
|
||||
background: var(--amber-light);
|
||||
padding: 20px;
|
||||
border-left: 4px solid var(--amber);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 0.95em;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.intro a {
|
||||
color: var(--amber);
|
||||
text-decoration: none;
|
||||
font-weight: 700;
|
||||
border-bottom: 2px solid var(--amber);
|
||||
}
|
||||
|
||||
.intro a:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.access ul,
|
||||
.exercise ul,
|
||||
.tips ul {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.access li,
|
||||
.exercise li,
|
||||
.tips li {
|
||||
padding-left: 30px;
|
||||
margin-bottom: 12px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.access li::before,
|
||||
.exercise li::before,
|
||||
.tips li::before {
|
||||
content: '▸';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--amber);
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.access a {
|
||||
color: var(--amber);
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
border-bottom: 2px solid var(--amber);
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.access a:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.exercise {
|
||||
background: var(--white);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 25px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.exercise-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
font-size: 1.1em;
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.exercise-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: var(--amber);
|
||||
color: var(--white);
|
||||
border-radius: 50%;
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-weight: 800;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.exercise-desc {
|
||||
font-size: 0.9em;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 15px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.exercise ul {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.exercise li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
padding: 10px 24px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
background: var(--amber);
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: var(--amber-dark);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 16px rgba(186, 117, 23, 0.25);
|
||||
}
|
||||
|
||||
.feedback {
|
||||
display: none;
|
||||
background: var(--amber-light);
|
||||
border-left: 4px solid var(--amber);
|
||||
padding: 15px;
|
||||
margin-top: 15px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.95em;
|
||||
color: var(--text-muted);
|
||||
animation: slideDown 0.3s ease;
|
||||
}
|
||||
|
||||
.feedback.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.case-study {
|
||||
background: var(--amber-light);
|
||||
border-left: 4px solid var(--amber);
|
||||
border-radius: 8px;
|
||||
padding: 25px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.case-study-title {
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: 1.1em;
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.case-study-desc {
|
||||
font-size: 0.95em;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 15px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.case-study ul {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.case-study li {
|
||||
padding-left: 30px;
|
||||
margin-bottom: 12px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.case-study li::before {
|
||||
content: '✓';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--amber);
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.tips {
|
||||
background: var(--amber-light);
|
||||
border-left: 4px solid var(--amber);
|
||||
border-radius: 8px;
|
||||
padding: 25px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tips h3 {
|
||||
margin-top: 0;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: 8px 0;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.page-header h1 {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.exercise {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.case-study {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.tips {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- HEADER NAVIGATION -->
|
||||
<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>
|
||||
|
||||
<!-- PROGRESS BAR -->
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="progressFill"></div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="page-header">
|
||||
<h1>🎯 Exercices Pratiques BioCID</h1>
|
||||
<p>Maîtriser la plateforme de recherche de produits biocides</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- SECTION CONTEXTE -->
|
||||
<div class="section">
|
||||
<h2>Contexte</h2>
|
||||
<div class="intro">
|
||||
BioCID est la plateforme officielle de l'ANSES pour consulter tous les produits biocides autorisés et déclarés en France. Cet exercice vous permettra de vous familiariser avec ses différents modes de recherche et de développer une autonomie dans la consultation des produits.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ACCÈS À LA PLATEFORME -->
|
||||
<div class="section">
|
||||
<h2>Accès à la Plateforme</h2>
|
||||
<div class="access">
|
||||
<ul>
|
||||
<li>Rendez-vous sur : <a href="https://biocid-anses.fr/biocid" target="_blank">https://biocid-anses.fr/biocid</a></li>
|
||||
<li>Cliquez sur « Rechercher un produit »</li>
|
||||
<li>Vous accédez à la base de données consultable</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EXERCICE 1 -->
|
||||
<div class="section">
|
||||
<h2>Exercice 1 : Recherche par Nom de Produit</h2>
|
||||
<div class="exercise">
|
||||
<div class="exercise-title">
|
||||
<div class="exercise-icon">1</div>
|
||||
Trouver un produit spécifique
|
||||
</div>
|
||||
<div class="exercise-desc">Durée : 5 minutes</div>
|
||||
<ul>
|
||||
<li>Recherchez un produit connu (ex. « Goliath », « Phobi », « Soricide »)</li>
|
||||
<li>Notez le type de produit, la substance active, et le fabricant</li>
|
||||
<li>Vérifiez la date d'autorisation du produit</li>
|
||||
<li>Observez les conditions d'utilisation mentionnées</li>
|
||||
</ul>
|
||||
<button onclick="toggleFeedback(1)">📝 Voir les indications</button>
|
||||
<div class="feedback" id="feedback1">
|
||||
<strong>Indications :</strong> Cherchez dans la zone de texte principale. Les résultats affichent le nom du produit, le fabricant et la substance active. C'est généralement le chemin le plus rapide pour localiser un produit connu.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EXERCICE 2 -->
|
||||
<div class="section">
|
||||
<h2>Exercice 2 : Recherche par Type de Produit</h2>
|
||||
<div class="exercise">
|
||||
<div class="exercise-title">
|
||||
<div class="exercise-icon">2</div>
|
||||
Explorer les catégories disponibles
|
||||
</div>
|
||||
<div class="exercise-desc">Durée : 5 minutes</div>
|
||||
<ul>
|
||||
<li>Utilisez le filtre « Type de produit »</li>
|
||||
<li>Sélectionnez « Rodenticides (TP14) » ou « Insecticides (TP18) »</li>
|
||||
<li>Comptez combien de produits sont disponibles dans cette catégorie</li>
|
||||
<li>Comparez avec une autre catégorie (ex. Désinfectants TP2/TP3/TP4)</li>
|
||||
</ul>
|
||||
<button onclick="toggleFeedback(2)">📝 Voir les indications</button>
|
||||
<div class="feedback" id="feedback2">
|
||||
<strong>Indications :</strong> Les filtres se trouvent généralement dans un menu latéral ou en haut de la page. Les types de produits correspondent aux codes TP (Type Product). Les rodenticides et insecticides sont souvent parmi les plus représentés.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EXERCICE 3 -->
|
||||
<div class="section">
|
||||
<h2>Exercice 3 : Recherche par Substance Active</h2>
|
||||
<div class="exercise">
|
||||
<div class="exercise-title">
|
||||
<div class="exercise-icon">3</div>
|
||||
Trouver tous les produits d'une même substance
|
||||
</div>
|
||||
<div class="exercise-desc">Durée : 5 minutes</div>
|
||||
<ul>
|
||||
<li>Recherchez une substance active (ex. « Brodifacoum », « Perméthrine »)</li>
|
||||
<li>Listez au minimum 3 produits différents contenant cette substance</li>
|
||||
<li>Observez si différents fabricants proposent des formulations variées</li>
|
||||
<li>Comparez les concentrations de la substance active d'un produit à l'autre</li>
|
||||
</ul>
|
||||
<button onclick="toggleFeedback(3)">📝 Voir les indications</button>
|
||||
<div class="feedback" id="feedback3">
|
||||
<strong>Indications :</strong> Cherchez dans la zone « Substance active » si elle existe. Une même substance active peut être formée différemment selon les fabricants (poudre, granulé, liquid, etc.).
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EXERCICE 4 : CAS DE SYNTHÈSE -->
|
||||
<div class="section">
|
||||
<h2>Exercice 4 : Cas de Synthèse</h2>
|
||||
<div class="case-study">
|
||||
<div class="case-study-title">Mise en situation réelle</div>
|
||||
<div class="case-study-desc">
|
||||
Un restaurant vous appelle pour vous demander un produit. En utilisant BioCID, vous devez présenter une recommandation argumentée.
|
||||
</div>
|
||||
<ul>
|
||||
<li>Filtrez par type : « Désinfectants (TP2/TP3/TP4) » ou « Produits de lutte contre les nuisibles (TP14/TP18/TP20) »</li>
|
||||
<li>Identifiez au minimum 3 produits applicables</li>
|
||||
<li>Pour chacun, relevez : nom, fabricant, substance active, usage spécifique</li>
|
||||
<li>Proposez un court argumentaire pour chaque produit</li>
|
||||
<li><strong>Bonus :</strong> Recherchez les fiches de sécurité (FDS) associées</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ASTUCES ET BONNES PRATIQUES -->
|
||||
<div class="section">
|
||||
<div class="tips">
|
||||
<h3>💡 Astuces d'Utilisation & Bonnes Pratiques</h3>
|
||||
<ul>
|
||||
<li>Combinez plusieurs filtres pour affiner votre recherche (type + substance + fabricant)</li>
|
||||
<li>Consultez toujours les fiches de sécurité (FDS) des produits avant recommandation</li>
|
||||
<li>Vérifiez les conditions d'utilisation : usage professionnel vs. domestique</li>
|
||||
<li>BioCID est mis à jour régulièrement avec les nouvelles autorisations et retraits</li>
|
||||
<li>En cas de doute, contactez l'ANSES ou consultez un expert en biocides</li>
|
||||
<li>Notez toujours les références exactes pour traçabilité (numéro d'AMM)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>© 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>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function toggleFeedback(exerciseNum) {
|
||||
const feedbackId = 'feedback' + exerciseNum;
|
||||
const element = document.getElementById(feedbackId);
|
||||
element.classList.toggle('show');
|
||||
}
|
||||
|
||||
// Mise à jour de la barre de progression au scroll
|
||||
window.addEventListener('scroll', () => {
|
||||
const scrollTop = window.scrollY;
|
||||
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
|
||||
const scrollPercent = docHeight > 0 ? (scrollTop / docHeight) * 100 : 0;
|
||||
document.getElementById('progressFill').style.width = scrollPercent + '%';
|
||||
});
|
||||
|
||||
// Animation au chargement
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const sections = document.querySelectorAll('.section');
|
||||
sections.forEach((section, index) => {
|
||||
section.style.animationDelay = (index * 0.1) + 's';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,977 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>TD FDS, FT et Étiquettes</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="../header-style.css"/>
|
||||
<style>
|
||||
body {
|
||||
padding-top: 70px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
background: var(--amber-light);
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, var(--amber-light) 0%, rgba(24, 95, 165, 0.05) 100%);
|
||||
color: var(--text);
|
||||
padding: 50px 40px;
|
||||
text-align: center;
|
||||
border-bottom: 3px solid var(--amber);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: 2.2em;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.5px;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
font-size: 1em;
|
||||
opacity: 0.8;
|
||||
font-weight: 400;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 50px 40px;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 40px;
|
||||
animation: fadeIn 0.6s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: 'Syne', sans-serif;
|
||||
color: var(--text);
|
||||
font-size: 1.6em;
|
||||
margin-bottom: 15px;
|
||||
border-bottom: 3px solid var(--amber);
|
||||
padding-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
h2::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--amber);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: var(--text);
|
||||
font-size: 1.15em;
|
||||
margin: 25px 0 15px 0;
|
||||
font-weight: 700;
|
||||
font-family: 'Syne', sans-serif;
|
||||
}
|
||||
|
||||
.intro {
|
||||
background: var(--coral-light);
|
||||
padding: 20px;
|
||||
border-left: 4px solid var(--coral-mid);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 0.95em;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.intro strong {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.tab-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 2px solid var(--border);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
font-family: 'Syne', sans-serif;
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 12px 20px;
|
||||
font-size: 0.95em;
|
||||
font-weight: 700;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.tab-button:hover {
|
||||
color: var(--coral-mid);
|
||||
}
|
||||
|
||||
.tab-button.active {
|
||||
color: var(--coral-mid);
|
||||
border-bottom: 3px solid var(--coral-mid);
|
||||
padding-bottom: 9px;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.answer-row {
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.answer-row:hover {
|
||||
border-color: var(--coral-mid);
|
||||
box-shadow: 0 8px 16px rgba(216, 90, 48, 0.1);
|
||||
}
|
||||
|
||||
.question-num {
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: 0.9em;
|
||||
font-weight: 700;
|
||||
color: var(--coral-mid);
|
||||
min-width: 35px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.question-text {
|
||||
flex-grow: 1;
|
||||
font-size: 0.95em;
|
||||
color: var(--text);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
select {
|
||||
font-family: 'DM Sans', sans-serif;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
background: var(--amber-light);
|
||||
color: var(--text);
|
||||
font-size: 0.9em;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
select:focus {
|
||||
outline: none;
|
||||
border-color: var(--coral-mid);
|
||||
box-shadow: 0 0 0 3px rgba(216, 90, 48, 0.1);
|
||||
}
|
||||
|
||||
.verify-btn {
|
||||
font-family: 'Syne', sans-serif;
|
||||
background: var(--coral-mid);
|
||||
color: var(--white);
|
||||
border: none;
|
||||
padding: 12px 30px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.05em;
|
||||
transition: all 0.3s ease;
|
||||
margin-top: 20px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.verify-btn:hover {
|
||||
background: var(--coral);
|
||||
box-shadow: 0 8px 16px rgba(216, 90, 48, 0.25);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.result-message {
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
font-weight: 700;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.result-message.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.result-message.success {
|
||||
background: var(--amber-light);
|
||||
border: 2px solid var(--amber-mid);
|
||||
color: var(--amber-dark);
|
||||
}
|
||||
|
||||
.result-message.error {
|
||||
background: var(--coral-light);
|
||||
border: 2px solid var(--coral-mid);
|
||||
color: var(--coral);
|
||||
}
|
||||
|
||||
.comparison-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
background: var(--amber-light);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.comparison-table th {
|
||||
background: var(--coral-light);
|
||||
color: var(--coral);
|
||||
padding: 15px;
|
||||
text-align: left;
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-weight: 700;
|
||||
border-bottom: 2px solid var(--coral-mid);
|
||||
}
|
||||
|
||||
.comparison-table td {
|
||||
padding: 15px;
|
||||
border-bottom: 2px solid var(--amber);
|
||||
color: var(--text-muted);
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
.comparison-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.comparison-table tr:hover {
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
.comparison-table strong {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
footer {
|
||||
background: var(--bg);
|
||||
padding: 25px;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.progress {
|
||||
height: 3px;
|
||||
background: var(--border);
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
top: 70px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: var(--coral-mid);
|
||||
width: 0%;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.page-header h1 {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.answer-row {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- HEADER NAVIGATION -->
|
||||
<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="container">
|
||||
<div class="page-header">
|
||||
<h1>📋 TD — ANALYSE FDS, FT ET ÉTIQUETTES</h1>
|
||||
<p>Maîtriser les documents essentiels des produits biocides</p>
|
||||
</div>
|
||||
|
||||
<div class="progress">
|
||||
<div class="progress-bar" id="progressBar"></div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- TABS NAVIGATION -->
|
||||
<div class="tab-container">
|
||||
<button class="tab-button active" onclick="switchTab('fds')">📄 FDS (Fiche Sécurité)</button>
|
||||
<button class="tab-button" onclick="switchTab('ft')">📋 FT (Fiche Technique)</button>
|
||||
<button class="tab-button" onclick="switchTab('etiquette')">🏷️ Étiquetage</button>
|
||||
</div>
|
||||
|
||||
<!-- ====== TAB FDS ====== -->
|
||||
<div id="fds" class="tab-content active">
|
||||
<div class="section">
|
||||
<h2>Partie 1 : Fiche de Données de Sécurité (FDS)</h2>
|
||||
<div class="intro">
|
||||
<strong>Durée : 10 minutes</strong>
|
||||
La FDS est le document de référence pour connaître les dangers, les EPI, les premiers secours et l'élimination des produits biocides. Elle contient 16 rubriques standards.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>Questions d'analyse FDS</h3>
|
||||
<p style="margin-bottom: 20px; color: #555;">Pour chaque question, sélectionnez la rubrique FDS où trouver la réponse (Rubrique 1 à 16 ou « Absent »).</p>
|
||||
|
||||
<div id="fds-questions">
|
||||
<!-- Les questions seront générées et mélangées par JavaScript -->
|
||||
</div>
|
||||
|
||||
<button class="verify-btn" onclick="verifyFDS()">✓ Vérifier les réponses</button>
|
||||
<div id="fds-result" class="result-message"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ====== TAB FT ====== -->
|
||||
<div id="ft" class="tab-content">
|
||||
<div class="section">
|
||||
<h2>Partie 2 : Fiche Technique (FT)</h2>
|
||||
<div class="intro">
|
||||
<strong>Durée : 10 minutes</strong>
|
||||
La FT complète la FDS en fournissant le mode d'emploi, les utilisations, les conditionnements et les compatibilités matières. C'est le document pratique de l'utilisateur.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>Questions d'analyse FT</h3>
|
||||
<p style="margin-bottom: 20px; color: #555;">Pour chaque question, sélectionnez la section FT où trouver la réponse (Description, Propriétés biocides, Conditionnements, Utilisations ou « Absent »).</p>
|
||||
|
||||
<div id="ft-questions">
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q1</div>
|
||||
<div class="question-text">Quel est le nom du produit et qui est le fabricant ?</div>
|
||||
<select id="ft1" data-answer="Description">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Description">Description</option>
|
||||
<option value="Propriétés biocides">Propriétés biocides</option>
|
||||
<option value="Conditionnements">Conditionnements</option>
|
||||
<option value="Utilisations">Utilisations</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q2</div>
|
||||
<div class="question-text">Sur quelles matières ce produit est-il compatible ?</div>
|
||||
<select id="ft2" data-answer="Description">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Description">Description</option>
|
||||
<option value="Propriétés biocides">Propriétés biocides</option>
|
||||
<option value="Conditionnements">Conditionnements</option>
|
||||
<option value="Utilisations">Utilisations</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q3</div>
|
||||
<div class="question-text">Quel est (sont) le(s) type(s) de ce produit ?</div>
|
||||
<select id="ft3" data-answer="Propriétés biocides">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Description">Description</option>
|
||||
<option value="Propriétés biocides">Propriétés biocides</option>
|
||||
<option value="Conditionnements">Conditionnements</option>
|
||||
<option value="Utilisations">Utilisations</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q4</div>
|
||||
<div class="question-text">Quels sont les types de conditionnements disponibles ?</div>
|
||||
<select id="ft4" data-answer="Conditionnements">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Description">Description</option>
|
||||
<option value="Propriétés biocides">Propriétés biocides</option>
|
||||
<option value="Conditionnements">Conditionnements</option>
|
||||
<option value="Utilisations">Utilisations</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q5</div>
|
||||
<div class="question-text">Où retrouver les différents modes d'utilisation de ce produit ?</div>
|
||||
<select id="ft5" data-answer="Utilisations">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Description">Description</option>
|
||||
<option value="Propriétés biocides">Propriétés biocides</option>
|
||||
<option value="Conditionnements">Conditionnements</option>
|
||||
<option value="Utilisations">Utilisations</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="verify-btn" onclick="verifyFT()">✓ Vérifier les réponses</button>
|
||||
<div id="ft-result" class="result-message"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ====== TAB ÉTIQUETAGE ====== -->
|
||||
<div id="etiquette" class="tab-content">
|
||||
<div class="section">
|
||||
<h2>Partie 3 : Étiquetage</h2>
|
||||
<div class="intro">
|
||||
<strong>Durée : 10 minutes</strong>
|
||||
L'étiquette est l'interface directe entre le produit et l'utilisateur. Elle communique rapidement les dangers essentiels, les EPI, et les consignes d'utilisation.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>Questions d'analyse d'étiquette</h3>
|
||||
<p style="margin-bottom: 20px; color: #555;">Pour chaque question, sélectionnez la zone de l'étiquette où trouver l'information (Identité, Pictogrammes, Mots d'avertissement, Mentions de danger, Conseils de prudence, Contenu net, Numéro d'urgence ou « Absent »).</p>
|
||||
|
||||
<div id="etiquette-questions">
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q1</div>
|
||||
<div class="question-text">Quel est le nom du produit et le nom de l'entreprise ?</div>
|
||||
<select id="eti1" data-answer="Identité">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Identité">Identité</option>
|
||||
<option value="Pictogrammes">Pictogrammes</option>
|
||||
<option value="Mots d'avertissement">Mots d'avertissement</option>
|
||||
<option value="Mentions de danger">Mentions de danger</option>
|
||||
<option value="Conseils de prudence">Conseils de prudence</option>
|
||||
<option value="Contenu net">Contenu net</option>
|
||||
<option value="Numéro d'urgence">Numéro d'urgence</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q2</div>
|
||||
<div class="question-text">Quels pictogrammes de danger sont affichés ?</div>
|
||||
<select id="eti2" data-answer="Pictogrammes">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Identité">Identité</option>
|
||||
<option value="Pictogrammes">Pictogrammes</option>
|
||||
<option value="Mots d'avertissement">Mots d'avertissement</option>
|
||||
<option value="Mentions de danger">Mentions de danger</option>
|
||||
<option value="Conseils de prudence">Conseils de prudence</option>
|
||||
<option value="Contenu net">Contenu net</option>
|
||||
<option value="Numéro d'urgence">Numéro d'urgence</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q3</div>
|
||||
<div class="question-text">Quels sont les mots d'avertissement (« Attention » ou « Danger ») ?</div>
|
||||
<select id="eti3" data-answer="Mots d'avertissement">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Identité">Identité</option>
|
||||
<option value="Pictogrammes">Pictogrammes</option>
|
||||
<option value="Mots d'avertissement">Mots d'avertissement</option>
|
||||
<option value="Mentions de danger">Mentions de danger</option>
|
||||
<option value="Conseils de prudence">Conseils de prudence</option>
|
||||
<option value="Contenu net">Contenu net</option>
|
||||
<option value="Numéro d'urgence">Numéro d'urgence</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q4</div>
|
||||
<div class="question-text">Quelles sont les mentions de danger (H-statements) identifiées ?</div>
|
||||
<select id="eti4" data-answer="Mentions de danger">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Identité">Identité</option>
|
||||
<option value="Pictogrammes">Pictogrammes</option>
|
||||
<option value="Mots d'avertissement">Mots d'avertissement</option>
|
||||
<option value="Mentions de danger">Mentions de danger</option>
|
||||
<option value="Conseils de prudence">Conseils de prudence</option>
|
||||
<option value="Contenu net">Contenu net</option>
|
||||
<option value="Numéro d'urgence">Numéro d'urgence</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q5</div>
|
||||
<div class="question-text">Quels équipements de protection individuelle (EPI) sont recommandés ?</div>
|
||||
<select id="eti5" data-answer="Conseils de prudence">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Identité">Identité</option>
|
||||
<option value="Pictogrammes">Pictogrammes</option>
|
||||
<option value="Mots d'avertissement">Mots d'avertissement</option>
|
||||
<option value="Mentions de danger">Mentions de danger</option>
|
||||
<option value="Conseils de prudence">Conseils de prudence</option>
|
||||
<option value="Contenu net">Contenu net</option>
|
||||
<option value="Numéro d'urgence">Numéro d'urgence</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q6</div>
|
||||
<div class="question-text">Quels premiers secours sont indiqués ?</div>
|
||||
<select id="eti6" data-answer="Conseils de prudence">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Identité">Identité</option>
|
||||
<option value="Pictogrammes">Pictogrammes</option>
|
||||
<option value="Mots d'avertissement">Mots d'avertissement</option>
|
||||
<option value="Mentions de danger">Mentions de danger</option>
|
||||
<option value="Conseils de prudence">Conseils de prudence</option>
|
||||
<option value="Contenu net">Contenu net</option>
|
||||
<option value="Numéro d'urgence">Numéro d'urgence</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q7</div>
|
||||
<div class="question-text">Comment éliminer l'emballage après utilisation ?</div>
|
||||
<select id="eti7" data-answer="Conseils de prudence">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Identité">Identité</option>
|
||||
<option value="Pictogrammes">Pictogrammes</option>
|
||||
<option value="Mots d'avertissement">Mots d'avertissement</option>
|
||||
<option value="Mentions de danger">Mentions de danger</option>
|
||||
<option value="Conseils de prudence">Conseils de prudence</option>
|
||||
<option value="Contenu net">Contenu net</option>
|
||||
<option value="Numéro d'urgence">Numéro d'urgence</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q8</div>
|
||||
<div class="question-text">Quel est le contenu net du produit ?</div>
|
||||
<select id="eti8" data-answer="Contenu net">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Identité">Identité</option>
|
||||
<option value="Pictogrammes">Pictogrammes</option>
|
||||
<option value="Mots d'avertissement">Mots d'avertissement</option>
|
||||
<option value="Mentions de danger">Mentions de danger</option>
|
||||
<option value="Conseils de prudence">Conseils de prudence</option>
|
||||
<option value="Contenu net">Contenu net</option>
|
||||
<option value="Numéro d'urgence">Numéro d'urgence</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q9</div>
|
||||
<div class="question-text">Y a-t-il un numéro d'appel d'urgence mentionné ?</div>
|
||||
<select id="eti9" data-answer="Numéro d'urgence">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Identité">Identité</option>
|
||||
<option value="Pictogrammes">Pictogrammes</option>
|
||||
<option value="Mots d'avertissement">Mots d'avertissement</option>
|
||||
<option value="Mentions de danger">Mentions de danger</option>
|
||||
<option value="Conseils de prudence">Conseils de prudence</option>
|
||||
<option value="Contenu net">Contenu net</option>
|
||||
<option value="Numéro d'urgence">Numéro d'urgence</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q10</div>
|
||||
<div class="question-text">Quelles consignes de stockage sont affichées ?</div>
|
||||
<select id="eti10" data-answer="Conseils de prudence">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Identité">Identité</option>
|
||||
<option value="Pictogrammes">Pictogrammes</option>
|
||||
<option value="Mots d'avertissement">Mots d'avertissement</option>
|
||||
<option value="Mentions de danger">Mentions de danger</option>
|
||||
<option value="Conseils de prudence">Conseils de prudence</option>
|
||||
<option value="Contenu net">Contenu net</option>
|
||||
<option value="Numéro d'urgence">Numéro d'urgence</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="verify-btn" onclick="verifyEtiquette()">✓ Vérifier les réponses</button>
|
||||
<div id="etiquette-result" class="result-message"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ====== SYNTHÈSE COMPARATIVE ====== -->
|
||||
<div class="section">
|
||||
<h2>Synthèse Comparative</h2>
|
||||
<table class="comparison-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Document</th>
|
||||
<th>Utilité Principale</th>
|
||||
<th>Moment de Consultation</th>
|
||||
<th>Public</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>FDS</strong></td>
|
||||
<td>Sécurité, dangers, EPI, premiers secours</td>
|
||||
<td>AVANT manipulation</td>
|
||||
<td>Professionnels</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>FT</strong></td>
|
||||
<td>Mode d'emploi, applications, conditionnements</td>
|
||||
<td>Pour l'utilisation</td>
|
||||
<td>Utilisateurs finaux</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Étiquette</strong></td>
|
||||
<td>Dangers essentiels, interface rapide</td>
|
||||
<td>À chaque prise du produit</td>
|
||||
<td>Tous les utilisateurs</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- CLÉS DE SUCCÈS -->
|
||||
<div class="section">
|
||||
<div class="tips">
|
||||
<h3>💡 Clés de Succès</h3>
|
||||
<ul>
|
||||
<li><strong>FDS = Sécurité et dangers</strong> → À consulter AVANT manipulation</li>
|
||||
<li><strong>FT = Mode d'emploi et applications</strong> → À consulter pour l'utilisation</li>
|
||||
<li><strong>Étiquette = Interface rapide</strong> → Lecture obligatoire avant ouverture du produit</li>
|
||||
<li>Les trois documents sont <strong>complémentaires</strong>, jamais redondants</li>
|
||||
<li>Chaque document cible un besoin différent et un public différent</li>
|
||||
<li>Un professionnel doit maîtriser les trois pour une utilisation sûre et efficace</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p><strong>Objectif pédagogique :</strong> Acquérir une autonomie dans la lecture et l'interprétation des documents réglementaires</p>
|
||||
<p style="margin-top: 15px; font-size: 0.95em;">Ces documents sont obligatoires pour tout produit biocide mis sur le marché en France.</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Questions FDS avec réponses
|
||||
const fdsQuestions = [
|
||||
{ id: 1, text: "Quel est le nom du produit et qui est le fabricant ?", answer: "1" },
|
||||
{ id: 2, text: "Quels sont les dangers du produit ?", answer: "2" },
|
||||
{ id: 3, text: "Quelles sont les molécules actives et leurs concentrations ?", answer: "3" },
|
||||
{ id: 4, text: "Quels gestes adopter en premiers secours ?", answer: "4" },
|
||||
{ id: 5, text: "En cas d'incendie, quels sont les moyens d'extinction conseillés ?", answer: "5" },
|
||||
{ id: 6, text: "Que faire en cas de dispersion accidentelle ?", answer: "6" },
|
||||
{ id: 7, text: "Dans quelles conditions stocker mon produit ?", answer: "7" },
|
||||
{ id: 8, text: "Quels équipements de protection individuelle (EPI) dois-je porter ?", answer: "8" },
|
||||
{ id: 9, text: "Quel est le pH de ce produit ?", answer: "9" },
|
||||
{ id: 10, text: "Le produit est-il stable dans des conditions normales ?", answer: "10" },
|
||||
{ id: 11, text: "Quels sont les effets potentiels sur la santé ?", answer: "11" },
|
||||
{ id: 12, text: "Quel est le degré de toxicité pour les poissons ? Potentiel de bioaccumulation ?", answer: "12" },
|
||||
{ id: 13, text: "Comment éliminer le produit ?", answer: "13" },
|
||||
{ id: 14, text: "Est-ce que mon produit est classé dangereux pour le transport ?", answer: "14" },
|
||||
{ id: 15, text: "En cas d'ingestion du produit, que faire ?", answer: "4,11" },
|
||||
{ id: 16, text: "Où trouver le mode d'emploi du produit ?", answer: "absent" }
|
||||
];
|
||||
|
||||
// Mélanger aléatoirement les questions
|
||||
function shuffleArray(array) {
|
||||
const newArray = [...array];
|
||||
for (let i = newArray.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[newArray[i], newArray[j]] = [newArray[j], newArray[i]];
|
||||
}
|
||||
return newArray;
|
||||
}
|
||||
|
||||
// Générer les questions FDS mélangées
|
||||
function generateFDSQuestions() {
|
||||
const shuffled = shuffleArray(fdsQuestions);
|
||||
const container = document.getElementById('fds-questions');
|
||||
container.innerHTML = '';
|
||||
|
||||
shuffled.forEach((question, index) => {
|
||||
const qNum = index + 1;
|
||||
const selectId = 'fds' + question.id;
|
||||
const answerAttr = question.answer;
|
||||
|
||||
const html = `
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q${qNum}</div>
|
||||
<div class="question-text">${question.text}</div>
|
||||
<select id="${selectId}" data-answer="${answerAttr}" data-qnum="${qNum}">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="1">Rubrique 1</option>
|
||||
<option value="2">Rubrique 2</option>
|
||||
<option value="3">Rubrique 3</option>
|
||||
<option value="4">Rubrique 4</option>
|
||||
<option value="5">Rubrique 5</option>
|
||||
<option value="6">Rubrique 6</option>
|
||||
<option value="7">Rubrique 7</option>
|
||||
<option value="8">Rubrique 8</option>
|
||||
<option value="9">Rubrique 9</option>
|
||||
<option value="10">Rubrique 10</option>
|
||||
<option value="11">Rubrique 11</option>
|
||||
<option value="12">Rubrique 12</option>
|
||||
<option value="13">Rubrique 13</option>
|
||||
<option value="14">Rubrique 14</option>
|
||||
<option value="15">Rubrique 15</option>
|
||||
<option value="16">Rubrique 16</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
`;
|
||||
container.innerHTML += html;
|
||||
});
|
||||
}
|
||||
|
||||
// Answers mapping
|
||||
const answers = {
|
||||
ft: {
|
||||
1: ['Description'],
|
||||
2: ['Description'],
|
||||
3: ['Propriétés biocides'],
|
||||
4: ['Conditionnements'],
|
||||
5: ['Utilisations']
|
||||
},
|
||||
etiquette: {
|
||||
1: ['Identité'],
|
||||
2: ['Pictogrammes'],
|
||||
3: ['Mots d\'avertissement'],
|
||||
4: ['Mentions de danger'],
|
||||
5: ['Conseils de prudence'],
|
||||
6: ['Conseils de prudence'],
|
||||
7: ['Conseils de prudence'],
|
||||
8: ['Contenu net'],
|
||||
9: ['Numéro d\'urgence'],
|
||||
10: ['Conseils de prudence']
|
||||
}
|
||||
};
|
||||
|
||||
function verifyFDS() {
|
||||
let correct = 0;
|
||||
let total = 16;
|
||||
let details = [];
|
||||
|
||||
fdsQuestions.forEach((question) => {
|
||||
const select = document.getElementById('fds' + question.id);
|
||||
const qNum = select.getAttribute('data-qnum');
|
||||
const userAnswer = select.value;
|
||||
const expectedAnswers = question.answer.split(',');
|
||||
|
||||
if (userAnswer === '') {
|
||||
details.push(`Q${qNum}: ❌ Pas de réponse`);
|
||||
} else if (expectedAnswers.includes(userAnswer)) {
|
||||
correct++;
|
||||
details.push(`Q${qNum}: ✓ Correct`);
|
||||
} else {
|
||||
const expected = expectedAnswers.length > 1 ?
|
||||
'Rubriques ' + expectedAnswers.join(' et ') :
|
||||
(expectedAnswers[0] === 'absent' ? 'Absent' : 'Rubrique ' + expectedAnswers[0]);
|
||||
details.push(`Q${qNum}: ❌ Incorrect (réponse: ${expected})`);
|
||||
}
|
||||
});
|
||||
|
||||
const resultDiv = document.getElementById('fds-result');
|
||||
const percentage = Math.round((correct / total) * 100);
|
||||
|
||||
let message = '';
|
||||
if (percentage === 100) {
|
||||
resultDiv.className = 'result-message success';
|
||||
message = `<strong>🎉 Excellent !</strong> Vous avez ${correct}/${total} bonnes réponses (${percentage}%)`;
|
||||
} else if (percentage >= 80) {
|
||||
resultDiv.className = 'result-message success';
|
||||
message = `<strong>✓ Très bien !</strong> Vous avez ${correct}/${total} bonnes réponses (${percentage}%)`;
|
||||
} else if (percentage >= 60) {
|
||||
resultDiv.className = 'result-message error';
|
||||
message = `<strong>⚠️ À revoir</strong> Vous avez ${correct}/${total} bonnes réponses (${percentage}%)`;
|
||||
} else {
|
||||
resultDiv.className = 'result-message error';
|
||||
message = `<strong>❌ Insuffisant</strong> Vous avez ${correct}/${total} bonnes réponses (${percentage}%)`;
|
||||
}
|
||||
|
||||
resultDiv.innerHTML = message + '<div class="answer-detail"><strong>Détails :</strong><br>' + details.join('<br>') + '</div>';
|
||||
}
|
||||
|
||||
function verifyFT() {
|
||||
let correct = 0;
|
||||
let total = 5;
|
||||
let details = [];
|
||||
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const select = document.getElementById('ft' + i);
|
||||
const userAnswer = select.value;
|
||||
const expectedAnswers = answers.ft[i];
|
||||
|
||||
if (userAnswer === '') {
|
||||
details.push(`Q${i}: ❌ Pas de réponse`);
|
||||
} else if (expectedAnswers.includes(userAnswer)) {
|
||||
correct++;
|
||||
details.push(`Q${i}: ✓ Correct`);
|
||||
} else {
|
||||
details.push(`Q${i}: ❌ Incorrect (réponse: ${expectedAnswers[0]})`);
|
||||
}
|
||||
}
|
||||
|
||||
const resultDiv = document.getElementById('ft-result');
|
||||
const percentage = Math.round((correct / total) * 100);
|
||||
|
||||
let message = '';
|
||||
if (percentage === 100) {
|
||||
resultDiv.className = 'result-message success';
|
||||
message = `<strong>🎉 Excellent !</strong> Vous avez ${correct}/${total} bonnes réponses (${percentage}%)`;
|
||||
} else if (percentage >= 80) {
|
||||
resultDiv.className = 'result-message success';
|
||||
message = `<strong>✓ Très bien !</strong> Vous avez ${correct}/${total} bonnes réponses (${percentage}%)`;
|
||||
} else if (percentage >= 60) {
|
||||
resultDiv.className = 'result-message error';
|
||||
message = `<strong>⚠️ À revoir</strong> Vous avez ${correct}/${total} bonnes réponses (${percentage}%)`;
|
||||
} else {
|
||||
resultDiv.className = 'result-message error';
|
||||
message = `<strong>❌ Insuffisant</strong> Vous avez ${correct}/${total} bonnes réponses (${percentage}%)`;
|
||||
}
|
||||
|
||||
resultDiv.innerHTML = message + '<div class="answer-detail"><strong>Détails :</strong><br>' + details.join('<br>') + '</div>';
|
||||
}
|
||||
|
||||
function verifyEtiquette() {
|
||||
let correct = 0;
|
||||
let total = 10;
|
||||
let details = [];
|
||||
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
const select = document.getElementById('eti' + i);
|
||||
const userAnswer = select.value;
|
||||
const expectedAnswers = answers.etiquette[i];
|
||||
|
||||
if (userAnswer === '') {
|
||||
details.push(`Q${i}: ❌ Pas de réponse`);
|
||||
} else if (expectedAnswers.includes(userAnswer)) {
|
||||
correct++;
|
||||
details.push(`Q${i}: ✓ Correct`);
|
||||
} else {
|
||||
details.push(`Q${i}: ❌ Incorrect (réponse: ${expectedAnswers[0]})`);
|
||||
}
|
||||
}
|
||||
|
||||
const resultDiv = document.getElementById('etiquette-result');
|
||||
const percentage = Math.round((correct / total) * 100);
|
||||
|
||||
let message = '';
|
||||
if (percentage === 100) {
|
||||
resultDiv.className = 'result-message success';
|
||||
message = `<strong>🎉 Excellent !</strong> Vous avez ${correct}/${total} bonnes réponses (${percentage}%)`;
|
||||
} else if (percentage >= 80) {
|
||||
resultDiv.className = 'result-message success';
|
||||
message = `<strong>✓ Très bien !</strong> Vous avez ${correct}/${total} bonnes réponses (${percentage}%)`;
|
||||
} else if (percentage >= 60) {
|
||||
resultDiv.className = 'result-message error';
|
||||
message = `<strong>⚠️ À revoir</strong> Vous avez ${correct}/${total} bonnes réponses (${percentage}%)`;
|
||||
} else {
|
||||
resultDiv.className = 'result-message error';
|
||||
message = `<strong>❌ Insuffisant</strong> Vous avez ${correct}/${total} bonnes réponses (${percentage}%)`;
|
||||
}
|
||||
|
||||
resultDiv.innerHTML = message + '<div class="answer-detail"><strong>Détails :</strong><br>' + details.join('<br>') + '</div>';
|
||||
}
|
||||
|
||||
function switchTab(tabName) {
|
||||
const contents = document.querySelectorAll('.tab-content');
|
||||
contents.forEach(content => {
|
||||
content.classList.remove('active');
|
||||
});
|
||||
|
||||
const buttons = document.querySelectorAll('.tab-button');
|
||||
buttons.forEach(button => {
|
||||
button.classList.remove('active');
|
||||
});
|
||||
|
||||
document.getElementById(tabName).classList.add('active');
|
||||
event.target.classList.add('active');
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
const scrollTop = window.scrollY;
|
||||
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
|
||||
const scrollPercent = (scrollTop / docHeight) * 100;
|
||||
document.getElementById('progressBar').style.width = scrollPercent + '%';
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
generateFDSQuestions();
|
||||
const sections = document.querySelectorAll('.section');
|
||||
sections.forEach((section, index) => {
|
||||
section.style.animationDelay = (index * 0.1) + 's';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -1,977 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>TD FDS, FT et Étiquettes</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="../header-style.css"/>
|
||||
<style>
|
||||
body {
|
||||
padding-top: 70px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
background: var(--white);
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, var(--blue-light) 0%, rgba(24, 95, 165, 0.05) 100%);
|
||||
color: var(--text);
|
||||
padding: 50px 40px;
|
||||
text-align: center;
|
||||
border-bottom: 3px solid var(--blue);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: 2.2em;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.5px;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
font-size: 1em;
|
||||
opacity: 0.8;
|
||||
font-weight: 400;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 50px 40px;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 40px;
|
||||
animation: fadeIn 0.6s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: 'Syne', sans-serif;
|
||||
color: var(--text);
|
||||
font-size: 1.6em;
|
||||
margin-bottom: 15px;
|
||||
border-bottom: 3px solid var(--blue);
|
||||
padding-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
h2::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--blue);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: var(--text);
|
||||
font-size: 1.15em;
|
||||
margin: 25px 0 15px 0;
|
||||
font-weight: 700;
|
||||
font-family: 'Syne', sans-serif;
|
||||
}
|
||||
|
||||
.intro {
|
||||
background: var(--coral-light);
|
||||
padding: 20px;
|
||||
border-left: 4px solid var(--coral-mid);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 0.95em;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.intro strong {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.tab-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 2px solid var(--border);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
font-family: 'Syne', sans-serif;
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 12px 20px;
|
||||
font-size: 0.95em;
|
||||
font-weight: 700;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.tab-button:hover {
|
||||
color: var(--coral-mid);
|
||||
}
|
||||
|
||||
.tab-button.active {
|
||||
color: var(--coral-mid);
|
||||
border-bottom: 3px solid var(--coral-mid);
|
||||
padding-bottom: 9px;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.answer-row {
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.answer-row:hover {
|
||||
border-color: var(--coral-mid);
|
||||
box-shadow: 0 8px 16px rgba(216, 90, 48, 0.1);
|
||||
}
|
||||
|
||||
.question-num {
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: 0.9em;
|
||||
font-weight: 700;
|
||||
color: var(--coral-mid);
|
||||
min-width: 35px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.question-text {
|
||||
flex-grow: 1;
|
||||
font-size: 0.95em;
|
||||
color: var(--text);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
select {
|
||||
font-family: 'DM Sans', sans-serif;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
background: var(--white);
|
||||
color: var(--text);
|
||||
font-size: 0.9em;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
select:focus {
|
||||
outline: none;
|
||||
border-color: var(--coral-mid);
|
||||
box-shadow: 0 0 0 3px rgba(216, 90, 48, 0.1);
|
||||
}
|
||||
|
||||
.verify-btn {
|
||||
font-family: 'Syne', sans-serif;
|
||||
background: var(--coral-mid);
|
||||
color: var(--white);
|
||||
border: none;
|
||||
padding: 12px 30px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.05em;
|
||||
transition: all 0.3s ease;
|
||||
margin-top: 20px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.verify-btn:hover {
|
||||
background: var(--coral);
|
||||
box-shadow: 0 8px 16px rgba(216, 90, 48, 0.25);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.result-message {
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
font-weight: 700;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.result-message.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.result-message.success {
|
||||
background: var(--teal-light);
|
||||
border: 2px solid var(--teal-mid);
|
||||
color: var(--teal-dark);
|
||||
}
|
||||
|
||||
.result-message.error {
|
||||
background: var(--coral-light);
|
||||
border: 2px solid var(--coral-mid);
|
||||
color: var(--coral);
|
||||
}
|
||||
|
||||
.comparison-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
background: var(--white);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.comparison-table th {
|
||||
background: var(--coral-light);
|
||||
color: var(--coral);
|
||||
padding: 15px;
|
||||
text-align: left;
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-weight: 700;
|
||||
border-bottom: 2px solid var(--coral-mid);
|
||||
}
|
||||
|
||||
.comparison-table td {
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
color: var(--text-muted);
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
.comparison-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.comparison-table tr:hover {
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
.comparison-table strong {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
footer {
|
||||
background: var(--bg);
|
||||
padding: 25px;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.progress {
|
||||
height: 3px;
|
||||
background: var(--border);
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
top: 70px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: var(--coral-mid);
|
||||
width: 0%;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.page-header h1 {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.answer-row {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- HEADER NAVIGATION -->
|
||||
<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="container">
|
||||
<div class="page-header">
|
||||
<h1>📋 TD — ANALYSE FDS, FT ET ÉTIQUETTES</h1>
|
||||
<p>Maîtriser les documents essentiels des produits biocides</p>
|
||||
</div>
|
||||
|
||||
<div class="progress">
|
||||
<div class="progress-bar" id="progressBar"></div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- TABS NAVIGATION -->
|
||||
<div class="tab-container">
|
||||
<button class="tab-button active" onclick="switchTab('fds')">📄 FDS (Fiche Sécurité)</button>
|
||||
<button class="tab-button" onclick="switchTab('ft')">📋 FT (Fiche Technique)</button>
|
||||
<button class="tab-button" onclick="switchTab('etiquette')">🏷️ Étiquetage</button>
|
||||
</div>
|
||||
|
||||
<!-- ====== TAB FDS ====== -->
|
||||
<div id="fds" class="tab-content active">
|
||||
<div class="section">
|
||||
<h2>Partie 1 : Fiche de Données de Sécurité (FDS)</h2>
|
||||
<div class="intro">
|
||||
<strong>Durée : 10 minutes</strong>
|
||||
La FDS est le document de référence pour connaître les dangers, les EPI, les premiers secours et l'élimination des produits biocides. Elle contient 16 rubriques standards.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>Questions d'analyse FDS</h3>
|
||||
<p style="margin-bottom: 20px; color: #555;">Pour chaque question, sélectionnez la rubrique FDS où trouver la réponse (Rubrique 1 à 16 ou « Absent »).</p>
|
||||
|
||||
<div id="fds-questions">
|
||||
<!-- Les questions seront générées et mélangées par JavaScript -->
|
||||
</div>
|
||||
|
||||
<button class="verify-btn" onclick="verifyFDS()">✓ Vérifier les réponses</button>
|
||||
<div id="fds-result" class="result-message"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ====== TAB FT ====== -->
|
||||
<div id="ft" class="tab-content">
|
||||
<div class="section">
|
||||
<h2>Partie 2 : Fiche Technique (FT)</h2>
|
||||
<div class="intro">
|
||||
<strong>Durée : 10 minutes</strong>
|
||||
La FT complète la FDS en fournissant le mode d'emploi, les utilisations, les conditionnements et les compatibilités matières. C'est le document pratique de l'utilisateur.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>Questions d'analyse FT</h3>
|
||||
<p style="margin-bottom: 20px; color: #555;">Pour chaque question, sélectionnez la section FT où trouver la réponse (Description, Propriétés biocides, Conditionnements, Utilisations ou « Absent »).</p>
|
||||
|
||||
<div id="ft-questions">
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q1</div>
|
||||
<div class="question-text">Quel est le nom du produit et qui est le fabricant ?</div>
|
||||
<select id="ft1" data-answer="Description">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Description">Description</option>
|
||||
<option value="Propriétés biocides">Propriétés biocides</option>
|
||||
<option value="Conditionnements">Conditionnements</option>
|
||||
<option value="Utilisations">Utilisations</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q2</div>
|
||||
<div class="question-text">Sur quelles matières ce produit est-il compatible ?</div>
|
||||
<select id="ft2" data-answer="Description">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Description">Description</option>
|
||||
<option value="Propriétés biocides">Propriétés biocides</option>
|
||||
<option value="Conditionnements">Conditionnements</option>
|
||||
<option value="Utilisations">Utilisations</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q3</div>
|
||||
<div class="question-text">Quel est (sont) le(s) type(s) de ce produit ?</div>
|
||||
<select id="ft3" data-answer="Propriétés biocides">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Description">Description</option>
|
||||
<option value="Propriétés biocides">Propriétés biocides</option>
|
||||
<option value="Conditionnements">Conditionnements</option>
|
||||
<option value="Utilisations">Utilisations</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q4</div>
|
||||
<div class="question-text">Quels sont les types de conditionnements disponibles ?</div>
|
||||
<select id="ft4" data-answer="Conditionnements">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Description">Description</option>
|
||||
<option value="Propriétés biocides">Propriétés biocides</option>
|
||||
<option value="Conditionnements">Conditionnements</option>
|
||||
<option value="Utilisations">Utilisations</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q5</div>
|
||||
<div class="question-text">Où retrouver les différents modes d'utilisation de ce produit ?</div>
|
||||
<select id="ft5" data-answer="Utilisations">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Description">Description</option>
|
||||
<option value="Propriétés biocides">Propriétés biocides</option>
|
||||
<option value="Conditionnements">Conditionnements</option>
|
||||
<option value="Utilisations">Utilisations</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="verify-btn" onclick="verifyFT()">✓ Vérifier les réponses</button>
|
||||
<div id="ft-result" class="result-message"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ====== TAB ÉTIQUETAGE ====== -->
|
||||
<div id="etiquette" class="tab-content">
|
||||
<div class="section">
|
||||
<h2>Partie 3 : Étiquetage</h2>
|
||||
<div class="intro">
|
||||
<strong>Durée : 10 minutes</strong>
|
||||
L'étiquette est l'interface directe entre le produit et l'utilisateur. Elle communique rapidement les dangers essentiels, les EPI, et les consignes d'utilisation.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>Questions d'analyse d'étiquette</h3>
|
||||
<p style="margin-bottom: 20px; color: #555;">Pour chaque question, sélectionnez la zone de l'étiquette où trouver l'information (Identité, Pictogrammes, Mots d'avertissement, Mentions de danger, Conseils de prudence, Contenu net, Numéro d'urgence ou « Absent »).</p>
|
||||
|
||||
<div id="etiquette-questions">
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q1</div>
|
||||
<div class="question-text">Quel est le nom du produit et le nom de l'entreprise ?</div>
|
||||
<select id="eti1" data-answer="Identité">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Identité">Identité</option>
|
||||
<option value="Pictogrammes">Pictogrammes</option>
|
||||
<option value="Mots d'avertissement">Mots d'avertissement</option>
|
||||
<option value="Mentions de danger">Mentions de danger</option>
|
||||
<option value="Conseils de prudence">Conseils de prudence</option>
|
||||
<option value="Contenu net">Contenu net</option>
|
||||
<option value="Numéro d'urgence">Numéro d'urgence</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q2</div>
|
||||
<div class="question-text">Quels pictogrammes de danger sont affichés ?</div>
|
||||
<select id="eti2" data-answer="Pictogrammes">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Identité">Identité</option>
|
||||
<option value="Pictogrammes">Pictogrammes</option>
|
||||
<option value="Mots d'avertissement">Mots d'avertissement</option>
|
||||
<option value="Mentions de danger">Mentions de danger</option>
|
||||
<option value="Conseils de prudence">Conseils de prudence</option>
|
||||
<option value="Contenu net">Contenu net</option>
|
||||
<option value="Numéro d'urgence">Numéro d'urgence</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q3</div>
|
||||
<div class="question-text">Quels sont les mots d'avertissement (« Attention » ou « Danger ») ?</div>
|
||||
<select id="eti3" data-answer="Mots d'avertissement">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Identité">Identité</option>
|
||||
<option value="Pictogrammes">Pictogrammes</option>
|
||||
<option value="Mots d'avertissement">Mots d'avertissement</option>
|
||||
<option value="Mentions de danger">Mentions de danger</option>
|
||||
<option value="Conseils de prudence">Conseils de prudence</option>
|
||||
<option value="Contenu net">Contenu net</option>
|
||||
<option value="Numéro d'urgence">Numéro d'urgence</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q4</div>
|
||||
<div class="question-text">Quelles sont les mentions de danger (H-statements) identifiées ?</div>
|
||||
<select id="eti4" data-answer="Mentions de danger">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Identité">Identité</option>
|
||||
<option value="Pictogrammes">Pictogrammes</option>
|
||||
<option value="Mots d'avertissement">Mots d'avertissement</option>
|
||||
<option value="Mentions de danger">Mentions de danger</option>
|
||||
<option value="Conseils de prudence">Conseils de prudence</option>
|
||||
<option value="Contenu net">Contenu net</option>
|
||||
<option value="Numéro d'urgence">Numéro d'urgence</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q5</div>
|
||||
<div class="question-text">Quels équipements de protection individuelle (EPI) sont recommandés ?</div>
|
||||
<select id="eti5" data-answer="Conseils de prudence">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Identité">Identité</option>
|
||||
<option value="Pictogrammes">Pictogrammes</option>
|
||||
<option value="Mots d'avertissement">Mots d'avertissement</option>
|
||||
<option value="Mentions de danger">Mentions de danger</option>
|
||||
<option value="Conseils de prudence">Conseils de prudence</option>
|
||||
<option value="Contenu net">Contenu net</option>
|
||||
<option value="Numéro d'urgence">Numéro d'urgence</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q6</div>
|
||||
<div class="question-text">Quels premiers secours sont indiqués ?</div>
|
||||
<select id="eti6" data-answer="Conseils de prudence">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Identité">Identité</option>
|
||||
<option value="Pictogrammes">Pictogrammes</option>
|
||||
<option value="Mots d'avertissement">Mots d'avertissement</option>
|
||||
<option value="Mentions de danger">Mentions de danger</option>
|
||||
<option value="Conseils de prudence">Conseils de prudence</option>
|
||||
<option value="Contenu net">Contenu net</option>
|
||||
<option value="Numéro d'urgence">Numéro d'urgence</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q7</div>
|
||||
<div class="question-text">Comment éliminer l'emballage après utilisation ?</div>
|
||||
<select id="eti7" data-answer="Conseils de prudence">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Identité">Identité</option>
|
||||
<option value="Pictogrammes">Pictogrammes</option>
|
||||
<option value="Mots d'avertissement">Mots d'avertissement</option>
|
||||
<option value="Mentions de danger">Mentions de danger</option>
|
||||
<option value="Conseils de prudence">Conseils de prudence</option>
|
||||
<option value="Contenu net">Contenu net</option>
|
||||
<option value="Numéro d'urgence">Numéro d'urgence</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q8</div>
|
||||
<div class="question-text">Quel est le contenu net du produit ?</div>
|
||||
<select id="eti8" data-answer="Contenu net">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Identité">Identité</option>
|
||||
<option value="Pictogrammes">Pictogrammes</option>
|
||||
<option value="Mots d'avertissement">Mots d'avertissement</option>
|
||||
<option value="Mentions de danger">Mentions de danger</option>
|
||||
<option value="Conseils de prudence">Conseils de prudence</option>
|
||||
<option value="Contenu net">Contenu net</option>
|
||||
<option value="Numéro d'urgence">Numéro d'urgence</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q9</div>
|
||||
<div class="question-text">Y a-t-il un numéro d'appel d'urgence mentionné ?</div>
|
||||
<select id="eti9" data-answer="Numéro d'urgence">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Identité">Identité</option>
|
||||
<option value="Pictogrammes">Pictogrammes</option>
|
||||
<option value="Mots d'avertissement">Mots d'avertissement</option>
|
||||
<option value="Mentions de danger">Mentions de danger</option>
|
||||
<option value="Conseils de prudence">Conseils de prudence</option>
|
||||
<option value="Contenu net">Contenu net</option>
|
||||
<option value="Numéro d'urgence">Numéro d'urgence</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q10</div>
|
||||
<div class="question-text">Quelles consignes de stockage sont affichées ?</div>
|
||||
<select id="eti10" data-answer="Conseils de prudence">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Identité">Identité</option>
|
||||
<option value="Pictogrammes">Pictogrammes</option>
|
||||
<option value="Mots d'avertissement">Mots d'avertissement</option>
|
||||
<option value="Mentions de danger">Mentions de danger</option>
|
||||
<option value="Conseils de prudence">Conseils de prudence</option>
|
||||
<option value="Contenu net">Contenu net</option>
|
||||
<option value="Numéro d'urgence">Numéro d'urgence</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="verify-btn" onclick="verifyEtiquette()">✓ Vérifier les réponses</button>
|
||||
<div id="etiquette-result" class="result-message"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ====== SYNTHÈSE COMPARATIVE ====== -->
|
||||
<div class="section">
|
||||
<h2>Synthèse Comparative</h2>
|
||||
<table class="comparison-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Document</th>
|
||||
<th>Utilité Principale</th>
|
||||
<th>Moment de Consultation</th>
|
||||
<th>Public</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>FDS</strong></td>
|
||||
<td>Sécurité, dangers, EPI, premiers secours</td>
|
||||
<td>AVANT manipulation</td>
|
||||
<td>Professionnels</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>FT</strong></td>
|
||||
<td>Mode d'emploi, applications, conditionnements</td>
|
||||
<td>Pour l'utilisation</td>
|
||||
<td>Utilisateurs finaux</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Étiquette</strong></td>
|
||||
<td>Dangers essentiels, interface rapide</td>
|
||||
<td>À chaque prise du produit</td>
|
||||
<td>Tous les utilisateurs</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- CLÉS DE SUCCÈS -->
|
||||
<div class="section">
|
||||
<div class="tips">
|
||||
<h3>💡 Clés de Succès</h3>
|
||||
<ul>
|
||||
<li><strong>FDS = Sécurité et dangers</strong> → À consulter AVANT manipulation</li>
|
||||
<li><strong>FT = Mode d'emploi et applications</strong> → À consulter pour l'utilisation</li>
|
||||
<li><strong>Étiquette = Interface rapide</strong> → Lecture obligatoire avant ouverture du produit</li>
|
||||
<li>Les trois documents sont <strong>complémentaires</strong>, jamais redondants</li>
|
||||
<li>Chaque document cible un besoin différent et un public différent</li>
|
||||
<li>Un professionnel doit maîtriser les trois pour une utilisation sûre et efficace</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p><strong>Objectif pédagogique :</strong> Acquérir une autonomie dans la lecture et l'interprétation des documents réglementaires</p>
|
||||
<p style="margin-top: 15px; font-size: 0.95em;">Ces documents sont obligatoires pour tout produit biocide mis sur le marché en France.</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Questions FDS avec réponses
|
||||
const fdsQuestions = [
|
||||
{ id: 1, text: "Quel est le nom du produit et qui est le fabricant ?", answer: "1" },
|
||||
{ id: 2, text: "Quels sont les dangers du produit ?", answer: "2" },
|
||||
{ id: 3, text: "Quelles sont les molécules actives et leurs concentrations ?", answer: "3" },
|
||||
{ id: 4, text: "Quels gestes adopter en premiers secours ?", answer: "4" },
|
||||
{ id: 5, text: "En cas d'incendie, quels sont les moyens d'extinction conseillés ?", answer: "5" },
|
||||
{ id: 6, text: "Que faire en cas de dispersion accidentelle ?", answer: "6" },
|
||||
{ id: 7, text: "Dans quelles conditions stocker mon produit ?", answer: "7" },
|
||||
{ id: 8, text: "Quels équipements de protection individuelle (EPI) dois-je porter ?", answer: "8" },
|
||||
{ id: 9, text: "Quel est le pH de ce produit ?", answer: "9" },
|
||||
{ id: 10, text: "Le produit est-il stable dans des conditions normales ?", answer: "10" },
|
||||
{ id: 11, text: "Quels sont les effets potentiels sur la santé ?", answer: "11" },
|
||||
{ id: 12, text: "Quel est le degré de toxicité pour les poissons ? Potentiel de bioaccumulation ?", answer: "12" },
|
||||
{ id: 13, text: "Comment éliminer le produit ?", answer: "13" },
|
||||
{ id: 14, text: "Est-ce que mon produit est classé dangereux pour le transport ?", answer: "14" },
|
||||
{ id: 15, text: "En cas d'ingestion du produit, que faire ?", answer: "4,11" },
|
||||
{ id: 16, text: "Où trouver le mode d'emploi du produit ?", answer: "absent" }
|
||||
];
|
||||
|
||||
// Mélanger aléatoirement les questions
|
||||
function shuffleArray(array) {
|
||||
const newArray = [...array];
|
||||
for (let i = newArray.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[newArray[i], newArray[j]] = [newArray[j], newArray[i]];
|
||||
}
|
||||
return newArray;
|
||||
}
|
||||
|
||||
// Générer les questions FDS mélangées
|
||||
function generateFDSQuestions() {
|
||||
const shuffled = shuffleArray(fdsQuestions);
|
||||
const container = document.getElementById('fds-questions');
|
||||
container.innerHTML = '';
|
||||
|
||||
shuffled.forEach((question, index) => {
|
||||
const qNum = index + 1;
|
||||
const selectId = 'fds' + question.id;
|
||||
const answerAttr = question.answer;
|
||||
|
||||
const html = `
|
||||
<div class="answer-row">
|
||||
<div class="question-num">Q${qNum}</div>
|
||||
<div class="question-text">${question.text}</div>
|
||||
<select id="${selectId}" data-answer="${answerAttr}" data-qnum="${qNum}">
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="1">Rubrique 1</option>
|
||||
<option value="2">Rubrique 2</option>
|
||||
<option value="3">Rubrique 3</option>
|
||||
<option value="4">Rubrique 4</option>
|
||||
<option value="5">Rubrique 5</option>
|
||||
<option value="6">Rubrique 6</option>
|
||||
<option value="7">Rubrique 7</option>
|
||||
<option value="8">Rubrique 8</option>
|
||||
<option value="9">Rubrique 9</option>
|
||||
<option value="10">Rubrique 10</option>
|
||||
<option value="11">Rubrique 11</option>
|
||||
<option value="12">Rubrique 12</option>
|
||||
<option value="13">Rubrique 13</option>
|
||||
<option value="14">Rubrique 14</option>
|
||||
<option value="15">Rubrique 15</option>
|
||||
<option value="16">Rubrique 16</option>
|
||||
<option value="absent">Absent</option>
|
||||
</select>
|
||||
</div>
|
||||
`;
|
||||
container.innerHTML += html;
|
||||
});
|
||||
}
|
||||
|
||||
// Answers mapping
|
||||
const answers = {
|
||||
ft: {
|
||||
1: ['Description'],
|
||||
2: ['Description'],
|
||||
3: ['Propriétés biocides'],
|
||||
4: ['Conditionnements'],
|
||||
5: ['Utilisations']
|
||||
},
|
||||
etiquette: {
|
||||
1: ['Identité'],
|
||||
2: ['Pictogrammes'],
|
||||
3: ['Mots d\'avertissement'],
|
||||
4: ['Mentions de danger'],
|
||||
5: ['Conseils de prudence'],
|
||||
6: ['Conseils de prudence'],
|
||||
7: ['Conseils de prudence'],
|
||||
8: ['Contenu net'],
|
||||
9: ['Numéro d\'urgence'],
|
||||
10: ['Conseils de prudence']
|
||||
}
|
||||
};
|
||||
|
||||
function verifyFDS() {
|
||||
let correct = 0;
|
||||
let total = 16;
|
||||
let details = [];
|
||||
|
||||
fdsQuestions.forEach((question) => {
|
||||
const select = document.getElementById('fds' + question.id);
|
||||
const qNum = select.getAttribute('data-qnum');
|
||||
const userAnswer = select.value;
|
||||
const expectedAnswers = question.answer.split(',');
|
||||
|
||||
if (userAnswer === '') {
|
||||
details.push(`Q${qNum}: ❌ Pas de réponse`);
|
||||
} else if (expectedAnswers.includes(userAnswer)) {
|
||||
correct++;
|
||||
details.push(`Q${qNum}: ✓ Correct`);
|
||||
} else {
|
||||
const expected = expectedAnswers.length > 1 ?
|
||||
'Rubriques ' + expectedAnswers.join(' et ') :
|
||||
(expectedAnswers[0] === 'absent' ? 'Absent' : 'Rubrique ' + expectedAnswers[0]);
|
||||
details.push(`Q${qNum}: ❌ Incorrect (réponse: ${expected})`);
|
||||
}
|
||||
});
|
||||
|
||||
const resultDiv = document.getElementById('fds-result');
|
||||
const percentage = Math.round((correct / total) * 100);
|
||||
|
||||
let message = '';
|
||||
if (percentage === 100) {
|
||||
resultDiv.className = 'result-message success';
|
||||
message = `<strong>🎉 Excellent !</strong> Vous avez ${correct}/${total} bonnes réponses (${percentage}%)`;
|
||||
} else if (percentage >= 80) {
|
||||
resultDiv.className = 'result-message success';
|
||||
message = `<strong>✓ Très bien !</strong> Vous avez ${correct}/${total} bonnes réponses (${percentage}%)`;
|
||||
} else if (percentage >= 60) {
|
||||
resultDiv.className = 'result-message error';
|
||||
message = `<strong>⚠️ À revoir</strong> Vous avez ${correct}/${total} bonnes réponses (${percentage}%)`;
|
||||
} else {
|
||||
resultDiv.className = 'result-message error';
|
||||
message = `<strong>❌ Insuffisant</strong> Vous avez ${correct}/${total} bonnes réponses (${percentage}%)`;
|
||||
}
|
||||
|
||||
resultDiv.innerHTML = message + '<div class="answer-detail"><strong>Détails :</strong><br>' + details.join('<br>') + '</div>';
|
||||
}
|
||||
|
||||
function verifyFT() {
|
||||
let correct = 0;
|
||||
let total = 5;
|
||||
let details = [];
|
||||
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const select = document.getElementById('ft' + i);
|
||||
const userAnswer = select.value;
|
||||
const expectedAnswers = answers.ft[i];
|
||||
|
||||
if (userAnswer === '') {
|
||||
details.push(`Q${i}: ❌ Pas de réponse`);
|
||||
} else if (expectedAnswers.includes(userAnswer)) {
|
||||
correct++;
|
||||
details.push(`Q${i}: ✓ Correct`);
|
||||
} else {
|
||||
details.push(`Q${i}: ❌ Incorrect (réponse: ${expectedAnswers[0]})`);
|
||||
}
|
||||
}
|
||||
|
||||
const resultDiv = document.getElementById('ft-result');
|
||||
const percentage = Math.round((correct / total) * 100);
|
||||
|
||||
let message = '';
|
||||
if (percentage === 100) {
|
||||
resultDiv.className = 'result-message success';
|
||||
message = `<strong>🎉 Excellent !</strong> Vous avez ${correct}/${total} bonnes réponses (${percentage}%)`;
|
||||
} else if (percentage >= 80) {
|
||||
resultDiv.className = 'result-message success';
|
||||
message = `<strong>✓ Très bien !</strong> Vous avez ${correct}/${total} bonnes réponses (${percentage}%)`;
|
||||
} else if (percentage >= 60) {
|
||||
resultDiv.className = 'result-message error';
|
||||
message = `<strong>⚠️ À revoir</strong> Vous avez ${correct}/${total} bonnes réponses (${percentage}%)`;
|
||||
} else {
|
||||
resultDiv.className = 'result-message error';
|
||||
message = `<strong>❌ Insuffisant</strong> Vous avez ${correct}/${total} bonnes réponses (${percentage}%)`;
|
||||
}
|
||||
|
||||
resultDiv.innerHTML = message + '<div class="answer-detail"><strong>Détails :</strong><br>' + details.join('<br>') + '</div>';
|
||||
}
|
||||
|
||||
function verifyEtiquette() {
|
||||
let correct = 0;
|
||||
let total = 10;
|
||||
let details = [];
|
||||
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
const select = document.getElementById('eti' + i);
|
||||
const userAnswer = select.value;
|
||||
const expectedAnswers = answers.etiquette[i];
|
||||
|
||||
if (userAnswer === '') {
|
||||
details.push(`Q${i}: ❌ Pas de réponse`);
|
||||
} else if (expectedAnswers.includes(userAnswer)) {
|
||||
correct++;
|
||||
details.push(`Q${i}: ✓ Correct`);
|
||||
} else {
|
||||
details.push(`Q${i}: ❌ Incorrect (réponse: ${expectedAnswers[0]})`);
|
||||
}
|
||||
}
|
||||
|
||||
const resultDiv = document.getElementById('etiquette-result');
|
||||
const percentage = Math.round((correct / total) * 100);
|
||||
|
||||
let message = '';
|
||||
if (percentage === 100) {
|
||||
resultDiv.className = 'result-message success';
|
||||
message = `<strong>🎉 Excellent !</strong> Vous avez ${correct}/${total} bonnes réponses (${percentage}%)`;
|
||||
} else if (percentage >= 80) {
|
||||
resultDiv.className = 'result-message success';
|
||||
message = `<strong>✓ Très bien !</strong> Vous avez ${correct}/${total} bonnes réponses (${percentage}%)`;
|
||||
} else if (percentage >= 60) {
|
||||
resultDiv.className = 'result-message error';
|
||||
message = `<strong>⚠️ À revoir</strong> Vous avez ${correct}/${total} bonnes réponses (${percentage}%)`;
|
||||
} else {
|
||||
resultDiv.className = 'result-message error';
|
||||
message = `<strong>❌ Insuffisant</strong> Vous avez ${correct}/${total} bonnes réponses (${percentage}%)`;
|
||||
}
|
||||
|
||||
resultDiv.innerHTML = message + '<div class="answer-detail"><strong>Détails :</strong><br>' + details.join('<br>') + '</div>';
|
||||
}
|
||||
|
||||
function switchTab(tabName) {
|
||||
const contents = document.querySelectorAll('.tab-content');
|
||||
contents.forEach(content => {
|
||||
content.classList.remove('active');
|
||||
});
|
||||
|
||||
const buttons = document.querySelectorAll('.tab-button');
|
||||
buttons.forEach(button => {
|
||||
button.classList.remove('active');
|
||||
});
|
||||
|
||||
document.getElementById(tabName).classList.add('active');
|
||||
event.target.classList.add('active');
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
const scrollTop = window.scrollY;
|
||||
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
|
||||
const scrollPercent = (scrollTop / docHeight) * 100;
|
||||
document.getElementById('progressBar').style.width = scrollPercent + '%';
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
generateFDSQuestions();
|
||||
const sections = document.querySelectorAll('.section');
|
||||
sections.forEach((section, index) => {
|
||||
section.style.animationDelay = (index * 0.1) + 's';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -124,12 +124,12 @@
|
||||
}
|
||||
|
||||
.tab-button:hover {
|
||||
color: var(--coral-mid);
|
||||
color: var(--amber);
|
||||
}
|
||||
|
||||
.tab-button.active {
|
||||
color: var(--coral-mid);
|
||||
border-bottom: 3px solid var(--coral-mid);
|
||||
color: var(--amber);
|
||||
border-bottom: 3px solid var(--amber);
|
||||
padding-bottom: 9px;
|
||||
}
|
||||
|
||||
@@ -154,18 +154,18 @@
|
||||
}
|
||||
|
||||
.answer-row:hover {
|
||||
border-color: var(--coral-mid);
|
||||
box-shadow: 0 8px 16px rgba(216, 90, 48, 0.1);
|
||||
border-color: var(--amber);
|
||||
box-shadow: 0 8px 16px rgba(186, 117, 23, 0.1);
|
||||
}
|
||||
|
||||
.answer-label {
|
||||
font-size: 0.82em;
|
||||
font-weight: 700;
|
||||
color: var(--coral);
|
||||
color: var(--amber-dark);
|
||||
white-space: nowrap;
|
||||
padding: 4px 10px;
|
||||
background: var(--coral-light);
|
||||
border: 1px solid var(--coral-mid);
|
||||
background: var(--amber-light);
|
||||
border: 1px solid var(--amber);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: 0.9em;
|
||||
font-weight: 700;
|
||||
color: var(--coral-mid);
|
||||
color: var(--amber);
|
||||
min-width: 35px;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -205,17 +205,17 @@
|
||||
|
||||
select:focus {
|
||||
outline: none;
|
||||
border-color: var(--coral-mid);
|
||||
box-shadow: 0 0 0 3px rgba(216, 90, 48, 0.1);
|
||||
border-color: var(--amber);
|
||||
box-shadow: 0 0 0 3px rgba(186, 117, 23, 0.1);
|
||||
}
|
||||
|
||||
.verify-btn {
|
||||
font-family: 'Syne', sans-serif;
|
||||
background: var(--coral-mid);
|
||||
background: var(--amber);
|
||||
color: var(--white);
|
||||
border: none;
|
||||
padding: 12px 30px;
|
||||
border-radius: 6px;
|
||||
border-radius: 40px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
font-weight: 700;
|
||||
@@ -226,8 +226,8 @@
|
||||
}
|
||||
|
||||
.verify-btn:hover {
|
||||
background: var(--coral);
|
||||
box-shadow: 0 8px 16px rgba(216, 90, 48, 0.25);
|
||||
background: var(--amber-dark);
|
||||
box-shadow: 0 8px 16px rgba(186, 117, 23, 0.25);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
@@ -266,13 +266,13 @@
|
||||
}
|
||||
|
||||
.comparison-table th {
|
||||
background: var(--coral-light);
|
||||
color: var(--coral);
|
||||
background: var(--amber-light);
|
||||
color: var(--amber-dark);
|
||||
padding: 15px;
|
||||
text-align: left;
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-weight: 700;
|
||||
border-bottom: 2px solid var(--coral-mid);
|
||||
border-bottom: 2px solid var(--amber);
|
||||
}
|
||||
|
||||
.comparison-table td {
|
||||
|
||||
394
exercices/memory-pictogrammes/assets/css/animations.css
Normal file
@@ -0,0 +1,394 @@
|
||||
/* ============================================
|
||||
ANIMATIONS AVANCÉES - Memory Pictogrammes
|
||||
============================================ */
|
||||
|
||||
/* ============================================
|
||||
FADE ANIMATIONS
|
||||
============================================ */
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
CARD ANIMATIONS
|
||||
============================================ */
|
||||
|
||||
/* Match Success - Explosion colorée */
|
||||
@keyframes matchSuccess {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
filter: drop-shadow(0 0 0px rgba(76, 175, 80, 0.6));
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.15) rotate(5deg);
|
||||
filter: drop-shadow(0 0 12px rgba(76, 175, 80, 0.8));
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
filter: drop-shadow(0 0 0px rgba(76, 175, 80, 0));
|
||||
}
|
||||
}
|
||||
|
||||
/* Card Shake - Erreur */
|
||||
@keyframes cardShake {
|
||||
0%, 100% {
|
||||
transform: translateX(0) rotateZ(0deg);
|
||||
}
|
||||
10%, 30%, 50%, 70%, 90% {
|
||||
transform: translateX(-8px) rotateZ(-1deg);
|
||||
}
|
||||
20%, 40%, 60%, 80% {
|
||||
transform: translateX(8px) rotateZ(1deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Pop In - Apparition */
|
||||
@keyframes popIn {
|
||||
0% {
|
||||
transform: scale(0.5);
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Bounce - Rebond */
|
||||
@keyframes bounce {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-12px);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
PROGRESS BAR ANIMATIONS
|
||||
============================================ */
|
||||
|
||||
/* Pulse Progress Bar */
|
||||
@keyframes progressPulse {
|
||||
0%, 100% {
|
||||
box-shadow: 0 0 8px rgba(29, 158, 117, 0.4);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 24px rgba(29, 158, 117, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
.progress-bar.pulse {
|
||||
animation: progressPulse 0.6s ease-out;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
MODAL ANIMATIONS
|
||||
============================================ */
|
||||
|
||||
/* Slide Up */
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(40px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Fade In Modal Background */
|
||||
@keyframes fadeInBlur {
|
||||
from {
|
||||
opacity: 0;
|
||||
backdrop-filter: blur(0px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
FLOAT ANIMATION (Confettis)
|
||||
============================================ */
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-12px);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
SCORE UPDATE ANIMATION
|
||||
============================================ */
|
||||
@keyframes scoreFlip {
|
||||
0% {
|
||||
transform: rotateX(90deg);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: rotateX(0deg);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
ACHIEVEMENT UNLOCK ANIMATION
|
||||
============================================ */
|
||||
@keyframes achievementUnlock {
|
||||
0% {
|
||||
transform: scale(0) rotateZ(-180deg);
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.15);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1) rotateZ(0deg);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
STAT BOX ANIMATIONS
|
||||
============================================ */
|
||||
@keyframes statUpdate {
|
||||
0% {
|
||||
transform: scale(0.8);
|
||||
background: rgba(29, 158, 117, 0.2);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
TIMER ANIMATIONS
|
||||
============================================ */
|
||||
@keyframes timerTick {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Timer warning (< 30 seconds) */
|
||||
@keyframes timerWarning {
|
||||
0%, 100% {
|
||||
color: #FF9800;
|
||||
}
|
||||
50% {
|
||||
color: #D85A30;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
LEADERBOARD ANIMATIONS
|
||||
============================================ */
|
||||
|
||||
/* Rank item slide in */
|
||||
@keyframes rankSlideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.rank-item {
|
||||
animation: rankSlideIn 0.3s ease-out forwards;
|
||||
}
|
||||
|
||||
.rank-item:nth-child(1) { animation-delay: 0.1s; }
|
||||
.rank-item:nth-child(2) { animation-delay: 0.2s; }
|
||||
.rank-item:nth-child(3) { animation-delay: 0.3s; }
|
||||
.rank-item:nth-child(4) { animation-delay: 0.4s; }
|
||||
.rank-item:nth-child(5) { animation-delay: 0.5s; }
|
||||
|
||||
/* ============================================
|
||||
GLOW ANIMATIONS
|
||||
============================================ */
|
||||
|
||||
/* Card glow hover */
|
||||
@keyframes glowCard {
|
||||
0% {
|
||||
box-shadow: 0 0 0px rgba(29, 158, 117, 0);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 16px rgba(29, 158, 117, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
/* Victory confetti animation */
|
||||
@keyframes confetti {
|
||||
0% {
|
||||
transform: translateY(-200px) rotateZ(0deg) scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(600px) rotateZ(720deg) scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
PULSING ANIMATIONS
|
||||
============================================ */
|
||||
|
||||
/* Pulse button */
|
||||
@keyframes buttonPulse {
|
||||
0%, 100% {
|
||||
box-shadow: 0 0 0px rgba(29, 158, 117, 0.7);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 16px rgba(29, 158, 117, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
/* Loading spinner */
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
FLIP ANIMATION (3D)
|
||||
============================================ */
|
||||
|
||||
@keyframes flip {
|
||||
0% {
|
||||
transform: rotateY(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
UTILITY ANIMATION CLASSES
|
||||
============================================ */
|
||||
|
||||
.animate-fade-in {
|
||||
animation: fadeIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
.animate-slide-up {
|
||||
animation: slideUp 0.4s ease-out;
|
||||
}
|
||||
|
||||
.animate-pop {
|
||||
animation: popIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
.animate-bounce {
|
||||
animation: bounce 0.6s ease-in-out;
|
||||
}
|
||||
|
||||
.animate-shake {
|
||||
animation: cardShake 0.4s ease-in-out;
|
||||
}
|
||||
|
||||
.animate-float {
|
||||
animation: float 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
STAGGERED ANIMATIONS (pour listes)
|
||||
============================================ */
|
||||
|
||||
.stagger-children > * {
|
||||
animation: fadeInUp 0.6s ease-out forwards;
|
||||
}
|
||||
|
||||
.stagger-children > *:nth-child(1) { animation-delay: 0.1s; }
|
||||
.stagger-children > *:nth-child(2) { animation-delay: 0.2s; }
|
||||
.stagger-children > *:nth-child(3) { animation-delay: 0.3s; }
|
||||
.stagger-children > *:nth-child(4) { animation-delay: 0.4s; }
|
||||
.stagger-children > *:nth-child(5) { animation-delay: 0.5s; }
|
||||
.stagger-children > *:nth-child(6) { animation-delay: 0.6s; }
|
||||
|
||||
/* ============================================
|
||||
TRANSITIONS (mouvements fluides)
|
||||
============================================ */
|
||||
|
||||
button, a, .stat-box, .rank-item {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
DARK MODE ANIMATIONS (optionnel)
|
||||
============================================ */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
@keyframes fadeInDark {
|
||||
from {
|
||||
opacity: 0;
|
||||
filter: brightness(0.8);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
filter: brightness(1);
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
animation: fadeInDark 0.6s ease-out;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
MOTION PREFERENCE RESPECT
|
||||
============================================ */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
437
exercices/memory-pictogrammes/assets/css/hud.css
Normal file
@@ -0,0 +1,437 @@
|
||||
/* ============================================
|
||||
HUD (Heads-Up Display) & SCORING
|
||||
============================================ */
|
||||
|
||||
/* ============================================
|
||||
GAME HUD - Stat Boxes
|
||||
============================================ */
|
||||
.game-hud {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 32px;
|
||||
animation: fadeInUp 0.6s ease-out 0.4s both;
|
||||
}
|
||||
|
||||
.stat-box {
|
||||
background: linear-gradient(135deg, var(--teal-light), var(--white));
|
||||
border: 2px solid var(--teal-mid);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stat-box::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
|
||||
transition: left 0.5s ease;
|
||||
}
|
||||
|
||||
.stat-box:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
border-color: var(--teal-dark);
|
||||
}
|
||||
|
||||
.stat-box:hover::before {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
.stat-box .label {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-muted);
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-box .value {
|
||||
font-size: 32px;
|
||||
font-weight: 800;
|
||||
background: linear-gradient(135deg, var(--teal-mid), var(--teal-dark));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
font-family: 'Syne', sans-serif;
|
||||
animation: popIn 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
.stat-box .value.updated {
|
||||
animation: scoreFlip 0.4s ease-out;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
PROGRESS BAR (Paires trouvées)
|
||||
============================================ */
|
||||
.progress-container {
|
||||
background: var(--teal-light);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
height: 12px;
|
||||
margin-bottom: 24px;
|
||||
position: relative;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--teal-mid), var(--teal-dark));
|
||||
width: 0%;
|
||||
transition: width 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
.progress-container.pulse .progress-bar {
|
||||
animation: progressPulse 0.6s ease-out;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
GAME CONTROLS
|
||||
============================================ */
|
||||
.game-controls {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
justify-content: center;
|
||||
margin-top: 32px;
|
||||
animation: fadeInUp 0.6s ease-out 0.5s both;
|
||||
}
|
||||
|
||||
.btn {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
transform: translate(-50%, -50%);
|
||||
transition: width 0.5s, height 0.5s;
|
||||
}
|
||||
|
||||
.btn:active::before {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
VICTORY MODAL
|
||||
============================================ */
|
||||
.victory-modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2000;
|
||||
backdrop-filter: blur(5px);
|
||||
animation: fadeInBlur 0.3s ease;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.victory-modal.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: linear-gradient(135deg, var(--white), var(--teal-light));
|
||||
border-radius: 24px;
|
||||
padding: 48px;
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
animation: slideUp 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
border: 2px solid var(--teal-mid);
|
||||
box-shadow: 0 32px 64px rgba(15, 110, 86, 0.3);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-content::before {
|
||||
content: '🎉 🏆 ⭐ 🎮 🌟';
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: block;
|
||||
font-size: 32px;
|
||||
letter-spacing: 12px;
|
||||
animation: float 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.modal-content h2 {
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: 42px;
|
||||
font-weight: 800;
|
||||
color: var(--teal);
|
||||
margin: 32px 0 12px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.modal-content .score-display {
|
||||
font-size: 64px;
|
||||
font-weight: 800;
|
||||
background: linear-gradient(135deg, var(--teal-mid), var(--teal-dark));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
margin: 24px 0;
|
||||
font-family: 'Syne', sans-serif;
|
||||
animation: popIn 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) 0.3s both;
|
||||
}
|
||||
|
||||
.stats-summary {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
.stats-summary .stat {
|
||||
background: var(--teal-light);
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--teal-mid);
|
||||
animation: popIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
.stats-summary .stat:nth-child(1) { animation-delay: 0.4s; }
|
||||
.stats-summary .stat:nth-child(2) { animation-delay: 0.5s; }
|
||||
.stats-summary .stat:nth-child(3) { animation-delay: 0.6s; }
|
||||
.stats-summary .stat:nth-child(4) { animation-delay: 0.7s; }
|
||||
|
||||
.stats-summary .stat-value {
|
||||
font-weight: 700;
|
||||
color: var(--teal-dark);
|
||||
font-size: 16px;
|
||||
font-family: 'Syne', sans-serif;
|
||||
}
|
||||
|
||||
.stats-summary .stat-label {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
ACHIEVEMENTS
|
||||
============================================ */
|
||||
.achievements-list {
|
||||
margin: 24px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.achievement {
|
||||
background: linear-gradient(135deg, var(--accent), rgba(255, 215, 0, 0.2));
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
text-align: left;
|
||||
animation: achievementUnlock 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.achievement-icon {
|
||||
font-size: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.achievement-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.achievement-name {
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.achievement-desc {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
MODAL ACTIONS
|
||||
============================================ */
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-top: 32px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-actions .btn {
|
||||
flex: 1;
|
||||
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
|
||||
============================================ */
|
||||
@media (max-width: 768px) {
|
||||
.game-hud {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.stat-box {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.stat-box .value {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
padding: 32px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.modal-content h2 {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.modal-content .score-display {
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.stats-summary {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.modal-actions .btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.game-controls {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.game-controls .btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.game-hud {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.stat-box {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.stat-box .label {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.stat-box .value {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.modal-content::before {
|
||||
font-size: 24px;
|
||||
letter-spacing: 8px;
|
||||
}
|
||||
|
||||
.modal-content h2 {
|
||||
font-size: 24px;
|
||||
margin: 24px 0 8px;
|
||||
}
|
||||
|
||||
.modal-content .score-display {
|
||||
font-size: 40px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.stats-summary {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.stats-summary .stat {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.stats-summary .stat-value {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
442
exercices/memory-pictogrammes/assets/css/leaderboard.css
Normal file
@@ -0,0 +1,442 @@
|
||||
/* ============================================
|
||||
LEADERBOARD & PLAYER STATS
|
||||
============================================ */
|
||||
|
||||
.leaderboard-section {
|
||||
margin-top: 80px;
|
||||
animation: fadeInUp 0.6s ease-out 0.6s both;
|
||||
}
|
||||
|
||||
.leaderboard-section h2 {
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: clamp(24px, 5vw, 32px);
|
||||
font-weight: 800;
|
||||
color: var(--teal);
|
||||
margin-bottom: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
TABS (Filtre par difficulté)
|
||||
============================================ */
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
margin-bottom: 32px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
padding: 10px 20px;
|
||||
background: var(--white);
|
||||
border: 2px solid var(--teal-light);
|
||||
border-radius: 8px;
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.tab-btn:hover {
|
||||
border-color: var(--teal-mid);
|
||||
color: var(--teal-mid);
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
background: linear-gradient(135deg, var(--teal-mid), var(--teal-dark));
|
||||
border-color: var(--teal-dark);
|
||||
color: var(--white);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
LEADERBOARD LIST
|
||||
============================================ */
|
||||
.leaderboard {
|
||||
background: linear-gradient(135deg, var(--teal-light), var(--white));
|
||||
border-radius: 16px;
|
||||
padding: 24px;
|
||||
border: 2px solid var(--teal-mid);
|
||||
margin-bottom: 48px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.leaderboard-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.rank-item {
|
||||
display: grid;
|
||||
grid-template-columns: 50px 1fr auto auto;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
background: var(--white);
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
border-left: 4px solid var(--teal-light);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.rank-item::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(29, 158, 117, 0.1), transparent);
|
||||
transition: left 0.5s ease;
|
||||
}
|
||||
|
||||
.rank-item:hover {
|
||||
transform: translateX(8px);
|
||||
box-shadow: 0 4px 12px rgba(15, 110, 86, 0.15);
|
||||
border-left-color: var(--teal-mid);
|
||||
}
|
||||
|
||||
.rank-item:hover::before {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
/* Rang numéro ou médaille */
|
||||
.rank-number {
|
||||
font-weight: 800;
|
||||
font-size: 18px;
|
||||
color: var(--teal-mid);
|
||||
text-align: center;
|
||||
font-family: 'Syne', sans-serif;
|
||||
}
|
||||
|
||||
.rank-number.medal-1 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.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 */
|
||||
.rank-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.player-name {
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
margin-bottom: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.player-stats {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.player-stats span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
/* Score et difficulté */
|
||||
.rank-score {
|
||||
font-weight: 700;
|
||||
color: var(--teal-dark);
|
||||
font-size: 18px;
|
||||
font-family: 'Syne', sans-serif;
|
||||
min-width: 60px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.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 */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.empty-state-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-state-text {
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.empty-state-hint {
|
||||
font-size: 12px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
PLAYER STATS SECTION
|
||||
============================================ */
|
||||
.player-stats {
|
||||
background: linear-gradient(135deg, rgba(15, 110, 86, 0.05), transparent);
|
||||
border-radius: 16px;
|
||||
padding: 32px;
|
||||
border: 2px solid var(--teal-light);
|
||||
}
|
||||
|
||||
.player-stats h3 {
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--teal);
|
||||
margin-bottom: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--white);
|
||||
border: 2px solid var(--teal-light);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--shadow-md);
|
||||
border-color: var(--teal-mid);
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 32px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: 800;
|
||||
background: linear-gradient(135deg, var(--teal-mid), var(--teal-dark));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
font-family: 'Syne', sans-serif;
|
||||
}
|
||||
|
||||
.stat-card.high-value .stat-value {
|
||||
font-size: 32px;
|
||||
animation: popIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
NO DATA STATE
|
||||
============================================ */
|
||||
.no-stats {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.no-stats-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.no-stats-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.no-stats-desc {
|
||||
font-size: 13px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
RESPONSIVE DESIGN
|
||||
============================================ */
|
||||
@media (max-width: 768px) {
|
||||
.leaderboard-section h2 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
padding: 8px 16px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.rank-item {
|
||||
grid-template-columns: 40px 1fr auto;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.rank-difficulty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rank-number {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.rank-score {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.player-stats {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.player-stats h3 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 28px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.leaderboard {
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
padding: 6px 12px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.rank-item {
|
||||
grid-template-columns: 40px 1fr;
|
||||
gap: 10px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.rank-score {
|
||||
grid-column: 1 / -1;
|
||||
text-align: left;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.rank-number {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.player-name {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.player-stats {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.player-stats h3 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
520
exercices/memory-pictogrammes/assets/css/memory-game.css
Normal file
@@ -0,0 +1,520 @@
|
||||
/* ============================================
|
||||
MEMORY PICTOGRAMMES - CSS PRINCIPAL
|
||||
NextGN Formation - Gamified Design
|
||||
============================================ */
|
||||
|
||||
:root {
|
||||
/* NextGN Base Colors */
|
||||
--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);
|
||||
|
||||
/* Gamification Colors */
|
||||
--success: #4CAF50;
|
||||
--warning: #FF9800;
|
||||
--info: #2196F3;
|
||||
--accent: #FFD700;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: 0 4px 8px rgba(15, 110, 86, 0.1);
|
||||
--shadow-md: 0 8px 16px rgba(15, 110, 86, 0.15);
|
||||
--shadow-lg: 0 16px 32px rgba(15, 110, 86, 0.25);
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'DM Sans', sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.6;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
HEADER & NAVIGATION
|
||||
============================================ */
|
||||
.nav-header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 70px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 40px;
|
||||
z-index: 1000;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.5px;
|
||||
background: linear-gradient(135deg, var(--teal-mid), var(--coral-mid));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.header-nav {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.header-nav a {
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
margin-left: 40px;
|
||||
transition: color 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header-nav a:hover {
|
||||
color: var(--teal-mid);
|
||||
}
|
||||
|
||||
.header-nav a::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -4px;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
background: var(--teal-mid);
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.header-nav a:hover::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
PROGRESS BAR
|
||||
============================================ */
|
||||
.progress {
|
||||
position: fixed;
|
||||
top: 70px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: var(--teal-light);
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--teal-mid), var(--teal-dark));
|
||||
width: 0%;
|
||||
transition: width 0.2s ease;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
MAIN LAYOUT
|
||||
============================================ */
|
||||
main {
|
||||
margin-top: 70px;
|
||||
padding: 40px;
|
||||
min-height: calc(100vh - 70px);
|
||||
}
|
||||
|
||||
.game-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
INTRO SECTION
|
||||
============================================ */
|
||||
.intro-section {
|
||||
margin-bottom: 60px;
|
||||
animation: fadeInDown 0.6s ease-out;
|
||||
}
|
||||
|
||||
.intro-section h1 {
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: clamp(28px, 5vw, 48px);
|
||||
font-weight: 800;
|
||||
color: var(--teal);
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.intro-section .subtitle {
|
||||
font-size: 18px;
|
||||
color: var(--text-muted);
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.intro-box {
|
||||
background: linear-gradient(135deg, var(--teal-light), var(--white));
|
||||
border-left: 4px solid var(--teal-mid);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 32px;
|
||||
font-size: 0.95em;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.6;
|
||||
max-width: 600px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
DIFFICULTY SELECTOR
|
||||
============================================ */
|
||||
.difficulty-selector {
|
||||
margin-bottom: 48px;
|
||||
text-align: center;
|
||||
animation: fadeInUp 0.6s ease-out 0.2s both;
|
||||
}
|
||||
|
||||
.difficulty-selector label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||
gap: 12px;
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.btn-difficulty {
|
||||
background: var(--white);
|
||||
border: 2px solid var(--teal-light);
|
||||
border-radius: 8px;
|
||||
padding: 16px 12px;
|
||||
font-family: 'Syne', sans-serif;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.btn-difficulty:hover {
|
||||
border-color: var(--teal-mid);
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.btn-difficulty.active {
|
||||
background: linear-gradient(135deg, var(--teal-light), var(--white));
|
||||
border-color: var(--teal-mid);
|
||||
color: var(--teal);
|
||||
}
|
||||
|
||||
.btn-label {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: var(--teal);
|
||||
}
|
||||
|
||||
.btn-desc {
|
||||
font-size: 12px;
|
||||
font-family: 'DM Sans', sans-serif;
|
||||
color: var(--text-muted);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
GAME SECTION
|
||||
============================================ */
|
||||
.game-section {
|
||||
margin-bottom: 80px;
|
||||
animation: fadeInUp 0.6s ease-out 0.3s both;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
GAME GRID & CARDS
|
||||
============================================ */
|
||||
.game-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(clamp(70px, 15vw, 140px), 1fr));
|
||||
gap: clamp(12px, 2vw, 20px);
|
||||
max-width: 900px;
|
||||
margin: 0 auto 40px;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, rgba(225, 245, 238, 0.4), transparent);
|
||||
border-radius: 16px;
|
||||
border: 1px solid var(--teal-light);
|
||||
}
|
||||
|
||||
/* MEMORY CARD STYLES */
|
||||
.memory-card {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
aspect-ratio: 1 / 1;
|
||||
cursor: pointer;
|
||||
perspective: 1000px;
|
||||
transform-style: preserve-3d;
|
||||
transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Card Front (Couverture) */
|
||||
.card-face {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
backface-visibility: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 12px;
|
||||
font-weight: bold;
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: clamp(32px, 8vw, 56px);
|
||||
}
|
||||
|
||||
.card-face.front {
|
||||
background: linear-gradient(135deg, var(--teal-light), var(--teal-light));
|
||||
border: 2px solid var(--teal-mid);
|
||||
color: var(--teal-dark);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.card-face.back {
|
||||
transform: rotateY(180deg);
|
||||
background: linear-gradient(135deg, var(--white), var(--teal-light));
|
||||
border: 2px solid var(--teal-mid);
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.card-face.back img {
|
||||
max-width: 70%;
|
||||
max-height: 60%;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
|
||||
}
|
||||
|
||||
.card-face.back span {
|
||||
font-size: clamp(10px, 2vw, 13px);
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: var(--text);
|
||||
font-family: 'DM Sans', sans-serif;
|
||||
}
|
||||
|
||||
/* Card States */
|
||||
.memory-card:hover:not(.matched):not(.disabled) {
|
||||
transform: translateY(-8px) scale(1.05);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.memory-card.flipped .card-face.front {
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
|
||||
.memory-card.flipped .card-face.back {
|
||||
transform: rotateY(0deg);
|
||||
}
|
||||
|
||||
.memory-card.matched {
|
||||
animation: matchSuccess 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.memory-card.matched .card-face.front {
|
||||
background: linear-gradient(135deg, var(--success), var(--success));
|
||||
border-color: var(--success);
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.memory-card.matched .card-face.back {
|
||||
background: linear-gradient(135deg, var(--white), rgba(76, 175, 80, 0.2));
|
||||
border-color: var(--success);
|
||||
}
|
||||
|
||||
.memory-card.error {
|
||||
animation: cardShake 0.4s ease-in-out;
|
||||
}
|
||||
|
||||
.memory-card.disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
FOOTER
|
||||
============================================ */
|
||||
footer {
|
||||
background: var(--text);
|
||||
color: var(--white);
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
margin-top: 80px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
BUTTONS
|
||||
============================================ */
|
||||
.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;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--teal-mid), var(--teal-dark));
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--white);
|
||||
border: 2px solid var(--teal-mid);
|
||||
color: var(--teal-mid);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--teal-light);
|
||||
border-color: var(--teal-dark);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
RESPONSIVE DESIGN
|
||||
============================================ */
|
||||
@media (max-width: 768px) {
|
||||
.nav-header {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.header-nav a {
|
||||
margin-left: 20px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 20px;
|
||||
margin-top: 70px;
|
||||
}
|
||||
|
||||
.intro-section h1 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.game-grid {
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.game-controls {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.game-controls .btn {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 24px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.nav-header {
|
||||
height: 60px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.header-nav a {
|
||||
margin-left: 12px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 16px;
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
.intro-section h1 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.difficulty-selector label {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.game-hud {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
387
exercices/memory-pictogrammes/assets/js/analytics.js
Normal file
@@ -0,0 +1,387 @@
|
||||
/* ============================================
|
||||
ANALYTICS & EVENT TRACKING
|
||||
============================================ */
|
||||
|
||||
/**
|
||||
* Manager d'analytics pour tracker les événements
|
||||
*/
|
||||
class AnalyticsManager {
|
||||
constructor() {
|
||||
this.sessionId = generateUUID();
|
||||
this.events = [];
|
||||
this.enabled = true;
|
||||
this.endpoint = window.ANALYTICS_ENDPOINT || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistrer un événement
|
||||
*/
|
||||
track(eventName, data = {}) {
|
||||
if (!this.enabled) return;
|
||||
|
||||
const event = {
|
||||
type: eventName,
|
||||
timestamp: Date.now(),
|
||||
sessionId: this.sessionId,
|
||||
userId: getUserId(),
|
||||
playerName: getPlayerName(),
|
||||
data: data
|
||||
};
|
||||
|
||||
this.events.push(event);
|
||||
|
||||
// Logger en debug
|
||||
DEBUG.log(`Event tracked: ${eventName}`, data);
|
||||
|
||||
// Envoyer au serveur si configuré
|
||||
if (this.endpoint) {
|
||||
this.sendToServer(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Démarrage du jeu
|
||||
*/
|
||||
trackGameStart(difficulty) {
|
||||
this.track('game_started', {
|
||||
difficulty: difficulty,
|
||||
mode: DIFFICULTY_MODES[difficulty].name
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retournement de carte
|
||||
*/
|
||||
trackFlip(cardIndex, totalFlips) {
|
||||
this.track('card_flipped', {
|
||||
cardIndex: cardIndex,
|
||||
totalFlips: totalFlips
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Paire trouvée
|
||||
*/
|
||||
trackMatch(pictogrammeId, totalMatches, totalMoves) {
|
||||
this.track('pair_matched', {
|
||||
pictogrammeId: pictogrammeId,
|
||||
totalMatches: totalMatches,
|
||||
totalMoves: totalMoves
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Paire incorrecte
|
||||
*/
|
||||
trackMismatch(pictogramme1Id, pictogramme2Id, move) {
|
||||
this.track('pair_mismatched', {
|
||||
pictogramme1: pictogramme1Id,
|
||||
pictogramme2: pictogramme2Id,
|
||||
move: move
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fin du jeu
|
||||
*/
|
||||
trackGameEnd(gameData) {
|
||||
this.track('game_completed', {
|
||||
difficulty: gameData.difficulty,
|
||||
finalScore: gameData.finalScore,
|
||||
moves: gameData.moves,
|
||||
duration: gameData.duration,
|
||||
won: gameData.won,
|
||||
achievements: gameData.achievements || []
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Changement de difficulté
|
||||
*/
|
||||
trackDifficultyChange(newDifficulty) {
|
||||
this.track('difficulty_changed', {
|
||||
newDifficulty: newDifficulty
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Affichage du classement
|
||||
*/
|
||||
trackLeaderboardView(filter) {
|
||||
this.track('leaderboard_viewed', {
|
||||
filter: filter
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoyer les événements au serveur
|
||||
*/
|
||||
sendToServer(event) {
|
||||
if (!this.endpoint) return;
|
||||
|
||||
fetch(this.endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(event)
|
||||
}).catch(err => {
|
||||
// Silencieusement échouer en offline
|
||||
DEBUG.log('Analytics offline:', err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoyer batch d'événements
|
||||
*/
|
||||
sendBatch() {
|
||||
if (!this.endpoint || this.events.length === 0) return;
|
||||
|
||||
fetch(this.endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
batch: true,
|
||||
events: this.events,
|
||||
sessionId: this.sessionId
|
||||
})
|
||||
}).then(() => {
|
||||
this.events = []; // Vider après envoi
|
||||
DEBUG.log('Analytics batch sent');
|
||||
}).catch(err => {
|
||||
DEBUG.log('Analytics batch failed:', err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir le résumé de la session
|
||||
*/
|
||||
getSessionSummary() {
|
||||
return {
|
||||
sessionId: this.sessionId,
|
||||
userId: getUserId(),
|
||||
playerName: getPlayerName(),
|
||||
eventCount: this.events.length,
|
||||
events: this.events
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Exporter les données en JSON
|
||||
*/
|
||||
exportJSON() {
|
||||
const data = {
|
||||
exportDate: new Date().toISOString(),
|
||||
sessionSummary: this.getSessionSummary(),
|
||||
allEvents: this.events
|
||||
};
|
||||
return JSON.stringify(data, null, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Télécharger les données
|
||||
*/
|
||||
downloadData() {
|
||||
const data = this.exportJSON();
|
||||
const blob = new Blob([data], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `analytics-${generateUUID().slice(0, 8)}.json`;
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Nettoyer les événements
|
||||
*/
|
||||
clear() {
|
||||
this.events = [];
|
||||
DEBUG.log('Analytics cleared');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Heatmap Analytics - Tracker l'interaction avec les cartes
|
||||
*/
|
||||
class HeatmapAnalytics {
|
||||
constructor() {
|
||||
this.heatmap = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistrer un clic de carte
|
||||
*/
|
||||
recordCardClick(cardIndex) {
|
||||
if (!this.heatmap.has(cardIndex)) {
|
||||
this.heatmap.set(cardIndex, 0);
|
||||
}
|
||||
this.heatmap.set(cardIndex, this.heatmap.get(cardIndex) + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir les données de heatmap
|
||||
*/
|
||||
getHeatmap() {
|
||||
const sorted = Array.from(this.heatmap.entries())
|
||||
.sort((a, b) => b[1] - a[1]);
|
||||
|
||||
return Object.fromEntries(sorted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculer la carte la plus cliquée
|
||||
*/
|
||||
getMostClickedCard() {
|
||||
let maxClicks = 0;
|
||||
let maxCard = -1;
|
||||
|
||||
for (const [card, clicks] of this.heatmap) {
|
||||
if (clicks > maxClicks) {
|
||||
maxClicks = clicks;
|
||||
maxCard = card;
|
||||
}
|
||||
}
|
||||
|
||||
return { card: maxCard, clicks: maxClicks };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performance Monitor - Analyser les performances du jeu
|
||||
*/
|
||||
class PerformanceMonitor {
|
||||
constructor() {
|
||||
this.metrics = {};
|
||||
this.startTime = performance.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Marquer un point de début
|
||||
*/
|
||||
mark(name) {
|
||||
this.metrics[name] = {
|
||||
start: performance.now(),
|
||||
end: null,
|
||||
duration: null
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Marquer la fin
|
||||
*/
|
||||
measure(name) {
|
||||
if (this.metrics[name]) {
|
||||
this.metrics[name].end = performance.now();
|
||||
this.metrics[name].duration = this.metrics[name].end - this.metrics[name].start;
|
||||
return this.metrics[name].duration;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir les métriques
|
||||
*/
|
||||
getMetrics() {
|
||||
return this.metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Afficher les métriques en console
|
||||
*/
|
||||
logMetrics() {
|
||||
console.table(this.metrics);
|
||||
}
|
||||
|
||||
/**
|
||||
* FPS Monitor
|
||||
*/
|
||||
monitorFPS(callback) {
|
||||
let lastTime = performance.now();
|
||||
let frames = 0;
|
||||
|
||||
const checkFPS = () => {
|
||||
frames++;
|
||||
const currentTime = performance.now();
|
||||
|
||||
if (currentTime - lastTime >= 1000) {
|
||||
const fps = frames;
|
||||
callback(fps);
|
||||
frames = 0;
|
||||
lastTime = currentTime;
|
||||
}
|
||||
|
||||
requestAnimationFrame(checkFPS);
|
||||
};
|
||||
|
||||
checkFPS();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A/B Testing Manager
|
||||
*/
|
||||
class ABTestingManager {
|
||||
constructor() {
|
||||
this.tests = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Créer un test A/B
|
||||
*/
|
||||
createTest(testName, variants) {
|
||||
const userId = getUserId();
|
||||
const variantIndex = parseInt(userId.slice(0, 8), 16) % variants.length;
|
||||
|
||||
this.tests[testName] = {
|
||||
variants: variants,
|
||||
selectedVariant: variants[variantIndex],
|
||||
variantIndex: variantIndex
|
||||
};
|
||||
|
||||
return this.tests[testName].selectedVariant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir le variant
|
||||
*/
|
||||
getVariant(testName) {
|
||||
return this.tests[testName]?.selectedVariant || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracker une conversion
|
||||
*/
|
||||
trackConversion(testName) {
|
||||
const test = this.tests[testName];
|
||||
if (test) {
|
||||
DEBUG.log(`Conversion tracked for test: ${testName}, variant: ${test.selectedVariant}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// INITIALISATION GLOBALES
|
||||
// ============================================
|
||||
|
||||
// Créer les managers globalement
|
||||
const analyticsManager = new AnalyticsManager();
|
||||
const heatmapAnalytics = new HeatmapAnalytics();
|
||||
const performanceMonitor = new PerformanceMonitor();
|
||||
const abTestingManager = new ABTestingManager();
|
||||
|
||||
// Exporter pour utilisation
|
||||
window.AnalyticsManager = AnalyticsManager;
|
||||
window.HeatmapAnalytics = HeatmapAnalytics;
|
||||
window.PerformanceMonitor = PerformanceMonitor;
|
||||
window.ABTestingManager = ABTestingManager;
|
||||
|
||||
// Event listener pour envoyer les analytics avant de quitter
|
||||
window.addEventListener('beforeunload', () => {
|
||||
analyticsManager.sendBatch();
|
||||
});
|
||||
|
||||
// Log de session au démarrage
|
||||
DEBUG.log('Analytics session started:', analyticsManager.sessionId);
|
||||
359
exercices/memory-pictogrammes/assets/js/game-state.js
Normal file
@@ -0,0 +1,359 @@
|
||||
/* ============================================
|
||||
GAME STATE & SCORING ENGINE
|
||||
============================================ */
|
||||
|
||||
/**
|
||||
* Moteur de scoring avancé
|
||||
* Calcule les points avec bonus temps et efficacité
|
||||
*/
|
||||
class ScoringEngine {
|
||||
constructor() {
|
||||
this.basePoints = 1000;
|
||||
this.difficultyMultiplier = 1;
|
||||
this.timeBonus = 0;
|
||||
this.efficiencyBonus = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculer le score final
|
||||
* Formule : basePoints × difficultyMultiplier + timeBonus + efficiencyBonus
|
||||
*/
|
||||
calculateFinalScore(moves, timeSeconds, pairesTotal, difficulty) {
|
||||
const mode = DIFFICULTY_MODES[difficulty];
|
||||
this.difficultyMultiplier = mode.scoreMultiplier;
|
||||
|
||||
// Bonus temps : points restants
|
||||
const timeRemaining = Math.max(0, mode.timeLimit - timeSeconds);
|
||||
this.timeBonus = Math.round((timeRemaining / mode.timeLimit) * 500);
|
||||
|
||||
// Bonus efficacité : pénalité pour erreurs
|
||||
// Jeu parfait = pairesTotal mouvements (2 cartes par paire)
|
||||
const perfectMoves = pairesTotal;
|
||||
const efficiency = Math.max(0, 1 - ((moves - perfectMoves) / (perfectMoves * 2)));
|
||||
this.efficiencyBonus = Math.round(efficiency * 500);
|
||||
|
||||
// Score final
|
||||
const baseScore = this.basePoints * this.difficultyMultiplier;
|
||||
const finalScore = Math.round(baseScore + this.timeBonus + this.efficiencyBonus);
|
||||
|
||||
return {
|
||||
baseScore: baseScore,
|
||||
timeBonus: this.timeBonus,
|
||||
efficiencyBonus: this.efficiencyBonus,
|
||||
totalScore: finalScore,
|
||||
duration: timeSeconds,
|
||||
breakdown: {
|
||||
base: baseScore,
|
||||
temps: this.timeBonus,
|
||||
efficacite: this.efficiencyBonus,
|
||||
total: finalScore
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Détecter les achievements
|
||||
*/
|
||||
detectAchievements(moves, timeSeconds, pairesTotal, difficulty) {
|
||||
const achievements = [];
|
||||
|
||||
// 🏅 Jeu parfait (0 erreurs)
|
||||
if (moves === pairesTotal) {
|
||||
achievements.push({
|
||||
id: 'perfect_game',
|
||||
name: '🏅 Jeu Parfait',
|
||||
icon: '🏅',
|
||||
description: 'Trouvé toutes les paires sans erreur!'
|
||||
});
|
||||
}
|
||||
|
||||
// ⚡ Vitesse extrême
|
||||
if (timeSeconds < DIFFICULTY_MODES[difficulty].timeLimit * 0.3) {
|
||||
achievements.push({
|
||||
id: 'speedrun',
|
||||
name: '⚡ Vitesse Éclair',
|
||||
icon: '⚡',
|
||||
description: `Complété en ${timeSeconds}s!`
|
||||
});
|
||||
}
|
||||
|
||||
// 🔥 Difficile réussi
|
||||
if (difficulty === 'hard') {
|
||||
achievements.push({
|
||||
id: 'hard_mode',
|
||||
name: '🔥 Maître du Mode Difficile',
|
||||
icon: '🔥',
|
||||
description: 'Défi chronométré réussi!'
|
||||
});
|
||||
}
|
||||
|
||||
// 🎯 Score parfait
|
||||
const perfectScore = 1000 * DIFFICULTY_MODES[difficulty].scoreMultiplier;
|
||||
if ((this.basePoints * this.difficultyMultiplier + this.timeBonus + this.efficiencyBonus) >= perfectScore + 900) {
|
||||
achievements.push({
|
||||
id: 'perfect_score',
|
||||
name: '🎯 Score Parfait',
|
||||
icon: '🎯',
|
||||
description: 'Score quasi-maximal!'
|
||||
});
|
||||
}
|
||||
|
||||
// 🏃 Rapidité respectable
|
||||
if (timeSeconds < 60 && pairesTotal === 9) {
|
||||
achievements.push({
|
||||
id: 'fast_completion',
|
||||
name: '🏃 Très Rapide',
|
||||
icon: '🏃',
|
||||
description: 'Moins d\'une minute!'
|
||||
});
|
||||
}
|
||||
|
||||
return achievements;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manager pour le localStorage et persistance
|
||||
*/
|
||||
class StorageManager {
|
||||
static setItem(key, value, isSession = false) {
|
||||
try {
|
||||
const storage = isSession ? sessionStorage : localStorage;
|
||||
storage.setItem(key, JSON.stringify(value));
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error('Storage error:', e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static getItem(key, isSession = false) {
|
||||
try {
|
||||
const storage = isSession ? sessionStorage : localStorage;
|
||||
const item = storage.getItem(key);
|
||||
return item ? JSON.parse(item) : null;
|
||||
} catch (e) {
|
||||
console.error('Storage error:', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static removeItem(key, isSession = false) {
|
||||
try {
|
||||
const storage = isSession ? sessionStorage : localStorage;
|
||||
storage.removeItem(key);
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error('Storage error:', e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static clear(isSession = false) {
|
||||
try {
|
||||
const storage = isSession ? sessionStorage : localStorage;
|
||||
storage.clear();
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error('Storage error:', e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Session state - données temporaires du jeu actuel
|
||||
*/
|
||||
class GameSessionState {
|
||||
constructor() {
|
||||
this.sessionId = generateUUID();
|
||||
this.userId = getUserId();
|
||||
this.playerName = getPlayerName();
|
||||
this.startTime = null;
|
||||
this.endTime = null;
|
||||
this.difficulty = 'normal';
|
||||
this.moves = 0;
|
||||
this.score = 0;
|
||||
this.matched = 0;
|
||||
this.isPaused = false;
|
||||
this.gameActive = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Démarrer une nouvelle session
|
||||
*/
|
||||
startSession(difficulty) {
|
||||
this.sessionId = generateUUID();
|
||||
this.startTime = Date.now();
|
||||
this.difficulty = difficulty;
|
||||
this.moves = 0;
|
||||
this.score = 0;
|
||||
this.matched = 0;
|
||||
this.gameActive = true;
|
||||
this.isPaused = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminer la session
|
||||
*/
|
||||
endSession() {
|
||||
this.endTime = Date.now();
|
||||
this.gameActive = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir la durée en secondes
|
||||
*/
|
||||
getDuration() {
|
||||
const end = this.endTime || Date.now();
|
||||
return Math.floor((end - this.startTime) / 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sérialiser pour transmission
|
||||
*/
|
||||
toJSON() {
|
||||
return {
|
||||
sessionId: this.sessionId,
|
||||
userId: this.userId,
|
||||
playerName: this.playerName,
|
||||
startTime: this.startTime,
|
||||
endTime: this.endTime,
|
||||
difficulty: this.difficulty,
|
||||
moves: this.moves,
|
||||
score: this.score,
|
||||
matched: this.matched,
|
||||
duration: this.getDuration()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gestionnaire des notifications
|
||||
*/
|
||||
class NotificationManager {
|
||||
static show(message, type = 'info', duration = 3000) {
|
||||
// Créer la notification
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `notification notification-${type}`;
|
||||
notification.textContent = message;
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
background: ${this.getTypeColor(type)};
|
||||
color: white;
|
||||
padding: 16px 24px;
|
||||
border-radius: 8px;
|
||||
font-family: 'Syne', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
z-index: 3000;
|
||||
animation: slideUp 0.3s ease-out;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
`;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// Auto-remove après durée
|
||||
setTimeout(() => {
|
||||
notification.style.animation = 'fadeOut 0.3s ease-out';
|
||||
setTimeout(() => notification.remove(), 300);
|
||||
}, duration);
|
||||
}
|
||||
|
||||
static success(message) {
|
||||
this.show(message, 'success');
|
||||
}
|
||||
|
||||
static error(message) {
|
||||
this.show(message, 'error');
|
||||
}
|
||||
|
||||
static warning(message) {
|
||||
this.show(message, 'warning');
|
||||
}
|
||||
|
||||
static info(message) {
|
||||
this.show(message, 'info');
|
||||
}
|
||||
|
||||
static getTypeColor(type) {
|
||||
const colors = {
|
||||
success: '#4CAF50',
|
||||
error: '#F44336',
|
||||
warning: '#FF9800',
|
||||
info: '#2196F3'
|
||||
};
|
||||
return colors[type] || colors.info;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gestionnaire des préférences utilisateur
|
||||
*/
|
||||
class PreferencesManager {
|
||||
static KEY_PREFERENCES = 'nextgn_preferences';
|
||||
|
||||
static defaults = {
|
||||
soundEnabled: true,
|
||||
animationsEnabled: true,
|
||||
darkMode: false,
|
||||
showStats: true,
|
||||
notifications: true
|
||||
};
|
||||
|
||||
static get() {
|
||||
const saved = localStorage.getItem(this.KEY_PREFERENCES);
|
||||
return saved ? JSON.parse(saved) : this.defaults;
|
||||
}
|
||||
|
||||
static set(preferences) {
|
||||
localStorage.setItem(this.KEY_PREFERENCES, JSON.stringify(preferences));
|
||||
this.applyPreferences(preferences);
|
||||
}
|
||||
|
||||
static update(key, value) {
|
||||
const prefs = this.get();
|
||||
prefs[key] = value;
|
||||
this.set(prefs);
|
||||
}
|
||||
|
||||
static applyPreferences(prefs) {
|
||||
// Appliquer le dark mode
|
||||
if (prefs.darkMode) {
|
||||
document.body.style.colorScheme = 'dark';
|
||||
} else {
|
||||
document.body.style.colorScheme = 'light';
|
||||
}
|
||||
|
||||
// Désactiver les animations si demandé
|
||||
if (!prefs.animationsEnabled) {
|
||||
document.documentElement.style.setProperty('--animation-duration', '0ms');
|
||||
}
|
||||
}
|
||||
|
||||
static toggle(key) {
|
||||
const prefs = this.get();
|
||||
prefs[key] = !prefs[key];
|
||||
this.set(prefs);
|
||||
return prefs[key];
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// INITIALISATION DES SINGLETONS
|
||||
// ============================================
|
||||
|
||||
// Créer une instance de session au chargement
|
||||
const gameSession = new GameSessionState();
|
||||
|
||||
// Appliquer les préférences utilisateur
|
||||
PreferencesManager.applyPreferences(PreferencesManager.get());
|
||||
|
||||
// Exporter pour utilisation globale
|
||||
window.ScoringEngine = ScoringEngine;
|
||||
window.StorageManager = StorageManager;
|
||||
window.GameSessionState = GameSessionState;
|
||||
window.NotificationManager = NotificationManager;
|
||||
window.PreferencesManager = PreferencesManager;
|
||||
389
exercices/memory-pictogrammes/assets/js/leaderboard.js
Normal file
@@ -0,0 +1,389 @@
|
||||
/* ============================================
|
||||
LEADERBOARD & PLAYER STATISTICS
|
||||
============================================ */
|
||||
|
||||
/**
|
||||
* Manager du classement
|
||||
*/
|
||||
class LeaderboardManager {
|
||||
static KEY_LEADERBOARD = 'nextgn_leaderboard';
|
||||
static MAX_SCORES = 100;
|
||||
|
||||
/**
|
||||
* Ajouter un score au classement
|
||||
*/
|
||||
static addScore(gameData) {
|
||||
const scores = this.getAllScores();
|
||||
|
||||
const newScore = {
|
||||
sessionId: generateUUID(),
|
||||
playerName: getPlayerName(),
|
||||
userId: getUserId(),
|
||||
score: gameData.score,
|
||||
moves: gameData.moves,
|
||||
timeSeconds: gameData.duration,
|
||||
difficulty: gameData.difficulty,
|
||||
date: new Date().toISOString(),
|
||||
isPerfect: gameData.moves === DIFFICULTY_MODES[gameData.difficulty].paires,
|
||||
won: gameData.won !== false
|
||||
};
|
||||
|
||||
scores.push(newScore);
|
||||
|
||||
// Limiter à MAX_SCORES (garder les plus récents)
|
||||
if (scores.length > this.MAX_SCORES) {
|
||||
scores.sort((a, b) => new Date(b.date) - new Date(a.date));
|
||||
scores.splice(this.MAX_SCORES);
|
||||
}
|
||||
|
||||
localStorage.setItem(this.KEY_LEADERBOARD, JSON.stringify(scores));
|
||||
|
||||
DEBUG.log('Score added:', newScore);
|
||||
|
||||
return newScore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir tous les scores
|
||||
*/
|
||||
static getAllScores() {
|
||||
try {
|
||||
const scores = JSON.parse(localStorage.getItem(this.KEY_LEADERBOARD)) || [];
|
||||
return scores;
|
||||
} catch (e) {
|
||||
console.error('Leaderboard error:', e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir le classement (top X)
|
||||
*/
|
||||
static getLeaderboard(difficulty = 'all', limit = 10) {
|
||||
let scores = this.getAllScores();
|
||||
|
||||
// Filtrer par difficulté
|
||||
if (difficulty !== 'all') {
|
||||
scores = scores.filter(s => s.difficulty === difficulty);
|
||||
}
|
||||
|
||||
// Trier par score (décroissant)
|
||||
scores.sort((a, b) => {
|
||||
if (b.score !== a.score) {
|
||||
return b.score - a.score;
|
||||
}
|
||||
// En cas d'égalité, par temps
|
||||
return a.timeSeconds - b.timeSeconds;
|
||||
});
|
||||
|
||||
return scores.slice(0, limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir les stats d'un joueur
|
||||
*/
|
||||
static getPlayerStats(userId) {
|
||||
const scores = this.getAllScores().filter(s => s.userId === userId);
|
||||
|
||||
if (scores.length === 0) {
|
||||
return {
|
||||
totalGames: 0,
|
||||
bestScore: 0,
|
||||
averageScore: 0,
|
||||
averageTime: 0,
|
||||
bestTime: Infinity,
|
||||
winRate: 0,
|
||||
perfectGames: 0
|
||||
};
|
||||
}
|
||||
|
||||
const bestScore = Math.max(...scores.map(s => s.score), 0);
|
||||
const totalScore = scores.reduce((a, b) => a + b.score, 0);
|
||||
const totalTime = scores.reduce((a, b) => a + b.timeSeconds, 0);
|
||||
const bestTime = Math.min(...scores.map(s => s.timeSeconds), Infinity);
|
||||
const perfectGames = scores.filter(s => s.isPerfect).length;
|
||||
const winRate = (perfectGames / scores.length) * 100;
|
||||
|
||||
return {
|
||||
totalGames: scores.length,
|
||||
bestScore: bestScore,
|
||||
averageScore: Math.round(totalScore / scores.length),
|
||||
averageTime: Math.round(totalTime / scores.length),
|
||||
bestTime: bestTime === Infinity ? 0 : bestTime,
|
||||
winRate: Math.round(winRate),
|
||||
perfectGames: perfectGames
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir le rang d'un score
|
||||
*/
|
||||
static getPlayerRank(userId, difficulty = 'all') {
|
||||
const leaderboard = this.getLeaderboard(difficulty, Infinity);
|
||||
const rank = leaderboard.findIndex(s => s.userId === userId) + 1;
|
||||
return rank > 0 ? rank : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifier si c'est un nouveau record personnel
|
||||
*/
|
||||
static isPersonalRecord(userId, newScore) {
|
||||
const stats = this.getPlayerStats(userId);
|
||||
return newScore > stats.bestScore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir les statistiques globales
|
||||
*/
|
||||
static getGlobalStats() {
|
||||
const scores = this.getAllScores();
|
||||
|
||||
if (scores.length === 0) {
|
||||
return {
|
||||
totalGames: 0,
|
||||
totalPlayers: 0,
|
||||
averageScore: 0,
|
||||
highestScore: 0,
|
||||
fastestCompletion: Infinity
|
||||
};
|
||||
}
|
||||
|
||||
const uniquePlayers = new Set(scores.map(s => s.userId));
|
||||
const totalScore = scores.reduce((a, b) => a + b.score, 0);
|
||||
const highestScore = Math.max(...scores.map(s => s.score), 0);
|
||||
const fastestCompletion = Math.min(...scores.map(s => s.timeSeconds), Infinity);
|
||||
|
||||
return {
|
||||
totalGames: scores.length,
|
||||
totalPlayers: uniquePlayers.size,
|
||||
averageScore: Math.round(totalScore / scores.length),
|
||||
highestScore: highestScore,
|
||||
fastestCompletion: fastestCompletion === Infinity ? 0 : fastestCompletion
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Mettre à jour l'affichage du classement
|
||||
*/
|
||||
static updateDisplay(difficulty = 'all') {
|
||||
const leaderboard = this.getLeaderboard(difficulty, 10);
|
||||
const leaderboardList = document.getElementById('leaderboard-list');
|
||||
|
||||
if (!leaderboardList) return;
|
||||
|
||||
if (leaderboard.length === 0) {
|
||||
leaderboardList.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-icon">🎮</div>
|
||||
<div class="empty-state-text">Aucun score pour le moment</div>
|
||||
<div class="empty-state-hint">Jouez une partie pour apparaître au classement!</div>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
leaderboardList.innerHTML = '';
|
||||
|
||||
leaderboard.forEach((score, index) => {
|
||||
const rankItem = document.createElement('div');
|
||||
rankItem.className = 'rank-item';
|
||||
|
||||
const rankNumber = this.getRankDisplay(index);
|
||||
const medal = index < 3 ? `class="rank-number medal-${index + 1}"` : 'class="rank-number"';
|
||||
|
||||
rankItem.innerHTML = `
|
||||
<div ${medal}>${rankNumber}</div>
|
||||
<div class="rank-info">
|
||||
<div class="player-name">${this.escapeHTML(score.playerName)}</div>
|
||||
<div class="player-stats">
|
||||
<span>⏱ ${formatTime(score.timeSeconds)}</span>
|
||||
<span>🎯 ${score.moves} mouvements</span>
|
||||
${score.isPerfect ? '<span>✨ Parfait</span>' : ''}
|
||||
</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);
|
||||
});
|
||||
|
||||
analyticsManager.track('leaderboard_viewed', { filter: difficulty });
|
||||
}
|
||||
|
||||
/**
|
||||
* Mettre à jour les statistiques du joueur
|
||||
*/
|
||||
static updatePlayerStats() {
|
||||
const userId = getUserId();
|
||||
const stats = this.getPlayerStats(userId);
|
||||
|
||||
// Remplir les éléments HTML
|
||||
document.getElementById('total-games').textContent = stats.totalGames;
|
||||
document.getElementById('best-score').textContent = formatNumber(stats.bestScore);
|
||||
document.getElementById('best-time').textContent = stats.bestTime === Infinity ? '--' : formatTime(stats.bestTime);
|
||||
document.getElementById('win-rate').textContent = `${stats.winRate}%`;
|
||||
|
||||
// Ajouter des classes pour animation si valeurs élevées
|
||||
if (stats.bestScore > 5000) {
|
||||
document.getElementById('best-score').parentElement.classList.add('high-value');
|
||||
}
|
||||
|
||||
if (stats.winRate >= 50) {
|
||||
document.getElementById('win-rate').parentElement.classList.add('high-value');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Afficher le rang du joueur
|
||||
*/
|
||||
static updatePlayerRank(difficulty = 'all') {
|
||||
const userId = getUserId();
|
||||
const rank = this.getPlayerRank(userId, difficulty);
|
||||
|
||||
if (rank) {
|
||||
const rankDisplay = document.getElementById('player-rank');
|
||||
if (rankDisplay) {
|
||||
rankDisplay.textContent = `Vous êtes #{rank}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Afficher les statistiques globales
|
||||
*/
|
||||
static displayGlobalStats() {
|
||||
const globalStats = this.getGlobalStats();
|
||||
DEBUG.log('Global Statistics:', globalStats);
|
||||
|
||||
return globalStats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Nettoyer les scores (DEBUG)
|
||||
*/
|
||||
static clearAll() {
|
||||
if (confirm('Êtes-vous ABSOLUMENT certain de vouloir supprimer TOUS les scores?')) {
|
||||
localStorage.removeItem(this.KEY_LEADERBOARD);
|
||||
this.updateDisplay();
|
||||
this.updatePlayerStats();
|
||||
DEBUG.log('✓ Tous les scores ont été supprimés');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exporter les scores en CSV
|
||||
*/
|
||||
static exportCSV() {
|
||||
const scores = this.getAllScores();
|
||||
|
||||
let csv = 'Rang,Joueur,Score,Difficulté,Temps (s),Mouvements,Parfait,Date\n';
|
||||
|
||||
scores.forEach((score, index) => {
|
||||
csv += `${index + 1},"${score.playerName}",${score.score},"${score.difficulty}",${score.timeSeconds},${score.moves},${score.isPerfect ? 'Oui' : 'Non'},"${score.date}"\n`;
|
||||
});
|
||||
|
||||
const blob = new Blob([csv], { type: 'text/csv' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `leaderboard-${new Date().toISOString().slice(0, 10)}.csv`;
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir l'affichage du rang (numéro ou médaille)
|
||||
*/
|
||||
static getRankDisplay(index) {
|
||||
const medals = ['🥇', '🥈', '🥉'];
|
||||
return medals[index] || `${index + 1}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Échapper le HTML (sécurité)
|
||||
*/
|
||||
static escapeHTML(str) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = str;
|
||||
return div.innerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Statistiques avancées
|
||||
*/
|
||||
class AdvancedStats {
|
||||
/**
|
||||
* Obtenir la distribution des scores par difficulté
|
||||
*/
|
||||
static getDistributionByDifficulty() {
|
||||
const scores = LeaderboardManager.getAllScores();
|
||||
const distribution = {};
|
||||
|
||||
Object.keys(DIFFICULTY_MODES).forEach(difficulty => {
|
||||
distribution[difficulty] = scores.filter(s => s.difficulty === difficulty).length;
|
||||
});
|
||||
|
||||
return distribution;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir la tendance temporelle
|
||||
*/
|
||||
static getTimelineTrend(days = 7) {
|
||||
const scores = LeaderboardManager.getAllScores();
|
||||
const now = new Date();
|
||||
const timeline = {};
|
||||
|
||||
for (let i = 0; i < days; i++) {
|
||||
const date = new Date(now);
|
||||
date.setDate(date.getDate() - i);
|
||||
const dateStr = date.toISOString().slice(0, 10);
|
||||
timeline[dateStr] = scores.filter(s => s.date.slice(0, 10) === dateStr).length;
|
||||
}
|
||||
|
||||
return timeline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir la moyenne de temps par difficulté
|
||||
*/
|
||||
static getAverageTimeByDifficulty() {
|
||||
const scores = LeaderboardManager.getAllScores();
|
||||
const averages = {};
|
||||
|
||||
Object.keys(DIFFICULTY_MODES).forEach(difficulty => {
|
||||
const diffScores = scores.filter(s => s.difficulty === difficulty);
|
||||
if (diffScores.length > 0) {
|
||||
const totalTime = diffScores.reduce((a, b) => a + b.timeSeconds, 0);
|
||||
averages[difficulty] = Math.round(totalTime / diffScores.length);
|
||||
}
|
||||
});
|
||||
|
||||
return averages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Taux de réussite global
|
||||
*/
|
||||
static getGlobalSuccessRate() {
|
||||
const scores = LeaderboardManager.getAllScores();
|
||||
if (scores.length === 0) return 0;
|
||||
|
||||
const perfectGames = scores.filter(s => s.isPerfect).length;
|
||||
return Math.round((perfectGames / scores.length) * 100);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// INITIALISATION
|
||||
// ============================================
|
||||
|
||||
// Exporter pour utilisation globale
|
||||
window.LeaderboardManager = LeaderboardManager;
|
||||
window.AdvancedStats = AdvancedStats;
|
||||
|
||||
DEBUG.log('Leaderboard Manager initialized');
|
||||
527
exercices/memory-pictogrammes/assets/js/memory-game.js
Normal file
@@ -0,0 +1,527 @@
|
||||
/* ============================================
|
||||
MEMORY PICTOGRAMMES - GAME ENGINE
|
||||
Logique principale du jeu
|
||||
============================================ */
|
||||
|
||||
class MemoryGameEngine {
|
||||
constructor(difficulty = 'normal') {
|
||||
this.difficulty = difficulty;
|
||||
this.mode = DIFFICULTY_MODES[difficulty];
|
||||
this.cards = [];
|
||||
this.gameState = {
|
||||
flipped: [],
|
||||
matched: 0,
|
||||
moves: 0,
|
||||
score: 0,
|
||||
isProcessing: false,
|
||||
isPaused: false,
|
||||
gameStarted: false
|
||||
};
|
||||
this.startTime = null;
|
||||
this.timerInterval = null;
|
||||
this.scoreEngine = new ScoringEngine();
|
||||
this.analytics = new AnalyticsManager();
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialiser le jeu
|
||||
*/
|
||||
init() {
|
||||
this.cards = this.initializeCards();
|
||||
this.renderCards();
|
||||
this.setupEventListeners();
|
||||
this.updateHUD();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialiser le deck de cartes
|
||||
*/
|
||||
initializeCards() {
|
||||
const selectedPictogrammes = PICTOGRAMMES.slice(0, this.mode.paires);
|
||||
const pairs = [];
|
||||
|
||||
// Créer paires (image + texte pour chaque pictogramme)
|
||||
selectedPictogrammes.forEach(pic => {
|
||||
pairs.push({
|
||||
type: 'image',
|
||||
data: pic,
|
||||
id: pic.id,
|
||||
pairId: `${pic.id}-img`
|
||||
});
|
||||
pairs.push({
|
||||
type: 'text',
|
||||
data: pic,
|
||||
id: pic.id,
|
||||
pairId: `${pic.id}-txt`
|
||||
});
|
||||
});
|
||||
|
||||
// Mélanger les cartes
|
||||
return this.shuffle(pairs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Algorithme de shuffling (Fisher-Yates)
|
||||
*/
|
||||
shuffle(array) {
|
||||
const shuffled = [...array];
|
||||
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
||||
}
|
||||
return shuffled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendre les cartes en HTML
|
||||
*/
|
||||
renderCards() {
|
||||
const gameGrid = document.getElementById('game-grid');
|
||||
gameGrid.innerHTML = '';
|
||||
|
||||
this.cards.forEach((card, index) => {
|
||||
const cardElement = document.createElement('div');
|
||||
cardElement.className = 'memory-card';
|
||||
cardElement.setAttribute('data-card-index', index);
|
||||
|
||||
// Face avant (couverture)
|
||||
const frontFace = document.createElement('div');
|
||||
frontFace.className = 'card-face front';
|
||||
frontFace.textContent = '?';
|
||||
|
||||
// Face arrière (contenu)
|
||||
const backFace = document.createElement('div');
|
||||
backFace.className = 'card-face back';
|
||||
|
||||
if (card.type === 'image') {
|
||||
const img = document.createElement('img');
|
||||
img.src = `assets/pictogrammes/${card.data.filename}`;
|
||||
img.alt = card.data.nom;
|
||||
backFace.appendChild(img);
|
||||
} else {
|
||||
const text = document.createElement('span');
|
||||
text.textContent = card.data.texte;
|
||||
backFace.appendChild(text);
|
||||
}
|
||||
|
||||
cardElement.appendChild(frontFace);
|
||||
cardElement.appendChild(backFace);
|
||||
|
||||
cardElement.addEventListener('click', () => this.onCardClicked(index));
|
||||
gameGrid.appendChild(cardElement);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gestion du clic sur une carte
|
||||
*/
|
||||
onCardClicked(cardIndex) {
|
||||
// Démarrer le timer au premier clic
|
||||
if (!this.gameState.gameStarted) {
|
||||
this.startGame();
|
||||
}
|
||||
|
||||
// Guard clauses
|
||||
if (this.gameState.isProcessing) return;
|
||||
if (this.gameState.flipped.length === 2) return;
|
||||
if (this.cards[cardIndex].isMatched) return;
|
||||
if (this.gameState.flipped.includes(cardIndex)) return;
|
||||
if (this.gameState.isPaused) return;
|
||||
|
||||
// Retourner la carte
|
||||
this.flipCard(cardIndex);
|
||||
this.gameState.flipped.push(cardIndex);
|
||||
|
||||
// Analytics
|
||||
this.analytics.trackFlip(cardIndex, this.gameState.moves);
|
||||
|
||||
// Vérifier si 2 cartes retournées
|
||||
if (this.gameState.flipped.length === 2) {
|
||||
this.gameState.isProcessing = true;
|
||||
this.gameState.moves++;
|
||||
this.updateHUD();
|
||||
|
||||
// Délai pour visualiser les 2 cartes
|
||||
setTimeout(() => this.checkMatch(), 1200);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourner une carte (animation)
|
||||
*/
|
||||
flipCard(index) {
|
||||
const cardElement = document.querySelector(`[data-card-index="${index}"]`);
|
||||
cardElement.classList.add('flipped');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourner une carte (inverse l'animation)
|
||||
*/
|
||||
unflipCard(index) {
|
||||
const cardElement = document.querySelector(`[data-card-index="${index}"]`);
|
||||
cardElement.classList.remove('flipped');
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifier si les 2 cartes retournées correspondent
|
||||
*/
|
||||
checkMatch() {
|
||||
const [idx1, idx2] = this.gameState.flipped;
|
||||
const card1 = this.cards[idx1];
|
||||
const card2 = this.cards[idx2];
|
||||
|
||||
if (card1.id === card2.id) {
|
||||
this.onMatchSuccess(idx1, idx2);
|
||||
} else {
|
||||
this.onMatchFailure(idx1, idx2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Paire correctement matchée
|
||||
*/
|
||||
onMatchSuccess(idx1, idx2) {
|
||||
const card1Element = document.querySelector(`[data-card-index="${idx1}"]`);
|
||||
const card2Element = document.querySelector(`[data-card-index="${idx2}"]`);
|
||||
|
||||
// Marquer comme matched
|
||||
this.cards[idx1].isMatched = true;
|
||||
this.cards[idx2].isMatched = true;
|
||||
card1Element.classList.add('matched');
|
||||
card2Element.classList.add('matched');
|
||||
|
||||
this.gameState.matched++;
|
||||
|
||||
// Augmenter score
|
||||
const points = 100 + (50 * this.mode.scoreMultiplier);
|
||||
this.gameState.score += points;
|
||||
|
||||
// Analytics
|
||||
this.analytics.trackMatch(
|
||||
this.cards[idx1].id,
|
||||
this.gameState.matched,
|
||||
this.gameState.moves
|
||||
);
|
||||
|
||||
// Mettre à jour UI
|
||||
this.updateHUD();
|
||||
|
||||
// Ajouter pulse à la barre de progression
|
||||
const progressContainer = document.querySelector('.progress-container');
|
||||
progressContainer.classList.add('pulse');
|
||||
setTimeout(() => progressContainer.classList.remove('pulse'), 600);
|
||||
|
||||
// Vérifier victoire
|
||||
if (this.gameState.matched === this.mode.paires) {
|
||||
setTimeout(() => this.endGame(true), 600);
|
||||
} else {
|
||||
// Reset pour prochaine tentative
|
||||
this.gameState.flipped = [];
|
||||
this.gameState.isProcessing = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Paire incorrecte
|
||||
*/
|
||||
onMatchFailure(idx1, idx2) {
|
||||
const card1Element = document.querySelector(`[data-card-index="${idx1}"]`);
|
||||
const card2Element = document.querySelector(`[data-card-index="${idx2}"]`);
|
||||
|
||||
// Animation shake
|
||||
card1Element.classList.add('error');
|
||||
card2Element.classList.add('error');
|
||||
|
||||
// Analytics
|
||||
this.analytics.trackMismatch(
|
||||
this.cards[idx1].id,
|
||||
this.cards[idx2].id,
|
||||
this.gameState.moves
|
||||
);
|
||||
|
||||
// Retourner après délai
|
||||
setTimeout(() => {
|
||||
this.unflipCard(idx1);
|
||||
this.unflipCard(idx2);
|
||||
card1Element.classList.remove('error');
|
||||
card2Element.classList.remove('error');
|
||||
this.gameState.flipped = [];
|
||||
this.gameState.isProcessing = false;
|
||||
}, 1200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Démarrer le timer du jeu
|
||||
*/
|
||||
startGame() {
|
||||
this.gameState.gameStarted = true;
|
||||
this.startTime = Date.now();
|
||||
|
||||
this.timerInterval = setInterval(() => {
|
||||
if (!this.gameState.isPaused) {
|
||||
this.updateTimer();
|
||||
}
|
||||
}, 100);
|
||||
|
||||
this.analytics.trackGameStart(this.difficulty);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mettre à jour le timer
|
||||
*/
|
||||
updateTimer() {
|
||||
const elapsed = Math.floor((Date.now() - this.startTime) / 1000);
|
||||
const minutes = Math.floor(elapsed / 60);
|
||||
const seconds = elapsed % 60;
|
||||
|
||||
const timerDisplay = document.getElementById('timer-display');
|
||||
timerDisplay.textContent = `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
|
||||
|
||||
// Animation warning si peu de temps reste
|
||||
if (this.mode.timeLimit && elapsed >= this.mode.timeLimit * 0.8) {
|
||||
timerDisplay.style.color = '#FF9800';
|
||||
timerDisplay.style.animation = 'timerWarning 0.5s infinite';
|
||||
}
|
||||
|
||||
// Game over si temps limite dépassé
|
||||
if (this.mode.timeLimit && elapsed >= this.mode.timeLimit) {
|
||||
this.endGame(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mettre à jour le HUD
|
||||
*/
|
||||
updateHUD() {
|
||||
// Score
|
||||
document.getElementById('score-display').textContent = this.gameState.score;
|
||||
|
||||
// Mouvements
|
||||
document.getElementById('moves-display').textContent = this.gameState.moves;
|
||||
|
||||
// Paires trouvées
|
||||
document.getElementById('pairs-display').textContent = `${this.gameState.matched}/${this.mode.paires}`;
|
||||
|
||||
// Barre de progression
|
||||
const progress = (this.gameState.matched / this.mode.paires) * 100;
|
||||
document.getElementById('progress-bar').style.width = `${progress}%`;
|
||||
|
||||
// Modifier le scroll progress bar en haut
|
||||
const scrollProgress = (window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100;
|
||||
document.getElementById('progress-fill').style.width = `${Math.min(scrollProgress, 100)}%`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fin du jeu
|
||||
*/
|
||||
endGame(isWon) {
|
||||
clearInterval(this.timerInterval);
|
||||
|
||||
const endTime = Date.now();
|
||||
const gameData = {
|
||||
duration: Math.floor((endTime - this.startTime) / 1000),
|
||||
moves: this.gameState.moves,
|
||||
score: this.gameState.score,
|
||||
difficulty: this.difficulty,
|
||||
won: isWon
|
||||
};
|
||||
|
||||
// Calculer score final avec bonus
|
||||
const finalScore = this.scoreEngine.calculateFinalScore(
|
||||
gameData.moves,
|
||||
gameData.duration,
|
||||
this.mode.paires,
|
||||
this.difficulty
|
||||
);
|
||||
|
||||
// Détecter achievements
|
||||
const achievements = this.scoreEngine.detectAchievements(
|
||||
gameData.moves,
|
||||
gameData.duration,
|
||||
this.mode.paires,
|
||||
this.difficulty
|
||||
);
|
||||
|
||||
// Analytics
|
||||
this.analytics.trackGameEnd({
|
||||
...gameData,
|
||||
finalScore: finalScore.totalScore,
|
||||
achievements: achievements.map(a => a.id)
|
||||
});
|
||||
|
||||
// Sauvegarder score
|
||||
LeaderboardManager.addScore({
|
||||
...gameData,
|
||||
score: finalScore.totalScore
|
||||
});
|
||||
|
||||
// Afficher modal victoire
|
||||
this.showVictoryModal(finalScore, achievements, isWon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Afficher la modal de victoire
|
||||
*/
|
||||
showVictoryModal(finalScore, achievements, isWon) {
|
||||
const modal = document.getElementById('victory-modal');
|
||||
const minutes = Math.floor(this.gameState.score / 60);
|
||||
const seconds = this.gameState.score % 60;
|
||||
|
||||
// Remplir les données
|
||||
document.getElementById('victory-title').textContent = isWon ? 'Bravo! 🎉' : 'Temps écoulé! ⏰';
|
||||
document.getElementById('final-score').textContent = finalScore.totalScore;
|
||||
document.getElementById('modal-moves').textContent = this.gameState.moves;
|
||||
document.getElementById('modal-time').textContent = `${String(Math.floor(finalScore.duration / 60)).padStart(2, '0')}:${String(finalScore.duration % 60).padStart(2, '0')}`;
|
||||
document.getElementById('modal-difficulty').textContent = this.mode.name;
|
||||
document.getElementById('modal-bonus').textContent = `+${finalScore.timeBonus}`;
|
||||
|
||||
// Achievements
|
||||
const achievementsList = document.getElementById('achievements-list');
|
||||
achievementsList.innerHTML = '';
|
||||
|
||||
if (achievements.length > 0) {
|
||||
const title = document.createElement('div');
|
||||
title.style.fontSize = '14px';
|
||||
title.style.fontWeight = '600';
|
||||
title.style.marginBottom = '12px';
|
||||
title.textContent = '🏅 Achievements Débloqués';
|
||||
achievementsList.appendChild(title);
|
||||
|
||||
achievements.forEach((ach, index) => {
|
||||
const achievementEl = document.createElement('div');
|
||||
achievementEl.className = 'achievement';
|
||||
achievementEl.style.animationDelay = `${(index * 0.1)}s`;
|
||||
achievementEl.innerHTML = `
|
||||
<div class="achievement-icon">${ach.icon}</div>
|
||||
<div class="achievement-text">
|
||||
<div class="achievement-name">${ach.name}</div>
|
||||
<div class="achievement-desc">${ach.description}</div>
|
||||
</div>
|
||||
`;
|
||||
achievementsList.appendChild(achievementEl);
|
||||
});
|
||||
}
|
||||
|
||||
// Boutons d'action
|
||||
document.getElementById('play-again-btn').onclick = () => this.restart();
|
||||
document.getElementById('view-leaderboard-btn').onclick = () => {
|
||||
modal.classList.add('hidden');
|
||||
document.querySelector('.leaderboard-section').scrollIntoView({ behavior: 'smooth' });
|
||||
};
|
||||
|
||||
// Afficher modal
|
||||
modal.classList.remove('hidden');
|
||||
}
|
||||
|
||||
/**
|
||||
* Redémarrer le jeu
|
||||
*/
|
||||
restart() {
|
||||
document.getElementById('victory-modal').classList.add('hidden');
|
||||
clearInterval(this.timerInterval);
|
||||
|
||||
// Réinitialiser l'état
|
||||
this.gameState = {
|
||||
flipped: [],
|
||||
matched: 0,
|
||||
moves: 0,
|
||||
score: 0,
|
||||
isProcessing: false,
|
||||
isPaused: false,
|
||||
gameStarted: false
|
||||
};
|
||||
|
||||
// Réinitialiser les cartes
|
||||
this.cards = this.initializeCards();
|
||||
this.renderCards();
|
||||
this.updateHUD();
|
||||
|
||||
// Reset timer display
|
||||
document.getElementById('timer-display').textContent = '00:00';
|
||||
document.getElementById('timer-display').style.color = '';
|
||||
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
|
||||
*/
|
||||
setupEventListeners() {
|
||||
// Bouton restart
|
||||
document.getElementById('restart-btn').addEventListener('click', () => this.restart());
|
||||
|
||||
// Bouton pause
|
||||
document.getElementById('pause-btn').addEventListener('click', () => this.togglePause());
|
||||
|
||||
// Sélecteur de difficulté
|
||||
document.querySelectorAll('.btn-difficulty').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const newDifficulty = btn.getAttribute('data-difficulty');
|
||||
if (newDifficulty !== this.difficulty) {
|
||||
this.difficulty = newDifficulty;
|
||||
this.mode = DIFFICULTY_MODES[newDifficulty];
|
||||
this.restart();
|
||||
|
||||
// Marquer comme actif
|
||||
document.querySelectorAll('.btn-difficulty').forEach(b => {
|
||||
b.classList.remove('active');
|
||||
});
|
||||
btn.classList.add('active');
|
||||
|
||||
this.analytics.trackDifficultyChange(newDifficulty);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Tabs du leaderboard
|
||||
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const filter = btn.getAttribute('data-filter');
|
||||
LeaderboardManager.updateDisplay(filter);
|
||||
|
||||
document.querySelectorAll('.tab-btn').forEach(b => {
|
||||
b.classList.remove('active');
|
||||
});
|
||||
btn.classList.add('active');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// INITIALISATION AU CHARGEMENT
|
||||
// ============================================
|
||||
let game;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Initialiser le jeu
|
||||
game = new MemoryGameEngine('normal');
|
||||
|
||||
// Afficher le leaderboard initial
|
||||
LeaderboardManager.updateDisplay('all');
|
||||
LeaderboardManager.updatePlayerStats();
|
||||
|
||||
// Scroll progress
|
||||
window.addEventListener('scroll', () => {
|
||||
game.updateHUD();
|
||||
});
|
||||
});
|
||||
404
exercices/memory-pictogrammes/assets/js/utils.js
Normal file
@@ -0,0 +1,404 @@
|
||||
/* ============================================
|
||||
UTILS & CONFIGURATION
|
||||
============================================ */
|
||||
|
||||
// ============================================
|
||||
// PICTOGRAMMES DE DANGER (GHS)
|
||||
// ============================================
|
||||
const PICTOGRAMMES = [
|
||||
{
|
||||
id: 1,
|
||||
nom: "Gaz comprimé",
|
||||
filename: "1-gaz-comprime.png",
|
||||
texte: "Je suis sous pression",
|
||||
danger: "Danger physique",
|
||||
couleur: "teal"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
nom: "Exclamation",
|
||||
filename: "2-exclamation.png",
|
||||
texte: "Je nuis gravement à la santé",
|
||||
danger: "Danger pour la santé",
|
||||
couleur: "teal"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
nom: "Explosion",
|
||||
filename: "3-explosion.png",
|
||||
texte: "J'explose",
|
||||
danger: "Danger physique",
|
||||
couleur: "teal"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
nom: "Crâne",
|
||||
filename: "4-crane.png",
|
||||
texte: "Je tue",
|
||||
danger: "Danger physique",
|
||||
couleur: "teal"
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
nom: "Flamme",
|
||||
filename: "5-flamme.png",
|
||||
texte: "Je flambe",
|
||||
danger: "Danger physique",
|
||||
couleur: "teal"
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
nom: "Comburant",
|
||||
filename: "6-comburant.png",
|
||||
texte: "Je fais flamber",
|
||||
danger: "Danger physique",
|
||||
couleur: "teal"
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
nom: "Environnement",
|
||||
filename: "7-environnement.png",
|
||||
texte: "Je pollue",
|
||||
danger: "Danger pour l'environnement",
|
||||
couleur: "teal"
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
nom: "Corrosion",
|
||||
filename: "8-corrosion.png",
|
||||
texte: "Je ronge",
|
||||
danger: "Danger physique",
|
||||
couleur: "teal"
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
nom: "Santé/Environnement",
|
||||
filename: "9-sante-environnement.png",
|
||||
texte: "J'altère la santé ou la couche d'ozone",
|
||||
danger: "Danger pour la santé/Environnement",
|
||||
couleur: "teal"
|
||||
}
|
||||
];
|
||||
|
||||
// ============================================
|
||||
// MODES DE DIFFICULTÉ
|
||||
// ============================================
|
||||
const DIFFICULTY_MODES = {
|
||||
easy: {
|
||||
name: "Facile",
|
||||
paires: 4,
|
||||
timeLimit: 180,
|
||||
scoreMultiplier: 1,
|
||||
description: "4 paires pour débuter",
|
||||
color: "#4CAF50"
|
||||
},
|
||||
|
||||
normal: {
|
||||
name: "Normal",
|
||||
paires: 9,
|
||||
timeLimit: 300,
|
||||
scoreMultiplier: 2,
|
||||
description: "Tous les 9 pictogrammes",
|
||||
color: "#1D9E75"
|
||||
},
|
||||
|
||||
hard: {
|
||||
name: "Difficile",
|
||||
paires: 9,
|
||||
timeLimit: 120,
|
||||
scoreMultiplier: 4,
|
||||
description: "Défi chronométré (2 min)",
|
||||
color: "#D85A30"
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// ACHIEVEMENTS
|
||||
// ============================================
|
||||
const ACHIEVEMENTS = {
|
||||
perfectGame: {
|
||||
id: 'perfect_game',
|
||||
name: '🏅 Jeu Parfait',
|
||||
icon: '🏅',
|
||||
description: 'Trouvé toutes les paires sans erreur!',
|
||||
condition: (moves, pairesTotal) => moves === pairesTotal
|
||||
},
|
||||
|
||||
speedrun: {
|
||||
id: 'speedrun',
|
||||
name: '⚡ Vitesse Éclair',
|
||||
icon: '⚡',
|
||||
description: 'Complété en moins de 60 secondes!',
|
||||
condition: (moves, time, pairesTotal, difficulty) => {
|
||||
const threshold = DIFFICULTY_MODES[difficulty].timeLimit * 0.5;
|
||||
return time < threshold;
|
||||
}
|
||||
},
|
||||
|
||||
fastHard: {
|
||||
id: 'fast_hard',
|
||||
name: '🔥 Mode Difficile Maître',
|
||||
icon: '🔥',
|
||||
description: 'Complété le mode difficile!',
|
||||
condition: (moves, time, pairesTotal, difficulty) => difficulty === 'hard'
|
||||
},
|
||||
|
||||
twoMinutes: {
|
||||
id: 'two_minutes',
|
||||
name: '⏱ Contre le Chrono',
|
||||
icon: '⏱',
|
||||
description: 'Terminé avant les 2 minutes!',
|
||||
condition: (moves, time, pairesTotal, difficulty) => time < 120
|
||||
},
|
||||
|
||||
noMistakes: {
|
||||
id: 'no_mistakes',
|
||||
name: '🎯 Tir Précis',
|
||||
icon: '🎯',
|
||||
description: '0 erreur dans le mode Normal!',
|
||||
condition: (moves, time, pairesTotal, difficulty) => {
|
||||
return difficulty === 'normal' && moves === pairesTotal;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// UTILITY FUNCTIONS
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Générer un UUID
|
||||
*/
|
||||
function generateUUID() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
const r = Math.random() * 16 | 0;
|
||||
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Formater le temps en mm:ss
|
||||
*/
|
||||
function formatTime(seconds) {
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const secs = seconds % 60;
|
||||
return `${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formater un nombre avec séparateurs
|
||||
*/
|
||||
function formatNumber(num) {
|
||||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajouter une classe temporaire
|
||||
*/
|
||||
function addTemporaryClass(element, className, duration = 300) {
|
||||
element.classList.add(className);
|
||||
setTimeout(() => {
|
||||
element.classList.remove(className);
|
||||
}, duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir l'ID utilisateur (avec localStorage)
|
||||
*/
|
||||
function getUserId() {
|
||||
let userId = localStorage.getItem('nextgn_userId');
|
||||
if (!userId) {
|
||||
userId = generateUUID();
|
||||
localStorage.setItem('nextgn_userId', userId);
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir le nom du joueur
|
||||
*/
|
||||
function getPlayerName() {
|
||||
let playerName = localStorage.getItem('nextgn_playerName');
|
||||
if (!playerName) {
|
||||
playerName = 'Joueur ' + Math.floor(Math.random() * 9000 + 1000);
|
||||
localStorage.setItem('nextgn_playerName', playerName);
|
||||
}
|
||||
return playerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Demander le nom du joueur (au premier lancement)
|
||||
*/
|
||||
function promptPlayerName() {
|
||||
const stored = localStorage.getItem('nextgn_playerName');
|
||||
if (stored) return stored;
|
||||
|
||||
const name = prompt('Quel est votre nom?', 'Joueur ' + Math.floor(Math.random() * 9000 + 1000));
|
||||
if (name) {
|
||||
localStorage.setItem('nextgn_playerName', name);
|
||||
return name;
|
||||
}
|
||||
return 'Anonyme';
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir la couleur d'une difficulté
|
||||
*/
|
||||
function getDifficultyColor(difficulty) {
|
||||
return DIFFICULTY_MODES[difficulty]?.color || '#1D9E75';
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir la couleur de badge difficulté
|
||||
*/
|
||||
function getDifficultyClass(difficulty) {
|
||||
switch(difficulty) {
|
||||
case 'easy': return 'easy';
|
||||
case 'normal': return 'normal';
|
||||
case 'hard': return 'hard';
|
||||
default: return 'normal';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparer deux scores
|
||||
*/
|
||||
function compareScores(a, b) {
|
||||
if (b.score !== a.score) {
|
||||
return b.score - a.score;
|
||||
}
|
||||
return a.timeSeconds - b.timeSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trier les scores par difficulty
|
||||
*/
|
||||
function sortByDifficulty(scores, difficulty) {
|
||||
if (difficulty === 'all') {
|
||||
return scores;
|
||||
}
|
||||
return scores.filter(s => s.difficulty === difficulty);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir le rang d'un score
|
||||
*/
|
||||
function getRank(index) {
|
||||
if (index === 0) return '🥇';
|
||||
if (index === 1) return '🥈';
|
||||
if (index === 2) return '🥉';
|
||||
return `${index + 1}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifier si le score est dans le top 10
|
||||
*/
|
||||
function isTopScore(score, difficulty = 'all') {
|
||||
const scores = LeaderboardManager.getLeaderboard(difficulty, 10);
|
||||
return scores.some(s => s.score === score.score && s.userId === score.userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formater la date d'un score
|
||||
*/
|
||||
function formatDate(isoDate) {
|
||||
const date = new Date(isoDate);
|
||||
const today = new Date();
|
||||
const yesterday = new Date(today);
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
|
||||
if (date.toDateString() === today.toDateString()) {
|
||||
return 'Aujourd\'hui';
|
||||
} else if (date.toDateString() === yesterday.toDateString()) {
|
||||
return 'Hier';
|
||||
} else {
|
||||
return date.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir les statistiques d'un joueur
|
||||
*/
|
||||
function getPlayerStatistics(userId) {
|
||||
const scores = LeaderboardManager.getAllScores().filter(s => s.userId === userId);
|
||||
|
||||
if (scores.length === 0) {
|
||||
return {
|
||||
totalGames: 0,
|
||||
bestScore: 0,
|
||||
averageScore: 0,
|
||||
averageTime: 0,
|
||||
bestTime: Infinity,
|
||||
winRate: 0,
|
||||
perfectGames: 0
|
||||
};
|
||||
}
|
||||
|
||||
const bestScore = Math.max(...scores.map(s => s.score), 0);
|
||||
const totalScore = scores.reduce((a, b) => a + b.score, 0);
|
||||
const totalTime = scores.reduce((a, b) => a + b.timeSeconds, 0);
|
||||
const bestTime = Math.min(...scores.map(s => s.timeSeconds), Infinity);
|
||||
const perfectGames = scores.filter(s => s.isPerfect).length;
|
||||
const winRate = (perfectGames / scores.length) * 100;
|
||||
|
||||
return {
|
||||
totalGames: scores.length,
|
||||
bestScore: bestScore,
|
||||
averageScore: Math.round(totalScore / scores.length),
|
||||
averageTime: Math.round(totalTime / scores.length),
|
||||
bestTime: bestTime === Infinity ? 0 : bestTime,
|
||||
winRate: Math.round(winRate),
|
||||
perfectGames: perfectGames
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Déboguer : afficher tous les scores en console
|
||||
*/
|
||||
function debugScores() {
|
||||
console.table(LeaderboardManager.getAllScores());
|
||||
}
|
||||
|
||||
/**
|
||||
* Déboguer : nettoyer tous les scores
|
||||
*/
|
||||
function clearAllScores() {
|
||||
if (confirm('Êtes-vous sûr de vouloir supprimer tous les scores?')) {
|
||||
localStorage.removeItem('nextgn_leaderboard');
|
||||
console.log('✓ Tous les scores ont été supprimés');
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mode développement
|
||||
*/
|
||||
const DEBUG = {
|
||||
enabled: localStorage.getItem('nextgn_debug') === 'true',
|
||||
|
||||
toggle() {
|
||||
this.enabled = !this.enabled;
|
||||
localStorage.setItem('nextgn_debug', this.enabled);
|
||||
console.log(`Debug mode: ${this.enabled ? 'ON' : 'OFF'}`);
|
||||
},
|
||||
|
||||
log(...args) {
|
||||
if (this.enabled) {
|
||||
console.log('[DEBUG]', ...args);
|
||||
}
|
||||
},
|
||||
|
||||
table(data) {
|
||||
if (this.enabled) {
|
||||
console.table(data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Activer le debug avec Ctrl+Shift+D
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.ctrlKey && e.shiftKey && e.key === 'D') {
|
||||
DEBUG.toggle();
|
||||
}
|
||||
});
|
||||
BIN
exercices/memory-pictogrammes/assets/pictogrammes/cmr 2.jpg
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
exercices/memory-pictogrammes/assets/pictogrammes/cmr.jpg
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
BIN
exercices/memory-pictogrammes/assets/pictogrammes/comburant.jpg
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
exercices/memory-pictogrammes/assets/pictogrammes/corrosif 2.jpg
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
exercices/memory-pictogrammes/assets/pictogrammes/corrosif.jpg
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
exercices/memory-pictogrammes/assets/pictogrammes/explosif 2.jpg
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
exercices/memory-pictogrammes/assets/pictogrammes/explosif.jpg
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
BIN
exercices/memory-pictogrammes/assets/pictogrammes/pollution.jpg
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
200
exercices/memory-pictogrammes/memory-pictogrammes 2.html
Normal file
@@ -0,0 +1,200 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Memory Pictogrammes GHS - NextGN Formation</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">
|
||||
|
||||
<!-- Styles -->
|
||||
<link rel="stylesheet" href="assets/css/memory-game.css">
|
||||
<link rel="stylesheet" href="assets/css/hud.css">
|
||||
<link rel="stylesheet" href="assets/css/animations.css">
|
||||
<link rel="stylesheet" href="assets/css/leaderboard.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- HEADER NextGN Formation -->
|
||||
<header class="nav-header">
|
||||
<a href="../../index.html" class="header-logo">
|
||||
<span>NextGN</span> Formation
|
||||
</a>
|
||||
<nav class="header-nav">
|
||||
<a href="../../index.html">Accueil</a>
|
||||
<a href="../../index.html#formations">Exercices interactifs</a>
|
||||
<a href="../../index.html#documentation">Documentation</a>
|
||||
<a href="../../index.html#rapports">Rapports de veille</a>
|
||||
<a href="../../index.html#about">À propos</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<!-- PROGRESS BAR -->
|
||||
<div class="progress">
|
||||
<div class="progress-fill" id="progress-fill"></div>
|
||||
</div>
|
||||
<!-- MAIN CONTENT -->
|
||||
<main class="game-container">
|
||||
<!-- INTRO SECTION -->
|
||||
<section class="intro-section">
|
||||
<h1>🎮 Memory Pictogrammes GHS</h1>
|
||||
<p class="subtitle">Maîtrisez les 9 symboles de danger</p>
|
||||
|
||||
<div class="intro-box">
|
||||
<p>
|
||||
<strong>Objectif :</strong> Retrouvez les paires en associant chaque pictogramme à sa signification.
|
||||
Plus rapide vous êtes, plus de points vous gagnez! 🏆
|
||||
</p>
|
||||
</div>
|
||||
<!-- DIFFICULTY SELECTOR -->
|
||||
<div class="difficulty-selector">
|
||||
<label>Choisir le niveau :</label>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-difficulty" data-difficulty="easy">
|
||||
<span class="btn-label">Facile</span>
|
||||
<span class="btn-desc">4 paires</span>
|
||||
</button>
|
||||
<button class="btn btn-difficulty active" data-difficulty="normal">
|
||||
<span class="btn-label">Normal</span>
|
||||
<span class="btn-desc">9 paires</span>
|
||||
</button>
|
||||
<button class="btn btn-difficulty" data-difficulty="hard">
|
||||
<span class="btn-label">Difficile</span>
|
||||
<span class="btn-desc">2 min chrono</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- GAME SECTION -->
|
||||
<section class="game-section">
|
||||
<!-- HUD (Stats en temps réel) -->
|
||||
<div class="game-hud">
|
||||
<div class="stat-box">
|
||||
<div class="label">Score</div>
|
||||
<div class="value" id="score-display">0</div>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="label">Mouvements</div>
|
||||
<div class="value" id="moves-display">0</div>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="label">Temps</div>
|
||||
<div class="value" id="timer-display">00:00</div>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="label">Paires</div>
|
||||
<div class="value" id="pairs-display">0/9</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- PROGRESS BAR -->
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar" 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>
|
||||
<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>
|
||||
<!-- PLAYER STATS -->
|
||||
<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>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- VICTORY MODAL -->
|
||||
<div class="victory-modal hidden" id="victory-modal">
|
||||
<div class="modal-content">
|
||||
<h2 id="victory-title">Bravo! 🎉</h2>
|
||||
<div class="score-display" id="final-score">0</div>
|
||||
|
||||
<div class="stats-summary">
|
||||
<div class="stat">
|
||||
<div class="stat-label">Mouvements</div>
|
||||
<div class="stat-value" id="modal-moves">0</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-label">Temps</div>
|
||||
<div class="stat-value" id="modal-time">00:00</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-label">Difficulté</div>
|
||||
<div class="stat-value" id="modal-difficulty">Normal</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-label">Bonus temps</div>
|
||||
<div class="stat-value" id="modal-bonus">+0</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ACHIEVEMENTS -->
|
||||
<div class="achievements-list" id="achievements-list"></div>
|
||||
|
||||
<div class="modal-actions">
|
||||
<button class="btn btn-primary" id="play-again-btn">
|
||||
🎮 Rejouer
|
||||
</button>
|
||||
<button class="btn btn-secondary" id="view-leaderboard-btn">
|
||||
🏆 Voir Classement
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- FOOTER -->
|
||||
<footer>
|
||||
<p>© 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>
|
||||
<!-- SCRIPTS -->
|
||||
<script src="assets/js/utils.js"></script>
|
||||
<script src="assets/js/game-state.js"></script>
|
||||
<script src="assets/js/analytics.js"></script>
|
||||
<script src="assets/js/leaderboard.js"></script>
|
||||
<script src="assets/js/memory-game.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
208
exercices/memory-pictogrammes/memory-pictogrammes.html
Normal file
@@ -0,0 +1,208 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Memory Pictogrammes GHS - NextGN Formation</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">
|
||||
|
||||
<!-- Styles -->
|
||||
<link rel="stylesheet" href="assets/css/memory-game.css">
|
||||
<link rel="stylesheet" href="assets/css/hud.css">
|
||||
<link rel="stylesheet" href="assets/css/animations.css">
|
||||
<link rel="stylesheet" href="assets/css/leaderboard.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- HEADER NextGN Formation -->
|
||||
<header class="nav-header">
|
||||
<a href="../../index.html" class="header-logo">
|
||||
<span>NextGN</span> Formation
|
||||
</a>
|
||||
<nav class="header-nav">
|
||||
<a href="../../index.html">Accueil</a>
|
||||
<a href="../../index.html#formations">Formations</a>
|
||||
<a href="../../index.html#contact">Contact</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<!-- PROGRESS BAR -->
|
||||
<div class="progress">
|
||||
<div class="progress-fill" id="progress-fill"></div>
|
||||
</div>
|
||||
|
||||
<!-- MAIN CONTENT -->
|
||||
<main class="game-container">
|
||||
<!-- INTRO SECTION -->
|
||||
<section class="intro-section">
|
||||
<h1>🎮 Memory Pictogrammes GHS</h1>
|
||||
<p class="subtitle">Maîtrisez les 9 symboles de danger</p>
|
||||
|
||||
<div class="intro-box">
|
||||
<p>
|
||||
<strong>Objectif :</strong> Retrouvez les paires en associant chaque pictogramme à sa signification.
|
||||
Plus rapide vous êtes, plus de points vous gagnez! 🏆
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- DIFFICULTY SELECTOR -->
|
||||
<div class="difficulty-selector">
|
||||
<label>Choisir le niveau :</label>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-difficulty" data-difficulty="easy">
|
||||
<span class="btn-label">Facile</span>
|
||||
<span class="btn-desc">4 paires</span>
|
||||
</button>
|
||||
<button class="btn btn-difficulty active" data-difficulty="normal">
|
||||
<span class="btn-label">Normal</span>
|
||||
<span class="btn-desc">9 paires</span>
|
||||
</button>
|
||||
<button class="btn btn-difficulty" data-difficulty="hard">
|
||||
<span class="btn-label">Difficile</span>
|
||||
<span class="btn-desc">2 min chrono</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- GAME SECTION -->
|
||||
<section class="game-section">
|
||||
<!-- HUD (Stats en temps réel) -->
|
||||
<div class="game-hud">
|
||||
<div class="stat-box">
|
||||
<div class="label">Score</div>
|
||||
<div class="value" id="score-display">0</div>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="label">Mouvements</div>
|
||||
<div class="value" id="moves-display">0</div>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="label">Temps</div>
|
||||
<div class="value" id="timer-display">00:00</div>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="label">Paires</div>
|
||||
<div class="value" id="pairs-display">0/9</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PROGRESS BAR -->
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar" 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>
|
||||
<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>
|
||||
|
||||
<!-- PLAYER STATS -->
|
||||
<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>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- VICTORY MODAL -->
|
||||
<div class="victory-modal hidden" id="victory-modal">
|
||||
<div class="modal-content">
|
||||
<h2 id="victory-title">Bravo! 🎉</h2>
|
||||
<div class="score-display" id="final-score">0</div>
|
||||
|
||||
<div class="stats-summary">
|
||||
<div class="stat">
|
||||
<div class="stat-label">Mouvements</div>
|
||||
<div class="stat-value" id="modal-moves">0</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-label">Temps</div>
|
||||
<div class="stat-value" id="modal-time">00:00</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-label">Difficulté</div>
|
||||
<div class="stat-value" id="modal-difficulty">Normal</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-label">Bonus temps</div>
|
||||
<div class="stat-value" id="modal-bonus">+0</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ACHIEVEMENTS -->
|
||||
<div class="achievements-list" id="achievements-list"></div>
|
||||
|
||||
<div class="modal-actions">
|
||||
<button class="btn btn-primary" id="play-again-btn">
|
||||
🎮 Rejouer
|
||||
</button>
|
||||
<button class="btn btn-secondary" id="view-leaderboard-btn">
|
||||
🏆 Voir Classement
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<footer>
|
||||
<p>© 2024 NextGN Formation - Memory Pictogrammes GHS</p>
|
||||
<p style="margin-top: 10px; opacity: 0.6;">Tous droits réservés - Formateurs indépendants</p>
|
||||
</footer>
|
||||
|
||||
<!-- SCRIPTS -->
|
||||
<script src="assets/js/utils.js"></script>
|
||||
<script src="assets/js/game-state.js"></script>
|
||||
<script src="assets/js/analytics.js"></script>
|
||||
<script src="assets/js/leaderboard.js"></script>
|
||||
<script src="assets/js/memory-game.js"></script>
|
||||
</body>
|
||||
</html>
|
||||