Attaque2 / templates /index.html
Docfile's picture
Update templates/index.html
e190b23 verified
raw
history blame
58.4 kB
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Générateur de Flashcards et Quiz</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: {
50: '#f0f9ff',
100: '#e0f2fe',
200: '#bae6fd',
300: '#7dd3fc',
400: '#38bdf8',
500: '#0ea5e9',
600: '#0284c7',
700: '#0369a1',
800: '#075985',
900: '#0c4a6e',
}
},
animation: {
'bounce-slow': 'bounce 3s linear infinite',
}
}
}
}
</script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
body {
font-family: 'Poppins', sans-serif;
background-color: #f8fafc;
}
.flashcard {
perspective: 1000px;
height: 220px;
}
.flashcard-inner {
position: relative;
width: 100%;
height: 100%;
transition: transform 0.8s;
transform-style: preserve-3d;
}
.flashcard.flipped .flashcard-inner {
transform: rotateY(180deg);
}
.flashcard-front, .flashcard-back {
position: absolute;
width: 100%;
height: 100%;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
display: flex;
align-items: center;
justify-content: center;
border-radius: 0.5rem;
padding: 1.5rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.flashcard-front {
background-color: #ffffff;
color: #1e293b;
border-left: 5px solid #0ea5e9;
}
.flashcard-back {
background-color: #0ea5e9;
color: white;
transform: rotateY(180deg);
}
.quiz-option {
position: relative;
padding-left: 2.5rem;
}
.quiz-option input[type="radio"] {
position: absolute;
opacity: 0;
}
.quiz-option label {
display: block;
position: relative;
padding: 1rem 1.5rem 1rem 2.5rem;
cursor: pointer;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
transition: all 0.3s ease;
}
.quiz-option label:before {
content: '';
position: absolute;
left: 1rem;
top: 50%;
transform: translateY(-50%);
width: 1.25rem;
height: 1.25rem;
border-radius: 50%;
border: 2px solid #cbd5e1;
transition: all 0.3s ease;
}
.quiz-option input[type="radio"]:checked + label {
background-color: #e0f2fe;
border-color: #0ea5e9;
}
.quiz-option input[type="radio"]:checked + label:before {
border-color: #0ea5e9;
background-color: #0ea5e9;
box-shadow: inset 0 0 0 4px #e0f2fe;
}
.quiz-option.correct input[type="radio"]:checked + label {
background-color: #d1fae5;
border-color: #10b981;
}
.quiz-option.correct input[type="radio"]:checked + label:before {
border-color: #10b981;
background-color: #10b981;
box-shadow: inset 0 0 0 4px #d1fae5;
}
.quiz-option.incorrect input[type="radio"]:checked + label {
background-color: #fee2e2;
border-color: #ef4444;
}
.quiz-option.incorrect input[type="radio"]:checked + label:before {
border-color: #ef4444;
background-color: #ef4444;
box-shadow: inset 0 0 0 4px #fee2e2;
}
.progress-bar-container {
width: 100%;
height: 8px;
background-color: #e2e8f0;
border-radius: 4px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background-color: #0ea5e9;
transition: width 0.5s ease;
}
.floating-label {
position: absolute;
top: -10px;
left: 10px;
padding: 0 5px;
background-color: white;
transition: all 0.3s ease;
pointer-events: none;
}
.pulse-animation {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(14, 165, 233, 0.4);
}
70% {
box-shadow: 0 0 0 10px rgba(14, 165, 233, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(14, 165, 233, 0);
}
}
.confetti {
position: fixed;
width: 10px;
height: 10px;
background-color: #f00;
opacity: 0;
animation: confetti-fall 3s linear forwards;
}
@keyframes confetti-fall {
0% {
transform: translateY(0) rotate(0deg);
opacity: 1;
}
100% {
transform: translateY(100vh) rotate(360deg);
opacity: 0;
}
}
.btn-primary {
@apply bg-primary-600 text-white px-6 py-3 rounded-lg shadow-md hover:bg-primary-700 transition duration-300 ease-in-out transform hover:-translate-y-1 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-opacity-50;
}
.btn-secondary {
@apply bg-white text-primary-600 border border-primary-600 px-6 py-3 rounded-lg shadow-md hover:bg-primary-50 transition duration-300 ease-in-out transform hover:-translate-y-1 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-opacity-50;
}
</style>
</head>
<body>
<div class="min-h-screen bg-gradient-to-b from-primary-50 to-white">
<header class="bg-white shadow-sm py-6">
<div class="container mx-auto px-4">
<div class="flex items-center justify-between">
<a href="#" class="text-3xl font-bold text-primary-700 flex items-center">
<i class="fas fa-home mr-3 text-primary-500"></i>
Mariam Quizz
</a>
<div class="flex space-x-2">
<button id="themeToggle" class="p-2 rounded-full hover:bg-gray-100">
<i class="fas fa-moon text-gray-600"></i>
</button>
<button class="p-2 rounded-full hover:bg-gray-100">
<i class="fas fa-question-circle text-gray-600"></i>
</button>
</div>
</div>
</div>
</header>
<main class="container mx-auto px-4 py-8">
<div class="max-w-3xl mx-auto">
<!-- Introduction -->
<div class="text-center mb-8">
<h2 class="text-4xl font-bold text-gray-800 mb-4">Apprenez plus efficacement</h2>
<p class="text-lg text-gray-600 mb-6">Créez des flashcards ou des quiz pour mémoriser n'importe quel sujet</p>
</div>
<!-- Section de configuration -->
<div class="bg-white rounded-xl shadow-lg p-8 mb-10 relative overflow-hidden">
<div class="absolute top-0 right-0 w-40 h-40 bg-primary-100 rounded-full -mr-20 -mt-20 z-0"></div>
<div class="absolute bottom-0 left-0 w-24 h-24 bg-primary-100 rounded-full -ml-12 -mb-12 z-0"></div>
<div class="relative z-10">
<h3 class="text-2xl font-semibold text-gray-800 mb-6">Que souhaitez-vous apprendre aujourd'hui ?</h3>
<div class="space-y-6">
<div class="relative">
<input type="text" id="topic" class="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 transition-all duration-300 pl-10" placeholder="Entrez un sujet...">
<i class="fas fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i>
<div class="text-xs text-gray-500 mt-1 ml-2">Exemples: "Capitales du monde", "Photosynthèse", "Verbes irréguliers en anglais"</div>
</div>
<div>
<p class="text-gray-700 font-medium mb-3">Choisissez votre méthode d'apprentissage :</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="relative">
<input type="radio" id="typeFlashcards" name="contentType" value="flashcards" checked class="peer absolute opacity-0">
<label for="typeFlashcards" class="flex flex-col items-center justify-center p-4 border-2 border-gray-200 rounded-lg cursor-pointer transition-all duration-300 hover:bg-gray-50 peer-checked:border-primary-500 peer-checked:bg-primary-50">
<div class="w-12 h-12 rounded-full bg-primary-100 flex items-center justify-center mb-3">
<i class="fas fa-clone text-xl text-primary-600"></i>
</div>
<h4 class="font-semibold text-gray-800">Flashcards</h4>
<p class="text-sm text-gray-600 text-center mt-2">Pour mémoriser des informations par répétition</p>
</label>
</div>
<div class="relative">
<input type="radio" id="typeQuiz" name="contentType" value="quiz" class="peer absolute opacity-0">
<label for="typeQuiz" class="flex flex-col items-center justify-center p-4 border-2 border-gray-200 rounded-lg cursor-pointer transition-all duration-300 hover:bg-gray-50 peer-checked:border-primary-500 peer-checked:bg-primary-50">
<div class="w-12 h-12 rounded-full bg-primary-100 flex items-center justify-center mb-3">
<i class="fas fa-question text-xl text-primary-600"></i>
</div>
<h4 class="font-semibold text-gray-800">Quiz</h4>
<p class="text-sm text-gray-600 text-center mt-2">Pour tester vos connaissances avec des QCM</p>
</label>
</div>
</div>
</div>
<div class="flex items-center justify-between pt-2">
<div class="text-sm text-gray-500 italic">
<i class="fas fa-info-circle mr-1"></i> La génération peut prendre jusqu'à 1 minute
</div>
<button id="generateBtn" class="btn-primary flex items-center">
<span class="mr-2">Générer</span>
<i class="fas fa-chevron-right"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Indicateur de chargement -->
<div id="loading" class="hidden">
<div class="bg-white rounded-xl shadow-lg p-8 text-center">
<div class="flex flex-col items-center justify-center space-y-4">
<div class="relative w-24 h-24">
<div class="absolute inset-0 border-4 border-primary-100 border-t-primary-500 rounded-full animate-spin"></div>
<div class="absolute inset-0 flex items-center justify-center">
<i class="fas fa-lightbulb text-2xl text-primary-500 animate-pulse"></i>
</div>
</div>
<h3 class="text-xl font-semibold text-gray-800">Création en cours...</h3>
<p class="text-gray-600">Nous élaborons votre contenu d'apprentissage personnalisé</p>
<div class="w-full max-w-md mt-4">
<div class="progress-bar-container">
<div id="progressBar" class="progress-bar" style="width: 0%"></div>
</div>
<div id="loadingSteps" class="mt-8 text-left">
<div class="flex items-center mb-3">
<div class="w-6 h-6 rounded-full bg-primary-500 flex items-center justify-center mr-3">
<i class="fas fa-check text-white text-xs"></i>
</div>
<span class="text-sm text-gray-700">Analyse du sujet</span>
</div>
<div class="flex items-center mb-3">
<div id="step2" class="w-6 h-6 rounded-full bg-gray-200 flex items-center justify-center mr-3">
<i class="fas fa-spinner text-white text-xs animate-spin"></i>
</div>
<span class="text-sm text-gray-500">Recherche d'informations</span>
</div>
<div class="flex items-center mb-3">
<div id="step3" class="w-6 h-6 rounded-full bg-gray-200 flex items-center justify-center mr-3">
<i class="fas fa-hourglass text-white text-xs"></i>
</div>
<span class="text-sm text-gray-500">Création du contenu</span>
</div>
<div class="flex items-center">
<div id="step4" class="w-6 h-6 rounded-full bg-gray-200 flex items-center justify-center mr-3">
<i class="fas fa-hourglass text-white text-xs"></i>
</div>
<span class="text-sm text-gray-500">Finalisation</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Conteneur pour les Flashcards -->
<div id="flashcardsContainer" class="mt-8 hidden">
<!-- Les flashcards seront injectées ici -->
</div>
<!-- Conteneur pour le Quiz -->
<div id="quizContainer" class="mt-8 hidden">
<!-- Les questions du quiz seront injectées ici -->
</div>
</div>
</main>
<!-- MODIFICATION ICI: Ajout de 'hidden md:block' -->
<footer class="hidden md:block bg-gray-900 text-white py-8 mt-20">
<div class="container mx-auto px-4">
<div class="flex flex-col md:flex-row justify-between items-center">
<div class="mb-4 md:mb-0">
<h2 class="text-xl font-bold flex items-center">
<i class="fas fa-brain mr-2"></i> Mémorisation Facile
</h2>
<p class="text-gray-400 text-sm mt-2">Votre outil d'apprentissage intelligent</p>
</div>
<div class="flex space-x-4">
<a href="#" class="text-gray-400 hover:text-white transition-colors"><i class="fab fa-twitter"></i></a>
<a href="#" class="text-gray-400 hover:text-white transition-colors"><i class="fab fa-facebook"></i></a>
<a href="#" class="text-gray-400 hover:text-white transition-colors"><i class="fab fa-instagram"></i></a>
<a href="#" class="text-gray-400 hover:text-white transition-colors"><i class="fab fa-youtube"></i></a>
</div>
</div>
<div class="mt-8 border-t border-gray-800 pt-6 text-sm text-gray-400 text-center">
© 2025 Mémorisation Facile. Tous droits réservés.
</div>
</div>
</footer>
</div>
<script>
const generateBtn = document.getElementById('generateBtn');
const loadingIndicator = document.getElementById('loading');
const flashcardsContainer = document.getElementById('flashcardsContainer');
const quizContainer = document.getElementById('quizContainer');
const topicInput = document.getElementById('topic');
const progressBar = document.getElementById('progressBar');
const themeToggle = document.getElementById('themeToggle');
const step2 = document.getElementById('step2');
const step3 = document.getElementById('step3');
const step4 = document.getElementById('step4');
// Gestion du thème clair/sombre
themeToggle.addEventListener('click', function() {
document.body.classList.toggle('dark-mode'); // Vous aurez besoin d'ajouter des styles pour .dark-mode si vous voulez un vrai thème sombre
const icon = this.querySelector('i');
if (icon.classList.contains('fa-moon')) {
icon.classList.remove('fa-moon');
icon.classList.add('fa-sun');
} else {
icon.classList.remove('fa-sun');
icon.classList.add('fa-moon');
}
// Implémentation basique pour l'exemple: changer le fond du body
if (document.body.classList.contains('dark-mode')) {
document.body.style.backgroundColor = '#1f2937'; // Un gris sombre
document.body.style.color = '#f3f4f6'; // Un gris clair pour le texte
} else {
document.body.style.backgroundColor = '#f8fafc'; // Retour au fond clair
document.body.style.color = 'initial'; // Retour à la couleur de texte par défaut
}
});
// Animation de chargement
function simulateLoading() {
loadingIndicator.classList.remove('hidden'); // Assurez-vous que le chargement est visible
progressBar.style.width = '0%'; // Reset progress bar
// Reset steps visual state
[step2, step3, step4].forEach(step => {
step.className = 'w-6 h-6 rounded-full bg-gray-200 flex items-center justify-center mr-3';
step.innerHTML = '<i class="fas fa-hourglass text-white text-xs"></i>';
});
// Start step 1 visual state
const step1IconContainer = loadingSteps.children[0].querySelector('div');
step1IconContainer.className = 'w-6 h-6 rounded-full bg-primary-500 flex items-center justify-center mr-3';
step1IconContainer.innerHTML = '<i class="fas fa-check text-white text-xs"></i>';
step2.innerHTML = '<i class="fas fa-spinner text-white text-xs animate-spin"></i>'; // Start step 2 loading
let progress = 0;
const interval = setInterval(() => {
progress += 1;
progressBar.style.width = `${Math.min(progress, 100)}%`; // Ne pas dépasser 100%
if (progress === 25) {
step2.classList.remove('bg-gray-200');
step2.classList.add('bg-primary-500');
step2.innerHTML = '<i class="fas fa-check text-white text-xs"></i>';
step3.innerHTML = '<i class="fas fa-spinner text-white text-xs animate-spin"></i>';
} else if (progress === 60) {
step3.classList.remove('bg-gray-200');
step3.classList.add('bg-primary-500');
step3.innerHTML = '<i class="fas fa-check text-white text-xs"></i>';
step4.innerHTML = '<i class="fas fa-spinner text-white text-xs animate-spin"></i>';
} else if (progress === 95) {
step4.classList.remove('bg-gray-200');
step4.classList.add('bg-primary-500');
step4.innerHTML = '<i class="fas fa-check text-white text-xs"></i>';
}
if (progress >= 100) {
clearInterval(interval);
// Ne cachez pas l'indicateur ici, laissez la fonction principale le faire après le fetch
}
}, 50); // 50ms * 100 = 5 secondes pour l'animation complète
return interval; // Retourne l'ID de l'intervalle pour pouvoir l'arrêter
}
// Fonction pour créer des confettis
function createConfetti() {
const confettiCount = 100;
const colors = ['#0ea5e9', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6'];
for (let i = 0; i < confettiCount; i++) {
const confetti = document.createElement('div');
confetti.className = 'confetti fixed top-0 pointer-events-none z-50'; // Assurez-vous qu'ils sont bien positionnés et visibles
confetti.style.left = `${Math.random() * 100}vw`;
confetti.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)];
confetti.style.width = `${Math.random() * 10 + 5}px`;
confetti.style.height = `${Math.random() * 10 + 5}px`;
// Ajuster l'animation pour partir du haut
confetti.style.animation = `confetti-fall ${Math.random() * 3 + 2}s linear forwards`;
confetti.style.transform = `translateY(-20px) rotate(${Math.random() * 360}deg)`; // Position initiale légèrement au-dessus
confetti.style.opacity = '1'; // Doit être visible au début
document.body.appendChild(confetti);
setTimeout(() => {
confetti.remove();
}, 5000); // Durée max avant suppression
}
}
// Gestion des flashcards
function flipCard(card) {
card.classList.toggle('flipped');
}
generateBtn.addEventListener('click', function() {
const topic = topicInput.value.trim();
// Retirer les anciens messages d'erreur s'il y en a
const existingError = topicInput.parentElement.querySelector('.text-red-500');
if (existingError) {
existingError.remove();
}
// Retirer les anciens conteneurs d'erreur globaux
document.querySelectorAll('.error-container-global').forEach(el => el.remove());
if (!topic) {
// Animation de secouement sur l'input
topicInput.classList.add('border-red-500');
topicInput.classList.add('animate-bounce'); // Attention, 'animate-bounce' fait rebondir, pas secouer. Il faudrait une animation custom pour secouer.
setTimeout(() => {
topicInput.classList.remove('animate-bounce');
topicInput.classList.remove('border-red-500');
}, 1000);
// Afficher un message d'erreur sous l'input
const errorMsg = document.createElement('div');
errorMsg.className = 'text-red-500 text-sm mt-1 ml-2';
errorMsg.textContent = 'Veuillez entrer un sujet';
topicInput.parentElement.appendChild(errorMsg);
return;
}
const contentType = document.querySelector('input[name="contentType"]:checked').value;
// Cacher les anciens résultats et afficher le chargement
flashcardsContainer.classList.add('hidden');
quizContainer.classList.add('hidden');
flashcardsContainer.innerHTML = '';
quizContainer.innerHTML = '';
loadingIndicator.classList.remove('hidden');
// Simuler l'animation de chargement
const loadingInterval = simulateLoading();
const minLoadingTime = 3000; // Temps minimum d'affichage du chargement (en ms)
const startTime = Date.now();
// ---- DEBUT BLOC SIMULATION (À REMPLACER PAR VOTRE VRAI FETCH) ----
// Simule une réponse de l'API après un délai
setTimeout(() => {
const elapsedTime = Date.now() - startTime;
const remainingTime = Math.max(0, minLoadingTime - elapsedTime);
// Simuler des données de réponse
let fakeData;
if (contentType === 'flashcards') {
fakeData = {
flashcards: [
{ question: `Question 1 sur ${topic}?`, answer: `Réponse 1 sur ${topic}` },
{ question: `Question 2 sur ${topic}?`, answer: `Réponse 2 sur ${topic}` },
{ question: `Question 3 sur ${topic}?`, answer: `Réponse 3 sur ${topic}` },
{ question: `Question 4 sur ${topic}?`, answer: `Réponse 4 sur ${topic}` },
{ question: `Question 5 sur ${topic}?`, answer: `Réponse 5 sur ${topic}` }
]
};
} else {
const options = [`Option A pour ${topic}`, `Option B pour ${topic}`, `Option C pour ${topic}`, `Option D pour ${topic}`];
fakeData = {
quiz: [
{ question: `Quiz 1: Quelle est la capitale de ${topic}?`, options: [...options].sort(() => Math.random() - 0.5), correctAnswer: options[0], explanation: `Explication pour quiz 1 sur ${topic}.` },
{ question: `Quiz 2: Comment fonctionne ${topic}?`, options: [...options].sort(() => Math.random() - 0.5), correctAnswer: options[1], explanation: `Explication pour quiz 2 sur ${topic}.` },
{ question: `Quiz 3: Pourquoi ${topic} est important?`, options: [...options].sort(() => Math.random() - 0.5), correctAnswer: options[2], explanation: `Explication pour quiz 3 sur ${topic}.` }
]
};
}
// Simuler une erreur parfois
// fakeData = { error: "Désolé, une erreur simulée s'est produite." };
// Attendre la fin du temps minimum de chargement avant de traiter la réponse
setTimeout(() => {
clearInterval(loadingInterval); // Arrêter l'intervalle de la barre de progression
progressBar.style.width = '100%'; // S'assurer que la barre est pleine
// Cacher l'indicateur de chargement après un court délai pour montrer la fin
setTimeout(() => {
loadingIndicator.classList.add('hidden');
if (fakeData.error) {
displayGlobalError(fakeData.error);
} else if (contentType === 'flashcards' && fakeData.flashcards) {
flashcardsContainer.classList.remove('hidden');
displayFlashcards(fakeData.flashcards);
createConfetti();
} else if (contentType === 'quiz' && fakeData.quiz) {
quizContainer.classList.remove('hidden');
displayQuiz(fakeData.quiz);
createConfetti();
} else {
displayGlobalError("Aucune donnée reçue ou format incorrect.");
}
}, 300); // Court délai pour voir la barre à 100%
}, remainingTime);
}, 1500); // Délai simulé de réponse de l'API (1.5s)
// ---- FIN BLOC SIMULATION ----
/*
// ---- VOTRE VRAI FETCH (DÉCOMMENTER ET ADAPTER) ----
fetch('/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ topic, type: contentType }),
})
.then(response => {
if (!response.ok) {
// Essayer de lire le message d'erreur du corps si possible
return response.text().then(text => {
throw new Error(`Erreur HTTP: ${response.status} - ${text || response.statusText}`);
});
}
return response.json();
})
.then(data => {
const elapsedTime = Date.now() - startTime;
const remainingTime = Math.max(0, minLoadingTime - elapsedTime);
setTimeout(() => {
clearInterval(loadingInterval);
progressBar.style.width = '100%';
setTimeout(() => {
loadingIndicator.classList.add('hidden');
if (data.error) {
displayGlobalError(data.error);
} else if (contentType === 'flashcards' && data.flashcards) {
flashcardsContainer.classList.remove('hidden');
displayFlashcards(data.flashcards);
createConfetti();
} else if (contentType === 'quiz' && data.quiz) {
quizContainer.classList.remove('hidden');
displayQuiz(data.quiz);
createConfetti();
} else {
displayGlobalError("Aucune donnée reçue ou format incorrect.");
}
}, 300);
}, remainingTime);
})
.catch(error => {
const elapsedTime = Date.now() - startTime;
const remainingTime = Math.max(0, minLoadingTime - elapsedTime);
setTimeout(() => {
clearInterval(loadingInterval);
loadingIndicator.classList.add('hidden');
console.error('Erreur lors de la génération:', error);
displayGlobalError(`Erreur lors de la génération: ${error.message}. Veuillez réessayer.`);
}, remainingTime);
});
// ---- FIN VRAI FETCH ----
*/
});
// Fonction pour afficher une erreur globale
function displayGlobalError(message) {
const errorContainer = document.createElement('div');
errorContainer.className = 'error-container-global bg-red-100 border-l-4 border-red-500 text-red-700 p-4 rounded-md mt-6 mb-6'; // Ajout de marge
errorContainer.innerHTML = `
<div class="flex">
<div class="flex-shrink-0">
<i class="fas fa-exclamation-triangle text-red-500 fa-lg"></i>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium">Une erreur est survenue</h3>
<p class="text-sm mt-1">
${message}
</p>
</div>
</div>
`;
// Insérer après la section de configuration
const configSection = document.querySelector('.bg-white.rounded-xl.shadow-lg.p-8.mb-10');
if (configSection) {
configSection.parentNode.insertBefore(errorContainer, configSection.nextSibling);
} else {
// Fallback si la section de config n'est pas trouvée
document.querySelector('main .max-w-3xl').appendChild(errorContainer);
}
}
function displayFlashcards(flashcards) {
flashcardsContainer.innerHTML = ''; // Vider le conteneur
const header = document.createElement('div');
header.className = 'mb-8 text-center';
header.innerHTML = `
<h2 class="text-3xl font-bold text-gray-800 mb-2">Vos Flashcards (${flashcards.length})</h2>
<p class="text-gray-600">Cliquez sur une carte pour la retourner et voir la réponse.</p>
<p class="text-gray-500 text-sm mt-1">
<i class="far fa-lightbulb mr-1"></i> Astuce : Essayez de répondre mentalement avant de retourner la carte.
</p>
`;
flashcardsContainer.appendChild(header);
const flashcardsGrid = document.createElement('div');
flashcardsGrid.className = 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6';
flashcardsContainer.appendChild(flashcardsGrid);
flashcards.forEach((card, index) => {
const cardElement = document.createElement('div');
// Ajouter un délai d'animation basé sur l'index pour un effet d'apparition décalé
cardElement.style.animation = `fadeInUp 0.5s ease-out ${index * 0.1}s forwards`;
cardElement.style.opacity = '0'; // Commencer invisible pour l'animation
cardElement.className = 'flashcard cursor-pointer';
cardElement.onclick = function() { flipCard(this); };
cardElement.innerHTML = `
<div class="flashcard-inner">
<div class="flashcard-front">
<div class="w-full flex flex-col justify-between h-full">
<div class="text-left text-xs text-gray-400">#${index + 1}</div>
<h3 class="text-lg font-semibold text-center flex-grow flex items-center justify-center px-2">${card.question}</h3>
<div class="text-center mt-auto">
<span class="text-xs text-gray-500">Cliquez pour voir la réponse</span>
</div>
</div>
</div>
<div class="flashcard-back">
<div class="w-full flex flex-col justify-between h-full">
<div class="text-right text-xs text-white opacity-70">#${index + 1}</div>
<div class="text-center flex-grow flex items-center justify-center px-2">
<p class="font-medium">${card.answer}</p>
</div>
<div class="text-right mt-auto">
<span class="text-xs text-white opacity-70">Cliquez pour retourner</span>
</div>
</div>
</div>
</div>
`;
flashcardsGrid.appendChild(cardElement);
});
// Ajouter les contrôles après les cartes
const controls = document.createElement('div');
controls.className = 'mt-10 flex flex-col items-center justify-center space-y-4';
controls.innerHTML = `
<div class="flex items-center space-x-4">
<button id="newTopicBtn" class="btn-primary">
<i class="fas fa-plus mr-2"></i> Nouveau sujet
</button>
<button id="restartFlashcardsBtn" class="btn-secondary">
<i class="fas fa-redo-alt mr-2"></i> Recommencer (retourner tout)
</button>
</div>
<!-- Ajouter d'autres boutons si nécessaire -->
`;
flashcardsContainer.appendChild(controls);
// Ajouter des interactions aux boutons
document.getElementById('newTopicBtn').addEventListener('click', function() {
// Cacher les résultats actuels
flashcardsContainer.classList.add('hidden');
flashcardsContainer.innerHTML = ''; // Vider pour la prochaine génération
quizContainer.classList.add('hidden');
quizContainer.innerHTML = '';
// Remonter en haut et focus
window.scrollTo({ top: 0, behavior: 'smooth' });
topicInput.value = ''; // Vider le champ sujet
topicInput.focus();
});
document.getElementById('restartFlashcardsBtn').addEventListener('click', function() {
const allCards = flashcardsContainer.querySelectorAll('.flashcard.flipped');
allCards.forEach(card => card.classList.remove('flipped'));
// Optionnel : Scroll vers la première carte
const firstCard = flashcardsContainer.querySelector('.flashcard');
if(firstCard) {
firstCard.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
});
}
function displayQuiz(quizQuestions) {
quizContainer.innerHTML = ''; // Vider le conteneur
const header = document.createElement('div');
header.className = 'mb-8 text-center';
header.innerHTML = `
<h2 class="text-3xl font-bold text-gray-800 mb-2">Votre Quiz (${quizQuestions.length} questions)</h2>
<p class="text-gray-600">Testez vos connaissances en répondant aux questions</p>
<div class="flex items-center justify-center mt-6">
<div class="bg-white px-4 py-2 rounded-full shadow-sm flex items-center border border-gray-200">
<span class="text-primary-700 font-medium">Score: </span>
<span id="score" class="ml-1 text-primary-700 font-bold">0</span>
<span class="mx-1 text-gray-400">/</span>
<span class="text-gray-600">${quizQuestions.length}</span>
</div>
</div>
`;
quizContainer.appendChild(header);
const questionsContainer = document.createElement('div');
questionsContainer.className = 'space-y-8';
quizContainer.appendChild(questionsContainer);
let currentScore = 0;
let answeredQuestions = 0; // Compteur pour savoir quand afficher les résultats
quizQuestions.forEach((question, qIndex) => {
const questionElement = document.createElement('div');
questionElement.className = 'bg-white rounded-xl shadow-md p-6 transition-all duration-300 question-card'; // Ajout de classe pour ciblage
questionElement.setAttribute('id', `question-${qIndex}`);
// Animation d'apparition
questionElement.style.animation = `fadeInUp 0.5s ease-out ${qIndex * 0.15}s forwards`;
questionElement.style.opacity = '0';
let optionsHtml = '';
// Assurez-vous que correctAnswer est une chaîne pour la comparaison
const safeCorrectAnswer = String(question.correctAnswer).replace(/"/g, '"');
// Mélanger les options une seule fois pour cette question
const shuffledOptions = [...question.options].sort(() => Math.random() - 0.5);
shuffledOptions.forEach((option, oIndex) => {
const safeOption = String(option).replace(/"/g, '"'); // Sécuriser aussi les options
optionsHtml += `
<div class="quiz-option mb-3" id="option-${qIndex}-${oIndex}">
<input type="radio" id="q${qIndex}-o${oIndex}" name="question-${qIndex}" value="${safeOption}" data-correct="${safeCorrectAnswer}">
<label for="q${qIndex}-o${oIndex}" class="group flex items-center">
<span class="flex-grow">${option}</span>
<span class="ml-2 hidden success-icon text-green-500 text-lg">
<i class="fas fa-check-circle"></i>
</span>
<span class="ml-2 hidden error-icon text-red-500 text-lg">
<i class="fas fa-times-circle"></i>
</span>
</label>
</div>
`;
});
questionElement.innerHTML = `
<div class="flex items-center justify-between mb-4">
<span class="bg-primary-100 text-primary-800 text-xs font-medium px-2.5 py-0.5 rounded-full">Question ${qIndex + 1}/${quizQuestions.length}</span>
<!-- On pourrait ajouter une icône d'info/aide ici si pertinent -->
</div>
<h3 class="text-lg md:text-xl font-medium text-gray-800 mb-5">${question.question}</h3>
<div class="options mt-5 space-y-3"> <!-- Ajout space-y pour l'espacement -->
${optionsHtml}
</div>
<div class="explanation hidden mt-6 p-4 bg-sky-50 border border-sky-200 rounded-lg" id="explanation-${qIndex}">
<div class="flex items-start">
<div class="flex-shrink-0 mt-0.5">
<i class="fas fa-info-circle text-sky-500"></i>
</div>
<div class="ml-3">
<h4 class="text-sm font-semibold text-sky-800">Explication</h4>
<div class="mt-1 text-sm text-sky-700">${question.explanation || "Pas d'explication fournie."}</div>
</div>
</div>
</div>
`;
questionsContainer.appendChild(questionElement);
const optionsInputs = questionElement.querySelectorAll('input[type="radio"]');
optionsInputs.forEach((optionInput) => {
optionInput.addEventListener('change', function() {
// Empêcher de répondre à nouveau si déjà répondu
if (questionElement.dataset.answered === 'true') return;
questionElement.dataset.answered = 'true'; // Marquer comme répondu
answeredQuestions++;
const qIdx = qIndex; // Utiliser qIndex directement
const selectedValue = this.value;
const correctAnswer = this.getAttribute('data-correct');
const parentOptionDiv = this.parentElement; // Le div .quiz-option
const explanationElement = document.getElementById(`explanation-${qIdx}`);
// Désactiver toutes les options pour cette question
optionsInputs.forEach(opt => opt.disabled = true);
let isCorrect = false;
if (selectedValue === correctAnswer) {
// Réponse correcte
parentOptionDiv.classList.add('correct'); // Appliquer sur le div parent
parentOptionDiv.querySelector('.success-icon').classList.remove('hidden');
currentScore++;
document.getElementById('score').textContent = currentScore;
isCorrect = true;
} else {
// Réponse incorrecte
parentOptionDiv.classList.add('incorrect');
parentOptionDiv.querySelector('.error-icon').classList.remove('hidden');
// Trouver et surligner la bonne réponse
optionsInputs.forEach(opt => {
if (opt.value === correctAnswer) {
const correctParentDiv = opt.parentElement;
correctParentDiv.classList.add('correct');
correctParentDiv.querySelector('.success-icon').classList.remove('hidden');
}
});
}
// Afficher l'explication
explanationElement.classList.remove('hidden');
explanationElement.style.animation = 'fadeIn 0.5s ease-in'; // Petite animation pour l'explication
// Si toutes les questions ont été répondues, afficher les résultats finaux
if (answeredQuestions === quizQuestions.length) {
// Délai avant d'afficher les résultats pour laisser le temps de voir la dernière explication
setTimeout(() => {
displayQuizResults(currentScore, quizQuestions.length);
}, isCorrect ? 1500 : 2500); // Plus long si incorrect
} else {
// Optionnel: Scroll automatique vers la question suivante après un délai
const nextQuestion = document.getElementById(`question-${qIdx + 1}`);
if (nextQuestion) {
setTimeout(() => {
nextQuestion.scrollIntoView({ behavior: 'smooth', block: 'center' });
}, isCorrect ? 1000 : 2000); // Délai plus court si correct
}
}
});
});
});
}
function displayQuizResults(score, total) {
// Supprimer les anciens résultats s'il y en a
const existingResults = quizContainer.querySelector('.quiz-results');
if (existingResults) existingResults.remove();
const percentage = total > 0 ? Math.round((score / total) * 100) : 0;
let resultClass, resultIcon, resultMessage, colorClass;
if (percentage >= 80) {
resultClass = 'bg-green-50 border-green-500 text-green-800';
resultIcon = '<i class="fas fa-trophy text-4xl text-yellow-400 mb-4"></i>';
resultMessage = 'Excellent ! Vous maîtrisez ce sujet.';
colorClass = 'bg-green-500';
} else if (percentage >= 50) {
resultClass = 'bg-blue-50 border-blue-500 text-blue-800';
resultIcon = '<i class="fas fa-award text-4xl text-blue-500 mb-4"></i>';
resultMessage = 'Bon travail ! Continuez vos efforts.';
colorClass = 'bg-blue-500';
} else {
resultClass = 'bg-red-50 border-red-500 text-red-800';
resultIcon = '<i class="fas fa-book-reader text-4xl text-red-500 mb-4"></i>';
resultMessage = 'Continuez à étudier pour améliorer vos connaissances.';
colorClass = 'bg-red-500';
}
const resultsElement = document.createElement('div');
resultsElement.className = `quiz-results mt-12 p-6 rounded-xl shadow-lg border-t-4 ${resultClass} text-center`; // Border top
resultsElement.style.animation = 'fadeInUp 0.6s ease-out';
resultsElement.innerHTML = `
<div class="flex flex-col items-center">
${resultIcon}
<h3 class="text-2xl font-bold mb-2">Résultat final</h3>
<p class="text-3xl font-bold mb-3">${score} / ${total}</p>
<div class="w-full max-w-xs bg-gray-200 rounded-full h-5 mb-4 overflow-hidden border border-gray-300">
<div class="h-full rounded-full ${colorClass} text-xs font-medium text-white text-center p-0.5 leading-none flex items-center justify-center" style="width: ${percentage}%">
${percentage}%
</div>
</div>
<p class="mb-6">${resultMessage}</p>
<div class="flex flex-col sm:flex-row space-y-3 sm:space-y-0 sm:space-x-4 mt-4">
<button id="retryQuizBtn" class="btn-secondary">
<i class="fas fa-redo mr-2"></i> Réessayer le Quiz
</button>
<button id="newQuizBtnResults" class="btn-primary">
<i class="fas fa-plus mr-2"></i> Nouveau Sujet
</button>
</div>
</div>
`;
quizContainer.appendChild(resultsElement);
// Animation de confettis si le score est bon
if (percentage >= 70) {
createConfetti();
}
// Scroll vers les résultats
resultsElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
// Ajouter des interactions aux boutons
document.getElementById('retryQuizBtn').addEventListener('click', function() {
// Cacher les résultats
resultsElement.remove();
// Réinitialiser le score affiché
document.getElementById('score').textContent = '0';
// Réactiver et déselectionner toutes les options, cacher les icônes/explications
const allQuestions = quizContainer.querySelectorAll('.question-card');
allQuestions.forEach(qElement => {
qElement.dataset.answered = 'false'; // Réinitialiser l'état "répondu"
const radios = qElement.querySelectorAll('input[type="radio"]');
radios.forEach(radio => {
radio.checked = false;
radio.disabled = false;
});
const optionsDivs = qElement.querySelectorAll('.quiz-option');
optionsDivs.forEach(div => {
div.classList.remove('correct', 'incorrect');
div.querySelectorAll('.success-icon, .error-icon').forEach(icon => icon.classList.add('hidden'));
});
const explanation = qElement.querySelector('.explanation');
if (explanation) explanation.classList.add('hidden');
});
// Remonter à la première question
const firstQuestion = document.getElementById('question-0');
if (firstQuestion) {
firstQuestion.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
// Réinitialiser les compteurs internes
currentScore = 0;
answeredQuestions = 0;
});
document.getElementById('newQuizBtnResults').addEventListener('click', function() {
// Cacher les résultats actuels
quizContainer.classList.add('hidden');
quizContainer.innerHTML = ''; // Vider pour la prochaine génération
flashcardsContainer.classList.add('hidden');
flashcardsContainer.innerHTML = '';
// Remonter en haut et focus
window.scrollTo({ top: 0, behavior: 'smooth' });
topicInput.value = ''; // Vider le champ sujet
topicInput.focus();
});
}
// Ajouter des animations CSS simples
const styleSheet = document.createElement("style");
styleSheet.type = "text/css";
styleSheet.innerText = `
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* Style pour le dark mode (très basique) */
body.dark-mode { background-color: #111827; color: #d1d5db; }
body.dark-mode .bg-white { background-color: #1f2937; border-color: #374151; }
body.dark-mode .bg-gray-50 { background-color: #374151; }
body.dark-mode .text-gray-800 { color: #f9fafb; }
body.dark-mode .text-gray-600 { color: #9ca3af; }
body.dark-mode .text-gray-700 { color: #d1d5db; }
body.dark-mode .text-gray-500 { color: #6b7280; }
body.dark-mode .text-gray-400 { color: #4b5563; }
body.dark-mode .border-gray-200 { border-color: #374151; }
body.dark-mode .border-gray-300 { border-color: #4b5563; }
body.dark-mode .shadow-sm { box-shadow: 0 1px 2px 0 rgba(255, 255, 255, 0.05); }
body.dark-mode .shadow-lg { box-shadow: 0 10px 15px -3px rgba(255, 255, 255, 0.05), 0 4px 6px -2px rgba(255, 255, 255, 0.03); }
body.dark-mode .shadow-md { box-shadow: 0 4px 6px -1px rgba(255, 255, 255, 0.06), 0 2px 4px -1px rgba(255, 255, 255, 0.04); }
body.dark-mode input[type="text"] { background-color: #374151; border-color: #4b5563; color: #f3f4f6; }
body.dark-mode input[type="text"]::placeholder { color: #6b7280; }
body.dark-mode .quiz-option label { border-color: #374151; }
body.dark-mode .quiz-option label:hover { background-color: #4b5563; }
body.dark-mode .quiz-option input[type="radio"]:checked + label { background-color: #0c4a6e; border-color: #0ea5e9; color: white; } /* Adjust primary dark check */
body.dark-mode .quiz-option input[type="radio"]:checked + label:before { border-color: #0ea5e9; background-color: #0ea5e9; box-shadow: inset 0 0 0 4px #0c4a6e; }
body.dark-mode .flashcard-front { background-color: #1f2937; color: #f3f4f6; border-left-color: #0ea5e9; }
/* Ajuster les couleurs primaires pour le dark mode si nécessaire */
body.dark-mode .bg-primary-50 { background-color: #0c4a6e; }
body.dark-mode .bg-primary-100 { background-color: #075985; }
body.dark-mode .text-primary-600 { color: #38bdf8; }
body.dark-mode .text-primary-700 { color: #7dd3fc; }
body.dark-mode .text-primary-800 { color: #bae6fd; }
body.dark-mode .border-primary-500 { border-color: #38bdf8; }
body.dark-mode .peer-checked\\:bg-primary-50:checked ~ label { background-color: #0c4a6e; }
body.dark-mode .peer-checked\\:border-primary-500:checked ~ label { border-color: #38bdf8; }
body.dark-mode .btn-primary { background-color: #0284c7; hover:bg-primary-700; } /* Exemple d'ajustement bouton */
body.dark-mode .btn-secondary { background-color: #374151; color:#38bdf8; border-color:#38bdf8; hover:bg-gray-600 } /* Exemple d'ajustement bouton */
body.dark-mode #themeToggle i { color: #e5e7eb; } /* Icône du thème */
body.dark-mode .progress-bar-container { background-color: #374151; }
body.dark-mode .progress-bar { background-color: #0ea5e9; }
body.dark-mode .bg-gradient-to-b { background-image: linear-gradient(to bottom, #0c4a6e, #111827); } /* Gradient dark */
body.dark-mode header { background-color: #1f2937; } /* Header dark */
`;
document.head.appendChild(styleSheet);
// Focus sur le champ de saisie au chargement
window.addEventListener('load', () => {
topicInput.focus();
});
</script>
</body>
</html>