0474 44 03 38 roeselare@schakelklas.be

Test

๐ŸŒ
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>