{"id":10149,"date":"2026-05-29T10:02:16","date_gmt":"2026-05-29T10:02:16","guid":{"rendered":"https:\/\/www.animcloud.fr\/?page_id=10149"},"modified":"2026-05-29T18:43:30","modified_gmt":"2026-05-29T18:43:30","slug":"player","status":"publish","type":"page","link":"https:\/\/www.animcloud.fr\/en\/player\/","title":{"rendered":"Player"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"fr\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n<title>Quiz Champions \u2014 Joueur<\/title>\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Orbitron:wght@400;700;900&#038;family=Exo+2:wght@300;400;600;700&#038;display=swap\" rel=\"stylesheet\">\n<script src=\"https:\/\/www.gstatic.com\/firebasejs\/10.12.0\/firebase-app-compat.js\"><\/script>\n<script src=\"https:\/\/www.gstatic.com\/firebasejs\/10.12.0\/firebase-database-compat.js\"><\/script>\n<style>\n* { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; }\n\nhtml { height: 100%; }\nbody {\n  font-family: 'Exo 2', sans-serif;\n  background: #0a0a1a;\n  color: #EEF0F8;\n  min-height: 100vh;\n  min-height: 100dvh;\n  display: flex;\n  flex-direction: column;\n  overflow-x: hidden;\n  overflow-y: auto;\n}\n\n\/* \u2500\u2500 BACKGROUND \u2500\u2500 *\/\n.bg-stars { position: fixed; inset: 0; z-index: 0; pointer-events: none; }\n.star {\n  position: absolute; border-radius: 50%;\n  background: #fff; opacity: .6;\n  animation: twinkle var(--dur, 3s) ease-in-out infinite alternate;\n}\n@keyframes twinkle { from { opacity:.2; } to { opacity:.8; } }\n\n\/* \u2500\u2500 HEADER \u2500\u2500 *\/\n.hdr {\n  position: fixed; top: 0; left: 0; right: 0; z-index: 100;\n  height: 44px;\n  background: rgba(10,10,26,.97);\n  border-bottom: 1px solid rgba(201,168,76,.2);\n  display: flex; align-items: center; justify-content: space-between;\n  padding: 0 16px;\n  flex-shrink: 0;\n}\n.hdr-title {\n  font-family: 'Orbitron', sans-serif;\n  font-size: 10px; letter-spacing: 3px; color: #C9A84C;\n}\n.hdr-live {\n  display: none; align-items: center; gap: 6px;\n  font-family: 'Orbitron', sans-serif;\n  font-size: 9px; letter-spacing: 3px; color: #10b981;\n}\n.hdr-live-dot {\n  width: 7px; height: 7px; border-radius: 50%;\n  background: #10b981;\n  animation: blink 1s ease-in-out infinite alternate;\n}\n@keyframes blink { from { opacity:1; } to { opacity:.2; } }\n\n\/* \u2500\u2500 PAGE \/ SCREEN \u2500\u2500 *\/\n.page {\n  position: relative; z-index: 1;\n  flex: 1;\n  width: 100%;\n  padding: 56px 16px 24px; \/* 56px = header + marge *\/\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  overflow-y: auto;\n  -webkit-overflow-scrolling: touch;\n}\n\n.screen { display: none; width: 100%; max-width: 420px; flex-direction: column; align-items: center; gap: 16px; padding-top: 12px; }\n.screen.active { display: flex; }\n\n\/* \u2500\u2500 JOIN \u2500\u2500 *\/\n.logo-circle {\n  width: 72px; height: 72px; border-radius: 50%;\n  border: 2px solid rgba(201,168,76,.6);\n  display: flex; align-items: center; justify-content: center;\n  font-size: 2.2rem;\n  background: rgba(201,168,76,.07);\n  box-shadow: 0 0 28px rgba(201,168,76,.2);\n}\n.page-title { text-align: center; }\n.page-title h1 {\n  font-family: 'Orbitron', sans-serif;\n  font-size: 20px; font-weight: 900; letter-spacing: 3px;\n  background: linear-gradient(135deg, #F0D080, #C9A84C);\n  -webkit-background-clip: text; -webkit-text-fill-color: transparent;\n  margin-bottom: 4px;\n}\n.page-title p { font-size: 10px; letter-spacing: 4px; color: rgba(238,240,248,.5); }\n\n.form { width: 100%; display: flex; flex-direction: column; gap: 14px; }\n.field { display: flex; flex-direction: column; gap: 6px; }\n.field label {\n  font-family: 'Orbitron', sans-serif;\n  font-size: 9px; letter-spacing: 4px; color: #C9A84C;\n}\n.field input {\n  width: 100%;\n  background: rgba(255,255,255,.06);\n  border: 1px solid rgba(201,168,76,.25);\n  border-radius: 10px;\n  color: #EEF0F8;\n  padding: 14px;\n  font-size: 16px;\n  font-family: 'Exo 2', sans-serif;\n  font-weight: 600;\n  text-align: center;\n  outline: none;\n  -webkit-appearance: none;\n}\n.field input:focus {\n  border-color: rgba(201,168,76,.7);\n  box-shadow: 0 0 14px rgba(201,168,76,.15);\n}\n.field input::placeholder { color: rgba(238,240,248,.35); font-weight: 400; font-size: 14px; }\n\n.btn-join {\n  width: 100%; padding: 16px;\n  background: linear-gradient(135deg, #C9A84C, #F0D080, #C9A84C);\n  background-size: 200% 100%;\n  border: none; border-radius: 12px;\n  color: #0a0a1a;\n  font-family: 'Orbitron', sans-serif;\n  font-size: 13px; font-weight: 900; letter-spacing: 3px;\n  cursor: pointer;\n  transition: all .3s;\n  -webkit-appearance: none;\n}\n.btn-join:active { transform: scale(.97); }\n.btn-join:disabled { opacity: .4; cursor: not-allowed; transform: none; }\n\n.err { text-align: center; font-size: 12px; color: #ef4444; min-height: 18px; }\n\n\/* \u2500\u2500 WAIT \u2500\u2500 *\/\n.wait-name {\n  font-family: 'Orbitron', sans-serif;\n  font-size: 22px; font-weight: 900; color: #F0D080;\n  text-align: center;\n}\n.wait-ok {\n  font-family: 'Orbitron', sans-serif;\n  font-size: 10px; letter-spacing: 5px; color: #10b981;\n}\n.pulse { font-size: 3rem; animation: pulse 1.5s ease-in-out infinite; }\n@keyframes pulse { 0%,100%{transform:scale(1);} 50%{transform:scale(1.15);} }\n.hint { text-align: center; font-size: 13px; color: rgba(238,240,248,.5); line-height: 1.7; }\n\n\/* \u2500\u2500 QUESTION \u2500\u2500 *\/\n.q-top { width: 100%; display: flex; align-items: center; justify-content: space-between; }\n.q-num {\n  font-family: 'Orbitron', sans-serif;\n  font-size: 11px; letter-spacing: 3px; color: rgba(238,240,248,.5);\n}\n.q-diff {\n  font-family: 'Orbitron', sans-serif;\n  font-size: 9px; letter-spacing: 3px;\n  padding: 4px 10px; border-radius: 20px;\n}\n.d-easy   { background: rgba(16,185,129,.15); color: #10b981; border: 1px solid rgba(16,185,129,.3); }\n.d-med    { background: rgba(201,168,76,.15);  color: #C9A84C; border: 1px solid rgba(201,168,76,.3); }\n.d-hard   { background: rgba(239,68,68,.15);   color: #ef4444; border: 1px solid rgba(239,68,68,.3); }\n\n.timer-row { width: 100%; display: flex; align-items: center; gap: 10px; }\n.timer-track {\n  flex: 1; height: 6px; border-radius: 3px;\n  background: rgba(255,255,255,.08); overflow: hidden;\n}\n.timer-fill {\n  height: 100%; border-radius: 3px;\n  background: linear-gradient(90deg, #7a6030, #F0D080);\n  transition: width .9s linear, background .3s;\n}\n.timer-num {\n  font-family: 'Orbitron', sans-serif;\n  font-size: 18px; font-weight: 900; color: #F0D080;\n  min-width: 28px; text-align: right;\n}\n\n.q-text-box {\n  width: 100%;\n  background: rgba(255,255,255,.06);\n  border: 1px solid rgba(201,168,76,.25);\n  border-radius: 14px;\n  padding: 18px;\n  font-size: 16px; font-weight: 600; line-height: 1.55;\n  text-align: center; color: #EEF0F8;\n}\n\n.opts-grid { width: 100%; display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }\n\n.opt-btn {\n  padding: 14px 10px;\n  border: 1px solid rgba(255,255,255,.12);\n  border-radius: 12px;\n  font-family: 'Exo 2', sans-serif;\n  font-size: 13px; font-weight: 600;\n  cursor: pointer;\n  transition: all .2s;\n  display: flex; align-items: center; gap: 8px;\n  color: #EEF0F8;\n  text-align: left;\n  min-height: 72px;\n  -webkit-appearance: none;\n}\n.opt-btn:active:not(:disabled) { transform: scale(.96); }\n.opt-btn:disabled { cursor: not-allowed; }\n\n\/* Couleurs par lettre \u2014 le JS g\u00e9n\u00e8re class=\"opt-btn A\/B\/C\/D\" *\/\n.opt-btn.A { background: rgba(0,63,127,.45);  border-color: rgba(0,100,200,.4); }\n.opt-btn.B { background: rgba(127,0,0,.45);   border-color: rgba(200,0,0,.4); }\n.opt-btn.C { background: rgba(0,80,40,.45);   border-color: rgba(0,150,70,.4); }\n.opt-btn.D { background: rgba(80,50,0,.45);   border-color: rgba(180,120,0,.4); }\n\n.opt-ltr {\n  width: 28px; height: 28px; border-radius: 50%;\n  display: flex; align-items: center; justify-content: center;\n  font-family: 'Orbitron', sans-serif; font-size: 10px; font-weight: 900;\n  flex-shrink: 0;\n}\n.opt-btn.A .opt-ltr { background: rgba(0,63,127,.8);  color: #90c8ff; }\n.opt-btn.B .opt-ltr { background: rgba(127,0,0,.8);   color: #ffa0a0; }\n.opt-btn.C .opt-ltr { background: rgba(0,80,40,.8);   color: #90ffb0; }\n.opt-btn.D .opt-ltr { background: rgba(80,50,0,.8);   color: #ffd090; }\n\n.opt-lbl { flex: 1; line-height: 1.3; }\n\n.opt-btn.selected {\n  border-color: #F0D080;\n  box-shadow: 0 0 16px rgba(240,208,128,.3);\n  transform: scale(1.03);\n}\n.opt-btn.dimmed  { opacity: .3; transform: scale(.97); }\n.opt-btn.correct { background: rgba(16,185,129,.25); border-color: #10b981; box-shadow: 0 0 16px rgba(16,185,129,.3); }\n.opt-btn.wrong   { opacity: .25; }\n\n\/* \u2500\u2500 ANSWERED \u2500\u2500 *\/\n.big-icon { font-size: 4rem; }\n.ans-title {\n  font-family: 'Orbitron', sans-serif;\n  font-size: 18px; font-weight: 900; letter-spacing: 3px;\n  color: #F0D080; text-align: center;\n}\n.ans-sub { text-align: center; font-size: 13px; color: rgba(238,240,248,.5); line-height: 1.7; }\n\n\/* \u2500\u2500 RESULT \u2500\u2500 *\/\n.res-icon { font-size: 4rem; }\n.res-msg {\n  font-family: 'Orbitron', sans-serif;\n  font-size: 16px; font-weight: 900; letter-spacing: 2px;\n  text-align: center;\n}\n.pts-card {\n  width: 100%;\n  background: rgba(201,168,76,.08);\n  border: 1px solid rgba(201,168,76,.3);\n  border-radius: 16px;\n  padding: 20px; text-align: center;\n}\n.pts-label {\n  font-family: 'Orbitron', sans-serif;\n  font-size: 9px; letter-spacing: 4px; color: rgba(238,240,248,.5);\n  margin-bottom: 8px;\n}\n.pts-value {\n  font-family: 'Orbitron', sans-serif;\n  font-size: 36px; font-weight: 900;\n  background: linear-gradient(135deg, #F0D080, #C9A84C);\n  -webkit-background-clip: text; -webkit-text-fill-color: transparent;\n}\n.pts-total { font-size: 12px; color: rgba(238,240,248,.5); margin-top: 6px; }\n.next-hint { font-size: 12px; color: rgba(238,240,248,.4); text-align: center; }\n\n\/* \u2500\u2500 FINAL \u2500\u2500 *\/\n.trophy { font-size: 5rem; }\n.fin-title {\n  font-family: 'Orbitron', sans-serif;\n  font-size: 18px; font-weight: 900; letter-spacing: 3px;\n  background: linear-gradient(135deg, #F0D080, #C9A84C);\n  -webkit-background-clip: text; -webkit-text-fill-color: transparent;\n  text-align: center;\n}\n.rank-card {\n  width: 100%;\n  background: rgba(201,168,76,.08);\n  border: 1px solid rgba(201,168,76,.3);\n  border-radius: 16px;\n  padding: 24px; text-align: center;\n  display: flex; flex-direction: column; gap: 8px;\n}\n.rank-emoji { font-size: 3rem; }\n.rank-pos   { font-family: 'Orbitron', sans-serif; font-size: 22px; font-weight: 900; color: #F0D080; }\n.rank-pts   { font-size: 14px; color: rgba(238,240,248,.6); }\n.rank-sub   { font-size: 12px; color: rgba(238,240,248,.4); }\n<\/style>\n<\/head>\n<body>\n\n<!-- STARS -->\n<div class=\"bg-stars\" id=\"bgStars\"><\/div>\n\n<!-- HEADER -->\n<div class=\"hdr\">\n  <div class=\"hdr-title\">\u26bd QUIZ CHAMPIONS<\/div>\n  <div class=\"hdr-live\" id=\"hdrLive\">\n    <div class=\"hdr-live-dot\"><\/div>\n    LIVE\n  <\/div>\n<\/div>\n\n<!-- PAGE -->\n<div class=\"page\">\n\n  <!-- JOIN -->\n  <div class=\"screen active\" id=\"sJoin\">\n    <div class=\"logo-circle\">\u26bd<\/div>\n    <div class=\"page-title\">\n      <h1>QUIZ CHAMPIONS<\/h1>\n      <p>Budapest \u00b7 30 mai 2026<\/p>\n    <\/div>\n    <div class=\"form\">\n      <div class=\"field\">\n        <label>Ton pseudo<\/label>\n        <input type=\"text\" id=\"inpName\" placeholder=\"Entre ton pr\u00e9nom\" maxlength=\"20\"\n          autocomplete=\"off\" autocorrect=\"off\" autocapitalize=\"words\">\n      <\/div>\n      <div class=\"field\">\n        <label>Code de la salle<\/label>\n        <input type=\"text\" id=\"inpRoom\" placeholder=\"ex: ROOM_ABC123\"\n          autocomplete=\"off\" autocorrect=\"off\" autocapitalize=\"none\"\n          style=\"text-transform:uppercase\">\n      <\/div>\n      <button class=\"btn-join\" id=\"btnJoin\" onclick=\"joinGame()\">REJOINDRE \u25b6<\/button>\n      <div class=\"err\" id=\"errMsg\"><\/div>\n    <\/div>\n  <\/div>\n\n  <!-- WAIT -->\n  <div class=\"screen\" id=\"sWait\">\n    <div style=\"font-size:3rem\">\ud83d\udc64<\/div>\n    <div class=\"wait-name\" id=\"waitName\"><\/div>\n    <div class=\"wait-ok\">CONNECT\u00c9<\/div>\n    <div class=\"pulse\">\u26bd<\/div>\n    <div class=\"hint\">En attente du lancement<br>par l&rsquo;animateur<br><br>Regarde l&rsquo;\u00e9cran principal !<\/div>\n  <\/div>\n\n  <!-- QUESTION -->\n  <div class=\"screen\" id=\"sQ\">\n    <div class=\"q-top\">\n      <span class=\"q-num\" id=\"qNum\">Q1\/15<\/span>\n      <span class=\"q-diff d-easy\" id=\"qDiff\">FACILE<\/span>\n    <\/div>\n    <div class=\"timer-row\">\n      <div class=\"timer-track\">\n        <div class=\"timer-fill\" id=\"tFill\" style=\"width:100%\"><\/div>\n      <\/div>\n      <div class=\"timer-num\" id=\"tNum\">20<\/div>\n    <\/div>\n    <div class=\"q-text-box\" id=\"qText\">&#8230;<\/div>\n    <div class=\"opts-grid\" id=\"optsGrid\"><\/div>\n  <\/div>\n\n  <!-- ANSWERED -->\n  <div class=\"screen\" id=\"sAns\">\n    <div class=\"big-icon\">\u23f3<\/div>\n    <div class=\"ans-title\">R\u00c9PONSE ENVOY\u00c9E !<\/div>\n    <div class=\"ans-sub\">En attente de la r\u00e9v\u00e9lation<br>sur l&rsquo;\u00e9cran principal&#8230;<\/div>\n  <\/div>\n\n  <!-- RESULT -->\n  <div class=\"screen\" id=\"sRes\">\n    <div class=\"res-icon\" id=\"resIcon\"><\/div>\n    <div class=\"res-msg\"  id=\"resMsg\"><\/div>\n    <div class=\"pts-card\">\n      <div class=\"pts-label\">POINTS GAGN\u00c9S<\/div>\n      <div class=\"pts-value\" id=\"ptsVal\">+0<\/div>\n      <div class=\"pts-total\" id=\"ptsTot\"><\/div>\n    <\/div>\n    <div class=\"next-hint\">\u23f3 Prochaine question bient\u00f4t&#8230;<\/div>\n  <\/div>\n\n  <!-- FINAL -->\n  <div class=\"screen\" id=\"sFin\">\n    <div class=\"trophy\">\ud83c\udfc6<\/div>\n    <div class=\"fin-title\">PARTIE TERMIN\u00c9E !<\/div>\n    <div class=\"rank-card\">\n      <div class=\"rank-emoji\" id=\"rkEmoji\"><\/div>\n      <div class=\"rank-pos\"   id=\"rkPos\"><\/div>\n      <div class=\"rank-pts\"   id=\"rkPts\"><\/div>\n      <div class=\"rank-sub\"   id=\"rkSub\"><\/div>\n    <\/div>\n  <\/div>\n\n<\/div><!-- \/page -->\n\n<script>\n\/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n   STARS\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\n(()=>{\n  const c = document.getElementById('bgStars');\n  for(let i = 0; i < 50; i++){\n    const s  = document.createElement('div');\n    s.className = 'star';\n    const sz = Math.random() * 2 + 1;\n    s.style.cssText = `width:${sz}px;height:${sz}px;left:${Math.random()*100}%;top:${Math.random()*100}%;--dur:${2+Math.random()*4}s;animation-delay:${Math.random()*4}s`;\n    c.appendChild(s);\n  }\n})();\n\n\/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n   FIREBASE\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\nfirebase.initializeApp({\n  apiKey:            \"AIzaSyDr2MTBJz12LPIcGrC-aeNHXgtrKRHDGNY\",\n  authDomain:        \"final-b59d8.firebaseapp.com\",\n  databaseURL:       \"https:\/\/final-b59d8-default-rtdb.europe-west1.firebasedatabase.app\",\n  projectId:         \"final-b59d8\",\n  storageBucket:     \"final-b59d8.firebasestorage.app\",\n  messagingSenderId: \"752381093261\",\n  appId:             \"1:752381093261:web:e09de65ba1a03ab39109c4\"\n});\nconst db = firebase.database();\n\n\/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n   QUESTIONS \u2014 charg\u00e9es depuis Firebase au join\n   (le host les envoie dans games\/ROOM\/questions)\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\nlet QS = []; \/\/ rempli dans joinGame()\n\n\/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n   VARIABLES GLOBALES\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\nconst TL = 20;                \/\/ dur\u00e9e timer en secondes\n\nlet gRef         = null;      \/\/ r\u00e9f\u00e9rence Firebase de la partie\nlet pid          = null;      \/\/ identifiant unique du joueur\nlet myName       = '';        \/\/ pr\u00e9nom du joueur\nlet myScore      = 0;        \/\/ score local cumul\u00e9\nlet myAns        = -99;      \/\/ r\u00e9ponse choisie (-99 = pas encore r\u00e9pondu)\nlet myTime       = 0;        \/\/ temps mis pour r\u00e9pondre\nlet qStartTs     = 0;        \/\/ timestamp de d\u00e9but de question\nlet ticker       = null;     \/\/ setInterval du timer\nlet lastQ        = -1;       \/\/ index de la derni\u00e8re question charg\u00e9e\nlet resultShown  = false;    \/\/ \u00e9vite d'afficher le r\u00e9sultat 2x\n\n\/\/ Listeners Firebase (gard\u00e9s pour pouvoir les d\u00e9tacher)\nlet stateRef     = null;\nlet currentQRef  = null;\n\n\/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n   UTILITAIRES\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\nfunction show(id) {\n  document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));\n  document.getElementById(id).classList.add('active');\n}\n\nfunction showErr(msg) {\n  const el = document.getElementById('errMsg');\n  el.textContent = msg;\n  setTimeout(() => { el.textContent = ''; }, 4000);\n}\n\n\/* Pr\u00e9-remplir le code salle depuis l'URL (?room=ROOM_XXXX) *\/\n(()=>{\n  const p = new URLSearchParams(window.location.search);\n  const r = p.get('room');\n  if(r) document.getElementById('inpRoom').value = r.toUpperCase();\n})();\n\n\/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n   JOIN\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\nasync function joinGame() {\n  const name = document.getElementById('inpName').value.trim();\n  const room = document.getElementById('inpRoom').value.trim().toUpperCase();\n  const btn  = document.getElementById('btnJoin');\n\n  if(!name) { showErr('Entre ton pr\u00e9nom !');        return; }\n  if(!room) { showErr('Entre le code de la salle !'); return; }\n\n  btn.disabled    = true;\n  btn.textContent = 'Connexion...';\n\n  try {\n    gRef = db.ref('games\/' + room);\n    const snap = await gRef.once('value');\n\n    if(!snap.exists()) {\n      showErr('Salle introuvable. V\u00e9rifie le code !');\n      btn.disabled = false; btn.textContent = 'REJOINDRE \u25b6';\n      return;\n    }\n\n    const g = snap.val();\n    if(g.state === 'finished') {\n      showErr('Cette partie est d\u00e9j\u00e0 termin\u00e9e.');\n      btn.disabled = false; btn.textContent = 'REJOINDRE \u25b6';\n      return;\n    }\n\n    \/\/ \u2500\u2500 Charger les questions depuis Firebase \u2500\u2500\n    if(g.questions && Array.isArray(g.questions) && g.questions.length > 0) {\n      QS = g.questions;\n    } else {\n      showErr('Questions non disponibles, recharge la page host.');\n      btn.disabled = false; btn.textContent = 'REJOINDRE \u25b6';\n      return;\n    }\n\n    \/\/ Enregistrement du joueur\n    myName = name;\n    pid    = 'p_' + Math.random().toString(36).slice(2, 10);\n    await gRef.child('players\/' + pid).set({ name, score: 0, correct: 0 });\n    gRef.child('players\/' + pid).onDisconnect().remove();\n\n    \/\/ Afficher l'\u00e9cran d'attente\n    show('sWait');\n    document.getElementById('waitName').textContent = name;\n\n    \/\/ Si la partie est d\u00e9j\u00e0 en cours (joueur qui rejoint en retard)\n    if(g.state === 'playing' && g.currentQ !== undefined && g.currentQ >= 0) {\n      lastQ = g.currentQ;\n      myAns = -99;\n      loadQuestion(g.currentQ);\n    }\n\n    \/\/ Lancer les listeners cibl\u00e9s\n    startListeners();\n\n  } catch(e) {\n    showErr('Erreur : ' + e.message);\n    btn.disabled    = false;\n    btn.textContent = 'REJOINDRE \u25b6';\n  }\n}\n\n\/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n   LISTENERS FIREBASE CIBL\u00c9S\n   \u2014 On \u00e9coute UNIQUEMENT state et currentQ\n   \u2014 Pas de lecture sur tout le jeu \u00e0 chaque\n     r\u00e9ponse d'un autre joueur\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\nfunction startListeners() {\n  \/\/ D\u00e9tacher les anciens listeners si on r\u00e9-appelle\n  if(stateRef)    { stateRef.off();    stateRef    = null; }\n  if(currentQRef) { currentQRef.off(); currentQRef = null; }\n\n  \/* \u2500\u2500 \u00c9coute l'\u00e9tat (lobby \/ playing \/ revealed \/ finished) \u2500\u2500 *\/\n  stateRef = gRef.child('state');\n  stateRef.on('value', snap => {\n    const state = snap.val();\n    if(!state) return;\n\n    if(state === 'playing' || state === 'revealed' || state === 'finished') {\n      document.getElementById('hdrLive').style.display = 'flex';\n    }\n\n    if(state === 'lobby') {\n      show('sWait');\n      return;\n    }\n\n    if(state === 'revealed') {\n      stopTicker();\n      \/\/ On r\u00e9cup\u00e8re les donn\u00e9es une seule fois pour afficher le r\u00e9sultat\n      if(!resultShown) {\n        resultShown = true;\n        gRef.once('value').then(s => showResult(s.val()));\n      }\n      return;\n    }\n\n    if(state === 'finished') {\n      stopTicker();\n      gRef.once('value').then(s => showFinal(s.val()));\n      return;\n    }\n  });\n\n  \/* \u2500\u2500 \u00c9coute le num\u00e9ro de question \u2500\u2500 *\/\n  currentQRef = gRef.child('currentQ');\n  currentQRef.on('value', snap => {\n    const qIdx = snap.val();\n    if(qIdx === null || qIdx === undefined || qIdx < 0) return;\n    if(qIdx === lastQ) return; \/\/ d\u00e9j\u00e0 affich\u00e9\n\n    \/\/ Nouvelle question \u2192 r\u00e9initialiser l'\u00e9tat local\n    lastQ       = qIdx;\n    myAns       = -99;\n    myTime      = 0;\n    resultShown = false;\n\n    \/\/ Charger directement depuis les donn\u00e9es locales (pas de gRef.once)\n    loadQuestion(qIdx);\n  });\n}\n\n\/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n   CHARGER LA QUESTION \u2014 lit depuis QS local\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\nfunction loadQuestion(qIdx) {\n  const q = QS[qIdx];\n  if(!q) { console.error('Question introuvable:', qIdx); return; }\n\n  show('sQ');\n\n  \/\/ En-t\u00eate\n  document.getElementById('qNum').textContent = `Q${qIdx + 1}\/${QS.length}`;\n\n  \/\/ Difficult\u00e9\n  const diffMap = { easy:'FACILE', medium:'MOYEN', hard:'DIFFICILE' };\n  const diffCls = { easy:'d-easy', medium:'d-med', hard:'d-hard' };\n  const diffEl  = document.getElementById('qDiff');\n  diffEl.textContent = diffMap[q.diff] || q.diff;\n  diffEl.className   = 'q-diff ' + (diffCls[q.diff] || 'd-easy');\n\n  \/\/ Texte\n  document.getElementById('qText').textContent = q.q;\n\n  \/\/ Options\n  const grid = document.getElementById('optsGrid');\n  const ltrs = ['A','B','C','D'];\n  grid.innerHTML = '';\n  q.opts.forEach((opt, i) => {\n    const btn       = document.createElement('button');\n    btn.className   = `opt-btn ${ltrs[i]}`;\n    btn.id          = 'ob' + i;\n    btn.innerHTML   = `<span class=\"opt-ltr\">${ltrs[i]}<\/span><span class=\"opt-lbl\">${opt}<\/span>`;\n    btn.onclick     = () => pick(i);\n    grid.appendChild(btn);\n  });\n\n  \/\/ Lancer le timer local\n  qStartTs = Date.now();\n  startTicker();\n}\n\n\/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n   TIMER LOCAL\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\nfunction startTicker() {\n  stopTicker();\n  let t = TL;\n  setTimer(t);\n  ticker = setInterval(() => {\n    t = Math.max(0, t - 1);\n    setTimer(t);\n    if(t === 0) {\n      stopTicker();\n      if(myAns === -99) noAnswer();\n    }\n  }, 1000);\n}\n\nfunction setTimer(t) {\n  const fill = document.getElementById('tFill');\n  const num  = document.getElementById('tNum');\n  if(!fill || !num) return;\n  fill.style.width      = (t \/ TL * 100) + '%';\n  fill.style.background = t <= 5\n    ? 'linear-gradient(90deg,#7f1d1d,#ef4444)'\n    : t <= 10\n      ? 'linear-gradient(90deg,#7a6030,#C9A84C)'\n      : 'linear-gradient(90deg,#7a6030,#F0D080)';\n  num.textContent   = t;\n  num.style.color   = t <= 5 ? '#ef4444' : '#F0D080';\n}\n\nfunction stopTicker() {\n  clearInterval(ticker);\n  ticker = null;\n}\n\n\/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n   R\u00c9PONDRE\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\nfunction pick(i) {\n  if(myAns !== -99) return; \/\/ d\u00e9j\u00e0 r\u00e9pondu\n  stopTicker();\n\n  myAns  = i;\n  myTime = Math.max(0, Math.floor((Date.now() - qStartTs) \/ 1000));\n\n  \/\/ Feedback visuel\n  for(let k = 0; k < 4; k++) {\n    const b = document.getElementById('ob' + k);\n    if(!b) continue;\n    b.disabled = true;\n    b.classList.add(k === i ? 'selected' : 'dimmed');\n  }\n\n  \/\/ Envoyer la r\u00e9ponse \u00e0 Firebase\n  gRef.child('answers\/' + pid).set({ answer: i, time: myTime });\n\n  show('sAns');\n}\n\nfunction noAnswer() {\n  if(myAns !== -99) return;\n  myAns = -1;\n  gRef.child('answers\/' + pid).set({ answer: -1, time: TL });\n  show('sAns');\n}\n\n\/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n   R\u00c9SULTAT (apr\u00e8s r\u00e9v\u00e9lation par l'h\u00f4te)\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\nfunction showResult(g) {\n  const q = QS[lastQ];\n  if(!q) return;\n\n  const correct = (myAns === q.ans);\n\n  \/\/ Lire le score depuis Firebase (source de v\u00e9rit\u00e9)\n  gRef.child('players\/' + pid).once('value').then(snap => {\n    const player = snap.val() || {};\n    myScore = player.score || myScore;\n\n    show('sRes');\n    document.getElementById('resIcon').textContent = correct ? '\u2705' : '\u274c';\n    document.getElementById('resMsg').textContent  = correct ? 'BONNE R\u00c9PONSE !' : 'MAUVAISE R\u00c9PONSE';\n    document.getElementById('resMsg').style.color  = correct ? '#10b981' : '#ef4444';\n\n    let pts = 0;\n    if(correct) {\n      const bonus = Math.max(0, TL - myTime);\n      pts = 100 + bonus * 5;\n    }\n    document.getElementById('ptsVal').textContent = correct ? '+' + pts : '+0';\n    document.getElementById('ptsTot').textContent = 'Total : ' + myScore + ' pts';\n  });\n}\n\n\/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n   \u00c9CRAN FINAL\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\nasync function showFinal(g) {\n  show('sFin');\n\n  const snap    = await gRef.child('players').once('value');\n  const players = Object.values(snap.val() || {})\n                        .sort((a, b) => (b.score || 0) - (a.score || 0));\n\n  const rank  = players.findIndex(p => p.name === myName) + 1;\n  const total = players.length;\n\n  \/\/ Mettre \u00e0 jour myScore depuis Firebase\n  const me = players.find(p => p.name === myName);\n  if(me) myScore = me.score || 0;\n\n  const emojis = ['\ud83e\udd47','\ud83e\udd48','\ud83e\udd49'];\n  document.getElementById('rkEmoji').textContent = rank <= 3 ? emojis[rank - 1] : '\ud83c\udfc5';\n  document.getElementById('rkPos').textContent   = `${rank}${rank === 1 ? 'er' : '\u00e8me'} \/ ${total}`;\n  document.getElementById('rkPts').textContent   = `${myScore} points`;\n\n  const msgs = [\n    '\ud83c\udf89 Tu es le champion !',\n    '\ud83d\udc4f Tr\u00e8s belle performance !',\n    '\ud83d\udc4d Sur le podium !'\n  ];\n  document.getElementById('rkSub').textContent =\n    rank <= 3 ? msgs[rank - 1] : \"Merci d'avoir jou\u00e9 !\";\n}\n<\/script>\n<\/body>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>Quiz Champions \u2014 Joueur \u26bd QUIZ CHAMPIONS LIVE \u26bd QUIZ CHAMPIONS Budapest \u00b7 30 mai 2026 Ton pseudo Code de la salle REJOINDRE \u25b6 \ud83d\udc64 CONNECT\u00c9 \u26bd En attente du lancementpar l&rsquo;animateur Regarde l&rsquo;\u00e9cran principal ! Q1\/15 FACILE 20 &#8230; \u23f3 R\u00c9PONSE ENVOY\u00c9E ! En attente de la r\u00e9v\u00e9lationsur l&rsquo;\u00e9cran principal&#8230; POINTS GAGN\u00c9S +0 \u23f3 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-10149","page","type-page","status-publish","hentry"],"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.animcloud.fr\/en\/wp-json\/wp\/v2\/pages\/10149","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.animcloud.fr\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.animcloud.fr\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.animcloud.fr\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.animcloud.fr\/en\/wp-json\/wp\/v2\/comments?post=10149"}],"version-history":[{"count":11,"href":"https:\/\/www.animcloud.fr\/en\/wp-json\/wp\/v2\/pages\/10149\/revisions"}],"predecessor-version":[{"id":10178,"href":"https:\/\/www.animcloud.fr\/en\/wp-json\/wp\/v2\/pages\/10149\/revisions\/10178"}],"wp:attachment":[{"href":"https:\/\/www.animcloud.fr\/en\/wp-json\/wp\/v2\/media?parent=10149"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}