๐
filename.html
<div class="ws-game" id="ws-game-20" data-correct="https://www.schakelklas.be/wp-content/uploads/sites/39/2026/02/correct.mp3" data-wrong="https://www.schakelklas.be/wp-content/uploads/sites/39/2026/02/wrong.mp3"></div>
#ws-game-20{
--bg:#0b1220; --card:#162243; --card2:#1b2a52;
--text:#e9eefc; --muted:#b8c3e6; --border:rgba(255,255,255,.12);
--shadow: 0 10px 25px rgba(0,0,0,.35); --radius:18px;
margin:16px 0;font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;color:var(--text)
}
#ws-game-20 *{box-sizing:border-box}
#ws-game-20 .wrap{background:radial-gradient(1200px 800px at 20% 0%,#1a2a5a 0%,var(--bg) 55%);border-radius:var(--radius);padding:16px}
#ws-game-20 header{max-width:1100px;margin:0 auto 10px;padding:0 4px;display:flex;gap:12px;align-items:flex-start;justify-content:space-between;flex-wrap:wrap}
#ws-game-20 h2{margin:0;font-size:clamp(18px,2.7vw,28px);letter-spacing:.2px}
#ws-game-20 .sub{color:var(--muted);margin-top:6px;font-size:14px;line-height:1.35}
#ws-game-20 .panel{max-width:1100px;margin:0 auto 6px;padding:16px;background:rgba(255,255,255,.06);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow)}
#ws-game-20 .controls{display:flex;gap:10px;flex-wrap:wrap;align-items:center;justify-content:space-between}
#ws-game-20 .leftControls,#ws-game-20 .rightControls{display:flex;gap:10px;flex-wrap:wrap;align-items:center}
#ws-game-20 select,#ws-game-20 button{font:inherit;background:rgba(255,255,255,.08);color:var(--text);border:1px solid var(--border);border-radius:12px;padding:10px 12px;cursor:pointer}
#ws-game-20 button:hover{background:rgba(255,255,255,.12)}
#ws-game-20 button:active{transform:translateY(1px)}
#ws-game-20 .stat{padding:10px 12px;background:rgba(0,0,0,.18);border:1px solid var(--border);border-radius:12px;color:var(--muted);font-size:14px;display:flex;gap:10px;align-items:center;flex-wrap:wrap}
#ws-game-20 .pill{padding:4px 10px;border-radius:999px;background:rgba(255,255,255,.09);border:1px solid var(--border);color:var(--text);font-weight:600;font-size:13px;display:inline-flex;align-items:center;gap:8px}
/* Memory */
#ws-game-20 .grid{margin-top:14px;display:grid;gap:12px;grid-template-columns:repeat(6,minmax(0,1fr))}
@media (max-width:1050px){#ws-game-20 .grid{grid-template-columns:repeat(4,minmax(0,1fr))}}
@media (max-width:650px){#ws-game-20 .grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
#ws-game-20 .card{position:relative;height:132px;border-radius:16px;border:1px solid var(--border);background:linear-gradient(180deg,rgba(255,255,255,.08),rgba(255,255,255,.04));overflow:hidden;box-shadow:0 6px 18px rgba(0,0,0,.25);cursor:pointer;user-select:none}
#ws-game-20 .cardInner{position:absolute;inset:0;display:grid;place-items:center;padding:12px;text-align:center;line-height:1.25;font-size:14px}
#ws-game-20 .cardFace{position:absolute;inset:0;display:grid;place-items:center;padding:14px;background:linear-gradient(180deg,var(--card),var(--card2));transform:rotateY(180deg);backface-visibility:hidden}
#ws-game-20 .cardBack{position:absolute;inset:0;display:grid;place-items:center;padding:14px;background:radial-gradient(500px 200px at 20% 0%,rgba(255,255,255,.18),rgba(255,255,255,.06));backface-visibility:hidden}
#ws-game-20 .cardBack .qmark{font-size:28px;opacity:.9}
#ws-game-20 .card.flipped .cardBack{transform:rotateY(180deg)}
#ws-game-20 .card.flipped .cardFace{transform:rotateY(0deg)}
#ws-game-20 .cardBack,#ws-game-20 .cardFace{transition:transform .28s ease;transform-style:preserve-3d}
#ws-game-20 .tag{position:absolute;top:10px;left:10px;font-size:11px;letter-spacing:.3px;text-transform:uppercase;color:rgba(255,255,255,.82);background:rgba(0,0,0,.22);border:1px solid var(--border);padding:4px 8px;border-radius:999px}
#ws-game-20 .matched{outline:2px solid rgba(56,211,159,.55);box-shadow:0 0 0 4px rgba(56,211,159,.14),0 12px 28px rgba(0,0,0,.35)}
#ws-game-20 .shake{animation:ws20-shake .22s ease-in-out 0s 2;outline:2px solid rgba(255,107,107,.55)}
@keyframes ws20-shake{0%{transform:translateX(0)}50%{transform:translateX(4px)}100%{transform:translateX(0)}}
/* Quiz */
#ws-game-20 .quizWrap{margin-top:14px;display:grid;gap:12px}
#ws-game-20 .quizCard{background:linear-gradient(180deg,rgba(255,255,255,.08),rgba(255,255,255,.04));border:1px solid var(--border);border-radius:var(--radius);padding:14px;box-shadow:var(--shadow)}
#ws-game-20 .qTop{display:flex;gap:10px;flex-wrap:wrap;align-items:center;justify-content:space-between;margin-bottom:10px}
#ws-game-20 .qTitle{font-size:16px;font-weight:700;margin:0}
#ws-game-20 .qPrompt{margin:8px 0 0;color:var(--muted);font-size:14px;line-height:1.35}
#ws-game-20 .choices{margin-top:12px;display:grid;gap:10px;grid-template-columns:1fr 1fr}
@media (max-width:700px){#ws-game-20 .choices{grid-template-columns:1fr}}
#ws-game-20 .choiceBtn{text-align:left;padding:12px;border-radius:14px;border:1px solid var(--border);background:linear-gradient(180deg,rgba(22,34,67,.95),rgba(27,42,82,.85));cursor:pointer;color:var(--text);line-height:1.25}
#ws-game-20 .choiceBtn:hover{filter:brightness(1.08)}
#ws-game-20 .choiceBtn.correct{border-color:rgba(56,211,159,.65);box-shadow:0 0 0 4px rgba(56,211,159,.12)}
#ws-game-20 .choiceBtn.wrong{border-color:rgba(255,107,107,.65);box-shadow:0 0 0 4px rgba(255,107,107,.12)}
#ws-game-20 .note{margin-top:10px;font-size:14px;color:var(--muted);display:flex;gap:10px;align-items:flex-start;flex-wrap:wrap}
#ws-game-20 .note strong{color:var(--text)}
#ws-game-20 .hidden{display:none!important}
<script>
(function(){
const mount = document.getElementById("ws-game-20");
if(!mount || mount.dataset.mounted === "1") return;
mount.dataset.mounted = "1";
const correctUrl = mount.getAttribute("data-correct") || "";
const wrongUrl = mount.getAttribute("data-wrong") || "";
mount.innerHTML = `
<div class="wrap">
<header>
<div>
<h2>Woordenschat โ alle 20 woordparen</h2>
<div class="sub">
Kies <b>Memory</b> (woord โ uitleg) of <b>Quiz</b> (multiple choice).
<br><small>Klik 1x in het spel om geluid toe te staan.</small>
</div>
</div>
</header>
<div class="panel">
<div class="controls">
<div class="leftControls">
<label>
<span class="pill">Modus</span>
Memory (woord โ uitleg)
Quiz (multiple choice)
</label>
<button>Nieuw</button>
<button>Toon alles (10s)</button>
<button><span data-el="soundIcon">๐</span> Geluid</button>
</div>
<div class="rightControls">
<div class="stat">
<span class="pill" data-el="s1">โ</span>
<span class="pill" data-el="s2">โ</span>
<span class="pill" data-el="s3">โ</span>
</div>
</div>
</div>
<div data-el="memoryArea">
<div class="grid" data-el="grid"></div>
</div>
<div data-el="quizArea" class="hidden">
<div class="quizWrap">
<div class="quizCard">
<div class="qTop">
<div class="pill" data-el="qProgress">Vraag โ / โ</div>
<div class="pill" data-el="qScore">Score: 0</div>
</div>
<p class="qTitle" data-el="qTitle">โ</p>
<p class="qPrompt" data-el="qPrompt">Klik het juiste antwoord.</p>
<div class="choices" data-el="choices"></div>
<div class="note" data-el="qFeedback"></div>
<div style="display:flex;gap:10px;margin-top:12px;flex-wrap:wrap">
<button>Volgende</button>
<button>Wissel richting (woordโuitleg)</button>
</div>
</div>
</div>
</div>
</div>
</div>
`;
const pairs = [
{ term: "begroeten", def: "elkaar gedag zeggen" },
{ term: "de groet", def: "een woord of gebaar bij een ontmoeting of afscheid" },
{ term: "de handdruk", def: "het drukken van de hand als teken van begroeting" },
{ term: "zwaaien", def: "de armen heen en weer bewegen" },
{ term: "gebaren", def: "het bewegen van het lichaam of van een lichaamsdeel om iets duidelijk te maken" },
{ term: "de knuffel", def: "een omhelzing of een pluchen dier" },
{ term: "knuffelen", def: "omhelzen" },
{ term: "een duim geven aan ...", def: "een duim in de lucht steken als compliment" },
{ term: "het schouderklopje", def: "een tikje op de schouder als compliment" },
{ term: "de handkus", def: "een zoen op de hand" },
{ term: "de voeding", def: "alles wat je kunt eten, waarmee je je kunt voeden" },
{ term: "de tip(s)", def: "een inlichting, een kort en handig advies" },
{ term: "de gezondheid", def: "gezond zijn, niet ziek zijn" },
{ term: "fit", def: "in topvorm, in goede conditie" },
{ term: "de ontspanning", def: "tot rust komen" },
{ term: "het ingrediรซnt", def: "een (bestand)deel dat nodig is om iets te bereiden, bijvoorbeeld bij een gerecht" },
{ term: "het menu", def: "een lijst met gerechten die je gaat eten of waaruit je kunt kiezen" },
{ term: "het gerecht", def: "eten dat in รฉรฉn gang wordt opgediend: vleesgerecht, hoofdgerecht, nagerecht, voorgerecht" },
{ term: "het onderzoek", def: "iets nauwkeurig nagaan of opsporen" },
{ term: "(on)gezond", def: "iets (voedsel, een beweging ...) dat (niet) goed is voor je lichaam" },
];
const $ = (sel) => mount.querySelector(sel);
const $$ = (sel) => Array.from(mount.querySelectorAll(sel));
function shuffle(arr){
const a = arr.slice();
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;
}
function sampleDifferent(arr, n, exclude){
const pool = arr.filter(x => x !== exclude);
return shuffle(pool).slice(0, n);
}
function escapeHtml(str){
return String(str)
.replaceAll("&","&")
.replaceAll("",">")
.replaceAll('"',""")
.replaceAll("'","'");
}
// Audio
let soundOn = true;
const soundIcon = $('[data-el="soundIcon"]');
const soundBtn = $('[data-el="sound"]');
const sndOk = correctUrl ? new Audio(correctUrl) : null;
const sndBad = wrongUrl ? new Audio(wrongUrl) : null;
if(sndOk){ sndOk.preload="auto"; sndOk.volume=0.85; }
if(sndBad){ sndBad.preload="auto"; sndBad.volume=0.85; }
function playSound(aud){
if(!soundOn || !aud) return;
try{
aud.pause();
aud.currentTime = 0;
const p = aud.play();
if(p && typeof p.catch === "function") p.catch(()=>{});
}catch(e){}
}
function soundCorrect(){ playSound(sndOk); }
function soundWrong(){ playSound(sndBad); }
// unlock audio on first user gesture
let unlocked = false;
mount.addEventListener("pointerdown", () => {
if(unlocked) return;
unlocked = true;
if(sndOk){ sndOk.play().then(()=>sndOk.pause()).catch(()=>{}); }
if(sndBad){ sndBad.play().then(()=>sndBad.pause()).catch(()=>{}); }
});
soundBtn.addEventListener("click", () => {
soundOn = !soundOn;
soundIcon.textContent = soundOn ? "๐" : "๐";
});
// UI
const modeSel = $('[data-el="mode"]');
const memoryArea = $('[data-el="memoryArea"]');
const quizArea = $('[data-el="quizArea"]');
const grid = $('[data-el="grid"]');
const s1 = $('[data-el="s1"]');
const s2 = $('[data-el="s2"]');
const s3 = $('[data-el="s3"]');
// Memory state
let memState = null;
let revealTimeout = null;
function startMemory(){
const cards = [];
pairs.forEach((p, idx) => {
cards.push({ id: `t-${idx}`, pairId: idx, type:"term", text: p.term });
cards.push({ id: `d-${idx}`, pairId: idx, type:"def", text: p.def });
});
memState = {
cards: shuffle(cards),
flipped: [],
matched: new Set(),
moves: 0,
lock: false,
startTime: Date.now(),
timer: null
};
renderMemory();
startMemTimer();
updateMemStats();
}
function startMemTimer(){
if(memState.timer) clearInterval(memState.timer);
memState.startTime = Date.now();
memState.timer = setInterval(updateMemStats, 500);
}
function renderMemory(){
grid.innerHTML = "";
memState.cards.forEach((c) => {
const el = document.createElement("div");
el.className = "card";
el.innerHTML = `
<div class="cardInner cardBack"><div class="qmark">?</div></div>
<div class="cardInner cardFace">
<div class="tag">${c.type === "term" ? "woord" : "uitleg"}</div>
<div>${escapeHtml(c.text)}</div>
</div>
`;
el.addEventListener("click", () => onCardClick(el, c));
grid.appendChild(el);
});
}
function onCardClick(el, card){
if(memState.lock) return;
if(memState.matched.has(card.pairId)) return;
if(memState.flipped.some(x => x.id === card.id)) return;
el.classList.add("flipped");
memState.flipped.push({ ...card, el });
if(memState.flipped.length === 2){
memState.moves += 1;
const [a,b] = memState.flipped;
const isMatch = (a.pairId === b.pairId) && (a.type !== b.type);
if(isMatch){
memState.matched.add(a.pairId);
a.el.classList.add("matched");
b.el.classList.add("matched");
memState.flipped = [];
soundCorrect();
updateMemStats();
if(memState.matched.size === pairs.length){
clearInterval(memState.timer);
const secs = Math.floor((Date.now() - memState.startTime)/1000);
s3.textContent = `Klaar in: ${secs}s โ
`;
}
} else {
memState.lock = true;
a.el.classList.add("shake");
b.el.classList.add("shake");
soundWrong();
setTimeout(() => {
a.el.classList.remove("shake");
b.el.classList.remove("shake");
a.el.classList.remove("flipped");
b.el.classList.remove("flipped");
memState.flipped = [];
memState.lock = false;
updateMemStats();
}, 650);
}
} else {
updateMemStats();
}
}
// Reveal 10s
$('[data-el="reveal"]').addEventListener("click", () => {
if(modeSel.value !== "memory") return;
if(revealTimeout) clearTimeout(revealTimeout);
const all = $$('.card');
all.forEach(c => c.classList.add("flipped"));
revealTimeout = setTimeout(() => {
all.forEach(c => { if(!c.classList.contains("matched")) c.classList.remove("flipped"); });
}, 10000);
});
function updateMemStats(){
if(!memState) return;
const secs = Math.floor((Date.now() - memState.startTime)/1000);
s1.textContent = `Paren: ${memState.matched.size}/${pairs.length}`;
s2.textContent = `Beurten: ${memState.moves}`;
s3.textContent = `Tijd: ${secs}s`;
}
// Quiz
let quizState = null;
let quizDirection = "term->def";
const qProgress = $('[data-el="qProgress"]');
const qScore = $('[data-el="qScore"]');
const qTitle = $('[data-el="qTitle"]');
const qPrompt = $('[data-el="qPrompt"]');
const choicesEl = $('[data-el="choices"]');
const qFeedback = $('[data-el="qFeedback"]');
function startQuiz(reset=false){
if(reset || !quizState){
quizState = { order: shuffle(pairs.map((_,i)=>i)), idx: 0, score: 0, answered: false };
}
renderQuizQuestion();
s1.textContent = `Quiz`;
s2.textContent = `Richting: ${quizDirection === "term->def" ? "woord โ uitleg" : "uitleg โ woord"}`;
s3.textContent = `Score: ${quizState.score}/${quizState.order.length}`;
}
function nextQuestion(){
if(!quizState) return;
quizState.idx++;
quizState.answered = false;
const total = quizState.order.length;
if(quizState.idx >= total){
qTitle.textContent = "Klaar!";
qPrompt.textContent = "Einde van de quiz.";
choicesEl.innerHTML = "";
qFeedback.innerHTML = `<span class="pill">Resultaat</span> <strong>${quizState.score}/${total}</strong> โ Klik โNieuwโ om opnieuw te starten.`;
qProgress.textContent = `Vraag ${total}/${total}`;
qScore.textContent = `Score: ${quizState.score}`;
s3.textContent = `Score: ${quizState.score}/${total} โ
`;
return;
}
renderQuizQuestion();
}
function renderQuizQuestion(){
const total = quizState.order.length;
const pairIndex = quizState.order[quizState.idx];
const p = pairs[pairIndex];
qProgress.textContent = `Vraag ${quizState.idx+1} / ${total}`;
qScore.textContent = `Score: ${quizState.score}`;
qFeedback.textContent = "";
qFeedback.className = "note";
let correctAnswer, allAnswers;
if(quizDirection === "term->def"){
qTitle.textContent = `Wat betekent: โ${p.term}โ?`;
qPrompt.textContent = "Kies de juiste uitleg.";
correctAnswer = p.def;
const wrongs = sampleDifferent(pairs.map(x=>x.def), 3, correctAnswer);
allAnswers = shuffle([correctAnswer, ...wrongs]);
} else {
qTitle.textContent = `Welk woord past bij deze uitleg?`;
qPrompt.textContent = `โ${p.def}โ`;
correctAnswer = p.term;
const wrongs = sampleDifferent(pairs.map(x=>x.term), 3, correctAnswer);
allAnswers = shuffle([correctAnswer, ...wrongs]);
}
choicesEl.innerHTML = "";
allAnswers.forEach(ans => {
const btn = document.createElement("button");
btn.className = "choiceBtn";
btn.type = "button";
btn.innerHTML = escapeHtml(ans);
btn.addEventListener("click", () => onChoose(btn, ans, correctAnswer));
choicesEl.appendChild(btn);
});
}
function onChoose(btn, picked, correct){
if(quizState.answered) return;
quizState.answered = true;
const buttons = [...choicesEl.querySelectorAll("button")];
buttons.forEach(b => b.disabled = true);
if(picked === correct){
quizState.score += 1;
btn.classList.add("correct");
qFeedback.innerHTML = `<span class="pill">Juist</span> <strong>Goed zo!</strong>`;
soundCorrect();
} else {
btn.classList.add("wrong");
const correctBtn = buttons.find(b => b.textContent === correct);
if(correctBtn) correctBtn.classList.add("correct");
qFeedback.innerHTML = `<span class="pill">Fout</span>
<strong>Juiste antwoord:</strong> ${escapeHtml(correct)}`;
soundWrong();
}
qScore.textContent = `Score: ${quizState.score}`;
s3.textContent = `Score: ${quizState.score}/${quizState.order.length}`;
}
// wiring
modeSel.addEventListener("change", () => {
if(modeSel.value === "memory"){
quizArea.classList.add("hidden");
memoryArea.classList.remove("hidden");
startMemory();
} else {
memoryArea.classList.add("hidden");
quizArea.classList.remove("hidden");
startQuiz(true);
}
});
$('[data-el="new"]').addEventListener("click", () => {
modeSel.value === "memory" ? startMemory() : startQuiz(true);
});
$('[data-el="next"]').addEventListener("click", () => nextQuestion());
$('[data-el="switch"]').addEventListener("click", () => {
quizDirection = (quizDirection === "term->def") ? "def->term" : "term->def";
startQuiz(true);
});
startMemory();
})();
</script>

