From fa62e6e01ef20f327da57e1b38d9cce67a8bc7b0 Mon Sep 17 00:00:00 2001 From: sebvtl Date: Mon, 15 Jun 2026 05:51:29 +0000 Subject: [PATCH] =?UTF-8?q?T=C3=A9l=C3=A9verser=20les=20fichiers=20vers=20?= =?UTF-8?q?"/"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 410 ++++++++++++++++++++ script.js | 906 +++++++++++++++++++++++++++++++++++++++++++ style.css | 1081 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 2397 insertions(+) create mode 100644 index.html create mode 100644 script.js create mode 100644 style.css diff --git a/index.html b/index.html new file mode 100644 index 0000000..b5fb958 --- /dev/null +++ b/index.html @@ -0,0 +1,410 @@ + + + + + + Cours -vPrendre en main Brevo + + + + + +
+
+

Arinfo · Niveau débutant à intermédiaire

+

Prendre en main Brevo : contacts, campagnes, formulaires et automatisations

+

Un cours step by step pour créer une première base de contacts propre, envoyer une campagne email simple et construire un scénario d’accueil automatisé.

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

0. Quiz de positionnement

+

Objectif : identifier le niveau de départ avant de lancer le cours.

+
+ +

+
+ +
+

1. Objectifs pédagogiques

+
    +
  • +
  • +
  • +
  • +
  • +
  • +
+
Point de vigilance RGPD : n’importez jamais de contacts sans preuve de consentement. Une base sale détruit la délivrabilité et l’image de marque.
+
+ +
+

2. Comprendre Brevo en 10 minutes

+

Brevo sert à gérer des contacts, envoyer des campagnes marketing, créer des formulaires, automatiser des actions et suivre les performances. Les emails transactionnels sont différents : ils sont déclenchés par une action utilisateur, par exemple une création de compte, une confirmation de commande ou une réinitialisation de mot de passe.

+
+

Contacts

Base de données : emails, attributs, listes, segments.

+

Campagnes

Emails marketing envoyés à une audience ciblée.

+

Formulaires

Collecte propre de contacts avec consentement.

+

Automatisations

Scénarios déclenchés par des événements ou comportements.

+
+
+

Exercice 2 : Classifier les types d'emails (Drag and Drop)

+

Glisse et dépose les exemples d'emails dans leur catégorie correspondante, ou clique sur les cartes pour les déplacer :

+ + +
+
Newsletter mensuelle
+
Email de bienvenue
+
Confirmation de commande
+
Relance panier abandonné
+
+ + +
+
+

Campagnes (Marketing)

+
+
+
+

Automatisations

+
+
+
+

Transactionnel

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

3. Step by step - Créer une base de contacts propre

+
    +
  1. Préparer le fichier CSV. Colonnes minimales : email, prénom, source, consentement, date d’inscription.
  2. +
  3. Créer une liste. Exemple : “Prospects - Atelier IA”.
  4. +
  5. Importer les contacts. Mapper chaque colonne avec le bon attribut Brevo.
  6. +
  7. Contrôler la qualité. Supprimer doublons, emails génériques douteux et contacts sans consentement.
  8. +
  9. Segmenter. Exemple : source = formulaire LinkedIn, intérêt = formation IA.
  10. +
+
+

Exercice 3 : Nettoyer et valider une liste de contacts avant import

+

Voici 5 contacts extraits d'un fichier CSV d'inscription. Décide pour chaque contact s'il est conforme aux règles RGPD et de délivrabilité de Brevo en choisissant "Importer" ou "Rejeter" :

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
EmailSourceConsentementAction
Formulaire NewsletterOui (Double Opt-In) + + +
Achat de base de données B2BNon + + +
Formulaire de contact généralOui + + +
Formulaire NewsletterOui + + +
Téléchargement Livre BlancOui + + +
+
+ +
+ + +
+
+
+
+ +
+

4. Step by step - Créer une campagne email

+
    +
  1. Définir l’objectif. Exemple : inscription à un webinaire, téléchargement d’un guide, prise de rendez-vous.
  2. +
  3. Choisir l’audience. Liste complète ou segment précis.
  4. +
  5. Rédiger l’objet. Court, concret, sans promesse floue.
  6. +
  7. Construire l’email. Une accroche, une valeur, une preuve, un appel à l’action.
  8. +
  9. Tester. Envoyer un test, vérifier mobile, liens, fautes, expéditeur.
  10. +
  11. Programmer. Choisir une date cohérente avec l’audience.
  12. +
+
+

Structure recommandée

+

Objet : [Bénéfice clair] en [temps court]

+

Corps : problème → solution → bénéfice → preuve → bouton unique.

+

CTA : “Réserver ma place”, “Télécharger le guide”, “Demander une démo”.

+
+
+

Exercice 4 : Rédiger un objet d'email percutant

+

Rédige un objet d'email pour promouvoir une formation "Découvrir l’IA générative en entreprise". Évite les termes spam, soigne la longueur et cherche à capter l'intérêt.

+ + +
+ + +
+
+ +
+ 💡 Voir des exemples d'objets recommandés +
+
    +
  • Approche Curiosité / Directe : L'IA générative dans votre entreprise : par où commencer ? (Clair, va droit au but).
  • +
  • Approche Bénéfice / Urgence : {{ contact.PRENOM }}, gagnez 2h par jour grâce à l'IA (Personnalisé, quantifie le gain de temps).
  • +
  • Approche Chiffre / Preuve : 3 cas d'usage réels de l'IA dans votre secteur (Donne envie de découvrir les exemples concrets).
  • +
+
+
+
+
+ +
+

5. Step by step - Créer un formulaire d’inscription

+
    +
  1. Définir la promesse. Pourquoi la personne laisserait-elle son email ?
  2. +
  3. Limiter les champs. Email + prénom suffisent souvent au départ.
  4. +
  5. Ajouter le consentement. Formulation claire, non ambiguë.
  6. +
  7. Associer à une liste. Exemple : “Inscrits newsletter”.
  8. +
  9. Tester l’inscription. Vérifier l’ajout dans la liste et le message de confirmation.
  10. +
+
+

Exercice 5 : Rédiger la clause de consentement RGPD

+

Écris la phrase de consentement (opt-in) qui accompagnera la case à cocher de ton formulaire d'inscription. Elle doit indiquer ce que l'abonné recevra et comment il peut se désabonner.

+ + +
+ + +
+
+ +
+ 💡 Voir la clause de consentement type (conforme RGPD) +
+

"En renseignant votre adresse email, vous acceptez de recevoir nos actualités et offres promotionnelles par courrier électronique. Vous pouvez vous désabonner à tout moment à l'aide des liens de désinscription intégrés dans nos emails."

+

Pourquoi cette clause est conforme :

+
    +
  • Précision : Elle définit la nature des envois (actualités, offres).
  • +
  • Libre arbitre : Elle rappelle que le désabonnement est possible à tout moment et facilement.
  • +
  • Actif : Elle doit être associée à une case à cocher non pré-cochée.
  • +
+
+
+
+
+ +
+

6. Step by step - Créer une automatisation d’accueil

+

Brevo permet de créer des automatisations marketing simples, comme des messages de bienvenue, de l’onboarding, des relances panier abandonné, des anniversaires, du lead scoring ou de la gestion de listes.

+
    +
  1. Déclencheur. Contact ajouté à la liste “Inscrits newsletter”.
  2. +
  3. Délai. Attendre 5 minutes pour éviter l’effet robot immédiat.
  4. +
  5. Email 1. Bienvenue + rappel de la promesse.
  6. +
  7. Délai. Attendre 2 jours.
  8. +
  9. Email 2. Ressource utile ou cas pratique.
  10. +
  11. Condition. Si clic sur le lien : tag “intéressé”. Sinon : relance légère.
  12. +
+
+ Entrée listeAttenteEmail 1AttenteCondition clic +
+
+

Exercice 6 : Ordonner les étapes d'un scénario d'automatisation

+

Organise ces 5 actions pour construire un scénario d'onboarding cohérent et efficace en cliquant sur les flèches pour déplacer les étapes vers le haut ou le bas :

