Preuve

body {
font-family: ‘Arial’, sans-serif;
line-height: 1.6;
max-width: 100%;
margin: 0 auto;
padding: 20px;
color: #333;
}

h1, h2 {
color: #8B4513;
text-align: center;
}

.intro {
background-color: #F5F5DC;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
border-left: 5px solid #8B4513;
}

.question-container {
margin-bottom: 20px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 8px;
background-color: #FEFEFE;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.options {
margin-top: 10px;
}

button {
background-color: #8B4513;
color: white;
border: none;
padding: 10px 15px;
border-radius: 5px;
cursor: pointer;
margin-top: 10px;
}

button:hover {
background-color: #A0522D;
}

input[type=”text”], textarea {
width: 100%;
padding: 8px;
margin-top: 5px;
border: 1px solid #ddd;
border-radius: 4px;
}

textarea {
min-height: 100px;
}

.result-section {
display: none;
text-align: center;
margin-top: 20px;
}

.badge {
border: 2px solid #8B4513;
padding: 20px;
margin: 20px auto;
text-align: center;
background-color: #F5F5DC;
max-width: 500px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
border-radius: 10px;
}

.progress-container {
width: 100%;
background-color: #ddd;
margin-bottom: 20px;
border-radius: 5px;
}

.progress-bar {
height: 20px;
background-color: #8B4513;
border-radius: 5px;
width: 0%;
text-align: center;
line-height: 20px;
color: white;
}

.controls {
display: flex;
justify-content: center;
margin-top: 15px;
}

#email-form {
margin-top: 20px;
display: none;
}

.stamp {
width: 100px;
height: 100px;
border: 2px solid #8B4513;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 20px auto;
background-color: #F0E68C;
font-weight: bold;
color: #8B4513;
transform: rotate(-15deg);
}

#badge {
border: 5px solid #8B4513;
border-radius: 10px;
padding: 20px;
max-width: 800px;
margin: 20px auto;
background-color: #F5F5DC;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
position: relative;
}

#grade-stamp {
position: absolute;
top: 20px;
right: 20px;
width: 60px;
height: 60px;
background-color: #CD853F;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 24px;
font-weight: bold;
border: 3px solid #8B4513;
transform: rotate(15deg);
}

#email-status {
padding: 10px;
background-color: #dff0d8;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
margin-top: 15px;
display: none;
}

.certificate-header {
text-align: center;
margin-bottom: 20px;
}

.certificate-header h2 {
color: #8B4513;
font-size: 28px;
margin-bottom: 5px;
}

.certificate-header p {
font-style: italic;
color: #666;
}

.certificate-body {
margin: 20px 0;
text-align: center;
}

.certificate-signature {
margin-top: 40px;
text-align: center;
}

.signature-line {
width: 200px;
height: 1px;
background-color: #8B4513;
margin: 10px auto;
}

.timer {
position: fixed;
top: 20px;
right: 20px;
background-color: #8B4513;
color: white;
padding: 15px;
border-radius: 50%;
width: 100px;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: bold;
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
z-index: 1000;
}

.texte-comprehension {
background-color: #FFFEF7;
padding: 20px;
border-left: 5px solid #8B4513;
margin-bottom: 20px;
line-height: 1.8;
font-size: 16px;
}

.audio-player {
background-color: #F5F5DC;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
text-align: center;
border: 2px solid #8B4513;
}

.audio-controls {
margin: 10px 0;
}

.audio-text {
background-color: #FFFEF7;
padding: 15px;
border-radius: 5px;
margin-top: 15px;
font-size: 14px;
line-height: 1.6;
}

.matching-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-top: 15px;
}

.items-list, .matches-list {
border: 1px solid #ddd;
padding: 15px;
border-radius: 5px;
background-color: #FAFAFA;
}

.match-item {
padding: 8px;
margin: 5px 0;
background-color: #F5F5DC;
border-radius: 3px;
border: 1px solid #8B4513;
}

.match-definition {
margin: 10px 0;
padding: 8px;
background-color: white;
border-radius: 3px;
border: 1px solid #ddd;
}

.match-select {
width: 100%;
margin-bottom: 5px;
padding: 5px;
}

.aplenguas-login-form {
max-width: 100%;
margin: 0 auto;
padding: 20px;
background-color: #F5F5DC;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}

.form-group {
margin-bottom: 15px;
}

.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}

.form-group input {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}

.btn-submit {
background-color: #8B4513;
color: white;
border: none;
padding: 10px 15px;
border-radius: 4px;
cursor: pointer;
}

#continuar {
background-color: #228B22;
color: white;
border: none;
padding: 10px 15px;
border-radius: 4px;
cursor: pointer;
margin-top: 10px;
}

2:00:00

Examen de Français A2 – Un chef d’œuvre

Cette évaluation porte sur le thème des chefs-d’œuvre, du patrimoine culturel, de l’art et de la littérature. L’examen est noté sur 100 points (5 catégories de 20 points chacune).

0%

Résultats de l’évaluation

Nom:

Prénom:

Score: /100

Commentaire:

Certificat de Réussite

Évaluation de Français A2 – Un chef d’œuvre

Ce certificat atteste que

a complété avec succès l’évaluation de français avec un score de

sur un total de 100 points

Signature du professeur

Envoyer le certificat par email

// Script para el formulario de inicio de sesión
const SCRIPT_URL = ‘https://script.google.com/macros/s/AKfycbz4Mw8KlIOeMhqPciP0ZHo43_D-3t5Q505vMpBqr5yBfpyYYGn_ISl6Wuti7ko4JnxGjQ/exec’;

