HistoireGeo / templates /histoire.html
Docfile's picture
Update templates/histoire.html
7f981b5 verified
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Histoire et Géographie - Assistant Académique</title>
<!-- Feuilles de style et polices -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/aos.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<!-- Scripts et Tailwind -->
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.0.2/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/aos.js"></script>
<style>
/* Police et réglages de base */
body {
font-family: 'Poppins', sans-serif;
background-color: #f1f5f9; /* slate-100 */
scroll-behavior: smooth;
}
/* Couleur et dégradé de la navigation et du footer */
.gradient-bg {
background: linear-gradient(120deg, #1a365d 0%, #2563eb 100%); /* Adjusted colors */
}
/* Cartes et transitions */
.card {
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
}
/* Animation du spinner */
.spinner {
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Inputs et textarea avec effet focus */
.form-input {
transition: all 0.3s ease;
}
.form-input:focus {
transform: scale(1.01);
border-color: #2563eb; /* blue-600 */
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.3);
}
/* Style pour le champ invalide */
.form-input.invalid {
border-color: #ef4444; /* red-500 */
}
.form-input.invalid:focus {
border-color: #ef4444; /* red-500 */
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.3); /* red-500 with opacity */
}
/* Prévisualisation d'images */
.image-preview {
max-width: 200px;
max-height: 200px;
margin: 10px;
border: 1px solid #ccc;
border-radius: 0.375rem; /* rounded-md */
}
/* Style Markdown optimisé */
.markdown-output {
width: 100%;
overflow-wrap: break-word;
word-wrap: break-word;
hyphens: auto;
}
.markdown-output p {
margin: 1em 0;
line-height: 1.6;
font-size: 16px;
}
.markdown-output h1, .markdown-output h2,
.markdown-output h3, .markdown-output h4 {
margin: 1.5em 0 0.5em;
line-height: 1.3;
}
.markdown-output ul, .markdown-output ol {
padding-left: 1.5em;
margin: 1em 0;
}
.markdown-output li {
margin: 0.5em 0;
line-height: 1.4;
}
/* Responsive Markdown */
@media (max-width: 768px) {
.markdown-output { font-size: 15px; }
.markdown-output p { margin: 0.8em 0; }
.markdown-output ul, .markdown-output ol { padding-left: 1.2em; }
.markdown-output h1 { font-size: 1.8em; }
.markdown-output h2 { font-size: 1.5em; }
.markdown-output h3 { font-size: 1.3em; }
.markdown-output h4 { font-size: 1.1em; }
.markdown-output pre { /* Styling for code blocks */
padding: 1em;
overflow-x: auto;
white-space: pre-wrap; /* Allow wrapping */
word-wrap: break-word; /* Break long words */
background-color: #f3f4f6; /* gray-100 */
border-radius: 0.375rem; /* rounded-md */
}
.markdown-output code { /* Styling for inline code */
word-break: break-word; /* Break long words */
background-color: #e5e7eb; /* gray-200 */
padding: 0.2em 0.4em;
border-radius: 0.25rem; /* rounded-sm */
}
/* Responsive table wrapper */
.table-wrapper {
overflow-x: auto;
width: 100%;
}
.markdown-output table {
width: 100%;
border-collapse: collapse;
margin: 1em 0;
}
.markdown-output th, .markdown-output td {
border: 1px solid #d1d5db; /* gray-300 */
padding: 0.5em;
text-align: left;
}
.markdown-output th {
background-color: #f3f4f6; /* gray-100 */
}
}
/* Bouton Retour en haut */
#back-to-top {
position: fixed;
bottom: 30px;
right: 30px;
background-color: #2563eb; /* blue-600 */
color: #fff;
border-radius: 50%;
padding: 0.75rem; /* Adjusted padding */
cursor: pointer;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
z-index: 50;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
#back-to-top:hover {
background-color: #1d4ed8; /* blue-700 */
}
#back-to-top.show {
opacity: 1;
visibility: visible;
}
</style>
</head>
<body>
<!-- En-tête sémantique -->
<header class="gradient-bg shadow-lg" role="banner">
<nav class="container mx-auto px-6 py-4 flex items-center justify-between" aria-label="Navigation principale">
<div class="flex items-center">
<i class="fas fa-book-open text-white text-2xl mr-3"></i>
<h1 class="text-white text-2xl font-bold">Mariam</h1>
</div>
<div class="flex items-center text-white text-sm">
<i class="fas fa-graduation-cap mr-2"></i>
Histoire et Géographie
</div>
</nav>
</header>
<!-- Contenu principal -->
<main role="main" class="container mx-auto p-6">
<!-- Section d'introduction -->
<section class="text-center mb-12" data-aos="fade-up">
<h2 class="text-3xl font-bold text-gray-800 mb-4">Assistant de Rédaction</h2>
<p class="text-gray-600 max-w-2xl mx-auto">
Générez des dissertations structurées et détaillées pour vos devoirs d'histoire et de géographie.
</p>
</section>
<!-- Bloc Dissertation d'Histoire Type 1 & 3 -->
<section class="mb-8" data-aos="fade-up">
<div class="bg-white rounded-xl shadow-xl p-8 card">
<div class="flex items-center mb-6">
<i class="fas fa-landmark text-blue-600 text-2xl mr-3"></i>
<h2 class="text-2xl font-bold text-gray-800">Dissertation d'Histoire - Type 1 & 3</h2>
</div>
<form id="histoire-form" class="space-y-6" aria-label="Formulaire dissertation histoire" novalidate> <!-- novalidate désactive la validation navigateur par défaut pour laisser JS gérer -->
<div class="space-y-2">
<label for="sujet-histoire" class="text-sm font-medium text-gray-700">
<i class="fas fa-pen-fancy mr-2"></i>Sujet de la dissertation <span class="text-red-500">*</span>
</label>
<textarea name="sujet-histoire" id="sujet-histoire" rows="3" class="form-input mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200 focus:ring-opacity-50" placeholder="Entrez le sujet de votre dissertation..." required aria-describedby="sujet-histoire-error"></textarea>
<div id="sujet-histoire-error" class="text-red-600 text-sm mt-1 hidden" role="alert">Le champ sujet est obligatoire.</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="space-y-2">
<label for="pointt1-histoire" class="text-sm font-medium text-gray-700">
<i class="fas fa-list-ol mr-2"></i>Premier point
</label>
<input type="text" name="pointt1-histoire" id="pointt1-histoire" class="form-input mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200 focus:ring-opacity-50" placeholder="Premier point...">
</div>
<div class="space-y-2">
<label for="point2-histoire" class="text-sm font-medium text-gray-700">
<i class="fas fa-list-ol mr-2"></i>Deuxième point
</label>
<input type="text" name="point2-histoire" id="point2-histoire" class="form-input mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200 focus:ring-opacity-50" placeholder="Deuxième point...">
</div>
<div class="space-y-2">
<label for="point3-histoire" class="text-sm font-medium text-gray-700">
<i class="fas fa-list-ol mr-2"></i>Troisième point
</label>
<input type="text" name="point3-histoire" id="point3-histoire" class="form-input mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200 focus:ring-opacity-50" placeholder="Troisième point...">
</div>
</div>
<button type="submit" class="w-full bg-blue-600 text-white px-6 py-3 rounded-lg font-medium flex items-center justify-center hover:bg-blue-700 transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed">
<i class="fas fa-magic mr-2"></i>
<span>Générer la dissertation</span>
</button>
</form>
<div id="histoire-output" class="mt-6 p-6 bg-gray-50 rounded-lg hidden" aria-live="polite"></div>
</div>
</section>
<!-- Bloc Histoire Type 2 (Images) -->
<section class="mb-8" data-aos="fade-up">
<div class="bg-white rounded-xl shadow-xl p-8 card">
<div class="flex items-center mb-6">
<i class="fas fa-scroll text-green-600 text-2xl mr-3"></i>
<h2 class="text-2xl font-bold text-gray-800">Histoire - Type 2 (Images)</h2>
</div>
<form id="histoire-type2-form" class="space-y-6" enctype="multipart/form-data" aria-label="Formulaire histoire images" novalidate>
<div class="space-y-2">
<label for="sujet-histoire-type2" class="text-sm font-medium text-gray-700">
<i class="fas fa-pen-fancy mr-2"></i>Sujet <span class="text-red-500">*</span>
</label>
<textarea name="sujet-histoire-type2" id="sujet-histoire-type2" rows="3" class="form-input mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-green-500 focus:ring focus:ring-green-200 focus:ring-opacity-50" placeholder="Entrez le sujet..." required aria-describedby="sujet-histoire-type2-error"></textarea>
<div id="sujet-histoire-type2-error" class="text-red-600 text-sm mt-1 hidden" role="alert">Le champ sujet est obligatoire.</div>
</div>
<div class="space-y-2">
<label for="images-histoire-type2" class="text-sm font-medium text-gray-700">
<i class="fas fa-images mr-2"></i>Images (Multiple)
</label>
<input type="file" name="images-histoire-type2" id="images-histoire-type2" multiple accept="image/*" class="form-input mt-1 block w-full rounded-lg border-gray-300 shadow-sm file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-green-50 file:text-green-700 hover:file:bg-green-100 focus:border-green-500 focus:ring focus:ring-green-200 focus:ring-opacity-50">
<div id="image-preview-histoire-type2" class="flex flex-wrap mt-2"></div>
</div>
<button type="submit" class="w-full bg-green-600 text-white px-6 py-3 rounded-lg font-medium flex items-center justify-center hover:bg-green-700 transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed">
<i class="fas fa-magic mr-2"></i>
<span>Générer l'analyse</span>
</button>
</form>
<div id="histoire-type2-output" class="mt-6 p-6 bg-gray-50 rounded-lg hidden" aria-live="polite"></div>
</div>
</section>
<!-- Bloc Dissertation de Géographie Type 1 & 3 -->
<section class="mb-8" data-aos="fade-up">
<div class="bg-white rounded-xl shadow-xl p-8 card">
<div class="flex items-center mb-6">
<i class="fas fa-globe-europe text-purple-600 text-2xl mr-3"></i>
<h2 class="text-2xl font-bold text-gray-800">Dissertation de Géographie - Type 1 & 3</h2>
</div>
<form id="geographie-form" class="space-y-6" aria-label="Formulaire dissertation géographie" novalidate>
<div class="space-y-2">
<label for="sujet-geographie" class="text-sm font-medium text-gray-700">
<i class="fas fa-pen-fancy mr-2"></i>Sujet de la dissertation <span class="text-red-500">*</span>
</label>
<textarea name="sujet-geographie" id="sujet-geographie" rows="3" class="form-input mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-purple-500 focus:ring focus:ring-purple-200 focus:ring-opacity-50" placeholder="Entrez le sujet de votre dissertation..." required aria-describedby="sujet-geographie-error"></textarea>
<div id="sujet-geographie-error" class="text-red-600 text-sm mt-1 hidden" role="alert">Le champ sujet est obligatoire.</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="space-y-2">
<label for="point1-geographie" class="text-sm font-medium text-gray-700">
<i class="fas fa-list-ol mr-2"></i>Premier point
</label>
<input type="text" name="point1-geographie" id="point1-geographie" class="form-input mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-purple-500 focus:ring focus:ring-purple-200 focus:ring-opacity-50" placeholder="Premier argument...">
</div>
<div class="space-y-2">
<label for="point2-geographie" class="text-sm font-medium text-gray-700">
<i class="fas fa-list-ol mr-2"></i>Deuxième point
</label>
<input type="text" name="point2-geographie" id="point2-geographie" class="form-input mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-purple-500 focus:ring focus:ring-purple-200 focus:ring-opacity-50" placeholder="Deuxième argument...">
</div>
<div class="space-y-2">
<label for="point3-geographie" class="text-sm font-medium text-gray-700">
<i class="fas fa-list-ol mr-2"></i>Troisième point
</label>
<input type="text" name="point3-geographie" id="point3-geographie" class="form-input mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-purple-500 focus:ring focus:ring-purple-200 focus:ring-opacity-50" placeholder="Troisième argument...">
</div>
</div>
<button type="submit" class="w-full bg-purple-600 text-white px-6 py-3 rounded-lg font-medium flex items-center justify-center hover:bg-purple-700 transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed">
<i class="fas fa-magic mr-2"></i>
<span>Générer la dissertation</span>
</button>
</form>
<div id="geographie-output" class="mt-6 p-6 bg-gray-50 rounded-lg hidden" aria-live="polite"></div>
</div>
</section>
<!-- Bloc Géographie Type 2 (Images) -->
<section class="mb-8" data-aos="fade-up">
<div class="bg-white rounded-xl shadow-xl p-8 card">
<div class="flex items-center mb-6">
<i class="fas fa-map-marked-alt text-yellow-600 text-2xl mr-3"></i>
<h2 class="text-2xl font-bold text-gray-800">Géographie - Type 2 (Images)</h2>
</div>
<form id="geographie-type2-form" class="space-y-6" enctype="multipart/form-data" aria-label="Formulaire géographie images" novalidate>
<div class="space-y-2">
<label for="sujet-geographie-type2" class="text-sm font-medium text-gray-700">
<i class="fas fa-pen-fancy mr-2"></i>Sujet <span class="text-red-500">*</span>
</label>
<textarea name="sujet-geographie-type2" id="sujet-geographie-type2" rows="3" class="form-input mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-yellow-500 focus:ring focus:ring-yellow-200 focus:ring-opacity-50" placeholder="Entrez le sujet..." required aria-describedby="sujet-geographie-type2-error"></textarea>
<div id="sujet-geographie-type2-error" class="text-red-600 text-sm mt-1 hidden" role="alert">Le champ sujet est obligatoire.</div>
</div>
<div class="space-y-2">
<label for="images-geographie-type2" class="text-sm font-medium text-gray-700">
<i class="fas fa-images mr-2"></i>Images (Multiple)
</label>
<input type="file" name="images-geographie-type2" id="images-geographie-type2" multiple accept="image/*" class="form-input mt-1 block w-full rounded-lg border-gray-300 shadow-sm file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-yellow-50 file:text-yellow-700 hover:file:bg-yellow-100 focus:border-yellow-500 focus:ring focus:ring-yellow-200 focus:ring-opacity-50">
<div id="image-preview-geographie-type2" class="flex flex-wrap mt-2"></div>
</div>
<button type="submit" class="w-full bg-yellow-600 text-white px-6 py-3 rounded-lg font-medium flex items-center justify-center hover:bg-yellow-700 transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed">
<i class="fas fa-magic mr-2"></i>
<span>Générer l'analyse</span>
</button>
</form>
<div id="geographie-type2-output" class="mt-6 p-6 bg-gray-50 rounded-lg hidden" aria-live="polite"></div>
</div>
</section>
</main>
<!-- Bouton Retour en haut -->
<button id="back-to-top" aria-label="Retour en haut">
<i class="fas fa-arrow-up"></i>
</button>
<!-- Pied de page -->
<footer class="gradient-bg mt-12 py-6" role="contentinfo">
<div class="container mx-auto px-6 text-center text-white">
<p class="text-sm">© 2024 Mariam - Tous droits réservés</p>
</div>
</footer>
<!-- Scripts personnalisés -->
<script>
window.addEventListener('load', function() {
// Initialisation AOS (Animations au scroll)
AOS.init({
duration: 800,
once: true,
offset: 100
});
// Configuration de Marked (convertisseur Markdown vers HTML)
marked.setOptions({
breaks: true, // Convertit les sauts de ligne simples en <br>
gfm: true, // Active les spécificités GitHub Flavored Markdown (tableaux, etc.)
headerIds: false, // Ne génère pas d'ID pour les titres
renderer: new marked.Renderer() // Utilise le rendu par défaut
});
// Fonction pour afficher l'indicateur de chargement
function showLoading(outputId, formButton) {
const output = document.getElementById(outputId);
output.classList.remove('hidden');
output.innerHTML = `
<div class="flex items-center justify-center space-x-3">
<div class="spinner h-8 w-8 border-4 border-blue-500 border-t-transparent rounded-full"></div>
<span class="text-gray-600">Génération en cours...</span>
</div>
`;
// Désactiver le bouton pendant le chargement
if (formButton) formButton.disabled = true;
}
// Fonction pour cacher l'indicateur de chargement et réactiver le bouton
function hideLoading(outputId, formButton) {
const output = document.getElementById(outputId);
// Ne cache pas l'output si on va afficher un résultat ou une erreur juste après
// output.classList.add('hidden');
// output.innerHTML = ''; // Optionnel : vider le contenu
if (formButton) formButton.disabled = false;
}
// Fonction principale pour gérer la soumission des formulaires
async function submitForm(formId, outputId, endpoint) {
const form = document.getElementById(formId);
const output = document.getElementById(outputId);
const submitButton = form.querySelector('button[type="submit"]');
// Trouver le champ sujet spécifique à ce formulaire
const subjectField = form.querySelector('textarea[id^="sujet-"]'); // Trouve le textarea dont l'ID commence par "sujet-"
if (!subjectField) {
console.error(`Champ sujet non trouvé pour le formulaire ${formId}`);
return; // Arrêter si le champ sujet n'existe pas
}
const errorMsgElement = form.querySelector('#' + subjectField.id + '-error'); // Trouve le div d'erreur correspondant
form.addEventListener('submit', async (e) => {
e.preventDefault(); // Toujours prévenir le comportement par défaut
// --- Validation du sujet ---
const subjectValue = subjectField.value.trim();
let isValid = true;
// Réinitialiser l'état d'erreur visuel avant la validation
subjectField.classList.remove('invalid');
if (errorMsgElement) errorMsgElement.classList.add('hidden');
// Ne pas cacher l'output ici, seulement le vider si on est sûr de le remplacer
// output.classList.add('hidden');
if (subjectValue === '') {
isValid = false;
subjectField.classList.add('invalid'); // Ajouter style d'erreur
if (errorMsgElement) errorMsgElement.classList.remove('hidden'); // Afficher message d'erreur
subjectField.focus(); // Mettre le focus sur le champ
}
// --- Fin Validation ---
// Si le formulaire n'est pas valide, arrêter ici
if (!isValid) {
return;
}
// Si valide, continuer : afficher le chargement et envoyer la requête
showLoading(outputId, submitButton);
try {
const formData = new FormData(form);
const response = await fetch(endpoint, {
method: 'POST',
body: formData
// Ajouter des headers si nécessaire (ex: 'Accept': 'application/json')
});
// Cacher le loading seulement après avoir reçu la réponse
// (que ce soit un succès ou une erreur HTTP)
// hideLoading(outputId, submitButton); // Fait avant l'affichage du résultat/erreur
if (!response.ok) {
// Essayer de lire le corps de l'erreur si possible
let errorBody = `Erreur HTTP: ${response.status}`;
try {
const errorData = await response.json();
errorBody += ` - ${errorData.message || JSON.stringify(errorData)}`;
} catch(jsonError) {
// Si le corps de l'erreur n'est pas du JSON, utiliser le texte brut
try {
const errorText = await response.text();
errorBody += ` - ${errorText}`;
} catch (textError) {
// Ignorer si même le texte ne peut être lu
}
}
throw new Error(errorBody);
}
const result = await response.json();
// Vérifier si la réponse contient bien la propriété 'output'
if (typeof result.output !== 'string') {
throw new Error("La réponse du serveur n'a pas le format attendu.");
}
output.classList.remove('hidden'); // S'assurer que la zone est visible
// Utilisation de DOMPurify si inclus, sinon directement marked.parse
// const sanitizedHtml = typeof DOMPurify !== 'undefined' ? DOMPurify.sanitize(marked.parse(result.output)) : marked.parse(result.output);
output.innerHTML = `
<div class="markdown-output prose max-w-none">
${marked.parse(result.output)}
</div>
`;
// Adaptation des tableaux générés pour mobile
Array.from(output.getElementsByTagName('table')).forEach(table => {
const wrapper = document.createElement('div');
wrapper.classList.add('table-wrapper');
table.parentNode.insertBefore(wrapper, table);
wrapper.appendChild(table);
});
} catch (error) {
console.error(`Erreur lors de la soumission du formulaire ${formId}:`, error);
output.classList.remove('hidden'); // S'assurer que la zone d'erreur est visible
output.innerHTML = `
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4" role="alert">
<div class="flex items-center">
<i class="fas fa-exclamation-triangle mr-3"></i>
<div>
<p class="font-bold">Une erreur est survenue</p>
<p>${error.message || "Impossible de contacter le serveur ou erreur inattendue."}</p>
</div>
</div>
</div>
`;
} finally {
// Réactiver le bouton dans tous les cas (succès, erreur fetch, erreur traitement)
hideLoading(outputId, submitButton); // Déplacé ici pour s'assurer qu'il est toujours réactivé
}
});
}
// Initialisation des gestionnaires pour chaque formulaire
submitForm('histoire-form', 'histoire-output', '/api/histoire');
submitForm('histoire-type2-form', 'histoire-type2-output', '/api/histoire-type2');
submitForm('geographie-form', 'geographie-output', '/api/geographie');
submitForm('geographie-type2-form', 'geographie-type2-output', '/api/geographie-type2');
// Fonction pour gérer la prévisualisation des images
function handleImagePreviews(inputId, previewContainerId) {
const input = document.getElementById(inputId);
const previewContainer = document.getElementById(previewContainerId);
if (!input || !previewContainer) return; // Ne rien faire si les éléments n'existent pas
input.addEventListener('change', function() {
previewContainer.innerHTML = ''; // Vider les prévisualisations précédentes
if (this.files) {
Array.from(this.files).forEach(file => {
if (file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = (e) => {
const img = document.createElement('img');
img.src = e.target.result;
img.alt = `Prévisualisation de ${file.name}`; // Texte alternatif
img.classList.add('image-preview', 'object-cover'); // object-cover pour éviter la déformation
previewContainer.appendChild(img);
};
reader.readAsDataURL(file);
} else {
// Optionnel : Afficher un message si le fichier n'est pas une image
const errorMsg = document.createElement('p');
errorMsg.textContent = `${file.name} n'est pas une image valide.`;
errorMsg.classList.add('text-red-600', 'text-sm', 'm-2');
previewContainer.appendChild(errorMsg);
}
});
}
});
}
// Lier la prévisualisation aux champs d'upload d'images
handleImagePreviews('images-histoire-type2', 'image-preview-histoire-type2');
handleImagePreviews('images-geographie-type2', 'image-preview-geographie-type2');
// Gestion du bouton "Retour en haut"
const backToTopBtn = document.getElementById('back-to-top');
if (backToTopBtn) {
window.addEventListener('scroll', () => {
if (window.scrollY > 300) {
backToTopBtn.classList.add('show');
} else {
backToTopBtn.classList.remove('show');
}
});
backToTopBtn.addEventListener('click', () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
});
}
});
</script>
</body>
</html>