+ +
    +
  • +
    + Délai Attendre 5 minutes avant d'envoyer +
    +
    + + +
    +
  • +
  • +
    + Action Envoyer l'Email 2 (Ressource pratique & conseils) +
    +
    + + +
    +
  • +
  • +
    + Déclencheur Le contact s'inscrit au formulaire "Newsletter" +
    +
    + + +
    +
  • +
  • +
    + Délai Attendre 2 jours +
    +
    + + +
    +
  • +
  • +
    + Action Envoyer l'Email 1 de bienvenue (Rappel de la promesse) +
    +
    + + +
    +
  • +
+ +
+ + +
+
+
+
+ +
+

7. Lire les résultats sans se noyer

+ + + + + + + + +
IndicateurCe qu’il ditAction si mauvais résultat
Taux d’ouvertureAttractivité de l’objet et confiance envers l’expéditeur.Tester un objet plus clair, segmenter mieux.
Taux de clicIntérêt pour le contenu et le CTA.Réduire le contenu, rendre le bouton plus visible.
DésinscriptionsÉcart entre promesse et contenu reçu.Revoir fréquence, ciblage et valeur de l’email.
RebondsQualité technique de la base email.Nettoyer la base, supprimer emails invalides.
+
+ +
+

8. Atelier final

+

Mission : préparer une mini-stratégie Brevo pour promouvoir une formation courte.

+
    +
  1. Créer une liste cible.
  2. +
  3. Définir un formulaire d’inscription.
  4. +
  5. Rédiger un objet de campagne.
  6. +
  7. Créer un scénario d’accueil en 3 emails.
  8. +
  9. Définir 3 indicateurs de suivi.
  10. +
+ + +
+ 💡 Afficher un exemple de plan d'action type pour s'auto-évaluer +
+

Exemple de plan d'action recommandé :

