Pastebin
Retrouvez, créez et partagez vos snippets en temps réel.
Rechercher un Pastebin
Aucun paste trouvé.
Créer un paste
Pastebin
Blog
Hdv
<!DOCTYPE html> <html lang="fr"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>ATA 31 - 34 Chpt 1 - 4</title> <style> :root{ --bg:#0f172a; --card:#1e293b; --text:#e5e7eb; --muted:#9ca3af; --ok:#22c55e; --bof:#fbbf24; --bad:#ef4444; --btn:#2563eb; --surface:#0b1223; --border:#334155; } /* ===== Palette inspirée du logo Sabena technics ===== */ :root[data-theme="light"]{ --bg:#f5f7fa; --card:#ffffff; --text:#111827; /* Texte principal : noir anthracite */ --muted:#374151; /* Sous-titres : gris foncé */ --ok:#007A33; /* Vert Sabena (validation) */ --bof:#FFD200; /* Jaune Sabena */ --bad:#C1121F; /* Rouge profond (erreur) */ --btn:#003366; /* Bleu Sabena */ --surface:#f0f4f8; /* Fond clair homogène */ --border:#cbd5e1; /* Bordures neutres */ } :root[data-theme="light"] .opt{ color: var(--text); background:#ffffff; border-color: var(--border); } :root[data-theme="light"] .opt:hover, :root[data-theme="light"] .opt:focus{ outline: 2px solid var(--btn); background: color-mix(in srgb, var(--btn) 6%, #fff); } :root[data-theme="light"] .opt.correct{ outline-color: var(--ok); background: color-mix(in srgb, var(--ok) 15%, #fff); color: var(--text); } :root[data-theme="light"] .opt.wrong{ outline-color: var(--bad); background: color-mix(in srgb, var(--bad) 10%, #fff); color: var(--text); } :root[data-theme="light"] .badge-status.badge-ok{ color: var(--ok); } :root[data-theme="light"] .badge-status.badge-bof{ color: var(--bof); } :root[data-theme="light"] .badge-status.badge-bad{ color: var(--bad); } :root[data-theme="light"] button{ background: var(--btn); color: #fff; } :root[data-theme="light"] .btn-gray{ background: var(--surface); color: var(--text); } html,body{height:100%} body{ margin:0; background:var(--bg); color:var(--text); font-family:system-ui,Segoe UI,Roboto,sans-serif; padding:16px; display:flex; justify-content:center; /* ✅ centre horizontalement */ align-items:flex-start; /* garde le haut visible (évite centrage vertical complet) */ min-height:100vh; /* occupe toute la hauteur de la fenêtre */ } .card{background:var(--card);border:1px solid var(--border);border-radius:12px;box-shadow:0 8px 24px rgba(0,0,0,.15);padding:16px} .main{ width:100%; max-width:900px; } .side{width:340px;min-width:300px;max-height:90vh;overflow:auto;display:flex;flex-direction:column;gap:12px} h1{margin:0 0 10px} .row{display:flex;gap:8px;align-items:center;flex-wrap:wrap} select,input,button{padding:8px;border-radius:10px;border:1px solid var(--border);background:var(--surface);color:var(--text)} button{cursor:pointer;background:var(--btn);color:#fff;border:none} .btn-gray{background:var(--surface);color:var(--text)} .pill{background:var(--surface);border:1px solid var(--border);border-radius:999px;padding:6px 10px;display:inline-flex;gap:6px;align-items:center} .grid{display:grid;grid-template-columns:1fr 1fr;gap:10px} .opt{ background:var(--surface);border:1px solid var(--btn);border-radius:10px; padding:14px 16px;cursor:pointer;text-align:left;font-size:1.05rem;font-weight:600; transition:transform .12s ease } .opt:hover{transform:scale(1.02)} .opt.correct{outline:2px solid var(--ok);background:rgba(34,197,94,.12)} .opt.wrong{outline:2px solid var(--bad);background:rgba(239,68,68,.12)} .muted{color:var(--muted);font-size:.92rem} .timer{background:var(--surface);border:1px solid var(--border);padding:6px 10px;border-radius:999px} .badge{background:var(--surface);border:1px solid var(--border);border-radius:999px;padding:2px 8px;font-size:.8rem} .badge-status{margin-left:8px;padding:2px 10px;border-radius:999px;font-size:.9rem;border:1px solid var(--border);display:none} .badge-ok{color:var(--ok)} .badge-bof{color:var(--bof)} .badge-bad{color:var(--bad)} table{width:100%;border-collapse:collapse} th,td{padding:8px;border-bottom:1px solid var(--border);text-align:left} th{color:var(--muted)} .log{background:var(--surface);padding:10px;border-radius:8px;height:300px;overflow:auto} .ok{color:var(--ok)} .bof{color:var(--bof)} .bad{color:var(--bad)} .hl{font-weight:700;text-decoration:underline} .progressbar{height:8px;background:var(--surface);border:1px solid var(--border);border-radius:999px;overflow:hidden} .progressbar > div{height:100%;background:var(--btn);width:0%} .svg-wrap{background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:8px} hr{border:none;border-top:1px solid var(--border);margin:12px 0} .small{font-size:.9rem} @media (max-width:640px){ .grid{grid-template-columns:1fr} } </style> </head> <body> <div class="card main"> <div class="row" style="justify-content:space-between"> <h1>✈️ ATA 31 - 34 Chpt 1 - 4</h1> <button id="themeToggle" class="btn-gray" title="Changer de thème">🌙/☀️</button> </div> <!-- Outils --> <div class="row"> <div style="min-width:160px"> <label class="small">Pseudo</label> <input id="pseudoInput" placeholder="Ton nom" maxlength="20" /> </div> <div class="pill"><span>⏱️</span><b id="timer">0</b>s</div> <div class="pill">Score: <b id="score">0</b></div> </div> <!-- Question --> <div class="card" style="margin-top:10px"> <div class="row" style="justify-content:space-between;align-items:center"> <div style="font-size:1.2rem;font-weight:700"> <span id="progressTxt">Question 0/0</span> <span id="prevBadge" class="badge-status">OK</span> </div> <div class="progressbar" style="flex:1;max-width:360px"> <div id="progressBar"></div> </div> </div> <p id="question" style="font-size:1.25rem;font-weight:700;margin:8px 0">Clique sur Démarrer</p> <div id="choices" class="grid"></div> <div id="feedback" class="muted" style="min-height:28px;margin-top:6px"></div> <div class="row" style="margin-top:8px"> <button id="startBtn">Démarrer</button> <button id="pauseBtn" class="btn-gray" disabled>Pause</button> <button id="stopBtn" class="btn-gray" disabled>Stop</button> <button id="resumeBtn" class="btn-gray" style="display:none">Reprendre</button> </div> </div> <!-- Résumé --> <div id="summaryCard" class="card" style="margin-top:10px;display:none"> <h3 style="margin:0 0 6px">Résumé</h3> <div id="summaryStats" class="muted"></div> <div id="summaryList" class="muted" style="height:220px;overflow:auto;margin-top:8px"></div> <div class="row" style="margin-top:8px"> <button id="printSummary" class="btn-gray">Imprimer</button> <button id="closeSummary" class="btn-gray">Fermer</button> </div> </div> <div id="scoreBoardCard" class="card" style="margin-top:10px"> <h3 style="margin:0 0 6px">Tableau des scores</h3> <div class="muted small">Affiche les 10 derniers (stocke 30 max).</div> <table> <thead> <tr><th>Pseudo</th><th>Score</th><th>%</th><th>Date</th></tr> </thead> <tbody id="scoreTableBody"></tbody> </table> <div class="row" style="margin-top:6px"> <button id="clearScores" class="btn-gray">Effacer</button> </div> </div> <!-- Progression --> <div class="card" style="margin-top:10px"> <h3 style="margin:0 0 6px">📈 Progression</h3> <div class="svg-wrap"><svg id="chart" viewBox="0 0 320 120" width="100%" height="120" aria-label="Courbe des %"></svg></div> </div> </div> <script> // ====== Helpers const $ = s => document.querySelector(s); const pad2 = n => String(n).padStart(2,'0'); const nowStamp = () => { const d=new Date(); return `${pad2(d.getDate())}/${pad2(d.getMonth()+1)}/${String(d.getFullYear()).slice(-2)} ${pad2(d.getHours())}:${pad2(d.getMinutes())}`; }; function shuffle(a){ for(let i=a.length-1;i>0;i--){ const j=Math.floor(Math.random()*(i+1)); [a[i],a[j]]=[a[j],a[i]] } return a; } // ====== Thème const THEME_KEY='ata_theme'; (function applyTheme(){ const t = localStorage.getItem(THEME_KEY)||'dark'; document.documentElement.setAttribute('data-theme', t==='light'?'light':'dark'); })(); $('#themeToggle').onclick=()=>{ const cur=document.documentElement.getAttribute('data-theme')==='light'?'light':'dark'; const nxt=cur==='light'?'dark':'light'; document.documentElement.setAttribute('data-theme',nxt); localStorage.setItem(THEME_KEY,nxt); }; // ====== Storage keys const LS_SCORES='ata313414_scores_v2'; const LS_PSEUDO='ata313414_pseudo_v2'; const LS_SNAPSHOT='ata313414_snapshot_v1'; // ====== État let running=false, paused=false, i=0, score=0; let questionOrder=[], filteredQuestions=[]; let timeLeft=0, tickHandle=null, mode='TRAIN', perQ=15; let answersLog=[]; // ====== DOM const qEl=$('#question'), choicesEl=$('#choices'), feedbackEl=$('#feedback'); const timerEl=$('#timer'), scoreEl=$('#score'), progressTxt=$('#progressTxt'), progressBar=$('#progressBar'); const pseudoInput=$('#pseudoInput'); pseudoInput.value=localStorage.getItem(LS_PSEUDO)||''; pseudoInput.addEventListener('change',()=>localStorage.setItem(LS_PSEUDO,pseudoInput.value.trim())); // ====== Questions (exemple) — remplace/complète par ta liste complète si besoin let QUESTIONS = [ { "q": "Quel est l’un des trois objectifs majeurs de l’instrumentation d’un avion moderne ? (chpt.1, p.2)", "choices": ["Réduire les coûts d’entretien planifiés","Stabiliser la température des capteurs","Garantir la pressurisation automatique","Permettre la conduite et la surveillance"], "answer": 3 }, { "q": "L’ICAO (OACI) définit : (chpt.1, p.2)", "choices": ["Les calendriers de maintenance types","Les procédures IFR constructeur","Les tolérances des capteurs ISA","Les normes minimales de disposition"], "answer": 3 }, { "q": "L’« organisation en T basique » regroupe principalement : (chpt.1, p.4)", "choices": ["Cap, taux, température, pression","Attitude, puissance, vitesse, route","Vitesse, carburant, cap, incidence","Vitesse, assiette, altitude, direction"], "answer": 3 }, { "q": "Sur multimoteur, les instruments moteurs identiques doivent : (chpt.1, p.4)", "choices": ["Être alternés pour l’équilibre visuel","Être alignés selon l’ordre moteur","Être dispersés sans ordre fixe","Être placés côté copilote seul"], "answer": 1 }, { "q": "L’éclairage des instruments doit : (chpt.1, p.4)", "choices": ["Être fixe pour la nuit uniquement","Interdire toute variation manuelle","Suivre les feux de cabine seuls","Rester variable sans éblouir le pilote"], "answer": 3 }, { "q": "Le tachymètre sert à : (chpt.1, p.6)", "choices": ["Déterminer le couple hélice","Afficher la puissance mécanique","Mesurer la poussée réacteur","Indiquer la vitesse de rotation"], "answer": 3 }, { "q": "L’altimètre est classé parmi les instruments de : (chpt.1, p.5)", "choices": ["Pression carburant","Navigation et conduite","Communication de bord","Moteur et servitudes avion"], "answer": 1 }, { "q": "Une capsule anéroïde contient : (chpt.1, p.8)", "choices": ["Un liquide compensateur","Un gaz plus un fluide","Un mélange air–huile","Du vide avec un ressort"], "answer": 3 }, { "q": "La variation de pression atmosphérique provoque : (chpt.1, p.8)", "choices": ["Une tension de capteur Hall","Une variation radiofréquence","Une dilatation ou contraction","Un changement magnétique"], "answer": 2 }, { "q": "La relation de référence affichée par l’altimètre suit : (chpt.1, p.9)", "choices": ["Une table constructeur","Une densité locale ISA","Une règle nationale","La loi P = f(Z) standard"], "answer": 3 }, { "q": "Le système de calage barométrique réalise notamment : (chpt.2, p.2)", "choices": ["Un calcul de densité de l’air","Une correction de SAT","Une translation autour de 1013 hPa","Un étalonnage du variomètre"], "answer": 2 }, { "q": "Le calage standard international vaut : (chpt.2, p.3)", "choices": ["1020,5 hPa","1000,0 hPa","950,0 hPa","1013,2 hPa"], "answer": 3 }, { "q": "Le QNH correspond à : (chpt.2, p.4)", "choices": ["La pression locale de la piste","La pression ramenée au niveau mer","La différence QFE–1013 hPa","La pression du point de rosée"], "answer": 1 }, { "q": "Le QFE permet d’obtenir : (chpt.2, p.4)", "choices": ["Une altitude standardisée FL choisi","Une hauteur nulle au sol, réelle en vol","Une altitude corrigée température","Une pression équivalente MSL ISA"], "answer": 1 }, { "q": "Pour lire la hauteur par rapport à la piste, on sélectionne : (chpt.2, p.4)", "choices": ["Standard 1013,2 hPa","QNH en fenêtre baro","Correction ISA locale","QFE en fenêtre baro"], "answer": 3 }, { "q": "L’alerte altitude pré-acquisition se déclenche : (chpt.2, p.15)", "choices": ["500 ft avant la consigne","250 ft avant la consigne","1000 ft après capture","750 ft avant la consigne"], "answer": 3 }, { "q": "Dans ±250 ft de la consigne, le comportement sonore : (chpt.2, p.15)", "choices": ["Passe en mode alterné","S’arrête à l’intérieur de ±250 ft","Se déclenche à +500 ft","Reste actif en continu"], "answer": 1 }, { "q": "Les alarmes d’altitude sont inhibées notamment : (chpt.2, p.15)", "choices": ["Durant le roulage sol","En test hydraulique","À la capture GLIDE PATH","Sous 200 ft radio"], "answer": 2 }, { "q": "Le mécanisme de l’altimètre linéarise l’indication via : (chpt.2, p.1)", "choices": ["Bielle–manivelle et étrier","Servo optique interne","Palpeur inductif","Crémaillère capillaire"], "answer": 0 }, { "q": "Un niveau de vol FL 240 correspond à : (chpt.2, p.3)", "choices": ["2400 m au-dessus mer","24 000 m en standard","24 000 ft d’altitude pression","2400 ft au-dessus sol"], "answer": 2 }, { "q": "Le variomètre indique : (chpt.3, p.1)", "choices": ["Rapport Mach en altitude","Taux de montée ou descente","Vitesse sol corrigée vent","Altitude vraie du terrain"], "answer": 1 }, { "q": "Le variomètre est alimenté par la : (chpt.3, p.2)", "choices": ["Pression totale Pitot","Pression statique avion","Pression dynamique q","Température d’air T"], "answer": 1 }, { "q": "Son temps de réponse typique vaut environ : (chpt.3, p.3)", "choices": ["Cinq secondes","Une seconde","Dix secondes","Trois secondes"], "answer": 3 }, { "q": "L’unité de vitesse la plus employée en aviation : (chpt.3, p.6)", "choices": ["Le kilomètre par heure","Le nœud (kt) usuel","Le mètre par seconde","Le mile par heure"], "answer": 1 }, { "q": "La correspondance 1 kt équivaut à : (chpt.3, p.6)", "choices": ["1,400 km/h","1,620 km/h","1,852 km/h","1,950 km/h"], "answer": 2 }, { "q": "Le secteur vert de l’anémomètre désigne : (chpt.3, p.7)", "choices": ["Vitesses haute turbulence","Vitesses interdites réglementées","Vitesses normales avion lisse","Vitesses basses de décrochage"], "answer": 2 }, { "q": "Le machmètre affiche : (chpt.3, p.11)", "choices": ["La vitesse sol corrigée densité","La pression dynamique équivalente","Le rapport vitesse/son local","La température au point Mach"], "answer": 2 }, { "q": "En atmosphère standard à 0 °C, Mach 1 est proche de : (chpt.3, p.10)", "choices": ["1 190 km/h","1 000 km/h","900 km/h","1 350 km/h"], "answer": 0 }, { "q": "La précision attendue pour l’anémomètre est d’environ : (chpt.3, p.6)", "choices": ["Cinq pour cent","Un pour cent","Vingt pour cent","Dix pour cent"], "answer": 1 }, { "q": "L’échelle courante de variomètre en transport commercial : (chpt.3, p.3)", "choices": ["Jusqu’à 2000 ft/min","Jusqu’à 6000 ft/min","Jusqu’à 3000 ft/min","Jusqu’à 8000 ft/min"], "answer": 1 }, { "q": "L’horizon artificiel indique : (chpt.4, p.1)", "choices": ["Position GNSS et cap magnétique","Assiette longitudinale et transversale","Vitesse de rotation en tangage","Incidence aérodynamique"], "answer": 1 }, { "q": "Le cœur de l’horizon artificiel est : (chpt.4, p.4)", "choices": ["Une capsule barométrique étanche","Un gyroscope vertical à trois degrés","Un double capteur inertiel optique","Un baromètre différentiel ressort"], "answer": 1 }, { "q": "Le recalage rapide au démarrage sert à : (chpt.4, p.5)", "choices": ["Éliminer vibrations de la maquette","Supprimer erreurs de roulis seul","Corriger la lenteur d’érection du gyro","Compenser la rotation de la Terre"], "answer": 2 }, { "q": "En cas de défaillance électrique, l’horizon affiche : (chpt.4, p.5)", "choices": ["La perte de l’échelle de tangage","Le gel de la barre d’horizon seul","L’apparition d’un drapeau de panne","L’arrêt de l’index de roulis seul"], "answer": 2 }, { "q": "L’indicateur « bille-aiguille » renseigne sur : (chpt.4, p.11)", "choices": ["Le taux de montée et le vent","La symétrie du vol et le sens du virage","Le cap gyro et la vitesse sol","La position du train et la portance"], "answer": 1 }, { "q": "La bille se déplace principalement sous l’effet : (chpt.4, p.12)", "choices": ["De la température de cabine","Des forces massiques latérales de vol","Du champ magnétique terrestre","De la pression statique extérieure"], "answer": 1 }, { "q": "Le compas magnétique mesure : (chpt.4, p.15)", "choices": ["L’angle axe avion / nord magnétique","L’écart thermique entre les pôles","Le flux électrostatique ambiant","L’intensité du rayonnement solaire"], "answer": 0 }, { "q": "Après compensation, l’erreur permanente tolérée vaut : (chpt.4, p.17)", "choices": ["Six degrés maximum","Deux degrés maximum","Huit degrés maximum","Quatre degrés maximum"], "answer": 3 }, { "q": "Le pôle nord magnétique se situe : (chpt.4, p.14)", "choices": ["Vers la région du Groenland","En Sibérie orientale stable","Dans l’hémisphère sud","Au pôle géographique exact"], "answer": 0 }, { "q": "Le gyroscope directionnel (HSI/DG) doit être recalé : (chpt.4, p.20)", "choices": ["En fin de vol uniquement à l’arrêt","Après chaque virage > 15° seulement","Au passage systématique du FL100","Au départ, sur piste, puis toutes ~10 min en palier"], "answer": 3 } ]; // 👉 Tu peux coller ici ta grosse liste complète : le code fonctionne pareil. // ====== Scores (stock 30, affichage 10) function loadScores(){ try{ return JSON.parse(localStorage.getItem(LS_SCORES)||'[]') }catch{ return [] } } function saveScores(list){ localStorage.setItem(LS_SCORES, JSON.stringify(list.slice(-30))); } function addScoreRow(entry){ const pct = entry.total ? Math.round(entry.score/entry.total*1000)/10 : 0; const tr=document.createElement('tr'); tr.innerHTML = `<td>${entry.name||'—'}</td><td>${entry.score}/${entry.total}</td><td>${pct}%</td><td>${entry.when}</td>`; $('#scoreTableBody').appendChild(tr); } function renderScoreTable(){ const body=$('#scoreTableBody'); body.innerHTML=''; const list=loadScores(); const view=list.slice(-10).reverse(); // 10 derniers, récents en haut view.forEach(addScoreRow); } $('#clearScores').onclick=()=>{ localStorage.removeItem(LS_SCORES); renderScoreTable(); drawChart(); }; // ====== Graph progression (utilise jusqu’à 30 entrées) function drawChart(){ const svg=$('#chart'); svg.innerHTML=''; const list=loadScores(); const values=list.map(e=>Math.round(e.score/e.total*100)); const N=values.length, W=320, H=120, pad=12; // axes const ax=document.createElementNS('http://www.w3.org/2000/svg','path'); ax.setAttribute('d',`M${pad},${H-pad}H${W-pad} M${pad},${pad}V${H-pad}`); ax.setAttribute('stroke','currentColor'); ax.setAttribute('fill','none'); ax.setAttribute('opacity','0.4'); svg.appendChild(ax); if(!N) return; const start=Math.max(0,N-20), arr=values.slice(start); const step=(W-2*pad)/Math.max(1,arr.length-1); const pts=arr.map((v,k)=>[pad+k*step,(H-pad)-(v/100)*(H-2*pad)]); const d=pts.map((p,k)=>(k?'L':'M')+p[0]+','+p[1]).join(' '); const path=document.createElementNS('http://www.w3.org/2000/svg','path'); path.setAttribute('d',d); path.setAttribute('fill','none'); path.setAttribute('stroke','currentColor'); svg.appendChild(path); } renderScoreTable(); drawChart(); // ====== Timer function clearTick(){ if(tickHandle){ clearInterval(tickHandle); tickHandle=null; } } function setTimer(v){ timerEl.textContent=v; } function startTick(){ clearTick(); timeLeft=perQ; setTimer(timeLeft); tickHandle=setInterval(()=>{ if(paused) return; timeLeft--; setTimer(Math.max(0,timeLeft)); if(timeLeft<=0){ clearTick(); onTimeout(); } },1000); } // ====== Progress + badge function renderProgress(){ const total=filteredQuestions.length; progressTxt.textContent=`Question ${Math.min(i+1,total)}/${total}`; const pct = total? Math.round(i/total*100) : 0; progressBar.style.width = pct+'%'; } function showBadge(kind){ const el=$('#prevBadge'); el.className='badge-status'; if(!kind){ el.style.display='none'; return; } el.style.display='inline-block'; el.textContent = kind==='ok'?'OK':(kind==='bad'?'FAUX':'BOF'); el.classList.add(kind==='ok'?'badge-ok':(kind==='bad'?'badge-bad':'badge-bof')); setTimeout(()=>{ el.style.display='none'; }, 1500); } // ====== Rendu question (mélange des réponses) function renderQuestion(){ const total=filteredQuestions.length; if(i>=total){ finishGame(); return; } const current=filteredQuestions[questionOrder[i]]; const shuffled = current.choices.map((text,idx)=>({text,idx})).sort(()=>Math.random()-0.5); current.shuffledMap = shuffled.map(o=>o.idx); current.shuffledAnswer = shuffled.findIndex(o=>o.idx===current.answer); qEl.textContent=current.q; renderProgress(); feedbackEl.textContent=''; choicesEl.innerHTML=''; shuffled.forEach((item, idx)=>{ const b=document.createElement('button'); b.className='opt'; b.textContent=item.text; b.addEventListener('click', ()=> onSelect(idx, b)); choicesEl.appendChild(b); }); if(running && !paused) startTick(); } function highlightGood(){ const cur=filteredQuestions[questionOrder[i]]; const good = (cur && typeof cur.shuffledAnswer==='number') ? cur.shuffledAnswer : cur.answer; document.querySelectorAll('.opt').forEach((b,idx)=>{ b.disabled=true; if(idx===good) b.classList.add('correct'); }); } function disableAll(){ document.querySelectorAll('.opt').forEach(b=>b.disabled=true); } // ====== Sélection / Timeout function onSelect(idx, btn){ if(!running || paused) return; clearTick(); const curQ=filteredQuestions[questionOrder[i]]; const goodShuffled = (typeof curQ.shuffledAnswer==='number') ? curQ.shuffledAnswer : curQ.answer; const pickedOriginal = Array.isArray(curQ.shuffledMap) ? curQ.shuffledMap[idx] : idx; // log pour le résumé answersLog[i] = { index:i, q:curQ.q, choices:curQ.choices.slice(), correctIndex:curQ.answer, pickedIndex:pickedOriginal, correct:(idx===goodShuffled), timedOut:false }; if(mode==='TRAIN'){ highlightGood(); if(idx===goodShuffled){ score++; scoreEl.textContent=score; feedbackEl.innerHTML = `<span class="ok"><b>Bonne réponse !</b></span>`; showBadge('ok'); }else{ btn.classList.add('wrong'); feedbackEl.innerHTML = `<span class="bad"><b>Faux.</b></span>`; showBadge('bad'); } }else{ disableAll(); // examen : pas de correction immédiate } setTimeout(()=>{ i++; renderQuestion(); }, 800); } function onTimeout(){ if(!running) return; const curQ=filteredQuestions[questionOrder[i]]; answersLog[i] = { index:i, q:curQ.q, choices:curQ.choices.slice(), correctIndex:curQ.answer, pickedIndex:null, correct:false, timedOut:true }; if(mode==='TRAIN'){ highlightGood(); feedbackEl.innerHTML = `<span class="muted"><b>Temps écoulé.</b></span>`; showBadge('bad'); }else{ disableAll(); } setTimeout(()=>{ i++; renderQuestion(); }, 800); } // ====== Sélection des questions function buildFilteredQuestions(){ // Utiliser TOUTES les questions disponibles const arr = shuffle(QUESTIONS.map((q, idx) => ({ q, idx }))); filteredQuestions = arr.map(o => o.q); questionOrder = shuffle([...filteredQuestions.keys()]); } // ====== Démarrer / Pause / Stop function resetState(){ running=false; paused=false; i=0; score=0; answersLog=[]; scoreEl.textContent='0'; clearTick(); timerEl.textContent='0'; progressBar.style.width='0%'; progressTxt.textContent='Question 0/0'; qEl.textContent='Clique sur Démarrer'; choicesEl.innerHTML=''; feedbackEl.textContent=''; $('#summaryCard').style.display='none'; } function startGame(fromSnapshot=false){ mode = 'TRAIN'; perQ = 15; // 10 secondes fixes par question if(!fromSnapshot) buildFilteredQuestions(); running=true; paused=false; i=0; score=0; answersLog=[]; renderQuestion(); $('#pauseBtn').disabled=false; $('#stopBtn').disabled=false; saveSnapshot(); } $('#startBtn').onclick=()=>{ resetState(); startGame(false); }; $('#pauseBtn').onclick=()=>{ if(!running) return; paused = !paused; $('#pauseBtn').textContent = paused? 'Reprendre' : 'Pause'; if(!paused) startTick(); saveSnapshot(); }; $('#stopBtn').onclick=()=> finishGame(); // ====== Résumé + enregistrement du score function recordFinalScore(){ const name = (pseudoInput.value||'').trim() || 'Anonyme'; const entry = { name, score, total: filteredQuestions.length, when: nowStamp() }; // garder l'historique détaillé pour ce QCM const list = loadScores(); list.push(entry); saveScores(list); // calcul du pourcentage actuel const pct = entry.total ? (entry.score / entry.total * 100) : 0; const pctTxt = Math.round(pct * 10) / 10; // ex: 82.5 const currentSimple = `${name} ${pctTxt}%`; // --- BEST SCORE POUR LE SOMMAIRE --- // Lire l'ancien meilleur s'il existe const prevSimple = localStorage.getItem('score_ata313414_v1') || ''; // Extraire le pourcentage numérique de l'ancien (ex "Adrien 82.5%" -> 82.5) let prevPct = -1; const m = prevSimple.match(/([0-9]+(?:\.[0-9]+)?)%/); if (m) { prevPct = parseFloat(m[1]); } // Si pas d'ancien ou si le pourcentage actuel est meilleur -> on remplace if (pct > prevPct) { localStorage.setItem('score_ata313414_v1', currentSimple); } // rafraîchir affichage interne renderScoreTable(); drawChart(); } function buildSummary(){ const total=filteredQuestions.length; let ok=0, bad=0, to=0; const lines = answersLog.map((r,idx)=>{ const corr=r.choices[r.correctIndex]; const user = r.pickedIndex===null ? '(aucune/temps)' : r.choices[r.pickedIndex]; if(r.correct) ok++; else if(r.timedOut) to++; else bad++; const cls=r.correct?'ok':'bad'; return `<div class="${cls}">Q${idx+1}. ${r.q}<div class="small">Ta réponse: <b>${user}</b> — Corr: <b>${corr}</b></div></div>`; }).join(''); $('#summaryStats').innerHTML = `Score: <b>${score}</b> / ${total} | OK: <span class="ok">${ok}</span> – Faux: <span class="bad">${bad}</span> – Temps: <span class="bad">${to}</span>`; $('#summaryList').innerHTML = lines || '<span class="muted">Rien à afficher</span>'; } function finishGame(){ if(!running) return; clearTick(); running=false; paused=false; $('#pauseBtn').disabled=true; $('#stopBtn').disabled=true; if(mode==='EXAM'){ score = answersLog.reduce((acc,r)=> acc + (r.correct?1:0), 0); scoreEl.textContent=score; } buildSummary(); $('#summaryCard').style.display='block'; recordFinalScore(); localStorage.removeItem(LS_SNAPSHOT); } $('#printSummary').onclick=()=>window.print(); $('#closeSummary').onclick=()=>$('#summaryCard').style.display='none'; // ====== Snapshot (reprendre une partie) function saveSnapshot(){ if(!running) return; const snap={ t:Date.now(), mode, perQ, i, score, filteredQuestions, questionOrder, answersLog }; localStorage.setItem(LS_SNAPSHOT, JSON.stringify(snap)); } function loadSnapshot(){ try{ return JSON.parse(localStorage.getItem(LS_SNAPSHOT)||'null') }catch{ return null } } function applySnapshot(s){ mode=s.mode; perQ=s.perQ; i=s.i; score=s.score; filteredQuestions=s.filteredQuestions; questionOrder=s.questionOrder; answersLog=s.answersLog||[]; $('#mode').value=mode; $('#qSeconds').value=perQ; scoreEl.textContent=score; renderQuestion(); } (function maybeShowResume(){ const s=loadSnapshot(); if(!s) return; const btn=$('#resumeBtn'); btn.style.display='inline-block'; btn.onclick=()=>{ btn.style.display='none'; running=true; paused=false; applySnapshot(s); $('#pauseBtn').disabled=false; $('#stopBtn').disabled=false; startTick(); }; })(); // ====== Wire + boot (function boot(){ progressTxt.textContent='Question 0/0'; timerEl.textContent='0'; renderScoreTable(); drawChart(); })(); </script> </body>
Créé il y a 1 mois.