document.getElementById(‘login-form’).addEventListener(‘submit’, function(e) {
e.preventDefault();

const email = document.getElementById(‘email’).value;
const nombre = document.getElementById(‘nombre’).value;
const curso = document.getElementById(‘curso’).value;

// Mostrar estado de procesamiento
document.getElementById(‘estado-envio’).style.display = ‘block’;
document.getElementById(‘mensaje-estado’).innerText = ‘Enviando datos…’;

// Guardar los datos localmente
sessionStorage.setItem(‘student_email’, email);
sessionStorage.setItem(‘student_name’, nombre);
sessionStorage.setItem(‘student_course’, curso);

// Enviar datos al servidor usando fetch
const formData = new FormData();
formData.append(‘email’, email);
formData.append(‘nombre’, nombre);
formData.append(‘curso’, curso);
formData.append(‘accion’, ‘iniciar’);

fetch(SCRIPT_URL, {
method: ‘POST’,
body: formData,
mode: ‘no-cors’
})
.then(response => {
document.getElementById(‘login-form’).style.display = ‘none’;
document.getElementById(‘mensaje-confirmacion’).style.display = ‘block’;
document.getElementById(‘estado-envio’).style.display = ‘none’;
})
.catch(error => {
console.error(‘Error:’, error);
document.getElementById(‘mensaje-estado’).innerText = ‘Error al enviar datos’;
document.getElementById(‘detalles-error’).innerText = error.message;
document.getElementById(‘detalles-error’).style.display = ‘block’;
});
});

document.getElementById(‘continuar’).addEventListener(‘click’, function() {
document.querySelector(‘.aplenguas-login-form’).style.display = ‘none’;
document.getElementById(‘contenido-ejercicio’).style.display = ‘block’;
});

// Variables globales para el examen
let currentQuestionIndex = 0;
let userAnswers = [];
let questionOrder = [];
let studentData = {};
let examTimer;
let timeLeft = 120 * 60; // 2h00 en segundos

// Función para actualizar el temporizador
function updateTimer() {
const hours = Math.floor(timeLeft / 3600);
const minutes = Math.floor((timeLeft % 3600) / 60);
const seconds = timeLeft % 60;

document.getElementById(‘exam-timer’).textContent =
(hours > 0 ? hours + ‘:’ : ”) +
(minutes < 10 ? '0' + minutes : minutes) + ':' +
(seconds < 10 ? '0' + seconds : seconds);

if (timeLeft <= 0) {
clearInterval(examTimer);
alert("Le temps est écoulé! L'examen va être terminé automatiquement.");
calculateResults();
} else {
timeLeft–;
}
}

// El texto de comprensión sobre los monumentos franceses y el arte
const texteComprehension = `

Les chefs-d’œuvre du patrimoine français : entre tradition et modernité

La France possède un patrimoine artistique et architectural d’une richesse exceptionnelle qui attire chaque année des millions de visiteurs du monde entier. Du château de Versailles aux cathédrales gothiques, en passant par les musées parisiens, ces monuments constituent de véritables chefs-d’œuvre qui témoignent de l’histoire et de la culture françaises.

Au cœur de Paris, le musée du Louvre abrite la plus célèbre des peintures : la Joconde de Léonard de Vinci. Cette œuvre mystérieuse fascine par son sourire énigmatique et continue d’inspirer artistes et visiteurs. “Je suis totalement passionné par cette peinture”, confie souvent Marie Dubois, guide au Louvre depuis vingt ans. “Chaque jour, je découvre un nouveau détail dans ce chef-d’œuvre.”

Non loin de là, la cathédrale Notre-Dame de Paris, malgré l’incendie de 2019, reste un symbole de l’art gothique français. Ses voûtes élancées, ses rosaces colorées et ses gargouilles sculptées représentent le savoir-faire des artisans du Moyen Âge. “Permettez-moi de vous proposer une visite guidée de cette merveille architecturale”, suggèrent régulièrement les guides touristiques aux visiteurs éblouis.

Au sud de la France, le Palais des Papes d’Avignon illustre parfaitement l’art de vivre médiéval. Situé au centre de la vieille ville, ce monument imposant domine la vallée du Rhône. En face du palais, sur la rive opposée du fleuve, le fameux pont d’Avignon évoque les chansons populaires françaises.

Mais le patrimoine français ne se limite pas aux monuments anciens. Le Centre Pompidou à Paris, avec sa structure colorée et ses tuyaux apparents, divise l’opinion publique. “Ça m’est égal, cette architecture moderne”, déclare parfois le public traditionnel, tandis que les amateurs d’art contemporain y voient un symbole de créativité et d’innovation.

Le château de Versailles, quant à lui, incarne la grandeur de la France classique. Sa galerie des Glaces, ses jardins à la française et ses appartements royaux transportent les visiteurs dans l’univers de Louis XIV. Derrière le château, les jardins s’étendent à perte de vue, offrant une promenade majestueuse entre sculptures et fontaines.

En Provence, le théâtre antique d’Orange témoigne de l’héritage romain en France. Construit au premier siècle après J.-C., il accueille encore aujourd’hui des représentations lyriques et théâtrales. À côté du théâtre, les ruines de l’arc de triomphe rappellent la puissance de l’Empire romain.

Les châteaux de la Loire constituent également un ensemble architectural remarquable. Chambord, avec ses tours et ses escaliers à vis, Chenonceau enjambant gracieusement le Cher, ou encore Amboise perché sur son promontoire, chacun raconte une page de l’Histoire de France. Ces résidences royales, situées dans la vallée de la Loire, attirent les passionnés d’histoire et d’architecture.

L’art français contemporain trouve aussi sa place dans ce paysage culturel. La pyramide du Louvre, œuvre de l’architecte sino-américain Ieoh Ming Pei, créa une polémique lors de son inauguration en 1989. Aujourd’hui, cette structure de verre et d’acier s’intègre parfaitement dans la cour du musée, créant un dialogue harmonieux entre tradition et modernité.

Ces monuments et œuvres d’art ne sont pas de simples curiosités touristiques : ils constituent le patrimoine vivant de la France. Chaque pierre, chaque peinture, chaque sculpture raconte une histoire et transmet un savoir-faire ancestral aux générations futures. C’est pourquoi leur préservation représente un enjeu majeur pour l’avenir culturel du pays.

`;