+
    +
  1. Créer une liste cible : Liste "Prospects - Formation Courte".
  2. +
  3. Formulaire d'inscription : Intégré sur le site, demandant Prénom et Email, avec une clause de consentement claire (opt-in actif).
  4. +
  5. Objet de campagne : "{{ contact.PRENOM }}, maîtrisez [Sujet] en 3 jours".
  6. +
  7. Scénario d'accueil en 3 emails : +
      +
    • Déclencheur : Inscription au formulaire.
    • +
    • Email 1 (immédiat) : Contenu promis + liens utiles.
    • +
    • Email 2 (J+1) : Étude de cas ou témoignage client pour asseoir la crédibilité.
    • +
    • Email 3 (J+3) : Offre commerciale avec appel à l'action direct (réserver un appel).
    • +
    +
  8. +
  9. 3 indicateurs de suivi : Taux d'ouverture (qualité de l'objet), Taux de clic (intérêt pour l'offre/CTA), et Taux de désinscription (pertinence globale).
  10. +
+
+
+
+ +
+

9. Quiz final

+
+ +

+
+ +
+

Félicitations ! Vous avez complété le cours 🎉

+

Vous êtes arrivé au bout de ce cours interactif Brevo. Vous pouvez exporter l'ensemble du cours incluant vos réponses, vos notes et vos corrections au format PDF pour le conserver ou le partager.

+
+ +
+
+ +
+

Sources utilisées pour la conception du cours

+
    +
  • Documentation Brevo : automatisations, templates, emails transactionnels, SMTP, migration vers le nouvel éditeur d’automatisation.
  • +
  • Le cours évite volontairement les captures d’écran figées : l’interface Brevo évolue, les libellés peuvent changer.
  • +
+
+
+ + + + + + diff --git a/script.js b/script.js new file mode 100644 index 0000000..e740c0b --- /dev/null +++ b/script.js @@ -0,0 +1,906 @@ +const quizzes = { + positioning: [ + { + q: "Brevo sert principalement à…", + a: ["Créer uniquement des sites web", "Gérer contacts, campagnes et automatisations", "Remplacer un CRM complet dans tous les cas"], + c: 1, + e: "Brevo est une plateforme marketing relationnelle tout-en-un pour gérer contacts, campagnes et scénarios automatisés." + }, + { + q: "Avant d’importer une liste de contacts, il faut vérifier…", + a: ["Le consentement et la qualité des emails", "La couleur du logo", "Le nombre d’images dans l’email"], + c: 0, + e: "Le RGPD et les règles de délivrabilité imposent de vérifier le consentement explicite et d'assainir la liste." + }, + { + q: "Un email transactionnel est déclenché par…", + a: ["Une action utilisateur ou un événement", "Une envie de communiquer une promotion", "Un post LinkedIn"], + c: 0, + e: "Les emails transactionnels répondent à une action directe (création de compte, achat) et sont attendus immédiatement." + }, + { + q: "Le meilleur premier indicateur d’une campagne est toujours…", + a: ["Le taux d’ouverture seul", "Le chiffre d’affaires seul", "Un ensemble ouverture + clic + désinscription + rebonds"], + c: 2, + e: "Analyser un ensemble d'indicateurs croisés donne une vision réelle des performances et de la santé de la délivrabilité." + }, + { + q: "Une automatisation commence généralement par…", + a: ["Un déclencheur", "Un PDF", "Une facture"], + c: 0, + e: "Tout scénario automatisé débute par un déclencheur (trigger) lié à une action ou un attribut de contact." + } + ], + final: [ + { + q: "Quel est le risque d’une base de contacts non qualifiée ?", + a: ["Améliorer la délivrabilité", "Dégrader la délivrabilité et la confiance", "Augmenter automatiquement les ventes"], + c: 1, + e: "Importer des adresses non consentantes ou invalides détruit la réputation d'expéditeur et peut mener au blocage du compte." + }, + { + q: "Quelle structure d’email est la plus efficace ?", + a: ["Long texte sans bouton", "Problème, solution, bénéfice, preuve, CTA", "Images uniquement"], + c: 1, + e: "La structure AIDA (Attention, Intérêt, Désir, Action) structure la valeur apportée et amène naturellement au clic." + }, + { + q: "Pourquoi limiter les champs d’un formulaire ?", + a: ["Pour réduire la friction d’inscription", "Pour collecter moins de consentement", "Pour éviter les statistiques"], + c: 0, + e: "Moins il y a d'étapes et de champs, plus le taux de conversion du formulaire d'inscription sera élevé." + }, + { + q: "Dans un scénario d’accueil, une condition de clic sert à…", + a: ["Supprimer tous les contacts", "Adapter la suite selon le comportement", "Changer le mot de passe"], + c: 1, + e: "Les branches conditionnelles adaptent la communication selon le niveau d'engagement de l'apprenant." + }, + { + q: "Le taux de clic mesure surtout…", + a: ["L’intérêt pour le contenu et le CTA", "La qualité du serveur DNS uniquement", "Le nombre de contacts importés"], + c: 0, + e: "Le clic démontre la pertinence du contenu proposé et la clarté du bouton d'appel à l'action." + }, + { + q: "Un template d’email sert à…", + a: ["Créer une structure réutilisable", "Bloquer les désinscriptions", "Importer un CSV"], + c: 0, + e: "Les templates assurent l'homogénéité graphique de la marque et font gagner du temps à chaque envoi." + }, + { + q: "Quel élément doit être testé avant l’envoi ?", + a: ["Liens, mobile, expéditeur, fautes", "Uniquement la taille du logo", "Rien si le design est joli"], + c: 0, + e: "Valider l'objet, l'expéditeur, l'affichage sur mobile et les liens évite les erreurs critiques de communication." + }, + { + q: "Une bonne segmentation permet de…", + a: ["Envoyer le même message à tout le monde", "Cibler selon des critères pertinents", "Éviter le consentement"], + c: 1, + e: "Segmenter permet d'envoyer des messages ciblés selon les besoins ou comportements, maximisant l'engagement." + }, + { + q: "Une phrase de consentement doit être…", + a: ["Floue", "Claire et explicite", "Cachée en bas de page"], + c: 1, + e: "Le RGPD impose une clause explicite, sans ambiguïté, informant l'utilisateur de l'usage fait de ses données." + }, + { + q: "Un workflow d’automatisation Brevo peut servir à…", + a: ["Messages de bienvenue, relance, onboarding", "Créer un logo automatiquement", "Corriger les fautes d’un site web"], + c: 0, + e: "L'automatisation gère l'onboarding, l'engagement et les rappels tout au long de la vie du contact." + } + ] +}; + +// --- INITIALISATION DES QUIZ --- +function renderQuiz(name) { + const box = document.querySelector(`[data-quiz="${name}"]`); + box.innerHTML = quizzes[name].map((item, i) => ` +
+ ${i + 1}. ${item.q} + ${item.a.map((x, j) => ` + + `).join("")} +
+ `).join(""); +} + +renderQuiz('positioning'); +renderQuiz('final'); + +function checkQuiz(name) { + let score = 0; + const quizItems = document.querySelectorAll(`[data-quiz="${name}"] .quiz-item`); + + quizzes[name].forEach((item, i) => { + const quizItem = quizItems[i]; + quizItem.classList.remove('correct-quiz', 'incorrect-quiz'); + const oldExplanation = quizItem.querySelector('.quiz-explanation'); + if (oldExplanation) oldExplanation.remove(); + + const picked = document.querySelector(`input[name="${name}_${i}"]:checked`); + const isCorrect = picked && Number(picked.value) === item.c; + + if (isCorrect) { + score++; + quizItem.classList.add('correct-quiz'); + } else { + quizItem.classList.add('incorrect-quiz'); + } + + const exp = document.createElement('div'); + exp.className = 'quiz-explanation'; + exp.innerHTML = `${isCorrect ? '✓' : '✗'} Correction : ${item.e}`; + quizItem.appendChild(exp); + }); + + const pct = Math.round(score / quizzes[name].length * 100); + const msg = name === 'positioning' + ? `Score : ${score}/${quizzes[name].length}. ${pct < 50 ? 'Niveau débutant : suivre toutes les étapes sans sauter les exercices.' : pct < 80 ? 'Base correcte : concentrer l’effort sur automatisations et analyse.' : 'Bon niveau : viser l’atelier final et l’optimisation.'}` + : `Score final : ${score}/${quizzes[name].length} (${pct}%). ${pct >= 80 ? 'Acquis validés.' : 'Revoir les modules contacts, campagnes et automatisations.'}`; + + const resultEl = document.getElementById(name === 'positioning' ? 'positioningResult' : 'finalResult'); + resultEl.textContent = msg; + resultEl.style.display = 'block'; + resultEl.className = `result ${pct >= 80 ? 'badge-success' : pct >= 50 ? 'warning' : 'badge-error'}`; + + saveState(); + updateProgress(); +} + +document.querySelectorAll('[data-check]').forEach(b => b.addEventListener('click', () => checkQuiz(b.dataset.check))); + +// --- EXERCICE 2 : DRAG & DROP & CLICK --- +let selectedDragCard = null; +let ex2Validated = false; +const ex2Solutions = { + "ex2_card_newsletter": "campagne", + "ex2_card_bienvenue": "automatisation", + "ex2_card_commande": "transactionnel", + "ex2_card_panier": "automatisation" +}; + +function setupDragAndDrop() { + const cards = document.querySelectorAll('#exercise2 .drag-card'); + const zones = document.querySelectorAll('#exercise2 .drop-zone'); + const sourceContainer = document.getElementById('ex2_cards'); + + cards.forEach(card => { + card.addEventListener('dragstart', (e) => { + card.classList.add('dragging'); + e.dataTransfer.setData('text/plain', card.id); + }); + + card.addEventListener('dragend', () => { + card.classList.remove('dragging'); + saveState(); + updateProgress(); + }); + + card.addEventListener('click', (e) => { + e.stopPropagation(); + cards.forEach(c => { + if (c !== card) c.style.outline = 'none'; + }); + + if (selectedDragCard === card) { + selectedDragCard.style.outline = 'none'; + selectedDragCard = null; + } else { + selectedDragCard = card; + selectedDragCard.style.outline = '3px solid var(--brand)'; + } + }); + }); + + sourceContainer.addEventListener('dragover', (e) => e.preventDefault()); + sourceContainer.addEventListener('drop', (e) => { + e.preventDefault(); + const cardId = e.dataTransfer.getData('text/plain'); + const card = document.getElementById(cardId); + if (card) { + sourceContainer.appendChild(card); + card.classList.remove('correct-card', 'incorrect-card'); + ex2Validated = false; + document.getElementById('exercise2Feedback').className = 'exercise-feedback'; + saveState(); + updateProgress(); + } + }); + + sourceContainer.addEventListener('click', () => { + if (selectedDragCard) { + sourceContainer.appendChild(selectedDragCard); + selectedDragCard.classList.remove('correct-card', 'incorrect-card'); + selectedDragCard.style.outline = 'none'; + selectedDragCard = null; + ex2Validated = false; + document.getElementById('exercise2Feedback').className = 'exercise-feedback'; + saveState(); + updateProgress(); + } + }); + + zones.forEach(zone => { + const content = zone.querySelector('.drop-zone-content'); + + zone.addEventListener('dragover', (e) => { + e.preventDefault(); + zone.classList.add('dragover'); + }); + + zone.addEventListener('dragleave', () => { + zone.classList.remove('dragover'); + }); + + zone.addEventListener('drop', (e) => { + e.preventDefault(); + zone.classList.remove('dragover'); + const cardId = e.dataTransfer.getData('text/plain'); + const card = document.getElementById(cardId); + if (card) { + content.appendChild(card); + card.classList.remove('correct-card', 'incorrect-card'); + ex2Validated = false; + document.getElementById('exercise2Feedback').className = 'exercise-feedback'; + saveState(); + updateProgress(); + } + }); + + zone.addEventListener('click', () => { + if (selectedDragCard) { + content.appendChild(selectedDragCard); + selectedDragCard.classList.remove('correct-card', 'incorrect-card'); + selectedDragCard.style.outline = 'none'; + selectedDragCard = null; + ex2Validated = false; + document.getElementById('exercise2Feedback').className = 'exercise-feedback'; + saveState(); + updateProgress(); + } + }); + }); +} + +function checkExercise2() { + const cards = document.querySelectorAll('#exercise2 .drag-card'); + let correctCount = 0; + const totalCards = cards.length; + let missing = 0; + + cards.forEach(card => { + const parentZone = card.closest('.drop-zone'); + card.classList.remove('correct-card', 'incorrect-card'); + + if (!parentZone) { + missing++; + return; + } + + const category = parentZone.dataset.category; + const expected = ex2Solutions[card.id]; + + if (category === expected) { + card.classList.add('correct-card'); + correctCount++; + } else { + card.classList.add('incorrect-card'); + } + }); + + const feedback = document.getElementById('exercise2Feedback'); + feedback.className = 'exercise-feedback show'; + + if (missing > 0) { + feedback.className = 'exercise-feedback show warning'; + feedback.innerHTML = `

⚠️ Attention : Il reste ${missing} carte(s) non classée(s). Glisse-les toutes dans une colonne avant de lancer la correction.

`; + return; + } + + ex2Validated = true; + if (correctCount === totalCards) { + feedback.className = 'exercise-feedback show correct'; + feedback.innerHTML = `

🎉 Félicitations ! Toutes les cartes sont bien classées.

+ `; + } else { + feedback.className = 'exercise-feedback show incorrect'; + feedback.innerHTML = `

Il y a des erreurs (${correctCount}/${totalCards} correctes).

+

Consulte la couleur des cartes : celles en vert sont correctes, celles en rouge sont incorrectes. Déplace à nouveau les cartes incorrectes pour résoudre l'exercice !

`; + } + + saveState(); + updateProgress(); +} + +function resetExercise2() { + const sourceContainer = document.getElementById('ex2_cards'); + const cards = document.querySelectorAll('#exercise2 .drag-card'); + cards.forEach(card => { + card.classList.remove('correct-card', 'incorrect-card'); + card.style.outline = 'none'; + sourceContainer.appendChild(card); + }); + selectedDragCard = null; + ex2Validated = false; + + const feedback = document.getElementById('exercise2Feedback'); + feedback.className = 'exercise-feedback'; + feedback.innerHTML = ''; + + saveState(); + updateProgress(); +} + +// --- EXERCICE 3 : SIMULATEUR DE NETTOYAGE --- +let ex3Validated = false; +const ex3Solutions = { + "ex3_row1": "import", + "ex3_row2": "reject", + "ex3_row3": "reject", + "ex3_row4": "reject", + "ex3_row5": "import" +}; +const ex3Explanations = { + "ex3_row1": { + correct: "Correct. Contact valide et double opt-in conforme RGPD.", + incorrect: "Erreur. Ce contact est parfaitement conforme et doit être importé." + }, + "ex3_row2": { + correct: "Correct. Rejeté à juste titre : l'achat de bases de données est strictement illégal sous le RGPD et banni par Brevo.", + incorrect: "Erreur critique. L'achat de contacts est interdit par le RGPD et les CGU de Brevo. Cela conduit au blocage de votre compte." + }, + "ex3_row3": { + correct: "Correct. Rejeté : les adresses génériques de rôles (support@, info@) polluent les listes et dégradent la délivrabilité.", + incorrect: "Erreur. L'adresse support@ est une adresse de rôle/générique à rejeter pour le marketing direct." + }, + "ex3_row4": { + correct: "Correct. Rejeté : l'adresse email est invalide (absence de domaine valide comme .com ou .fr), ce qui causerait un rebond dur.", + incorrect: "Erreur. L'adresse ne contient pas d'extension de domaine (@gmail) et provoquera un rebond critique (hard bounce)." + }, + "ex3_row5": { + correct: "Correct. Contact valide : l'inscription via livre blanc avec opt-in est une source légitime.", + incorrect: "Erreur. Ce contact est individuel et a donné son consentement via le livre blanc, il doit être importé." + } +}; + +function checkExercise3() { + let score = 0; + let answeredRows = 0; + const tbody = document.getElementById('ex3_tbody'); + + document.querySelectorAll('.row-explanation').forEach(el => el.remove()); + document.querySelectorAll('#ex3_tbody tr').forEach(tr => tr.className = ''); + + for (let i = 1; i <= 5; i++) { + const rowId = `ex3_row${i}`; + const selected = document.querySelector(`input[name="${rowId}"]:checked`); + const tr = tbody.querySelector(`tr[data-row="${i}"]`); + + if (selected) { + answeredRows++; + const val = selected.value; + const expected = ex3Solutions[rowId]; + const isCorrect = val === expected; + + const expDiv = document.createElement('div'); + expDiv.className = `row-explanation ${isCorrect ? 'text-success' : 'text-error'}`; + + if (isCorrect) { + tr.classList.add('correct-row'); + expDiv.innerHTML = `✓ ${ex3Explanations[rowId].correct}`; + score++; + } else { + tr.classList.add('incorrect-row'); + expDiv.innerHTML = `✗ ${ex3Explanations[rowId].incorrect}`; + } + tr.querySelector('.cell-action').appendChild(expDiv); + } + } + + const feedback = document.getElementById('exercise3Feedback'); + feedback.className = 'exercise-feedback show'; + + if (answeredRows < 5) { + feedback.className = 'exercise-feedback show warning'; + feedback.innerHTML = `

⚠️ Attention : Fais un choix (Importer/Rejeter) pour les 5 contacts avant de valider.

`; + return; + } + + ex3Validated = true; + if (score === 5) { + feedback.className = 'exercise-feedback show correct'; + feedback.innerHTML = `

🎉 Félicitations ! 5/5 correct. Tu as nettoyé ta liste comme un professionnel et protégé ta délivrabilité.

`; + } else { + feedback.className = 'exercise-feedback show incorrect'; + feedback.innerHTML = `

Score : ${score}/5. Tu as commis des erreurs de qualification. Relis le détail sous chaque adresse pour comprendre les règles.

`; + } + + saveState(); + updateProgress(); +} + +function resetExercise3() { + document.querySelectorAll('input[name^="ex3_row"]').forEach(r => r.checked = false); + document.querySelectorAll('.row-explanation').forEach(el => el.remove()); + document.querySelectorAll('#ex3_tbody tr').forEach(tr => tr.className = ''); + + const feedback = document.getElementById('exercise3Feedback'); + feedback.className = 'exercise-feedback'; + feedback.innerHTML = ''; + ex3Validated = false; + + saveState(); + updateProgress(); +} + +// --- EXERCICE 4 : ANALYSEUR D'OBJET D'EMAIL --- +let ex4Validated = false; + +function checkExercise4() { + const input = document.getElementById('ex4_input'); + const text = input.value.trim(); + const feedback = document.getElementById('exercise4Feedback'); + feedback.className = 'exercise-feedback show'; + + if (!text) { + feedback.className = 'exercise-feedback show warning'; + feedback.innerHTML = `

⚠️ Attention : Rédige un objet d'email avant de lancer l'analyseur.

`; + return; + } + + ex4Validated = true; + let score = 5; + const issues = []; + const successes = []; + + if (text.length < 15) { + score -= 1.5; + issues.push("L'objet est trop court. Il manque d'impact descriptif."); + } else if (text.length > 55) { + score -= 1.5; + issues.push(`L'objet est trop long (${text.length} car.). Il sera tronqué sur la majorité des téléphones mobiles (limite à ~50 car.).`); + } else { + successes.push(`Longueur optimale de l'objet (${text.length} caractères) !`); + } + + const hasPersonalization = /\{\{|contact\.|prenom|prénom|\[\w+\]/i.test(text); + if (hasPersonalization) { + successes.push("Présence d'un tag de personnalisation détectée. Excellent pour booster l'ouverture !"); + } else { + issues.push("Aucune variable de personnalisation détectée (ex: {{ contact.PRENOM }}). C'est un atout d'attention."); + } + + const spamWords = ["gratuit", "offert", "vite", "100%", "argent", "gagner", "promotion", "promo", "urgent", "cadeau", "!!!"]; + const foundSpam = []; + spamWords.forEach(word => { + if (text.toLowerCase().includes(word)) foundSpam.push(word); + }); + + if (foundSpam.length > 0) { + score -= foundSpam.length * 1.0; + issues.push(`Mots risqués identifiés : ${foundSpam.join(', ')}. Ils risquent de déclencher les filtres anti-spam.`); + } else { + successes.push("Pas de spam-words flagrants détectés."); + } + + score = Math.max(1, Math.min(5, score)); + const stars = "★".repeat(Math.round(score)) + "☆".repeat(5 - Math.round(score)); + + if (score >= 4) { + feedback.className = 'exercise-feedback show correct'; + } else if (score >= 2.5) { + feedback.className = 'exercise-feedback show warning'; + } else { + feedback.className = 'exercise-feedback show incorrect'; + } + + let html = `

Optimisation de l'objet : ${stars} (${score.toFixed(1)}/5)

`; + if (successes.length > 0) { + html += `

Points forts :
` + successes.map(s => `✓ ${s}`).join('
') + `

`; + } + if (issues.length > 0) { + html += `

Recommandations :
` + issues.map(i => `✗ ${i}`).join('
') + `

`; + } + + feedback.innerHTML = html; + saveState(); + updateProgress(); +} + +function resetExercise4() { + document.getElementById('ex4_input').value = ''; + const feedback = document.getElementById('exercise4Feedback'); + feedback.className = 'exercise-feedback'; + feedback.innerHTML = ''; + ex4Validated = false; + + saveState(); + updateProgress(); +} + +// --- EXERCICE 5 : CLAUSE RGPD --- +let ex5Validated = false; + +function checkExercise5() { + const textarea = document.getElementById('ex5_textarea'); + const text = textarea.value.trim(); + const feedback = document.getElementById('exercise5Feedback'); + feedback.className = 'exercise-feedback show'; + + if (!text) { + feedback.className = 'exercise-feedback show warning'; + feedback.innerHTML = `

⚠️ Attention : Saisis une clause de consentement avant de lancer la validation.

`; + return; + } + + ex5Validated = true; + let score = 0; + const findings = []; + + const serviceCheck = /recevoir|envoi|email|newsletter|courriel|communication|actualité|actualite|offre|lettre/i.test(text); + if (serviceCheck) { + score += 50; + findings.push("✓ Description des communications : Conforme. L'utilisateur sait quel contenu il va recevoir."); + } else { + findings.push("✗ Description du service manquante : Tu dois mentionner précisément le but de la collecte (ex : 'recevoir nos conseils hebdomadaires')."); + } + + const unsubCheck = /désabonner|desabonner|désinscription|desinscription|lien|retirer|moment/i.test(text); + if (unsubCheck) { + score += 50; + findings.push("✓ Droit de retrait (Désabonnement) : Conforme. La méthode pour se rétracter simplement et à tout moment est mentionnée."); + } else { + findings.push("✗ Droit de retrait manquant : Précise impérativement la possibilité de retrait (ex : 'Vous pouvez vous désabonner à tout moment via le lien de désinscription')."); + } + + if (score === 100) { + feedback.className = 'exercise-feedback show correct'; + feedback.innerHTML = `

🎉 Clause de consentement 100% conforme au RGPD !

` + findings.map(f => `

${f}

`).join(''); + } else if (score === 50) { + feedback.className = 'exercise-feedback show warning'; + feedback.innerHTML = `

⚠️ Clause partiellement conforme (50% de conformité) :

` + findings.map(f => `

${f}

`).join(''); + } else { + feedback.className = 'exercise-feedback show incorrect'; + feedback.innerHTML = `

Clause non conforme (0% de conformité) :

` + findings.map(f => `

${f}

`).join(''); + } + + saveState(); + updateProgress(); +} + +function resetExercise5() { + document.getElementById('ex5_textarea').value = ''; + const feedback = document.getElementById('exercise5Feedback'); + feedback.className = 'exercise-feedback'; + feedback.innerHTML = ''; + ex5Validated = false; + + saveState(); + updateProgress(); +} + +// --- EXERCICE 6 : SÉQUENCEUR DE WORKFLOW --- +const ex6SolutionOrder = ["declencheur", "attente1", "email1", "attente2", "email2"]; +let ex6Validated = false; + +function moveStepUp(button) { + const item = button.closest('.sortable-item'); + const previous = item.previousElementSibling; + if (previous) { + item.parentNode.insertBefore(item, previous); + clearEx6Feedback(); + saveState(); + updateProgress(); + } +} + +function moveStepDown(button) { + const item = button.closest('.sortable-item'); + const next = item.nextElementSibling; + if (next) { + item.parentNode.insertBefore(next, item); + clearEx6Feedback(); + saveState(); + updateProgress(); + } +} + +function clearEx6Feedback() { + const items = document.querySelectorAll('#ex6_list .sortable-item'); + items.forEach(item => { + item.classList.remove('correct-item', 'incorrect-item'); + }); + const feedback = document.getElementById('exercise6Feedback'); + feedback.className = 'exercise-feedback'; + feedback.innerHTML = ''; + ex6Validated = false; +} + +function checkExercise6() { + const items = document.querySelectorAll('#ex6_list .sortable-item'); + let correctCount = 0; + + items.forEach((item, index) => { + const step = item.dataset.step; + const expected = ex6SolutionOrder[index]; + item.classList.remove('correct-item', 'incorrect-item'); + + if (step === expected) { + item.classList.add('correct-item'); + correctCount++; + } else { + item.classList.add('incorrect-item'); + } + }); + + const feedback = document.getElementById('exercise6Feedback'); + feedback.className = 'exercise-feedback show'; + ex6Validated = true; + + if (correctCount === 5) { + feedback.className = 'exercise-feedback show correct'; + feedback.innerHTML = `

🎉 Scénario d'accueil 100% correct ! Tu as configuré le flux logique idéal :

+
    +
  1. Déclencheur : Le client s'inscrit au formulaire (entrée de données).
  2. +
  3. Délai (5 minutes) : Pour temporiser l'envoi et paraître naturel.
  4. +
  5. Action (Email 1) : L'email de bienvenue confirme immédiatement la promesse.
  6. +
  7. Délai (2 jours) : Laisse le temps d'assimiler l'accueil avant la suite.
  8. +
  9. Action (Email 2) : Offre du contenu de valeur pour relancer la relation commerciale.
  10. +
`; + } else { + feedback.className = 'exercise-feedback show incorrect'; + feedback.innerHTML = `

Erreurs détectées (${correctCount}/5 étapes bien placées).

+

Rappels : Les déclencheurs lancent le scénario, puis on alterne des temps d'attente et des actions. Utilise les flèches pour corriger les blocs rouges.

`; + } + + saveState(); + updateProgress(); +} + +function resetExercise6() { + const list = document.getElementById('ex6_list'); + const items = Array.from(list.children); + const initialOrder = ["attente1", "email2", "declencheur", "attente2", "email1"]; + + initialOrder.forEach(step => { + const item = items.find(el => el.dataset.step === step); + if (item) list.appendChild(item); + }); + + clearEx6Feedback(); + saveState(); + updateProgress(); +} + +// --- PERSISTANCE ET SAUVEGARDE --- +function saveState() { + const tasks = [...document.querySelectorAll('.task')].map(x => x.checked); + + // Quiz radios + const radios = [...document.querySelectorAll('input[name^="positioning_"], input[name^="final_"]')].map(x => x.checked); + + // Text inputs + const texts = { + ex4_input: document.getElementById('ex4_input')?.value || '', + ex5_textarea: document.getElementById('ex5_textarea')?.value || '', + ex8_textarea: document.getElementById('ex8_textarea')?.value || '' + }; + + // Drag-and-drop state + const ex2State = {}; + document.querySelectorAll('#exercise2 .drag-card').forEach(card => { + const parent = card.parentNode; + ex2State[card.id] = parent ? parent.id : 'ex2_cards'; + }); + + // Cleaning table radios + const ex3State = {}; + for (let i = 1; i <= 5; i++) { + const checked = document.querySelector(`input[name="ex3_row${i}"]:checked`); + ex3State[`ex3_row${i}`] = checked ? checked.value : null; + } + + // Automation sequence state + const ex6State = [...document.querySelectorAll('#ex6_list .sortable-item')].map(item => item.dataset.step); + + const state = { + tasks, + radios, + texts, + ex2State, + ex3State, + ex6State, + ex2Validated, + ex3Validated, + ex4Validated, + ex5Validated, + ex6Validated + }; + + localStorage.setItem('brevoCourseState', JSON.stringify(state)); +} + +function loadState() { + const raw = localStorage.getItem('brevoCourseState'); + if (!raw) return; + const d = JSON.parse(raw); + + // Restore task checkboxes + if (d.tasks) { + document.querySelectorAll('.task').forEach((x, i) => { + if (i < d.tasks.length) x.checked = !!d.tasks[i]; + }); + } + + // Restore quiz radios + const quizRadios = document.querySelectorAll('input[name^="positioning_"], input[name^="final_"]'); + if (d.radios && quizRadios.length > 0) { + quizRadios.forEach((x, i) => { + if (i < d.radios.length) x.checked = !!d.radios[i]; + }); + } + + // Restore inputs & textareas + if (d.texts) { + if (d.texts.ex4_input !== undefined && document.getElementById('ex4_input')) { + document.getElementById('ex4_input').value = d.texts.ex4_input; + } + if (d.texts.ex5_textarea !== undefined && document.getElementById('ex5_textarea')) { + document.getElementById('ex5_textarea').value = d.texts.ex5_textarea; + } + if (d.texts.ex8_textarea !== undefined && document.getElementById('ex8_textarea')) { + document.getElementById('ex8_textarea').value = d.texts.ex8_textarea; + } + } + + // Restore Exercise 2 drag elements + if (d.ex2State) { + Object.keys(d.ex2State).forEach(cardId => { + const card = document.getElementById(cardId); + const parentId = d.ex2State[cardId]; + const parent = document.getElementById(parentId); + if (card && parent) { + parent.appendChild(card); + } + }); + } + + // Restore Exercise 3 radio choices + if (d.ex3State) { + Object.keys(d.ex3State).forEach(name => { + const val = d.ex3State[name]; + if (val) { + const input = document.querySelector(`input[name="${name}"][value="${val}"]`); + if (input) input.checked = true; + } + }); + } + + // Restore Exercise 6 sequence + if (d.ex6State) { + const list = document.getElementById('ex6_list'); + if (list) { + const items = Array.from(list.children); + d.ex6State.forEach(step => { + const item = items.find(el => el.dataset.step === step); + if (item) list.appendChild(item); + }); + } + } + + // Restore validation triggers + ex2Validated = !!d.ex2Validated; + ex3Validated = !!d.ex3Validated; + ex4Validated = !!d.ex4Validated; + ex5Validated = !!d.ex5Validated; + ex6Validated = !!d.ex6Validated; + + // Run validation overlays if previously checked + if (ex2Validated) checkExercise2(); + if (ex3Validated) checkExercise3(); + if (ex4Validated) checkExercise4(); + if (ex5Validated) checkExercise5(); + if (ex6Validated) checkExercise6(); + + updateProgress(); +} + +function updateProgress() { + const tasks = [...document.querySelectorAll('.task')]; + const checkedTasks = tasks.filter(t => t.checked).length; + + let answeredQuizQuestions = 0; + for (let i = 0; i < quizzes.positioning.length; i++) { + if (document.querySelector(`input[name="positioning_${i}"]:checked`)) answeredQuizQuestions++; + } + for (let i = 0; i < quizzes.final.length; i++) { + if (document.querySelector(`input[name="final_${i}"]:checked`)) answeredQuizQuestions++; + } + + let exercisePoints = 0; + if (ex2Validated) exercisePoints++; + if (ex3Validated) exercisePoints++; + if (ex4Validated) exercisePoints++; + if (ex5Validated) exercisePoints++; + if (ex6Validated) exercisePoints++; + + const finalActText = document.getElementById('ex8_textarea')?.value.trim() || ''; + if (finalActText.length > 10) exercisePoints++; + + const earned = checkedTasks + answeredQuizQuestions + exercisePoints; + const total = tasks.length + quizzes.positioning.length + quizzes.final.length + 6; + + const pct = Math.round(earned / total * 100); + const progEl = document.getElementById('progress'); + const labelEl = document.getElementById('progressLabel'); + + if (progEl) progEl.value = pct; + if (labelEl) labelEl.textContent = pct + '%'; +} + +document.addEventListener('input', () => { + saveState(); + updateProgress(); +}); +document.addEventListener('change', () => { + saveState(); + updateProgress(); +}); + +// print +document.getElementById('printBtn').addEventListener('click', () => window.print()); +document.getElementById('finalPrintBtn').addEventListener('click', () => window.print()); + +// reset +document.getElementById('resetBtn').addEventListener('click', () => { + localStorage.removeItem('brevoCourseState'); + location.reload(); +}); + +// md export +function htmlToMd() { + let md = '# Cours interactif — Prendre en main Brevo\n\n'; + document.querySelectorAll('section.module').forEach(sec => { + const title = sec.querySelector('h2')?.innerText || ''; + md += '## ' + title + '\n\n'; + sec.querySelectorAll('p, li, h3').forEach(el => { + if ( + el.closest('.quiz') || + el.closest('.exercise-feedback') || + el.closest('.exercise-actions') || + el.closest('.item-actions') || + el.closest('details') + ) return; + + const tag = el.tagName.toLowerCase(); + const txt = el.innerText.trim(); + if (!txt) return; + if (tag === 'h3') md += '### ' + txt + '\n\n'; + else if (tag === 'li') md += '- ' + txt + '\n'; + else md += txt + '\n\n'; + }); + }); + return md; +} + +document.getElementById('mdBtn').addEventListener('click', () => { + const blob = new Blob([htmlToMd()], { type: 'text/markdown;charset=utf-8' }); + const a = document.createElement('a'); + a.href = URL.createObjectURL(blob); + a.download = 'cours-brevo.md'; + a.click(); + URL.revokeObjectURL(a.href); +}); + +// Setup drag and drop events & load saved states on DOMContentLoaded +setupDragAndDrop(); +loadState(); +updateProgress(); diff --git a/style.css b/style.css new file mode 100644 index 0000000..552eeac --- /dev/null +++ b/style.css @@ -0,0 +1,1081 @@ +:root { + /* Palette de couleurs de la charte graphique IA-PRO */ + --bg: #F4F4FB; /* --paper */ + --paper-2: #F0F0F8; + --paper-3: #E8E8F4; + --card: #FFFFFF; + --ink: #1E1E2E; + --ink-2: #4B5563; + --ink-3: #9CA3AF; + --line: #E0E0EF; + --line-2: #D1D5DB; + + /* Couleurs de marque (Indigo/Purple) */ + --brand: #5B4FE8; /* --amber */ + --brand2: #3730A3; /* --amber-dark */ + --soft: #EEF0FB; /* --amber-bg */ + --brand-line: #C7C9F0; /* --amber-line */ + + /* Couleurs de statut */ + --success: #65A30D; /* --green */ + --success-bg: #F0F9E8; /* --green-bg */ + --success-line: #CCEAAA; /* --green-line */ + + --error: #B91C1C; /* --red */ + --error-bg: #FEE2E2; /* --red-bg */ + --error-line: #FECACA; /* --red-line */ + + --blue: #6366F1; + --blue-bg: #EEF2FF; + --blue-line: #C7D2FE; + + --violet: #60A5FA; + --violet-bg: #EFF6FF; + --violet-line: #BFDBFE; + + --warn: #fff7ed; + --warning: #f59e0b; + --warning-bg: #fffbeb; + --warning-text: #b45309; + --warning-line: #fef3c7; + + /* Typographies de la charte */ + --serif: 'Plus Jakarta Sans', 'Inter', sans-serif; + --sans: 'Inter', 'Plus Jakarta Sans', sans-serif; + --mono: 'IBM Plex Mono', monospace; + + /* Arrondis */ + --r-sm: 6px; + --r-md: 12px; + --r-lg: 18px; + + /* Ombres */ + --sh-sm: 0 1px 2px rgba(30,30,46,.05); + --sh-md: 0 4px 14px rgba(30,30,46,.07); + --sh-lg: 0 16px 40px rgba(30,30,46,.10); +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + scroll-behavior: smooth; +} + +body { + font-family: var(--sans); + background: var(--bg); + color: var(--ink); + line-height: 1.55; + min-height: 100vh; + position: relative; + /* Double gradient radial d'ambiance */ + background-image: + radial-gradient(at 8% -10%, rgba(91, 79, 232, 0.08) 0, transparent 45%), + radial-gradient(at 95% 110%, rgba(55, 48, 163, 0.06) 0, transparent 40%); + background-attachment: fixed; +} + +/* Effet de grain papier SVG */ +body::before { + content: ""; + position: fixed; + inset: 0; + pointer-events: none; + z-index: 0; + opacity: 0.35; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.025 0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E"); +} + +::selection { + background: var(--brand); + color: #fff; +} + +/* ========================================================================== + LAYOUT & CONTAINERS + ========================================================================== */ + +.hero { + display: grid; + grid-template-columns: 1fr 300px; + gap: 32px; + padding: 56px 24px; + max-width: 1080px; + margin: auto; + position: relative; + z-index: 1; +} + +.eyebrow { + display: inline-flex; + align-items: center; + gap: 8px; + font-size: 11.5px; + font-weight: 800; + letter-spacing: 2px; + text-transform: uppercase; + color: var(--brand2); + background: var(--soft); + border: 1px solid var(--brand-line); + border-radius: 999px; + padding: 5px 14px; + margin-bottom: 14px; +} + +h1 { + font-family: var(--serif); + font-size: clamp(28px, 4.5vw, 42px); + font-weight: 800; + line-height: 1.12; + letter-spacing: -0.5px; + margin: 0.2em 0; + color: var(--ink); +} + +.lead { + font-size: 1.15rem; + color: var(--ink-2); + max-width: 820px; +} + +.actions { + display: flex; + gap: 12px; + flex-wrap: wrap; + margin-top: 24px; +} + +/* ========================================================================== + BOUTONS DE LA CHARTE + ========================================================================== */ + +button { + font-family: var(--sans); + font-weight: 800; + font-size: 14px; + border: none; + cursor: pointer; + border-radius: 10px; + padding: 11px 22px; + display: inline-flex; + align-items: center; + gap: 8px; + transition: transform 0.15s, box-shadow 0.15s, background-color 0.2s; + background: var(--brand); + color: white; + box-shadow: var(--sh-sm); +} + +button:hover { + background-color: var(--brand2); + box-shadow: var(--sh-md); + transform: translateY(-1px); +} + +button:active { + transform: scale(0.97); +} + +button.secondary { + background: var(--ink); + color: var(--bg); +} + +button.secondary:hover { + background: #000; +} + +button.ghost { + background: transparent; + color: var(--ink-2); + border: 1px solid var(--line-2); + box-shadow: none; +} + +button.ghost:hover { + background: var(--paper-2); +} + +/* ========================================================================== + MODULES & CARDS + ========================================================================== */ + +.course-card, .module { + background: var(--card); + border: 1px solid var(--line); + border-radius: var(--r-lg); + box-shadow: var(--sh-sm); + position: relative; + z-index: 1; +} + +.course-card { + padding: 24px; + display: grid; + gap: 6px; + align-self: start; +} + +.course-card strong { + color: var(--ink-3); + font-size: 0.75rem; + text-transform: uppercase; + font-weight: 700; + letter-spacing: 0.5px; +} + +.course-card span { + margin-bottom: 14px; + font-weight: 600; + font-size: 0.95rem; +} + +main { + max-width: 1080px; + margin: auto; + padding: 0 24px 80px; +} + +/* Sticky topbar progress */ +.progress { + position: sticky; + top: 0; + z-index: 50; + background: rgba(244, 244, 251, 0.9); + backdrop-filter: blur(10px); + border-bottom: 1px solid var(--line); + display: flex; + align-items: center; + gap: 16px; + padding: 14px 24px; + margin: 0 -24px 22px; +} + +.progress span { + font-family: var(--serif); + font-weight: 700; + font-size: 0.9rem; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--ink-2); +} + +.progress progress { + flex-grow: 1; + height: 8px; + border-radius: 999px; + border: none; + -webkit-appearance: none; + appearance: none; +} + +.progress progress::-webkit-progress-bar { + background-color: var(--paper-3); + border-radius: 999px; +} + +.progress progress::-webkit-progress-value { + background: linear-gradient(90deg, var(--brand), #818CF8); + border-radius: 999px; + transition: width 0.4s ease; +} + +.progress strong { + font-family: var(--mono); + font-size: 0.95rem; + color: var(--brand); +} + +.module { + padding: 36px; + margin: 24px 0; +} + +h2 { + font-family: var(--serif); + font-size: 1.7rem; + font-weight: 700; + margin-top: 0; + margin-bottom: 16px; + color: var(--ink); +} + +h3 { + font-family: var(--serif); + font-size: 1.25rem; + font-weight: 700; + margin-bottom: 8px; +} + +.grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 16px; +} + +.grid article, .template-box, .exercise, .note { + border: 1px solid var(--line); + border-radius: var(--r-md); + padding: 20px; +} + +.grid article { + background: var(--card); + box-shadow: var(--sh-sm); + transition: transform 0.2s, box-shadow 0.2s; +} + +.grid article:hover { + transform: translateY(-2px); + box-shadow: var(--sh-md); +} + +.grid article h3 { + font-family: var(--serif); + font-size: 1.05rem; + margin-bottom: 4px; + color: var(--brand2); +} + +.grid article p { + font-size: 0.88rem; + color: var(--ink-2); +} + +.note { + background: var(--soft); + border-color: var(--brand-line); + color: var(--brand2); + font-size: 0.95rem; +} + +.exercise { + background: var(--paper-2); + border-color: var(--paper-3); + margin-top: 24px; +} + +textarea, input[type=text] { + width: 100%; + border: 1.5px solid var(--line-2); + border-radius: 10px; + padding: 14px; + font-family: var(--sans); + background: white; + transition: border-color 0.2s, box-shadow 0.2s; +} + +textarea:focus, input[type=text]:focus { + border-color: var(--brand); + outline: none; + box-shadow: 0 0 0 3px var(--soft); +} + +textarea { + min-height: 92px; + resize: vertical; +} + +.large { + min-height: 200px; +} + +.steps { + padding-left: 20px; +} + +.steps li { + margin: 12px 0; + padding-left: 6px; +} + +.checklist { + list-style: none; + padding: 0; +} + +.checklist li { + padding: 8px 0; + font-weight: 500; +} + +.checklist label { + cursor: pointer; +} + +.checklist input[type="checkbox"] { + accent-color: var(--brand); + margin-right: 8px; + transform: scale(1.1); +} + +.scenario { + display: flex; + gap: 12px; + flex-wrap: wrap; + align-items: center; + margin: 20px 0; +} + +.scenario span { + background: var(--soft); + border: 1px solid var(--brand-line); + color: var(--brand2); + padding: 10px 16px; + border-radius: 999px; + font-weight: 700; + font-size: 0.9rem; +} + +/* ========================================================================== + QUIZ SYSTEM + ========================================================================== */ + +.quiz-item { + border: 1px solid var(--line); + border-radius: var(--r-md); + padding: 20px; + margin: 16px 0; + background: var(--card); + box-shadow: var(--sh-sm); + transition: all 0.25s ease; +} + +.quiz-item.correct-quiz { + border-color: var(--success); + background-color: var(--success-bg); +} + +.quiz-item.incorrect-quiz { + border-color: var(--error); + background-color: var(--error-bg); +} + +.quiz-item label { + display: flex; + align-items: center; + gap: 10px; + margin: 10px 0; + padding: 10px 14px; + border: 1.5px solid var(--line); + border-radius: 8px; + cursor: pointer; + font-weight: 600; + font-size: 0.92rem; + transition: all 0.2s; + background: var(--bg); +} + +.quiz-item label:hover { + border-color: var(--brand); + background: var(--soft); +} + +.quiz-item input[type="radio"] { + accent-color: var(--brand); + transform: scale(1.15); +} + +.quiz-explanation { + margin-top: 12px; + font-size: 0.9rem; + font-weight: 600; + color: var(--ink-2); + border-top: 1px dashed var(--line); + padding-top: 10px; +} + +.result { + font-family: var(--serif); + font-weight: 800; + margin-top: 18px; + padding: 14px; + border-radius: 10px; + text-align: center; +} + +/* ========================================================================== + INTERACTIVE COMPONENT STYLES (EXERCISES) + ========================================================================== */ + +/* Badges */ +.badge { + display: inline-flex; + align-items: center; + padding: 4px 10px; + font-size: 0.75rem; + font-weight: 700; + border-radius: 9999px; + text-transform: uppercase; +} + +.badge-success { + background-color: var(--success-bg); + color: var(--success); + border: 1px solid var(--success-line); +} + +.badge-error { + background-color: var(--error-bg); + color: var(--error); + border: 1px solid var(--error-line); +} + +.badge-warning, .warning { + background-color: var(--warning-bg) !important; + color: var(--warning-text) !important; + border: 1px solid var(--warning-line) !important; +} + +/* Actions d'exercices */ +.exercise-actions { + display: flex; + gap: 12px; + margin: 18px 0; +} + +/* Zone de feedback */ +.exercise-feedback { + margin-top: 16px; + padding: 18px; + border-radius: var(--r-md); + font-weight: 600; + display: none; + font-size: 0.95rem; + animation: fadeIn 0.3s ease; + line-height: 1.55; + box-shadow: var(--sh-sm); +} + +.exercise-feedback p { + margin: 6px 0; +} + +.exercise-feedback.show { + display: block; +} + +.exercise-feedback.correct { + background-color: var(--success-bg); + border: 1px solid var(--success); + color: #37530F; +} + +.exercise-feedback.incorrect { + background-color: var(--error-bg); + border: 1px solid var(--error); + color: #7F1D1D; +} + +.exercise-feedback.warning { + background-color: var(--warning-bg); + border: 1px solid var(--warning); + color: var(--warning-text); +} + +/* Accordéon de correction */ +.correction-accordion { + margin-top: 16px; + border: 1px solid var(--line); + border-radius: var(--r-md); + background: var(--card); + overflow: hidden; + box-shadow: var(--sh-sm); +} + +.correction-accordion summary { + padding: 14px 20px; + font-weight: 700; + cursor: pointer; + outline: none; + transition: background 0.2s; + user-select: none; + color: var(--brand); +} + +.correction-accordion summary:hover { + background: var(--bg); +} + +.correction-accordion[open] summary { + border-bottom: 1px solid var(--line); +} + +.accordion-content { + padding: 20px; + font-size: 0.95rem; + line-height: 1.6; +} + +.model-text { + border-left: 4px solid var(--brand); + padding: 12px 16px; + color: var(--ink); + font-style: italic; + background: var(--bg); + border-radius: 0 var(--r-md) var(--r-md) 0; + margin: 10px 0; +} + +/* Exercice 2: Drag and Drop */ +.drag-cards-container { + display: flex; + flex-wrap: wrap; + gap: 12px; + padding: 16px; + background: var(--paper-3); + border: 1.5px solid var(--line); + border-radius: var(--r-md); + margin-bottom: 20px; + min-height: 72px; + align-items: center; +} + +.drag-card { + background: var(--card); + border: 1.5px solid var(--line); + border-radius: 10px; + padding: 10px 16px; + cursor: grab; + user-select: none; + font-weight: 700; + box-shadow: var(--sh-sm); + transition: transform 0.2s, box-shadow 0.2s, background-color 0.2s, border-color 0.2s; +} + +.drag-card:hover { + transform: translateY(-2px); + box-shadow: var(--sh-md); + border-color: var(--brand); +} + +.drag-card:active { + cursor: grabbing; +} + +.drag-card.dragging { + opacity: 0.4; + border-style: dashed; +} + +.drop-zones-container { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 16px; + margin-bottom: 20px; +} + +.drop-zone { + background: var(--card); + border: 2px dashed var(--line); + border-radius: var(--r-lg); + padding: 16px; + min-height: 200px; + display: flex; + flex-direction: column; + transition: all 0.25s ease; + box-shadow: var(--sh-sm); +} + +.drop-zone h4 { + margin-top: 0; + margin-bottom: 12px; + font-size: 0.85rem; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--ink-2); + border-bottom: 1px solid var(--line); + padding-bottom: 8px; + text-align: center; +} + +.drop-zone-content { + flex-grow: 1; + display: flex; + flex-direction: column; + gap: 10px; + min-height: 120px; +} + +.drop-zone.dragover { + border-color: var(--brand); + background-color: var(--soft); +} + +.drop-zone-content .drag-card { + font-size: 0.88rem; + padding: 8px 12px; + width: 100%; + text-align: center; +} + +.drag-card.correct-card { + border-color: var(--success) !important; + background-color: var(--success-bg) !important; + color: #37530F; +} + +.drag-card.incorrect-card { + border-color: var(--error) !important; + background-color: var(--error-bg) !important; + color: #7F1D1D; +} + +/* Exercice 3: Cleaning Table */ +.table-container { + overflow-x: auto; + border: 1px solid var(--line); + border-radius: var(--r-md); + margin: 18px 0; + background: white; + box-shadow: var(--sh-sm); +} + +.cleaning-table { + width: 100%; + border-collapse: collapse; +} + +.cleaning-table th { + font-weight: 700; + color: var(--ink-2); + font-size: 0.8rem; + text-transform: uppercase; + background: var(--paper-2); + border-bottom: 1px solid var(--line); +} + +.cleaning-table td { + padding: 14px 16px; +} + +.cleaning-table tr { + transition: background-color 0.25s ease; + border-bottom: 1px solid var(--line); +} + +.cleaning-table tr:last-child { + border-bottom: none; +} + +.cleaning-table tr.correct-row { + background-color: var(--success-bg) !important; +} + +.cleaning-table tr.incorrect-row { + background-color: var(--error-bg) !important; +} + +.cell-email { + font-family: var(--mono); + font-weight: 600; + font-size: 0.9rem; + color: var(--ink); +} + +.cell-action { + white-space: nowrap; +} + +.radio-label { + display: inline-flex; + align-items: center; + gap: 6px; + cursor: pointer; + margin-right: 12px; + font-weight: 700; + font-size: 0.88rem; + padding: 6px 10px; + border-radius: 8px; + border: 1px solid var(--line); + background: var(--paper-2); + transition: all 0.2s; +} + +.radio-label:hover { + background-color: var(--soft); + border-color: var(--brand); +} + +.radio-label input[type="radio"] { + accent-color: var(--brand); +} + +.row-explanation { + font-size: 0.88rem; + font-weight: 600; + margin-top: 6px; + white-space: normal; +} + +.row-explanation.text-success { + color: #37530F; +} + +.row-explanation.text-error { + color: var(--error); +} + +/* Exercice 6: Sortable List */ +.sortable-list { + list-style: none; + padding: 0; + margin: 20px 0; + display: flex; + flex-direction: column; + gap: 10px; +} + +.sortable-item { + display: flex; + justify-content: space-between; + align-items: center; + background: var(--card); + border: 1.5px solid var(--line); + border-radius: var(--r-md); + padding: 14px 20px; + box-shadow: var(--sh-sm); + transition: all 0.2s ease; +} + +.sortable-item:hover { + border-color: var(--brand); + transform: translateY(-1px); + box-shadow: var(--sh-md); +} + +.sortable-item.correct-item { + border-color: var(--success); + background-color: var(--success-bg); +} + +.sortable-item.incorrect-item { + border-color: var(--error); + background-color: var(--error-bg); +} + +.step-badge { + font-size: 0.72rem; + font-weight: 800; + padding: 4px 8px; + border-radius: 6px; + text-transform: uppercase; + margin-right: 12px; + display: inline-block; +} + +.text-trigger { + background-color: var(--blue-bg); + color: var(--blue); + border: 1px solid var(--blue-line); +} + +.text-delay { + background-color: var(--warning-bg); + color: var(--warning-text); + border: 1px solid var(--warning-line); +} + +.text-action { + background-color: var(--success-bg); + color: var(--success); + border: 1px solid var(--success-line); +} + +.item-content { + display: flex; + align-items: center; + font-weight: 700; + font-size: 0.95rem; +} + +.item-actions { + display: flex; + gap: 6px; +} + +.item-actions button { + padding: 6px 12px; + background-color: var(--bg); + color: var(--ink); + border: 1.5px solid var(--line); + font-weight: 800; + font-size: 0.9rem; + border-radius: 8px; + box-shadow: none; +} + +.item-actions button:hover { + background-color: var(--brand); + color: white; + border-color: var(--brand); +} + +/* ========================================================================== + COMPLETION SECTION (EXPORT PDF) + ========================================================================== */ + +.end-course { + background: linear-gradient(135deg, var(--brand) 0%, var(--brand2) 100%); + color: white; + border: none; + text-align: center; + padding: 44px 32px; + border-radius: var(--r-lg); + box-shadow: var(--sh-lg); +} + +.end-course h2 { + color: white; + font-size: 2rem; + margin-bottom: 12px; + font-family: var(--serif); + font-weight: 800; +} + +.end-course p { + color: rgba(255, 255, 255, 0.95); + max-width: 680px; + margin: 0 auto 24px; + font-size: 1.1rem; +} + +.end-course .actions { + justify-content: center; +} + +.end-course button { + background-color: white !important; + color: var(--brand) !important; + font-size: 1.05rem; + padding: 14px 28px; + box-shadow: 0 4px 14px rgba(0, 0, 0, 0.15); + border-radius: 12px; +} + +.end-course button:hover { + background-color: #f8fafc !important; + color: var(--brand2) !important; + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25); +} + +/* Footer & Sources */ +.sources { + font-size: .95rem; + color: var(--ink-2); +} + +.sources h2 { + font-family: var(--serif); + font-size: 1.4rem; + color: var(--ink); +} + +footer { + text-align: center; + color: var(--ink-3); + padding: 40px; + font-weight: 500; +} + +/* ========================================================================== + ANIMATIONS & MEDIA QUERIES + ========================================================================== */ + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(6px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Responsive */ +@media (max-width: 860px) { + .hero { + grid-template-columns: 1fr; + padding: 32px 24px; + } + .grid { + grid-template-columns: repeat(2, 1fr); + } + .drop-zones-container { + grid-template-columns: 1fr; + } + .drop-zone { + min-height: 150px; + } + .module { + padding: 24px; + } + .scenario { + display: block; + } + .scenario > * { + display: inline-block; + margin: 4px; + } + .sortable-item { + flex-direction: column; + align-items: flex-start; + gap: 12px; + } + .item-actions { + align-self: flex-end; + } +} + +@media (max-width: 560px) { + .grid { + grid-template-columns: 1fr; + } +} + +@media print { + body { + background: white; + color: #111; + } + .no-print, .actions, button, .exercise-actions, .item-actions, .correction-accordion { + display: none !important; + } + .hero, main { + max-width: none; + padding: 0; + } + .hero { + display: block; + } + .course-card, .module { + box-shadow: none; + border: 1px solid #ccc; + break-inside: avoid; + margin: 14px 0; + } + .module { + padding: 18px; + } + textarea, input { + border: 1px solid #999; + } + .sources { + display: block; + } + a { + color: #111; + } + .exercise-feedback { + display: block !important; + border: 1px solid #aaa !important; + background: transparent !important; + color: #000 !important; + } +}