feat: add Level 7 Millionaire quiz with SVG comarca preview and 4-option A/B/C/D format
This commit is contained in:
313
index.html
313
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; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -333,6 +398,11 @@
|
||||
<div class="lvl-name">Nivell 6 · Mapa Cec</div>
|
||||
<div class="lvl-desc">Sense noms al mapa. El repte màxim!</div>
|
||||
</div>
|
||||
<div class="level-card" onclick="startLevel(7)" style="grid-column:span 2;background:linear-gradient(135deg,#1a1200,#3d2c00);border:2px solid #FFD700;">
|
||||
<span class="emoji">💰</span>
|
||||
<div class="lvl-name" style="color:#FFD700;">Nivell 7 · Milionari</div>
|
||||
<div class="lvl-desc">Qui vol ser milionari? 4 opcions, 10 preguntes!</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="home-actions">
|
||||
<button class="btn btn-green" onclick="goToStats()" id="btn-stats" style="display:none;">
|
||||
@@ -468,6 +538,36 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════════ MILIONARI (Level 7) ═══════════════════ -->
|
||||
<div id="screen-mill" class="screen">
|
||||
<button class="btn btn-back" onclick="goHome()" style="color:#FFD700;background:rgba(255,215,0,.12);border:1px solid #FFD700;">← Tornar</button>
|
||||
|
||||
<!-- Progress dots: 10 circles showing answered/current/pending state -->
|
||||
<div id="mill-dots" style="display:flex;gap:7px;justify-content:center;margin:10px 0 8px;"></div>
|
||||
|
||||
<!-- Score header: correct/total -->
|
||||
<div style="text-align:right;font-size:.9rem;font-weight:700;color:#FFD700;margin-bottom:8px;" id="mill-score-hdr">0/0</div>
|
||||
|
||||
<!-- Mini SVG preview map of the target comarca -->
|
||||
<div id="mill-map-wrap" style="width:100%;border-radius:14px;overflow:hidden;margin-bottom:12px;background:#08082a;border:1.5px solid #3d7fd4;">
|
||||
<svg id="mill-map-svg" viewBox="0 0 820 600" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;display:block;"></svg>
|
||||
</div>
|
||||
|
||||
<!-- Question text -->
|
||||
<div id="mill-qtext" style="text-align:center;font-size:1.25rem;font-weight:800;color:#fff;margin-bottom:14px;min-height:56px;line-height:1.35;"></div>
|
||||
|
||||
<!-- 4 option buttons (A/B/C/D) -->
|
||||
<div id="mill-opts" style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:14px;"></div>
|
||||
|
||||
<!-- Next button (hidden until answered) -->
|
||||
<div style="text-align:center;">
|
||||
<button id="mill-next" class="btn" onclick="millNext()"
|
||||
style="display:none;background:#FFD700;color:#08082a;font-weight:800;font-size:1rem;padding:13px 32px;border-radius:50px;">
|
||||
➡ Següent
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════════ STATS ═══════════════════ -->
|
||||
<div id="screen-stats" class="screen">
|
||||
<button class="btn btn-back" onclick="goHome()">← Tornar</button>
|
||||
@@ -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 <comarca>?" → options = 4 capitals
|
||||
type B: "De quina comarca és capital <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,'"')
|
||||
.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 <comarca>?" → options = capitals
|
||||
// type 'comarca': "De quina comarca és capital <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) => `
|
||||
<button class="mill-opt"
|
||||
data-opt="${escapeHtml(opt)}"
|
||||
data-correct="${escapeHtml(correctAnswer)}"
|
||||
onclick="millAnswer(this)">
|
||||
<span class="opt-letter">${letters[i]}</span>${escapeHtml(opt)}
|
||||
</button>`).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');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user