// Audio para comprensión oral integrado con Google Drive
const audioText = `

Compréhension orale – Interview avec une artiste

Instructions : Écoutez attentivement l’interview suivante. Vous pouvez l’écouter plusieurs fois si nécessaire.

💡 Conseils pour l’écoute :

  • Écoutez une première fois pour comprendre le sens général
  • Prenez des notes si nécessaire
  • Réécouter les passages difficiles
  • Portez attention aux détails importants
📝 Afficher la transcription (à utiliser seulement si nécessaire)

Transcription de l’interview :

Journaliste : Bonjour Sophie Moreau. Vous êtes peintre depuis quinze ans. Qu’est-ce qui vous passionne le plus dans votre art ?

Sophie Moreau : Bonjour. Ce qui me passionne, c’est la possibilité de transformer une toile blanche en univers coloré. Chaque coup de pinceau raconte une histoire. J’adore mélanger les couleurs vives – le rouge, le jaune, le bleu – pour créer des émotions.

Journaliste : Vos œuvres sont exposées dans plusieurs galeries parisiennes. Comment décririez-vous votre style ?

Sophie Moreau : Mon style ? Je dirais qu’il est entre l’impressionnisme et l’art contemporain. J’aime peindre en plein air, dans les parcs, devant les monuments. L’année dernière, j’ai peint une série sur Notre-Dame, avant et après l’incendie. C’était très émouvant.

Journaliste : Vous travaillez aussi avec des écoles. Pourquoi cette démarche ?

Sophie Moreau : L’art doit être accessible à tous, surtout aux enfants. Je vais dans les écoles primaires et les collèges pour montrer aux élèves que l’art n’est pas réservé aux musées. Avec quelques tubes de peinture et de l’imagination, on peut créer des merveilles !

Journaliste : Quels conseils donneriez-vous aux jeunes qui veulent devenir artistes ?

Sophie Moreau : Il faut d’abord être passionné. Ensuite, pratiquer tous les jours, observer la nature, visiter les musées. Et surtout, ne jamais abandonner ses rêves, même si c’est difficile au début.

`;

