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) => `
⚠️ 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.
❌ 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('
') + `
Recommandations :
` + issues.map(i => `✗ ${i}`).join('
') + `
⚠️ 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 :
❌ 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();