From 63df11b843d91f45c7a335f97e10215611b5f772 Mon Sep 17 00:00:00 2001 From: Jaume Garriga Maestre Date: Mon, 4 May 2026 11:22:12 +0200 Subject: [PATCH] feat: add Level 7 Millionaire quiz with SVG comarca preview and 4-option A/B/C/D format --- index.html | 313 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 312 insertions(+), 1 deletion(-) diff --git a/index.html b/index.html index 08ac339..bcb20c8 100644 --- a/index.html +++ b/index.html @@ -243,6 +243,71 @@ .players-grid{grid-template-columns:repeat(auto-fill,minmax(110px,1fr));} .avatar-grid{grid-template-columns:repeat(4,1fr);} } + + /* ── Nivell 7 · Milionari ── */ + #screen-mill { + background: linear-gradient(180deg, #08082a 0%, #140a3e 100%); + min-height: 100vh; + padding-bottom: 40px; + } + /* Option buttons — show style */ + .mill-opt { + display: flex; + align-items: center; + gap: 10px; + padding: 13px 14px; + border-radius: 12px; + font-family: inherit; + font-size: .92rem; + font-weight: 600; + border: 2px solid #3d7fd4; + background: linear-gradient(135deg, #0a2152, #153a7a); + color: #e8eeff; + cursor: pointer; + text-align: left; + transition: border-color .15s, background .15s; + min-height: 52px; + line-height: 1.3; + } + .mill-opt:hover:not(.disabled) { + border-color: #FFD700; + background: linear-gradient(135deg, #12295e, #1d4a9a); + } + .mill-opt.correct { + border-color: #2ecc71; + background: linear-gradient(135deg, #0a3d1f, #145e2d); + color: #b2ffce; + } + .mill-opt.wrong { + border-color: #e74c3c; + background: linear-gradient(135deg, #3d0a0a, #6e1212); + color: #ffb2b2; + } + .mill-opt.disabled { cursor: default; } + /* Letter badge (A/B/C/D) */ + .opt-letter { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: 50%; + border: 2px solid currentColor; + font-size: .82rem; + font-weight: 800; + flex-shrink: 0; + } + /* Progress dots */ + .mill-dot { + width: 14px; + height: 14px; + border-radius: 50%; + border: 2px solid #555; + background: #222; + transition: background .3s, border-color .3s; + } + .mill-dot.done { background: #FFD700; border-color: #FFD700; } + .mill-dot.current { background: #fff; border-color: #fff; } @@ -333,6 +398,11 @@
Nivell 6 · Mapa Cec
Sense noms al mapa. El repte màxim!
+
+ 💰 +
Nivell 7 · Milionari
+
Qui vol ser milionari? 4 opcions, 10 preguntes!
+
+ +
+ + + +
+ + +
0/0
+ + +
+ +
+ + +
+ + +
+ + +
+ +
+
+
@@ -1249,7 +1349,8 @@ function startLevel(n){ sessionStart=Date.now(); if(n===1)initL1();else if(n===2)initL2();else if(n===3)initL3(); else if(n===4)initL4();else if(n===5){mapBlind=false;initMap();} - else{mapBlind=true;initMap();} + else if(n===6){mapBlind=true;initMap();} + else{initMill();} } function repeatLevel(){ startLevel(currentLevel); } @@ -1459,6 +1560,216 @@ function handleMapClick(path){ setTimeout(()=>{path.classList.remove('wrong-shake');currentQ++;nextMapQ();},2400); } } + +/* ══════════════════════════════════════════════════════ + NIVELL 7 · MILIONARI + "Qui vol ser milionari?" — 10 questions, 4 options A/B/C/D, + alternating between two question types: + type A: "Quina és la capital de ?" → options = 4 capitals + type B: "De quina comarca és capital ?" → options = 4 comarca names + A mini SVG map highlights the target comarca in gold. +══════════════════════════════════════════════════════ */ + +const MILL_TOTAL = 10; // questions per session +let millQuestions = []; // selected slice from shuffled pool +let millQ = 0; // current question index (0-based) +let millScore = 0; // correct answers count + +/** Escape HTML special chars for safe insertion into data-attributes. */ +function escapeHtml(s) { + return String(s) + .replace(/&/g,'&') + .replace(//g,'>') + .replace(/"/g,'"') + .replace(/'/g,'''); +} + +/** Entry point called by startLevel(7). */ +function initMill() { + millQ = 0; + millScore = 0; + // millQuestions already assigned as shuffled mapComarques in startLevel() + millQuestions = questions.slice(0, MILL_TOTAL); + showScreen('screen-mill'); + renderMill(); +} + +/** Render the current question. */ +function renderMill() { + const comarca = millQuestions[millQ]; + + // ── Progress dots ────────────────────────────────────────── + const dotsEl = document.getElementById('mill-dots'); + dotsEl.innerHTML = ''; + for (let i = 0; i < millQuestions.length; i++) { + const d = document.createElement('div'); + d.className = 'mill-dot' + (i < millQ ? ' done' : i === millQ ? ' current' : ''); + dotsEl.appendChild(d); + } + + // ── Score header ────────────────────────────────────────── + document.getElementById('mill-score-hdr').textContent = `${millScore}/${millQ}`; + + // ── Mini map ────────────────────────────────────────────── + buildMillSVG(comarca); + + // ── Decide question type — alternate randomly ───────────── + // type 'capital': "Quina és la capital de ?" → options = capitals + // type 'comarca': "De quina comarca és capital ?" → options = comarca names + const qType = Math.random() < 0.5 ? 'capital' : 'comarca'; + + let questionText, correctAnswer, pool, optionFn; + + if (qType === 'capital') { + questionText = `Quina és la capital de ${comarca.emoji} ${comarca.name}?`; + correctAnswer = comarca.capital; + // Distractors: other capitals from the same questions pool + pool = millQuestions.filter(c => c.name !== comarca.name); + optionFn = c => c.capital; + } else { + questionText = `De quina comarca és capital 📍 ${comarca.capital}?`; + correctAnswer = comarca.name; + pool = millQuestions.filter(c => c.name !== comarca.name); + optionFn = c => c.name; + } + + document.getElementById('mill-qtext').textContent = questionText; + + // Build 4 options: 1 correct + 3 random distractors + const distractors = shuffle(pool).slice(0, 3).map(optionFn); + const options = shuffle([correctAnswer, ...distractors]); + const letters = ['A', 'B', 'C', 'D']; + + const optsEl = document.getElementById('mill-opts'); + optsEl.innerHTML = options.map((opt, i) => ` + `).join(''); + + // Hide the Next button until an answer is chosen + document.getElementById('mill-next').style.display = 'none'; +} + +/** Handle option click. Disables all options, colours correct/wrong, shows Next. */ +function millAnswer(btn) { + const chosen = btn.dataset.opt; + const correct = btn.dataset.correct; + + // Disable all option buttons + document.querySelectorAll('.mill-opt').forEach(b => { + b.classList.add('disabled'); + b.onclick = null; + }); + + if (chosen === correct) { + btn.classList.add('correct'); + millScore++; + score++; // global score used by showResult() + showFeedback(true); + } else { + btn.classList.add('wrong'); + // Highlight the correct answer in green + document.querySelectorAll('.mill-opt').forEach(b => { + if (b.dataset.opt === correct) b.classList.add('correct'); + }); + showFeedback(false); + } + + millQ++; // advance AFTER recording the answer + + // Update score header immediately + document.getElementById('mill-score-hdr').textContent = `${millScore}/${millQ}`; + + // Show Next / Result button + const nextBtn = document.getElementById('mill-next'); + nextBtn.style.display = 'block'; + nextBtn.textContent = millQ >= millQuestions.length ? '🏆 Veure resultat' : '➡ Següent'; +} + +/** Advance to next question or show final result. */ +function millNext() { + if (millQ >= millQuestions.length) { + // questions.length must match millQuestions.length for showResult() pct calc + questions = millQuestions; + showResult(7); + } else { + renderMill(); + } +} + +/** + * Build the mini SVG preview map for the Milionari screen. + * Target comarca: gold (#FFD700), others: dark blue (#1c3a5e). + * Red double-circle marks the capital centroid. + * ViewBox zoomed to comarca centroid ±110×82.5 units (220×165 window), clamped. + */ +function buildMillSVG(comarca) { + const svg = document.getElementById('mill-map-svg'); + svg.innerHTML = ''; + + const info = COMARCA_PATHS[comarca.name]; + + // ── Background sea ────────────────────────────────────────── + const sea = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); + sea.setAttribute('width', '820'); + sea.setAttribute('height', '600'); + sea.setAttribute('fill', '#08082a'); + svg.appendChild(sea); + + // ── All comarca paths ──────────────────────────────────────── + for (const [name, pInfo] of Object.entries(COMARCA_PATHS)) { + const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + path.setAttribute('d', pInfo.d); + if (name === comarca.name) { + path.setAttribute('fill', '#FFD700'); + path.setAttribute('stroke', '#fff'); + path.setAttribute('stroke-width', '1.5'); + } else { + path.setAttribute('fill', '#1c3a5e'); + path.setAttribute('stroke', '#0a1e38'); + path.setAttribute('stroke-width', '0.6'); + } + svg.appendChild(path); + } + + // ── Capital marker: outer ring + inner dot (no CSS animation needed) ─ + if (info) { + const cx = info.cx; + const cy = info.cy; + + // Outer ring (semi-transparent red) + const outer = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); + outer.setAttribute('cx', cx); + outer.setAttribute('cy', cy); + outer.setAttribute('r', '9'); + outer.setAttribute('fill', 'rgba(220,50,50,0.35)'); + outer.setAttribute('stroke', '#e03030'); + outer.setAttribute('stroke-width', '1.5'); + svg.appendChild(outer); + + // Inner dot (solid red) + const inner = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); + inner.setAttribute('cx', cx); + inner.setAttribute('cy', cy); + inner.setAttribute('r', '4'); + inner.setAttribute('fill', '#e03030'); + svg.appendChild(inner); + + // ── Zoom viewBox centred on target comarca ──────────────── + // Window: 220 × 165 units; clamp to [0,820] × [0,600] + const W = 220, H = 165; + const x0 = Math.max(0, Math.min(820 - W, cx - W / 2)); + const y0 = Math.max(0, Math.min(600 - H, cy - H / 2)); + svg.setAttribute('viewBox', `${x0} ${y0} ${W} ${H}`); + } else { + // Fallback: full map view if no centroid data + svg.setAttribute('viewBox', '0 0 820 600'); + } +}