// Las preguntas de la evaluación organizadas por categorías
const questionsData = [
// CATÉGORIE 1: Compréhension de texte (20 points)
{
type: “text_intro”,
content: texteComprehension,
category: “Compréhension de texte”
},
{
type: “qcm”,
question: “Selon le texte, où se trouve la Joconde de Léonard de Vinci ?”,
options: [“Au Centre Pompidou”, “Au musée du Louvre”, “À Versailles”, “À Notre-Dame”],
correct: 1,
category: “Compréhension de texte”,
points: 2
},
{
type: “text”,
question: “Quel événement important a affecté Notre-Dame de Paris en 2019 ?”,
correct: “incendie”,
acceptableAnswers: [“incendie”, “un incendie”, “l’incendie”],
category: “Compréhension de texte”,
points: 2
},
{
type: “choix_phrase”,
question: “Quelle phrase exprime une passion dans le texte ?”,
options: [
“Ça m’est égal, cette architecture moderne”,
“Je suis totalement passionné par cette peinture”,
“Permettez-moi de vous proposer une visite guidée”,
“Cette structure divise l’opinion publique”
],
correct: 1,
category: “Compréhension de texte”,
points: 2
},
{
type: “qcm”,
question: “Où se situe le Palais des Papes ?”,
options: [“À Paris”, “À Versailles”, “À Avignon”, “À Orange”],
correct: 2,
category: “Compréhension de texte”,
points: 2
},
{
type: “choix_phrase”,
question: “Quelle expression montre l’indifférence dans le texte ?”,
options: [
“Je suis totalement passionné”,
“Ça m’est égal, cette architecture moderne”,
“Permettez-moi de vous proposer”,
“Cette œuvre mystérieuse fascine”
],
correct: 1,
category: “Compréhension de texte”,
points: 2
},
{
type: “text”,
question: “Selon le texte, où se trouvent les jardins par rapport au château de Versailles ?”,
correct: “derrière”,
acceptableAnswers: [“derrière”, “derrière le château”, “à l’arrière”],
category: “Compréhension de texte”,
points: 2
},
{
type: “qcm”,
question: “Le théâtre antique d’Orange date de quelle époque ?”,
options: [“Du Moyen Âge”, “Du premier siècle après J.-C.”, “De la Renaissance”, “Du 18ème siècle”],
correct: 1,
category: “Compréhension de texte”,
points: 2
},
{
type: “choix_phrase”,
question: “Quelle phrase contient une proposition ?”,
options: [
“La France possède un patrimoine exceptionnel”,
“Permettez-moi de vous proposer une visite guidée”,
“Cette œuvre fascine par son sourire”,
“Le patrimoine français ne se limite pas aux monuments”
],
correct: 1,
category: “Compréhension de texte”,
points: 2
},
{
type: “text”,
question: “Quel architecte a créé la pyramide du Louvre ?”,
correct: “Ieoh Ming Pei”,
acceptableAnswers: [“Ieoh Ming Pei”, “Ming Pei”, “Pei”],
category: “Compréhension de texte”,
points: 2
},
{
type: “qcm”,
question: “Dans quelle vallée se trouvent les châteaux de la Loire ?”,
options: [“La vallée du Rhône”, “La vallée de la Seine”, “La vallée de la Loire”, “La vallée de la Garonne”],
correct: 2,
category: “Compréhension de texte”,
points: 2
},

// CATÉGORIE 2: Grammaire (20 points)
{
type: “text”,
question: “Conjuguez à l’imparfait : ‘Quand j’étais petit, je _____ (visiter) souvent les musées avec mes parents.'”,
correct: “visitais”,
category: “Imparfait”,
points: 2
},
{
type: “qcm”,
question: “Choisissez la phrase correcte avec l’imparfait :”,
options: [
“Les artistes peignent dans l’atelier hier”,
“Les artistes peignaient dans l’atelier chaque jour”,
“Les artistes ont peint dans l’atelier hier”,
“Les artistes peigneront dans l’atelier demain”
],
correct: 1,
category: “Imparfait”,
points: 2
},
{
type: “text”,
question: “Complétez à l’imparfait : ‘Nous _____ (être) toujours émerveillés par les œuvres d’art.'”,
correct: “étions”,
acceptableAnswers: [“étions”],
category: “Imparfait”,
points: 2
},
{
type: “qcm”,
question: “Quelle phrase utilise correctement l’imparfait ?”,
options: [
“Hier, il pleut toute la journée”,
“Hier, il a plu toute la journée”,
“Avant, il pleuvait souvent en automne”,
“Demain, il pleuvra sûrement”
],
correct: 2,
category: “Imparfait”,
points: 2
},
{
type: “text”,
question: “Transformez au passé composé : ‘Je visite le Louvre.’ → ‘J’_____ _____ le Louvre.'”,
correct: “ai visité”,
acceptableAnswers: [“ai visité”],
category: “Passé composé”,
points: 2
},
{
type: “qcm”,
question: “Choisissez la forme correcte du passé composé :”,
options: [
“Elle a parti en vacances”,
“Elle est partie en vacances”,
“Elle a partis en vacances”,
“Elle est parti en vacances”
],
correct: 1,
category: “Passé composé”,
points: 2
},
{
type: “text”,
question: “Complétez au passé composé : ‘Les touristes se _____ _____ (promener) dans le musée.'”,
correct: “sont promenés”,
acceptableAnswers: [“sont promenés”],
category: “Passé composé”,
points: 2
},
{
type: “qcm”,
question: “Quel auxiliaire utilise-t-on avec le verbe ‘monter’ dans cette phrase : ‘Il _____ monté les escaliers’ ?”,
options: [“a”, “est”, “soit”, “ait”],
correct: 0,
category: “Passé composé”,
points: 2
},
{
type: “text”,
question: “Transformez à la forme négative : ‘Il comprend quelque chose.’ → ‘Il ne comprend _____.'”,
correct: “rien”,
acceptableAnswers: [“rien”],
category: “Négation complexe”,
points: 2
},
{
type: “qcm”,
question: “Choisissez la négation correcte :”,
options: [
“Je n’ai vu personne au musée”,
“Je n’ai vu quelqu’un au musée”,
“Je ai vu personne au musée”,
“Je ne ai vu personne au musée”
],
correct: 0,
category: “Négation complexe”,
points: 2
},
{
type: “text”,
question: “Complétez la négation : ‘Elle n’a _____ visité ce musée.’ (jamais)”,
correct: “jamais”,
acceptableAnswers: [“jamais”],
category: “Négation complexe”,
points: 2
},
{
type: “qcm”,
question: “Quelle phrase exprime une négation complexe correcte ?”,
options: [
“Il ne parle à personne jamais”,
“Il ne parle jamais à personne”,
“Il parle ne jamais à personne”,
“Il jamais ne parle à personne”
],
correct: 1,
category: “Négation complexe”,
points: 2
},
{
type: “text”,
question: “Transformez avec la restriction ‘ne…que’ : ‘Il visite seulement le Louvre.’ → ‘Il _____ visite _____ le Louvre.'”,
correct: “ne visite que”,
acceptableAnswers: [“ne visite que”, “ne…que”],
category: “Restriction ne…que”,
points: 2
},
{
type: “qcm”,
question: “Choisissez la phrase correcte avec ‘ne…que’ :”,
options: [
“Je ne veux seulement que ce tableau”,
“Je ne veux que ce tableau”,
“Je veux ne que ce tableau”,
“Je que ne veux ce tableau”
],
correct: 1,
category: “Restriction ne…que”,
points: 2
},
{
type: “text”,
question: “Complétez avec ‘ne…que’ : ‘Elle _____ collectionne _____ des œuvres modernes.'”,
correct: “ne collectionne que”,
acceptableAnswers: [“ne collectionne que”, “ne…que”],
category: “Restriction ne…que”,
points: 2
},
{
type: “qcm”,
question: “Quelle phrase utilise correctement la restriction ‘ne…que’ ?”,
options: [
“Il ne travaille que le matin seulement”,
“Il ne travaille que le matin”,
“Il travaille ne que le matin”,
“Il que ne travaille le matin”
],
correct: 1,
category: “Restriction ne…que”,
points: 2
}

// CATÉGORIE 3: Compréhension orale (20 points)
{
type: “audio_intro”,
content: audioText,
category: “Compréhension orale”
},
{
type: “qcm”,
question: “Depuis combien d’années Sophie Moreau est-elle peintre ?”,
options: [“10 ans”, “15 ans”, “20 ans”, “25 ans”],
correct: 1,
category: “Compréhension orale”,
points: 2
},
{
type: “text”,
question: “Quelles sont les trois couleurs vives mentionnées par Sophie Moreau ?”,
correct: “rouge, jaune, bleu”,
acceptableAnswers: [“rouge, jaune, bleu”, “le rouge, le jaune, le bleu”, “rouge jaune bleu”],
category: “Compréhension orale”,
points: 2
},
{
type: “qcm”,
question: “Comment Sophie décrit-elle son style artistique ?”,
options: [
“Uniquement impressionniste”,
“Uniquement contemporain”,
“Entre l’impressionnisme et l’art contemporain”,
“Complètement moderne”
],
correct: 2,
category: “Compréhension orale”,
points: 2
},
{
type: “text”,
question: “Sur quel monument Sophie a-t-elle peint une série l’année dernière ?”,
correct: “Notre-Dame”,
acceptableAnswers: [“Notre-Dame”, “Notre Dame”, “la cathédrale Notre-Dame”],
category: “Compréhension orale”,
points: 2
},
{
type: “qcm”,
question: “Où Sophie aime-t-elle peindre ?”,
options: [
“Seulement dans son atelier”,
“Dans les galeries”,
“En plein air, dans les parcs, devant les monuments”,
“Uniquement dans les musées”
],
correct: 2,
category: “Compréhension orale”,
points: 2
},
{
type: “text”,
question: “Dans quels types d’établissements scolaires Sophie va-t-elle travailler ?”,
correct: “écoles primaires et collèges”,
acceptableAnswers: [“écoles primaires et collèges”, “écoles primaires et les collèges”, “primaires et collèges”],
category: “Compréhension orale”,
points: 2
},
{
type: “qcm”,
question: “Selon Sophie, que faut-il pour créer des merveilles ?”,
options: [
“Beaucoup d’argent”,
“Quelques tubes de peinture et de l’imagination”,
“Un grand atelier”,
“Des pinceaux professionnels”
],
correct: 1,
category: “Compréhension orale”,
points: 2
},
{
type: “text”,
question: “Quel est le premier conseil que donne Sophie aux jeunes qui veulent devenir artistes ?”,
correct: “être passionné”,
acceptableAnswers: [“être passionné”, “il faut être passionné”, “la passion”],
category: “Compréhension orale”,
points: 2
},
{
type: “qcm”,
question: “Que recommande Sophie de faire tous les jours ?”,
options: [“Visiter des galeries”, “Pratiquer”, “Acheter du matériel”, “Rencontrer d’autres artistes”],
correct: 1,
category: “Compréhension orale”,
points: 2
},
{
type: “text”,
question: “Complétez cette phrase de Sophie : ‘Et surtout, ne jamais _____ ses rêves.'”,
correct: “abandonner”,
acceptableAnswers: [“abandonner”],
category: “Compréhension orale”,
points: 2
},

// CATÉGORIE 4: Vocabulaire (20 points)
{
type: “matching”,
question: “Associez chaque mot à sa définition :”,
items: [“Chef-d’œuvre”, “Patrimoine”, “Vernissage”, “Esquisse”, “Exposition”],
definitions: [
“Première ébauche d’une œuvre d’art”,
“Œuvre d’art remarquable et parfaite”,
“Présentation publique d’œuvres d’art”,
“Héritage culturel d’une société”,
“Inauguration d’une exposition”
],
correctMatches: [1, 3, 4, 0, 2],
category: “Vocabulaire”,
points: 4
},
{
type: “qcm”,
question: “Quel mot désigne une peinture représentant une personne ?”,
options: [“Paysage”, “Nature morte”, “Portrait”, “Esquisse”],
correct: 2,
category: “Vocabulaire”,
points: 2
},
{
type: “text”,
question: “Comment appelle-t-on l’art de sculpter dans la pierre ou le marbre ?”,
correct: “sculpture”,
acceptableAnswers: [“sculpture”, “la sculpture”],
category: “Vocabulaire”,
points: 2
},
{
type: “qcm”,
question: “Qu’est-ce qu’un ‘mécène’ ?”,
options: [
“Un critique d’art”,
“Une personne qui finance les arts”,
“Un conservateur de musée”,
“Un marchand d’art”
],
correct: 1,
category: “Vocabulaire”,
points: 2
},
{
type: “text”,
question: “Quel terme désigne l’ensemble des œuvres d’un artiste ?”,
correct: “œuvre”,
acceptableAnswers: [“œuvre”, “l’œuvre”, “corpus”],
category: “Vocabulaire”,
points: 2
},
{
type: “qcm”,
question: “Que signifie ‘restaurer’ une œuvre d’art ?”,
options: [
“La vendre”,
“La critiquer”,
“La réparer et la rénover”,
“L’exposer”
],
correct: 2,
category: “Vocabulaire”,
points: 2
},
{
type: “text”,
question: “Comment appelle-t-on une personne qui collectionne les œuvres d’art ?”,
correct: “collectionneur”,
acceptableAnswers: [“collectionneur”, “un collectionneur”, “collectionneuse”],
category: “Vocabulaire”,
points: 2
},
{
type: “qcm”,
question: “Qu’est-ce qu’un ‘atelier’ d’artiste ?”,
options: [
“Un magasin d’art”,
“Un lieu de travail de l’artiste”,
“Une école d’art”,
“Un musée privé”
],
correct: 1,
category: “Vocabulaire”,
points: 2
},
{
type: “text”,
question: “Quel mot désigne l’art de dessiner et peindre des paysages ?”,
correct: “paysage”,
acceptableAnswers: [“paysage”, “le paysage”, “paysagisme”],
category: “Vocabulaire”,
points: 2
},

// CATÉGORIE 5: Expression écrite (20 points)
{
type: “essay”,
question: “Rédigez un texte de 150-200 mots sur le sujet suivant : ‘Décrivez votre œuvre d’art préférée (peinture, sculpture, monument, etc.) et expliquez pourquoi elle vous touche particulièrement. Utilisez le vocabulaire artistique approprié et variez vos expressions.'”,
category: “Expression écrite”,
points: 20,
criteria: {
“Respect de la consigne et longueur”: 4,
“Richesse du vocabulaire artistique”: 4,
“Correction grammaticale”: 4,
“Cohérence et organisation”: 4,
“Originalité et créativité”: 4
}
}
];

