Refonte de la slide 3 FDS en glisser-déposer

- Renomme la carte formation en 'FDS, FT & Étiquettes'
- Slide 3 : deux colonnes de huit questions, banques d'étiquettes (rubriques 1 à 16 + Absent) de part et d'autre à glisser-déposer dans chaque case (repli au clic pour le tactile)
- Bloque la vérification tant que les seize cases ne sont pas remplies
- Retire l'étiquette 'Partie 1/3' et remonte le titre pour éviter le défilement
- Ordre d'affichage après correction : réponse, emoji, case
This commit is contained in:
2026-07-16 08:58:34 +02:00
parent a418558393
commit fb5c7e9720
2 changed files with 202 additions and 25 deletions

View File

@@ -196,6 +196,73 @@
/* QUIZ ROWS */ /* QUIZ ROWS */
.quiz-list { width: 100%; max-width: 720px; display: flex; flex-direction: column; gap: 12px; } .quiz-list { width: 100%; max-width: 720px; display: flex; flex-direction: column; gap: 12px; }
.quiz-list.quiz-grid {
max-width: 1080px;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
@media (max-width: 900px) {
.quiz-list.quiz-grid { grid-template-columns: 1fr; max-width: 720px; }
}
/* SLIDE 3 — DRAG & DROP */
#slide-3 {
justify-content: flex-start;
padding-top: calc(70px + 0.9rem);
padding-bottom: calc(52px + 0.6rem);
}
#slide-3 .slide-title { margin: 0.1rem 0 0.15rem; font-size: clamp(19px, 2.4vw, 26px); }
.dnd-hint { font-size: 12.5px; color: var(--text-muted); text-align: center; margin: 0 0 10px; }
.dnd-layout {
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
align-items: start;
gap: 14px;
width: 100%;
max-width: 1200px;
margin-bottom: 0.5rem;
}
#slide-3 .quiz-grid { gap: 8px; }
#slide-3 .verify-btn { padding: 9px 26px; }
#slide-3 .actions-row { margin-top: 0.8rem; }
.dnd-bank { display: grid; grid-template-columns: 1fr; gap: 6px; align-content: start; }
.dnd-tile {
font-family: 'Syne', sans-serif;
font-size: 12px; font-weight: 800;
padding: 8px 10px; min-width: 66px; text-align: center;
border-radius: 8px;
background: var(--amber-light); color: var(--amber-dark);
border: 1px solid var(--amber);
cursor: grab; user-select: none;
transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.dnd-tile:hover { transform: translateY(-1px); box-shadow: 0 4px 10px rgba(186,117,23,0.15); }
.dnd-tile:active { cursor: grabbing; }
.dnd-tile.armed { outline: 2px solid var(--amber-dark); box-shadow: 0 0 0 3px rgba(186,117,23,0.18); }
.dnd-tile.dragging { opacity: 0.4; }
#slide-3 .answer-row { padding: 6px 12px; gap: 10px; }
#slide-3 .question-num { min-width: 26px; height: 26px; font-size: 11px; }
#slide-3 .question-text { font-size: 13px; }
.drop-slot {
min-width: 92px; min-height: 36px;
border: 2px dashed var(--amber); border-radius: 8px;
display: flex; align-items: center; justify-content: center;
font-family: 'Syne', sans-serif; font-size: 12px; font-weight: 800;
color: var(--amber-dark); background: var(--amber-light);
cursor: pointer; flex-shrink: 0;
transition: all 0.15s ease;
}
.drop-slot::before { content: 'Déposez ici'; font-weight: 600; font-size: 11px; opacity: 0.55; }
.drop-slot.filled { border-style: solid; background: white; }
.drop-slot.filled::before { content: none; }
.drop-slot.drag-over { border-color: var(--amber-dark); background: white; transform: scale(1.04); }
.drop-slot.slot-correct { border-color: #2e7d32; color: #2e7d32; }
.drop-slot.slot-wrong { border-color: #c62828; color: #c62828; }
@media (max-width: 900px) {
.dnd-layout { grid-template-columns: 1fr; }
.dnd-bank { grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); }
}
.answer-row { .answer-row {
background: white; background: white;
border: 1px solid var(--border); border: 1px solid var(--border);
@@ -259,6 +326,7 @@ select:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px
transition: all 0.2s ease; transition: all 0.2s ease;
} }
.verify-btn:hover { transform: translateY(-1px); } .verify-btn:hover { transform: translateY(-1px); }
.verify-btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; }
.result-message { .result-message {
margin-top: 4px; margin-top: 4px;
@@ -471,17 +539,19 @@ select:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px
<button class="btn btn-amber" onclick="goTo(3)" style="margin-top:1.5rem;">Répondre aux questions →</button> <button class="btn btn-amber" onclick="goTo(3)" style="margin-top:1.5rem;">Répondre aux questions →</button>
</div> </div>
<!-- ============ SLIDE 3 — FDS QUIZ ============ --> <!-- ============ SLIDE 3 — FDS QUIZ (DRAG & DROP) ============ -->
<div class="slide" id="slide-3"> <div class="slide" id="slide-3">
<div class="scroll-area"> <div class="slide-title">Où trouver l'information ?</div>
<div class="section-tag">Partie 1 / 3 — FDS</div> <p class="dnd-hint">Glissez-déposez la bonne rubrique dans chaque case — ou cliquez une étiquette puis une case.</p>
<div class="slide-title">Où trouver l'information ?</div> <div class="dnd-layout">
<div class="quiz-list" id="fds-questions"></div> <div class="dnd-bank" id="fds-bank-left"></div>
<button class="verify-btn btn-amber" onclick="verifyFDS()">✓ Vérifier mes réponses</button> <div class="quiz-list quiz-grid" id="fds-questions"></div>
<div id="fds-result" class="result-message"></div> <div class="dnd-bank" id="fds-bank-right"></div>
<div class="actions-row"> </div>
<button class="btn btn-coral" onclick="goTo(4)">Passer à la partie 2 →</button> <button class="verify-btn btn-amber" id="fds-verify" onclick="verifyFDS()" disabled>✓ Vérifier mes réponses</button>
</div> <div id="fds-result" class="result-message"></div>
<div class="actions-row">
<button class="btn btn-coral" onclick="goTo(4)">Passer à la partie 2 →</button>
</div> </div>
</div> </div>
@@ -504,7 +574,7 @@ select:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px
<div class="section-tag" style="background:var(--coral-light);color:var(--coral);">Partie 2 / 3 — FT</div> <div class="section-tag" style="background:var(--coral-light);color:var(--coral);">Partie 2 / 3 — FT</div>
<div class="slide-title">Sections de la fiche technique</div> <div class="slide-title">Sections de la fiche technique</div>
<div class="quiz-list" id="ft-questions"></div> <div class="quiz-list" id="ft-questions"></div>
<button class="verify-btn btn-coral" onclick="verifyFT()">✓ Vérifier mes réponses</button> <button class="verify-btn btn-coral" id="ft-verify" onclick="verifyFT()" disabled>✓ Vérifier mes réponses</button>
<div id="ft-result" class="result-message"></div> <div id="ft-result" class="result-message"></div>
<div class="actions-row"> <div class="actions-row">
<button class="btn btn-teal" onclick="goTo(6)">Passer à la partie 3 →</button> <button class="btn btn-teal" onclick="goTo(6)">Passer à la partie 3 →</button>
@@ -531,7 +601,7 @@ select:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px
<div class="section-tag" style="background:var(--teal-light);color:var(--teal-dark);">Partie 3 / 3 — Étiquette</div> <div class="section-tag" style="background:var(--teal-light);color:var(--teal-dark);">Partie 3 / 3 — Étiquette</div>
<div class="slide-title">Zones de l'étiquette</div> <div class="slide-title">Zones de l'étiquette</div>
<div class="quiz-list" id="etiquette-questions"></div> <div class="quiz-list" id="etiquette-questions"></div>
<button class="verify-btn btn-teal" onclick="verifyEtiquette()">✓ Vérifier mes réponses</button> <button class="verify-btn btn-teal" id="eti-verify" onclick="verifyEtiquette()" disabled>✓ Vérifier mes réponses</button>
<div id="etiquette-result" class="result-message"></div> <div id="etiquette-result" class="result-message"></div>
<div class="actions-row"> <div class="actions-row">
<button class="btn btn-amber" onclick="goTo(8)">Voir la synthèse →</button> <button class="btn btn-amber" onclick="goTo(8)">Voir la synthèse →</button>
@@ -644,14 +714,8 @@ function buildRow(qNum, selectId, answerAttr, text, options, badgeStyle) {
} }
function generateQuizzes() { function generateQuizzes() {
// FDS — shuffled, 16 rubriques // FDS — glisser-déposer : banques d'étiquettes + cases de dépôt
const rubriqueOpts = Array.from({ length: 16 }, (_, i) => ({ value: String(i + 1), label: 'Rubrique ' + (i + 1) })); buildFdsDnd();
rubriqueOpts.push({ value: 'absent', label: 'Absent' });
const fdsC = document.getElementById('fds-questions');
fdsC.innerHTML = '';
shuffleArray(fdsQuestions).forEach((q, i) => {
fdsC.innerHTML += buildRow(i + 1, 'fds' + q.id, q.answer, q.text, rubriqueOpts, 'background:var(--amber-light);color:var(--amber-dark);');
});
// FT // FT
const ftOpts = ftOptions.map(o => ({ value: o, label: o })); const ftOpts = ftOptions.map(o => ({ value: o, label: o }));
@@ -670,6 +734,100 @@ function generateQuizzes() {
etiquetteQuestions.forEach((q, i) => { etiquetteQuestions.forEach((q, i) => {
etiC.innerHTML += buildRow(i + 1, 'eti' + q.id, q.answer, q.text, etiOpts, 'background:var(--teal-light);color:var(--teal-dark);'); etiC.innerHTML += buildRow(i + 1, 'eti' + q.id, q.answer, q.text, etiOpts, 'background:var(--teal-light);color:var(--teal-dark);');
}); });
// Bloque la vérification tant que toutes les questions ne sont pas répondues
wireVerifyGate('ft-questions', 'ft-verify');
wireVerifyGate('etiquette-questions', 'eti-verify');
}
/* ---- FDS : glisser-déposer ---- */
function buildFdsDnd() {
const tile = (v, l) => `<div class="dnd-tile" draggable="true" data-value="${v}" data-label="${l}">${l}</div>`;
let left = '', right = '';
for (let i = 1; i <= 8; i++) left += tile(String(i), 'R' + i);
for (let i = 9; i <= 16; i++) right += tile(String(i), 'R' + i);
right += tile('absent', 'Absent');
document.getElementById('fds-bank-left').innerHTML = left;
document.getElementById('fds-bank-right').innerHTML = right;
const fdsC = document.getElementById('fds-questions');
fdsC.innerHTML = '';
shuffleArray(fdsQuestions).forEach((q, i) => {
fdsC.innerHTML += `
<div class="answer-row">
<div class="question-num" style="background:var(--amber-light);color:var(--amber-dark);">Q${i + 1}</div>
<div class="question-text">${q.text}</div>
<div class="drop-slot" id="fds${q.id}" data-answer="${q.answer}" data-value=""></div>
</div>`;
});
initFdsDnd();
}
function initFdsDnd() {
const slide = document.getElementById('slide-3');
const tiles = slide.querySelectorAll('.dnd-tile');
const slots = slide.querySelectorAll('.drop-slot');
let armed = null;
const resetResults = () => {
slide.querySelectorAll('.answer-label, .answer-icon').forEach(e => e.remove());
slide.querySelectorAll('.drop-slot').forEach(s => s.classList.remove('slot-correct', 'slot-wrong'));
const r = document.getElementById('fds-result');
r.className = 'result-message'; r.innerHTML = '';
};
const fill = (slot, v, l) => {
slot.dataset.value = v; slot.textContent = l; slot.classList.add('filled');
resetResults(); updateFdsGate();
};
const clear = (slot) => {
slot.dataset.value = ''; slot.textContent = ''; slot.classList.remove('filled');
resetResults(); updateFdsGate();
};
tiles.forEach(t => {
t.addEventListener('dragstart', e => {
e.dataTransfer.setData('text/plain', JSON.stringify({ v: t.dataset.value, l: t.dataset.label }));
t.classList.add('dragging');
});
t.addEventListener('dragend', () => t.classList.remove('dragging'));
t.addEventListener('click', () => {
if (armed === t) { t.classList.remove('armed'); armed = null; return; }
if (armed) armed.classList.remove('armed');
armed = t; t.classList.add('armed');
});
});
slots.forEach(s => {
s.addEventListener('dragover', e => { e.preventDefault(); s.classList.add('drag-over'); });
s.addEventListener('dragleave', () => s.classList.remove('drag-over'));
s.addEventListener('drop', e => {
e.preventDefault(); s.classList.remove('drag-over');
try { const d = JSON.parse(e.dataTransfer.getData('text/plain')); fill(s, d.v, d.l); } catch (_) {}
});
s.addEventListener('click', () => {
if (armed) { fill(s, armed.dataset.value, armed.dataset.label); armed.classList.remove('armed'); armed = null; }
else if (s.classList.contains('filled')) { clear(s); }
});
});
updateFdsGate();
}
function updateFdsGate() {
const slots = document.querySelectorAll('#slide-3 .drop-slot');
const all = Array.from(slots).every(s => (s.dataset.value || '') !== '');
document.getElementById('fds-verify').disabled = !all;
}
function wireVerifyGate(containerId, btnId) {
const container = document.getElementById(containerId);
const btn = document.getElementById(btnId);
if (!container || !btn) return;
const selects = Array.from(container.querySelectorAll('select'));
const update = () => { btn.disabled = !selects.every(s => s.value !== ''); };
selects.forEach(s => s.addEventListener('change', update));
update();
} }
/* ======================== VÉRIFICATION ======================== */ /* ======================== VÉRIFICATION ======================== */
@@ -686,7 +844,7 @@ function showInlineResult(selectEl, isCorrect, isUnanswered, correctAnswerText)
const icon = document.createElement('div'); const icon = document.createElement('div');
icon.className = 'answer-icon'; icon.className = 'answer-icon';
icon.textContent = isUnanswered ? '❓' : (isCorrect ? '✅' : '❌'); icon.textContent = isUnanswered ? '❓' : (isCorrect ? '✅' : '❌');
selectEl.after(icon); selectEl.before(icon);
} }
function showScore(resultDiv, correct, total) { function showScore(resultDiv, correct, total) {
@@ -709,19 +867,38 @@ function showScore(resultDiv, correct, total) {
function verifyFDS() { function verifyFDS() {
let correct = 0; let correct = 0;
fdsQuestions.forEach(q => { fdsQuestions.forEach(q => {
const select = document.getElementById('fds' + q.id); const slot = document.getElementById('fds' + q.id);
const userAnswer = select.value; const userAnswer = slot.dataset.value || '';
const expected = q.answer.split(','); const expected = q.answer.split(',');
const isCorrect = expected.includes(userAnswer); const isCorrect = expected.includes(userAnswer);
const labelText = expected.length > 1 const labelText = expected.length > 1
? 'Rubriques ' + expected.join(' et ') ? 'Rubriques ' + expected.join(' et ')
: (expected[0] === 'absent' ? 'Absent' : 'Rubrique ' + expected[0]); : (expected[0] === 'absent' ? 'Absent' : 'Rubrique ' + expected[0]);
if (isCorrect) correct++; if (isCorrect) correct++;
showInlineResult(select, isCorrect, userAnswer === '', labelText); showSlotResult(slot, isCorrect, userAnswer === '', labelText);
}); });
showScore(document.getElementById('fds-result'), correct, fdsQuestions.length); showScore(document.getElementById('fds-result'), correct, fdsQuestions.length);
} }
function showSlotResult(slot, isCorrect, isUnanswered, correctAnswerText) {
const row = slot.closest('.answer-row');
row.querySelector('.answer-label')?.remove();
row.querySelector('.answer-icon')?.remove();
slot.classList.remove('slot-correct', 'slot-wrong');
const label = document.createElement('div');
label.className = 'answer-label';
label.textContent = correctAnswerText;
slot.before(label);
const icon = document.createElement('div');
icon.className = 'answer-icon';
icon.textContent = isUnanswered ? '❓' : (isCorrect ? '✅' : '❌');
slot.before(icon);
if (!isUnanswered) slot.classList.add(isCorrect ? 'slot-correct' : 'slot-wrong');
}
function verifyFT() { function verifyFT() {
let correct = 0; let correct = 0;
ftQuestions.forEach(q => { ftQuestions.forEach(q => {

View File

@@ -103,7 +103,7 @@
<div class="formation-card"> <div class="formation-card">
<div class="formation-header"> <div class="formation-header">
<div class="formation-icon">📋</div> <div class="formation-icon">📋</div>
<h3 class="formation-title">FDS & Étiquettes</h3> <h3 class="formation-title">FDS, FT & Étiquettes</h3>
<p class="formation-subtitle">Aller plus loin</p> <p class="formation-subtitle">Aller plus loin</p>
</div> </div>
<div class="formation-body"> <div class="formation-body">