// Variables globales
let currentQuestionIndex = 0;
let userAnswers = [];
let questionsData = [];
let studentData = {};
let examTimer;
let timeLeft = 120 * 60; // 2 heures en secondes
const SCRIPT_URL = ‘https://script.google.com/macros/s/YOUR_SCRIPT_ID/exec’;

// Fonction pour afficher une question
function displayQuestion(questionIndex) {
const question = questionsData[questionIndex];
const container = document.getElementById(‘question-container’);

let html = `

`;

// En-tête de catégorie
if (questionIndex === 0 || questionsData[questionIndex – 1].category !== question.category) {
html += `

${question.category}

`;
}

// Contenu selon le type de question
switch (question.type) {
case ‘text_intro’:
html += question.content;
html += `

`;
break;

case ‘audio_intro’:
html += question.content;
html += `

`;
break;

case ‘qcm’:
html += `

Question ${getQuestionNumber(questionIndex)} (${question.points} points)

${question.question}

`;
question.options.forEach((option, index) => {
html += `

`;
break;

case ‘text’:
html += `

Question ${getQuestionNumber(questionIndex)} (${question.points} points)

${question.question}

`;
break;

case ‘choix_phrase’:
html += `

Question ${getQuestionNumber(questionIndex)} (${question.points} points)

${question.question}

`;
question.options.forEach((option, index) => {
html += `

`;
break;

case ‘matching’:
html += `

Question ${getQuestionNumber(questionIndex)} (${question.points} points)

${question.question}

Termes :

`;
question.items.forEach((item, index) => {
html += `

${index + 1}. ${item}

`;
});
html += `

Définitions :

`;
question.definitions.forEach((def, index) => {
html += `

${String.fromCharCode(65 + index)}.
${def}

Choisir…`;
question.items.forEach((item, itemIndex) => {
html += `${itemIndex + 1}`;
});
html += `

`;
});
html += `

`;
break;

case ‘essay’:
html += `

Question ${getQuestionNumber(questionIndex)} (${question.points} points)

${question.question}

0 mots

Critères d’évaluation :

    `;
    Object.entries(question.criteria).forEach(([criterion, points]) => {
    html += `

  • ${criterion} : ${points} points
  • `;
    });
    html += `

`;
break;
}

html += ‘

‘;

// Navigation
if (question.type !== ‘text_intro’ && question.type !== ‘audio_intro’) {
html += `

‘;
}

container.innerHTML = html;

// Restaurer les réponses précédentes
restoreAnswer(questionIndex);

// Ajouter les event listeners pour les essais
if (question.type === ‘essay’) {
const textarea = document.getElementById(`essay-${questionIndex}`);
const wordCountSpan = document.getElementById(`word-count-${questionIndex}`);

textarea.addEventListener(‘input’, function() {
const words = this.value.trim().split(/s+/).filter(word => word.length > 0);
wordCountSpan.textContent = words.length;
});
}

// Mettre à jour les indicateurs de progression
updateProgressIndicators();
}

// Fonction pour obtenir le numéro de question (sans compter les intros)
function getQuestionNumber(questionIndex) {
let number = 1;
for (let i = 0; i {
const defIndex = parseInt(select.dataset.def);
const selectedValue = select.value;
if (selectedValue !== ”) {
matchingAnswers[defIndex] = parseInt(selectedValue);
}
});
userAnswers[currentQuestionIndex] = matchingAnswers;
break;

case ‘essay’:
const essayAnswer = document.getElementById(`essay-${currentQuestionIndex}`);
if (essayAnswer) {
userAnswers[currentQuestionIndex] = essayAnswer.value.trim();
}
break;
}
}

// Fonction pour restaurer une réponse précédente
function restoreAnswer(questionIndex) {
const question = questionsData[questionIndex];
const answer = userAnswers[questionIndex];

if (!answer) return;

switch (question.type) {
case ‘qcm’:
case ‘choix_phrase’:
const radioButton = document.querySelector(`input[name=”q${questionIndex}”][value=”${answer}”]`);
if (radioButton) {
radioButton.checked = true;
}
break;

case ‘text’:
const textInput = document.getElementById(`answer-${questionIndex}`);
if (textInput) {
textInput.value = answer;
}
break;

case ‘matching’:
Object.entries(answer).forEach(([defIndex, itemIndex]) => {
const select = document.querySelector(`.matching-select[data-def=”${defIndex}”]`);
if (select) {
select.value = itemIndex;
}
});
break;

case ‘essay’:
const textarea = document.getElementById(`essay-${questionIndex}`);
if (textarea) {
textarea.value = answer;
// Mettre à jour le compteur de mots
const words = answer.trim().split(/s+/).filter(word => word.length > 0);
const wordCountSpan = document.getElementById(`word-count-${questionIndex}`);
if (wordCountSpan) {
wordCountSpan.textContent = words.length;
}
}
break;
}
}

// Fonction de navigation vers la question suivante
function nextQuestion() {
saveCurrentAnswer();

if (currentQuestionIndex 0) {
currentQuestionIndex–;
displayQuestion(currentQuestionIndex);
}
}

// Fonction pour mettre à jour les indicateurs de progression
function updateProgressIndicators() {
const totalQuestions = questionsData.filter(q => q.type !== ‘text_intro’ && q.type !== ‘audio_intro’).length;
const currentNumber = getQuestionNumber(currentQuestionIndex);

document.getElementById(‘question-progress’).textContent =
`Question ${currentNumber > 0 ? currentNumber : ‘Introduction’} sur ${totalQuestions}`;

const progressBar = document.getElementById(‘progress-bar’);
const progressPercentage = currentNumber > 0 ? (currentNumber / totalQuestions) * 100 : 0;
progressBar.style.width = progressPercentage + ‘%’;
}

// Fonction pour mettre à jour le timer
function updateTimer() {
if (timeLeft <= 0) {
clearInterval(examTimer);
alert('Temps écoulé ! L'examen se termine automatiquement.');
calculateResults();
return;
}

const hours = Math.floor(timeLeft / 3600);
const minutes = Math.floor((timeLeft % 3600) / 60);
const seconds = timeLeft % 60;

const timerDisplay = document.getElementById('exam-timer');
if (timerDisplay) {
timerDisplay.textContent = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;

// Changer la couleur si moins de 10 minutes
if (timeLeft q.type === ‘text_intro’ || q.type === ‘audio_intro’);
const otherQuestions = questionsData.filter(q => q.type !== ‘text_intro’ && q.type !== ‘audio_intro’);

// Mélanger les autres questions
for (let i = otherQuestions.length – 1; i > 0; i–) {
const j = Math.floor(Math.random() * (i + 1));
[otherQuestions[i], otherQuestions[j]] = [otherQuestions[j], otherQuestions[i]];
}

return […introQuestions, …otherQuestions];
}

// Fonction pour calculer les résultats
function calculateResults() {
saveCurrentAnswer();

let totalScore = 0;
let maxScore = 0;
const results = {};

questionsData.forEach((question, index) => {
if (question.type === ‘text_intro’ || question.type === ‘audio_intro’) return;

maxScore += question.points;
const category = question.category;

if (!results[category]) {
results[category] = { score: 0, maxScore: 0, details: [] };
}

results[category].maxScore += question.points;

let questionScore = 0;
const userAnswer = userAnswers[index];

switch (question.type) {
case ‘qcm’:
case ‘choix_phrase’:
if (userAnswer === question.correct) {
questionScore = question.points;
}
results[category].details.push({
question: question.question,
userAnswer: question.options[userAnswer] || ‘Pas de réponse’,
correctAnswer: question.options[question.correct],
score: questionScore,
maxScore: question.points
});
break;

case ‘text’:
if (userAnswer && question.acceptableAnswers) {
const isCorrect = question.acceptableAnswers.some(acceptable =>
userAnswer.toLowerCase().includes(acceptable.toLowerCase())
);
if (isCorrect) questionScore = question.points;
}
results[category].details.push({
question: question.question,
userAnswer: userAnswer || ‘Pas de réponse’,
correctAnswer: question.acceptableAnswers ? question.acceptableAnswers.join(‘ ou ‘) : ‘Non défini’,
score: questionScore,
maxScore: question.points
});
break;

case ‘matching’:
if (userAnswer && question.correctMatches) {
let correctMatches = 0;
question.correctMatches.forEach((correct, defIndex) => {
if (userAnswer[defIndex] === correct) {
correctMatches++;
}
});
questionScore = Math.round((correctMatches / question.correctMatches.length) * question.points);
}
results[category].details.push({
question: question.question,
userAnswer: ‘Associations réalisées’,
correctAnswer: ‘Voir correction détaillée’,
score: questionScore,
maxScore: question.points
});
break;

case ‘essay’:
// Pour l’expression écrite, on attribue une note basique
// (dans un vrai contexte, ce serait évalué manuellement)
if (userAnswer && userAnswer.length > 50) {
questionScore = Math.round(question.points * 0.7); // 70% de base si réponse fournie
}
results[category].details.push({
question: question.question,
userAnswer: userAnswer ? `Texte de ${userAnswer.split(‘ ‘).length} mots` : ‘Pas de réponse’,
correctAnswer: ‘Évaluation manuelle requise’,
score: questionScore,
maxScore: question.points
});
break;
}

results[category].score += questionScore;
totalScore += questionScore;
});

// Afficher les résultats
displayResults(results, totalScore, maxScore);

// Envoyer les résultats au serveur
sendResultsToServer(results, totalScore, maxScore);
}

// Fonction pour afficher les résultats
function displayResults(results, totalScore, maxScore) {
const container = document.getElementById(‘question-container’);
const percentage = Math.round((totalScore / maxScore) * 100);

let html = `

Résultats de l’examen

Score général : ${totalScore}/${maxScore} (${percentage}%)

`;

Object.entries(results).forEach(([category, data]) => {
const categoryPercentage = Math.round((data.score / data.maxScore) * 100);
html += `

${category} : ${data.score}/${data.maxScore} (${categoryPercentage}%)

`;
});

html += `


`;

container.innerHTML = html;

// Arrêter le timer
clearInterval(examTimer);
const timerElement = document.getElementById(‘exam-timer’);
if (timerElement) {
timerElement.textContent = ‘Terminé’;
}
}

// Fonction pour envoyer les résultats au serveur
function sendResultsToServer(results, totalScore, maxScore) {
const studentEmail = sessionStorage.getItem(‘student_email’);
const studentName = sessionStorage.getItem(‘student_name’);
const studentCourse = sessionStorage.getItem(‘student_course’);

const formData = new FormData();
formData.append(‘email’, studentEmail);
formData.append(‘nombre’, studentName);
formData.append(‘curso’, studentCourse);
formData.append(‘accion’, ‘finalizar’);
formData.append(‘puntuacion’, totalScore);
formData.append(‘puntuacion_maxima’, maxScore);
formData.append(‘porcentaje’, Math.round((totalScore / maxScore) * 100));
formData.append(‘resultados_detallados’, JSON.stringify(results));
formData.append(‘respuestas_usuario’, JSON.stringify(userAnswers));

fetch(SCRIPT_URL, {
method: ‘POST’,
body: formData,
mode: ‘no-cors’
})
.then(() => {
console.log(‘Résultats envoyés avec succès’);
})
.catch(error => {
console.error(‘Erreur lors de l’envoi des résultats:’, error);
});
}

// Fonction pour afficher les corrections détaillées
function showDetailedResults() {
const container = document.getElementById(‘question-container’);

let html = `

Corrections détaillées


`;

questionsData.forEach((question, index) => {
if (question.type === ‘text_intro’ || question.type === ‘audio_intro’) return;

const userAnswer = userAnswers[index];
const questionNumber = getQuestionNumber(index);

html += `

Question ${questionNumber} (${question.category})

Question : ${question.question}

Votre réponse : ${getUserAnswerText(question, userAnswer, index)}

Réponse correcte : ${getCorrectAnswerText(question)}

Points obtenus : ${getQuestionScore(question, userAnswer, index)}/${question.points}

`;
});

html += ‘

‘;
container.innerHTML = html;
}

// Fonction utilitaire pour obtenir le texte de la réponse utilisateur
function getUserAnswerText(question, userAnswer, index) {
switch (question.type) {
case ‘qcm’:
case ‘choix_phrase’:
return question.options[userAnswer] || ‘Pas de réponse’;
case ‘text’:
return userAnswer || ‘Pas de réponse’;
case ‘matching’:
if (!userAnswer) return ‘Pas de réponse’;
let matchText = ”;
Object.entries(userAnswer).forEach(([defIndex, itemIndex]) => {
matchText += `${String.fromCharCode(65 + parseInt(defIndex))} → ${parseInt(itemIndex) + 1}; `;
});
return matchText || ‘Pas de réponse’;
case ‘essay’:
return userAnswer ? `Texte de ${userAnswer.split(‘ ‘).length} mots` : ‘Pas de réponse’;
default:
return ‘Type de question non reconnu’;
}
}

// Fonction utilitaire pour obtenir le texte de la réponse correcte
function getCorrectAnswerText(question) {
switch (question.type) {
case ‘qcm’:
case ‘choix_phrase’:
return question.options[question.correct];
case ‘text’:
return question.acceptableAnswers ? question.acceptableAnswers.join(‘ ou ‘) : ‘Non défini’;
case ‘matching’:
if (!question.correctMatches) return ‘Non défini’;
let correctText = ”;
question.correctMatches.forEach((itemIndex, defIndex) => {
correctText += `${String.fromCharCode(65 + defIndex)} → ${itemIndex + 1}; `;
});
return correctText;
case ‘essay’:
return ‘Évaluation manuelle requise’;
default:
return ‘Type de question non reconnu’;
}
}

// Fonction utilitaire pour calculer le score d’une question
function getQuestionScore(question, userAnswer, index) {
switch (question.type) {
case ‘qcm’:
case ‘choix_phrase’:
return userAnswer === question.correct ? question.points : 0;
case ‘text’:
if (userAnswer && question.acceptableAnswers) {
return question.acceptableAnswers.some(acceptable =>
userAnswer.toLowerCase().includes(acceptable.toLowerCase())
) ? question.points : 0;
}
return 0;
case ‘matching’:
if (userAnswer && question.correctMatches) {
let correctMatches = 0;
question.correctMatches.forEach((correct, defIndex) => {
if (userAnswer[defIndex] === correct) {
correctMatches++;
}
});
return Math.round((correctMatches / question.correctMatches.length) * question.points);
}
return 0;
case ‘essay’:
return (userAnswer && userAnswer.length > 50) ? Math.round(question.points * 0.7) : 0;
default:
return 0;
}
}

// Fonction pour redémarrer l’examen
function restartExam() {
if (confirm(‘Êtes-vous sûr de vouloir redémarrer l’examen ? Tous vos progrès seront perdus.’)) {
currentQuestionIndex = 0;
userAnswers = [];
timeLeft = 120 * 60;
clearInterval(examTimer);
examTimer = setInterval(updateTimer, 1000);
questionsData = shuffleQuestions();
displayQuestion(0);
}
}

// Fonction pour terminer l’examen
function finishExam() {
if (confirm(‘Êtes-vous sûr de vouloir terminer l’examen maintenant ?’)) {
calculateResults();
}
}

// Fonction pour sauvegarder automatiquement les réponses
function autoSave() {
saveCurrentAnswer();
setTimeout(autoSave, 30000); // Sauvegarde automatique toutes les 30 secondes
}

// Initialisation de l’examen
function initializeExam() {
// Vérifier si les données de l’étudiant sont présentes
if (!sessionStorage.getItem(‘student_email’) || !sessionStorage.getItem(‘student_name’)) {
alert(‘Données d’identification manquantes. Veuillez vous reconnecter.’);
window.location.href = ‘login.html’;
return;
}

// Mélanger les questions
questionsData = shuffleQuestions();

// Récupérer les données de l’étudiant
studentData = {
email: sessionStorage.getItem(‘student_email’),
name: sessionStorage.getItem(‘student_name’),
course: sessionStorage.getItem(‘student_course’)
};

// Démarrer le timer
examTimer = setInterval(updateTimer, 1000);

// Démarrer la sauvegarde automatique
autoSave();

// Prévenir la fermeture accidentelle
window.addEventListener(‘beforeunload’, function(e) {
e.preventDefault();
e.returnValue = ‘Êtes-vous sûr de vouloir quitter l’examen ?’;
});

// Afficher la première question
displayQuestion(0);
}

// Fonction pour charger les données de questions (à implémenter selon vos besoins)
function loadQuestionsData() {
// Cette fonction devrait charger les données de questions depuis un fichier JSON ou une API
// Pour l’exemple, on suppose que questionsData est déjà défini
if (typeof questionsData === ‘undefined’ || questionsData.length === 0) {
console.error(‘Aucune donnée de questions trouvée’);
alert(‘Erreur: Impossible de charger les questions de l’examen’);
return false;
}
return true;
}

// Démarrer l’examen quand le contenu est chargé
document.addEventListener(‘DOMContentLoaded’, function() {
console.log(‘Script d’examen chargé’);

// Vérifier si les questions sont chargées
if (loadQuestionsData()) {
// Attendre un peu pour s’assurer que tout est prêt
setTimeout(initializeExam, 100);
}
});

// Gestion des erreurs globales
window.addEventListener(‘error’, function(e) {
console.error(‘Erreur dans l’examen:’, e.error);
// Optionnel: envoyer l’erreur au serveur pour le débogage
});

// Export des fonctions principales pour les tests (optionnel)
if (typeof module !== ‘undefined’ && module.exports) {
module.exports = {
displayQuestion,
saveCurrentAnswer,
calculateResults,
initializeExam
};
}