{"id":454,"date":"2026-06-04T09:21:48","date_gmt":"2026-06-04T09:21:48","guid":{"rendered":"https:\/\/seanholden.xyz\/?page_id=454"},"modified":"2026-06-04T09:48:54","modified_gmt":"2026-06-04T09:48:54","slug":"short-sentences","status":"publish","type":"page","link":"https:\/\/seanholden.xyz\/?page_id=454","title":{"rendered":"Short Sentences"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\" \/>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \/>\n  <title>Sentence Wizard<\/title>\n  <style>\n    :root {\n      --bg1: #ffe0f0;\n      --bg2: #d7f7ff;\n      --bg3: #fff3b0;\n      --ink: #243044;\n      --card: rgba(255, 255, 255, 0.93);\n      --green: #7bed9f;\n      --yellow: #ffd166;\n      --blue: #74b9ff;\n      --pink: #ff8fab;\n      --purple: #b197fc;\n      --orange: #ffb86b;\n      --shadow: 0 18px 45px rgba(34, 48, 68, 0.18);\n    }\n\n    * {\n      box-sizing: border-box;\n    }\n\n    body {\n      margin: 0;\n      min-height: 100vh;\n      font-family: \"Comic Sans MS\", \"Trebuchet MS\", Arial, sans-serif;\n      color: var(--ink);\n      background:\n        radial-gradient(circle at 18% 20%, rgba(255,255,255,0.65), transparent 25%),\n        linear-gradient(135deg, var(--bg1), var(--bg2), var(--bg3));\n      background-size: 300% 300%;\n      animation: floatBackground 14s ease infinite;\n      overflow-x: hidden;\n    }\n\n    @keyframes floatBackground {\n      0% { background-position: 0% 50%; }\n      50% { background-position: 100% 50%; }\n      100% { background-position: 0% 50%; }\n    }\n\n    .app {\n      width: min(1040px, 94vw);\n      margin: 0 auto;\n      padding: 22px 0 36px;\n    }\n\n    header {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      gap: 12px;\n      margin-bottom: 18px;\n      flex-wrap: wrap;\n    }\n\n    h1 {\n      font-size: clamp(34px, 6vw, 58px);\n      margin: 0;\n      letter-spacing: -1px;\n      text-shadow: 2px 2px 0 rgba(255,255,255,0.8);\n    }\n\n    .top-buttons {\n      display: flex;\n      gap: 10px;\n      flex-wrap: wrap;\n      align-items: center;\n    }\n\n    .pill, select {\n      border: none;\n      border-radius: 999px;\n      padding: 12px 15px;\n      font-size: 15px;\n      font-weight: 900;\n      cursor: pointer;\n      background: white;\n      box-shadow: 0 8px 20px rgba(0,0,0,0.12);\n      color: var(--ink);\n      max-width: 300px;\n    }\n\n    select {\n      cursor: pointer;\n    }\n\n    .score-row {\n      display: grid;\n      grid-template-columns: repeat(4, 1fr);\n      gap: 12px;\n      margin-bottom: 18px;\n    }\n\n    .score-box {\n      background: rgba(255,255,255,0.82);\n      border: 3px solid rgba(255,255,255,0.9);\n      border-radius: 22px;\n      padding: 14px;\n      text-align: center;\n      box-shadow: 0 8px 18px rgba(0,0,0,0.09);\n    }\n\n    .score-box .label {\n      font-size: 14px;\n      opacity: 0.75;\n      font-weight: 900;\n    }\n\n    .score-box .value {\n      font-size: 27px;\n      font-weight: 900;\n      margin-top: 4px;\n    }\n\n    .card {\n      background: var(--card);\n      border: 4px solid rgba(255,255,255,0.9);\n      border-radius: 36px;\n      box-shadow: var(--shadow);\n      padding: clamp(22px, 5vw, 44px);\n      text-align: center;\n      position: relative;\n      overflow: hidden;\n    }\n\n    .mascot {\n      font-size: clamp(58px, 12vw, 110px);\n      line-height: 1;\n      margin-bottom: 8px;\n      filter: drop-shadow(0 8px 8px rgba(0,0,0,0.15));\n      user-select: none;\n    }\n\n    .instruction {\n      font-size: clamp(18px, 3.4vw, 28px);\n      font-weight: 900;\n      margin: 6px 0 16px;\n    }\n\n    .sentence-box {\n      background: white;\n      border: 6px solid #fff0a8;\n      border-radius: 34px;\n      padding: clamp(22px, 5vw, 38px);\n      margin: 10px auto 20px;\n      min-height: 190px;\n      max-width: 920px;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      box-shadow: inset 0 -8px 0 rgba(0,0,0,0.05), 0 12px 22px rgba(0,0,0,0.12);\n      cursor: pointer;\n    }\n\n    .sentence {\n      font-size: clamp(34px, 6.2vw, 68px);\n      line-height: 1.22;\n      font-weight: 900;\n      letter-spacing: 0.2px;\n    }\n\n    .sentence.pop {\n      animation: pop 0.35s ease;\n    }\n\n    @keyframes pop {\n      0% { transform: scale(0.9); opacity: 0; }\n      70% { transform: scale(1.03); opacity: 1; }\n      100% { transform: scale(1); }\n    }\n\n    .button-row {\n      display: flex;\n      justify-content: center;\n      gap: 14px;\n      flex-wrap: wrap;\n      margin-top: 16px;\n    }\n\n    .big-button {\n      border: none;\n      border-radius: 28px;\n      padding: 20px 26px;\n      font-size: clamp(18px, 3.4vw, 28px);\n      font-weight: 900;\n      cursor: pointer;\n      color: var(--ink);\n      box-shadow: 0 11px 0 rgba(0,0,0,0.12), 0 17px 24px rgba(0,0,0,0.12);\n      min-width: 190px;\n      transition: transform 0.08s ease, box-shadow 0.08s ease;\n    }\n\n    .big-button:active {\n      transform: translateY(8px);\n      box-shadow: 0 3px 0 rgba(0,0,0,0.12), 0 8px 12px rgba(0,0,0,0.1);\n    }\n\n    .listen { background: var(--blue); }\n    .read { background: var(--green); }\n    .help { background: var(--yellow); }\n    .answer1 { background: var(--pink); }\n    .answer2 { background: var(--purple); }\n    .answer3 { background: var(--orange); }\n\n    .help-box {\n      display: none;\n      background: #f3f6ff;\n      border-radius: 24px;\n      padding: 18px;\n      margin: 16px auto 0;\n      max-width: 820px;\n      font-family: Arial, sans-serif;\n      font-size: 18px;\n      line-height: 1.5;\n      text-align: left;\n    }\n\n    .help-box.open {\n      display: block;\n    }\n\n    .question-panel {\n      display: none;\n      margin-top: 18px;\n      border-top: 3px dashed rgba(36,48,68,0.16);\n      padding-top: 18px;\n    }\n\n    .question-panel.open {\n      display: block;\n    }\n\n    .question {\n      font-size: clamp(24px, 4vw, 38px);\n      font-weight: 900;\n      margin-bottom: 10px;\n    }\n\n    .message {\n      min-height: 42px;\n      margin-top: 18px;\n      font-size: clamp(18px, 3.2vw, 26px);\n      font-weight: 900;\n    }\n\n    .progress-wrap {\n      margin: 22px auto 0;\n      max-width: 660px;\n      text-align: left;\n    }\n\n    .progress-label {\n      display: flex;\n      justify-content: space-between;\n      font-weight: 900;\n      margin-bottom: 6px;\n    }\n\n    .progress {\n      height: 24px;\n      background: rgba(255,255,255,0.8);\n      border-radius: 999px;\n      overflow: hidden;\n      border: 3px solid white;\n    }\n\n    .progress-fill {\n      height: 100%;\n      width: 0%;\n      background: linear-gradient(90deg, #7bed9f, #74b9ff, #b197fc);\n      border-radius: 999px;\n      transition: width 0.4s ease;\n    }\n\n    .badges {\n      display: flex;\n      flex-wrap: wrap;\n      justify-content: center;\n      gap: 10px;\n      margin-top: 18px;\n    }\n\n    .badge {\n      background: white;\n      border-radius: 999px;\n      padding: 10px 14px;\n      font-size: 16px;\n      font-weight: 900;\n      box-shadow: 0 8px 16px rgba(0,0,0,0.08);\n      opacity: 0.38;\n      filter: grayscale(1);\n    }\n\n    .badge.unlocked {\n      opacity: 1;\n      filter: none;\n      border: 3px solid #ffe66d;\n    }\n\n    .panel {\n      display: none;\n      margin-top: 18px;\n      background: rgba(255,255,255,0.9);\n      border-radius: 26px;\n      padding: 20px;\n      box-shadow: var(--shadow);\n    }\n\n    .panel.open {\n      display: block;\n    }\n\n    .panel h2 {\n      margin-top: 0;\n    }\n\n    table {\n      width: 100%;\n      border-collapse: collapse;\n      background: white;\n      border-radius: 18px;\n      overflow: hidden;\n      font-family: Arial, sans-serif;\n    }\n\n    th, td {\n      padding: 10px;\n      border-bottom: 1px solid #eee;\n      text-align: left;\n      font-size: 14px;\n    }\n\n    th {\n      background: #f3f6ff;\n    }\n\n    textarea {\n      width: 100%;\n      min-height: 130px;\n      border-radius: 18px;\n      border: 2px solid #d7dff5;\n      padding: 14px;\n      font-size: 16px;\n      font-family: Arial, sans-serif;\n    }\n\n    .small-note {\n      font-family: Arial, sans-serif;\n      opacity: 0.78;\n      font-size: 14px;\n      line-height: 1.45;\n    }\n\n    .confetti {\n      position: fixed;\n      top: -20px;\n      width: 12px;\n      height: 18px;\n      opacity: 0.9;\n      pointer-events: none;\n      animation: fall 1.6s linear forwards;\n      z-index: 1000;\n    }\n\n    @keyframes fall {\n      to {\n        transform: translateY(110vh) rotate(720deg);\n        opacity: 0.4;\n      }\n    }\n\n    @media (max-width: 740px) {\n      .score-row {\n        grid-template-columns: repeat(2, 1fr);\n      }\n\n      .big-button {\n        width: 100%;\n      }\n\n      select {\n        width: 100%;\n        max-width: none;\n      }\n\n      .sentence-box {\n        min-height: 170px;\n      }\n    }\n  <\/style>\n<\/head>\n<body>\n  <div class=\"app\">\n    <header>\n      <h1>\ud83e\uddd9\u200d\u2642\ufe0f Sentence Wizard<\/h1>\n      <div class=\"top-buttons\">\n        <button class=\"pill\" onclick=\"togglePanel('parentPanel')\">\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67 Parent<\/button>\n        <button class=\"pill\" onclick=\"togglePanel('sentencePanel')\">\u2795 Sentences<\/button>\n        <button class=\"pill\" onclick=\"resetProgress()\">\ud83d\udd04 Reset<\/button>\n        <label class=\"pill\">\ud83d\udde3\ufe0f Voice<\/label>\n        <select id=\"voiceSelect\" onchange=\"saveVoiceChoice()\">\n          <option>Loading voices&#8230;<\/option>\n        <\/select>\n      <\/div>\n    <\/header>\n\n    <section class=\"score-row\">\n      <div class=\"score-box\">\n        <div class=\"label\">Points<\/div>\n        <div class=\"value\" id=\"score\">0<\/div>\n      <\/div>\n      <div class=\"score-box\">\n        <div class=\"label\">Level<\/div>\n        <div class=\"value\" id=\"level\">1<\/div>\n      <\/div>\n      <div class=\"score-box\">\n        <div class=\"label\">Streak<\/div>\n        <div class=\"value\" id=\"streak\">0<\/div>\n      <\/div>\n      <div class=\"score-box\">\n        <div class=\"label\">Sentences<\/div>\n        <div class=\"value\" id=\"tried\">0<\/div>\n      <\/div>\n    <\/section>\n\n    <main class=\"card\">\n      <div class=\"mascot\" id=\"mascot\">\ud83e\ude84<\/div>\n      <div class=\"instruction\" id=\"instruction\">Read the sentence first. Then press &#8220;I read it&#8221;.<\/div>\n\n      <div class=\"sentence-box\" onclick=\"speakCurrentSentence()\">\n        <div class=\"sentence\" id=\"sentence\">The cat ran.<\/div>\n      <\/div>\n\n      <div class=\"button-row\" id=\"readButtons\">\n        <button class=\"big-button listen\" onclick=\"speakCurrentSentence()\">\ud83d\udd0a Read it<\/button>\n        <button class=\"big-button read\" onclick=\"markRead()\">\u2705 I read it<\/button>\n        <button class=\"big-button help\" onclick=\"toggleHelp()\">\ud83d\udca1 Help<\/button>\n      <\/div>\n\n      <div class=\"help-box\" id=\"helpBox\"><\/div>\n\n      <div class=\"question-panel\" id=\"questionPanel\">\n        <div class=\"question\" id=\"question\">What ran?<\/div>\n        <div class=\"button-row\" id=\"answerButtons\"><\/div>\n      <\/div>\n\n      <div class=\"message\" id=\"message\"><\/div>\n\n      <div class=\"progress-wrap\">\n        <div class=\"progress-label\">\n          <span id=\"levelName\">Level 1 Sentence Reader<\/span>\n          <span id=\"nextLevel\">0 \/ 60<\/span>\n        <\/div>\n        <div class=\"progress\">\n          <div class=\"progress-fill\" id=\"progressFill\"><\/div>\n        <\/div>\n      <\/div>\n\n      <div class=\"badges\" id=\"badges\"><\/div>\n    <\/main>\n\n    <section class=\"panel\" id=\"parentPanel\">\n      <h2>Parent Stats<\/h2>\n      <p class=\"small-note\">\n        Sentences marked as difficult are shown more often. Sentences answered correctly still return occasionally for revision.\n        Progress is saved only in this browser using local storage.\n      <\/p>\n      <div id=\"statsTable\"><\/div>\n    <\/section>\n\n    <section class=\"panel\" id=\"sentencePanel\">\n      <h2>Add Your Own Sentences<\/h2>\n      <p class=\"small-note\">\n        Add one sentence per line. The game will create a simple &#8220;Did you read it?&#8221; question for custom sentences.\n      <\/p>\n      <textarea id=\"newSentences\" placeholder=\"The frog jumps.&#10;I can see a red car.&#10;Mum has a big hat.\"><\/textarea>\n      <div class=\"button-row\">\n        <button class=\"big-button listen\" onclick=\"addSentences()\">\u2795 Add Sentences<\/button>\n      <\/div>\n    <\/section>\n  <\/div>\n\n  <script>\n    const DEFAULT_SENTENCES = [\n      {\n        text: \"The cat ran home.\",\n        difficulty: 1,\n        help: \"Try it in small parts: The cat \/ ran home.\",\n        question: \"Where did the cat run?\",\n        answers: [\"Home\", \"School\", \"The moon\"],\n        correct: 0\n      },\n      {\n        text: \"I can jump.\",\n        difficulty: 1,\n        help: \"This is a short sentence. Point to each word as you read.\",\n        question: \"What can I do?\",\n        answers: [\"Jump\", \"Sleep\", \"Swim\"],\n        correct: 0\n      },\n      {\n        text: \"Dad has a hat.\",\n        difficulty: 1,\n        help: \"Look for the rhyming words: Dad \/ had \/ hat.\",\n        question: \"What does Dad have?\",\n        answers: [\"A hat\", \"A dog\", \"A fish\"],\n        correct: 0\n      },\n      {\n        text: \"The dog is big.\",\n        difficulty: 1,\n        help: \"Try reading the small words first: The \/ is.\",\n        question: \"What is big?\",\n        answers: [\"The dog\", \"The bird\", \"The cup\"],\n        correct: 0\n      },\n      {\n        text: \"The fish can swim.\",\n        difficulty: 2,\n        help: \"The word swim starts with sw. Say it slowly: s-wim.\",\n        question: \"What can swim?\",\n        answers: [\"The fish\", \"The hat\", \"The tree\"],\n        correct: 0\n      },\n      {\n        text: \"The bird sat on a tree.\",\n        difficulty: 2,\n        help: \"Pause after bird. The bird \/ sat on a tree.\",\n        question: \"Where did the bird sit?\",\n        answers: [\"On a tree\", \"In a car\", \"Under a bed\"],\n        correct: 0\n      },\n      {\n        text: \"I like yellow ducks.\",\n        difficulty: 2,\n        help: \"Yellow is the describing word. It tells us about the ducks.\",\n        question: \"What colour are the ducks?\",\n        answers: [\"Yellow\", \"Blue\", \"Black\"],\n        correct: 0\n      },\n      {\n        text: \"Mum made a red cake.\",\n        difficulty: 2,\n        help: \"This sentence tells us who made something and what it was.\",\n        question: \"What did Mum make?\",\n        answers: [\"A red cake\", \"A blue bike\", \"A small boat\"],\n        correct: 0\n      },\n      {\n        text: \"The happy monkey climbed the tree.\",\n        difficulty: 3,\n        help: \"Climbed means went up. Try: The happy monkey \/ climbed the tree.\",\n        question: \"Who climbed the tree?\",\n        answers: [\"The monkey\", \"The fish\", \"The duck\"],\n        correct: 0\n      },\n      {\n        text: \"My friend plays soccer after school.\",\n        difficulty: 3,\n        help: \"After tells us when something happens.\",\n        question: \"When does my friend play soccer?\",\n        answers: [\"After school\", \"Before breakfast\", \"At night\"],\n        correct: 0\n      },\n      {\n        text: \"The little rabbit hid under the chair.\",\n        difficulty: 3,\n        help: \"Under tells us where the rabbit hid.\",\n        question: \"Where did the rabbit hide?\",\n        answers: [\"Under the chair\", \"On the roof\", \"In the water\"],\n        correct: 0\n      },\n      {\n        text: \"We went to the park because it was sunny.\",\n        difficulty: 4,\n        help: \"Because tells us why something happened.\",\n        question: \"Why did we go to the park?\",\n        answers: [\"It was sunny\", \"It was dark\", \"It was snowing\"],\n        correct: 0\n      },\n      {\n        text: \"The shiny star twinkled in the sky.\",\n        difficulty: 4,\n        help: \"Twinkled means it sparkled or flashed gently.\",\n        question: \"Where was the star?\",\n        answers: [\"In the sky\", \"In the box\", \"In the sea\"],\n        correct: 0\n      },\n      {\n        text: \"Because it was raining, we stayed inside.\",\n        difficulty: 5,\n        help: \"This sentence starts with because. It explains why we stayed inside.\",\n        question: \"Why did we stay inside?\",\n        answers: [\"It was raining\", \"It was sunny\", \"It was bedtime\"],\n        correct: 0\n      }\n    ];\n\n    const LEVELS = [\n      { name: \"Level 1 Sentence Reader\", min: 0 },\n      { name: \"Level 2 Meaning Finder\", min: 60 },\n      { name: \"Level 3 Question Champion\", min: 150 },\n      { name: \"Level 4 Story Starter\", min: 280 },\n      { name: \"Level 5 Reading Wizard\", min: 460 },\n      { name: \"Level 6 Adventure Ready\", min: 700 }\n    ];\n\n    const BADGES = [\n      { id: \"first\", label: \"\ud83c\udf1f First Sentence\", points: 5 },\n      { id: \"read5\", label: \"\ud83d\udcd8 5 Sentences\", tried: 5 },\n      { id: \"correct5\", label: \"\ud83c\udfaf 5 Correct\", correct: 5 },\n      { id: \"streak5\", label: \"\ud83d\udd25 5 Streak\", streak: 5 },\n      { id: \"hundred\", label: \"\ud83c\udfc6 100 Points\", points: 100 },\n      { id: \"adventure\", label: \"\ud83e\udded Adventure Ready\", points: 250 }\n    ];\n\n    const mascots = [\"\ud83e\ude84\", \"\ud83e\udd89\", \"\ud83d\udc31\", \"\ud83e\udd84\", \"\ud83d\udc38\", \"\ud83d\udc36\", \"\ud83d\udc3c\", \"\ud83e\udd8a\"];\n    const niceMessages = [\n      \"Great reading!\",\n      \"You found the meaning!\",\n      \"Fantastic effort!\",\n      \"That was brilliant!\",\n      \"You are getting stronger!\",\n      \"Sentence magic!\"\n    ];\n\n    let sentences = loadSentences();\n    let currentSentence = null;\n    let hasUsedNarration = false;\n    let hasUsedHelp = false;\n\n    let score = Number(localStorage.getItem(\"sentenceWizardScore\")) || 0;\n    let streak = Number(localStorage.getItem(\"sentenceWizardStreak\")) || 0;\n\n    function loadSentences() {\n      const saved = localStorage.getItem(\"sentenceWizardSentences\");\n\n      if (saved) {\n        return JSON.parse(saved);\n      }\n\n      return DEFAULT_SENTENCES.map(sentence => ({\n        ...sentence,\n        shown: 0,\n        read: 0,\n        correctCount: 0,\n        missed: 0,\n        helpUsed: 0,\n        narrationUsed: 0,\n        lastSeen: null\n      }));\n    }\n\n    function saveAll() {\n      localStorage.setItem(\"sentenceWizardSentences\", JSON.stringify(sentences));\n      localStorage.setItem(\"sentenceWizardScore\", String(score));\n      localStorage.setItem(\"sentenceWizardStreak\", String(streak));\n    }\n\n    function getSentenceWeight(sentence) {\n      const notSeenBonus = sentence.shown === 0 ? 4 : 0;\n      const missBonus = sentence.missed * 3;\n      const helpBonus = sentence.helpUsed * 1.2;\n      const knownReduction = sentence.correctCount * 1.5;\n      const difficultyBonus = sentence.difficulty * 1.4;\n      const recentPenalty = sentence.lastSeen && Date.now() - sentence.lastSeen < 15000 ? 3 : 0;\n\n      return Math.max(0.5, difficultyBonus + missBonus + helpBonus + notSeenBonus - knownReduction - recentPenalty);\n    }\n\n    function chooseSentence() {\n      const weighted = [];\n\n      sentences.forEach(sentence => {\n        const weight = Math.round(getSentenceWeight(sentence) * 10);\n        for (let i = 0; i < weight; i++) weighted.push(sentence);\n      });\n\n      currentSentence = weighted[Math.floor(Math.random() * weighted.length)];\n      currentSentence.shown++;\n      currentSentence.lastSeen = Date.now();\n\n      hasUsedNarration = false;\n      hasUsedHelp = false;\n\n      const sentenceElement = document.getElementById(\"sentence\");\n      sentenceElement.textContent = currentSentence.text;\n      sentenceElement.classList.remove(\"pop\");\n      void sentenceElement.offsetWidth;\n      sentenceElement.classList.add(\"pop\");\n\n      document.getElementById(\"mascot\").textContent = mascots[Math.floor(Math.random() * mascots.length)];\n      document.getElementById(\"instruction\").textContent = \"Read the sentence first. Then press \\\"I read it\\\".\";\n      document.getElementById(\"message\").textContent = \"\";\n      document.getElementById(\"helpBox\").classList.remove(\"open\");\n      document.getElementById(\"helpBox\").innerHTML = `<strong>Reading help:<\/strong><br>${currentSentence.help}`;\n      document.getElementById(\"questionPanel\").classList.remove(\"open\");\n\n      updateDisplay();\n      updateStats();\n      saveAll();\n    }\n\n    function markRead() {\n      if (!currentSentence) return;\n\n      currentSentence.read++;\n      score += 5;\n\n      if (!hasUsedNarration) {\n        score += 5;\n        document.getElementById(\"message\").textContent = \"Bonus points for reading before listening!\";\n      } else {\n        document.getElementById(\"message\").textContent = \"Great. Now answer the question!\";\n      }\n\n      showQuestion();\n      saveAll();\n      updateDisplay();\n      updateStats();\n    }\n\n    function showQuestion() {\n      const panel = document.getElementById(\"questionPanel\");\n      const question = document.getElementById(\"question\");\n      const answerButtons = document.getElementById(\"answerButtons\");\n\n      question.textContent = currentSentence.question;\n\n      answerButtons.innerHTML = currentSentence.answers.map((answer, index) => `\n        <button class=\"big-button answer${index + 1}\" onclick=\"answerQuestion(${index})\">${answer}<\/button>\n      `).join(\"\");\n\n      panel.classList.add(\"open\");\n      document.getElementById(\"instruction\").textContent = \"Now answer the question.\";\n    }\n\n    function answerQuestion(index) {\n      if (!currentSentence) return;\n\n      const wasCorrect = index === currentSentence.correct;\n\n      if (wasCorrect) {\n        currentSentence.correctCount++;\n        streak++;\n        score += 10;\n\n        if (!hasUsedHelp && !hasUsedNarration) {\n          score += 5;\n        }\n\n        document.getElementById(\"message\").textContent = niceMessages[Math.floor(Math.random() * niceMessages.length)];\n        burstConfetti(28);\n      } else {\n        currentSentence.missed++;\n        streak = 0;\n        score += 1;\n        document.getElementById(\"message\").textContent = \"Good try. Let's practise that one again soon.\";\n      }\n\n      saveAll();\n      updateDisplay();\n      updateStats();\n\n      setTimeout(chooseSentence, 1050);\n    }\n\n    function toggleHelp() {\n      if (!currentSentence) return;\n\n      const helpBox = document.getElementById(\"helpBox\");\n      helpBox.classList.toggle(\"open\");\n\n      if (!hasUsedHelp) {\n        hasUsedHelp = true;\n        currentSentence.helpUsed++;\n        score += 1;\n        saveAll();\n        updateDisplay();\n        updateStats();\n      }\n    }\n\n    function speakCurrentSentence() {\n      if (!currentSentence) return;\n\n      hasUsedNarration = true;\n      currentSentence.narrationUsed++;\n\n      window.speechSynthesis.cancel();\n\n      const speech = new SpeechSynthesisUtterance(currentSentence.text);\n      speech.voice = getSelectedVoice();\n      speech.rate = 0.74;\n      speech.pitch = 1.05;\n      speech.volume = 1;\n\n      window.speechSynthesis.speak(speech);\n      saveAll();\n      updateStats();\n    }\n\n    function getCurrentLevelIndex() {\n      let index = 0;\n      LEVELS.forEach((level, i) => {\n        if (score >= level.min) index = i;\n      });\n      return index;\n    }\n\n    function getNextLevelMin(levelIndex) {\n      return LEVELS[levelIndex + 1] ? LEVELS[levelIndex + 1].min : LEVELS[levelIndex].min + 250;\n    }\n\n    function updateDisplay() {\n      const tried = sentences.reduce((total, sentence) => total + sentence.read, 0);\n      const correct = sentences.reduce((total, sentence) => total + sentence.correctCount, 0);\n      const levelIndex = getCurrentLevelIndex();\n      const level = LEVELS[levelIndex];\n      const levelStart = level.min;\n      const levelEnd = getNextLevelMin(levelIndex);\n      const progress = Math.min(100, ((score - levelStart) \/ (levelEnd - levelStart)) * 100);\n\n      document.getElementById(\"score\").textContent = score;\n      document.getElementById(\"level\").textContent = levelIndex + 1;\n      document.getElementById(\"streak\").textContent = streak;\n      document.getElementById(\"tried\").textContent = tried;\n      document.getElementById(\"levelName\").textContent = level.name;\n      document.getElementById(\"nextLevel\").textContent = `${score} \/ ${levelEnd}`;\n      document.getElementById(\"progressFill\").style.width = `${progress}%`;\n\n      renderBadges();\n    }\n\n    function getUnlockedBadges() {\n      const tried = sentences.reduce((total, sentence) => total + sentence.read, 0);\n      const correct = sentences.reduce((total, sentence) => total + sentence.correctCount, 0);\n\n      return BADGES.filter(badge => {\n        if (badge.points !== undefined && score >= badge.points) return true;\n        if (badge.tried !== undefined && tried >= badge.tried) return true;\n        if (badge.correct !== undefined && correct >= badge.correct) return true;\n        if (badge.streak !== undefined && streak >= badge.streak) return true;\n        return false;\n      });\n    }\n\n    function renderBadges() {\n      const unlockedIds = getUnlockedBadges().map(badge => badge.id);\n      const container = document.getElementById(\"badges\");\n\n      container.innerHTML = BADGES.map(badge => {\n        const unlocked = unlockedIds.includes(badge.id) ? \"unlocked\" : \"\";\n        return `<span class=\"badge ${unlocked}\">${badge.label}<\/span>`;\n      }).join(\"\");\n    }\n\n    function updateStats() {\n      const sorted = [...sentences].sort((a, b) => {\n        const aAccuracy = a.read ? a.correctCount \/ a.read : 1;\n        const bAccuracy = b.read ? b.correctCount \/ b.read : 1;\n        return aAccuracy - bAccuracy || b.missed - a.missed;\n      });\n\n      const rows = sorted.map(sentence => {\n        const accuracy = sentence.read ? Math.round((sentence.correctCount \/ sentence.read) * 100) : 0;\n        return `\n          <tr>\n            <td><strong>${sentence.text}<\/strong><\/td>\n            <td>${sentence.difficulty}<\/td>\n            <td>${sentence.read}<\/td>\n            <td>${sentence.correctCount}<\/td>\n            <td>${sentence.missed}<\/td>\n            <td>${sentence.helpUsed}<\/td>\n            <td>${sentence.narrationUsed}<\/td>\n            <td>${accuracy}%<\/td>\n          <\/tr>\n        `;\n      }).join(\"\");\n\n      document.getElementById(\"statsTable\").innerHTML = `\n        <table>\n          <thead>\n            <tr>\n              <th>Sentence<\/th>\n              <th>Difficulty<\/th>\n              <th>Read<\/th>\n              <th>Correct<\/th>\n              <th>Missed<\/th>\n              <th>Help<\/th>\n              <th>Audio<\/th>\n              <th>Correct Rate<\/th>\n            <\/tr>\n          <\/thead>\n          <tbody>${rows}<\/tbody>\n        <\/table>\n      `;\n    }\n\n    function togglePanel(id) {\n      document.getElementById(id).classList.toggle(\"open\");\n      updateStats();\n    }\n\n    function addSentences() {\n      const textarea = document.getElementById(\"newSentences\");\n      const newSentences = textarea.value\n        .split(\"\\n\")\n        .map(sentence => sentence.trim())\n        .filter(Boolean);\n\n      const existing = new Set(sentences.map(sentence => sentence.text.toLowerCase()));\n\n      newSentences.forEach(text => {\n        if (!existing.has(text.toLowerCase())) {\n          const wordCount = text.split(\/\\s+\/).length;\n          const difficulty = Math.min(5, Math.max(1, Math.ceil(wordCount \/ 3)));\n\n          sentences.push({\n            text,\n            difficulty,\n            help: \"Point to each word as you read. Try the sentence in small chunks.\",\n            question: \"Did you read the sentence?\",\n            answers: [\"Yes\", \"Not yet\", \"I need help\"],\n            correct: 0,\n            shown: 0,\n            read: 0,\n            correctCount: 0,\n            missed: 0,\n            helpUsed: 0,\n            narrationUsed: 0,\n            lastSeen: null\n          });\n        }\n      });\n\n      textarea.value = \"\";\n      saveAll();\n      updateStats();\n      document.getElementById(\"message\").textContent = \"New sentences added!\";\n      chooseSentence();\n    }\n\n    function resetProgress() {\n      const sure = confirm(\"Reset all points and sentence progress?\");\n\n      if (!sure) return;\n\n      localStorage.removeItem(\"sentenceWizardSentences\");\n      localStorage.removeItem(\"sentenceWizardScore\");\n      localStorage.removeItem(\"sentenceWizardStreak\");\n\n      sentences = loadSentences();\n      score = 0;\n      streak = 0;\n\n      document.getElementById(\"message\").textContent = \"Progress reset.\";\n      chooseSentence();\n    }\n\n    function burstConfetti(amount) {\n      const colours = [\"#ff8fab\", \"#ffd166\", \"#7bed9f\", \"#74b9ff\", \"#b197fc\", \"#ffffff\"];\n\n      for (let i = 0; i < amount; i++) {\n        const piece = document.createElement(\"div\");\n        piece.className = \"confetti\";\n        piece.style.left = `${Math.random() * 100}vw`;\n        piece.style.background = colours[Math.floor(Math.random() * colours.length)];\n        piece.style.animationDelay = `${Math.random() * 0.35}s`;\n        piece.style.transform = `rotate(${Math.random() * 360}deg)`;\n        document.body.appendChild(piece);\n\n        setTimeout(() => piece.remove(), 2000);\n      }\n    }\n\n    let availableVoices = [];\n\n    function populateVoiceList() {\n      const voiceSelect = document.getElementById(\"voiceSelect\");\n      if (!voiceSelect) return;\n\n      availableVoices = window.speechSynthesis.getVoices();\n\n      voiceSelect.innerHTML = \"\";\n\n      if (!availableVoices.length) {\n        const option = document.createElement(\"option\");\n        option.textContent = \"Loading voices...\";\n        option.value = \"\";\n        voiceSelect.appendChild(option);\n        return;\n      }\n\n      const savedVoiceName = localStorage.getItem(\"sentenceWizardVoiceName\");\n      const englishVoices = availableVoices.filter(voice => voice.lang && voice.lang.toLowerCase().startsWith(\"en\"));\n      const voicesToShow = englishVoices.length ? englishVoices : availableVoices;\n\n      voicesToShow.forEach(voice => {\n        const option = document.createElement(\"option\");\n        option.value = voice.name;\n        option.textContent = `${voice.name} (${voice.lang})`;\n        voiceSelect.appendChild(option);\n      });\n\n      const preferredVoice =\n        voicesToShow.find(v => v.name === savedVoiceName) ||\n        voicesToShow.find(v => v.name.includes(\"Natural\")) ||\n        voicesToShow.find(v => v.name.includes(\"Sonia\")) ||\n        voicesToShow.find(v => v.name.includes(\"Jenny\")) ||\n        voicesToShow.find(v => v.lang && v.lang.toLowerCase().startsWith(\"en-au\")) ||\n        voicesToShow.find(v => v.lang && v.lang.toLowerCase().startsWith(\"en\")) ||\n        voicesToShow[0];\n\n      if (preferredVoice) {\n        voiceSelect.value = preferredVoice.name;\n      }\n    }\n\n    function getSelectedVoice() {\n      const voiceSelect = document.getElementById(\"voiceSelect\");\n      const selectedName = voiceSelect ? voiceSelect.value : \"\";\n\n      return (\n        availableVoices.find(voice => voice.name === selectedName) ||\n        availableVoices.find(voice => voice.name.includes(\"Natural\")) ||\n        availableVoices.find(voice => voice.lang && voice.lang.toLowerCase().startsWith(\"en-au\")) ||\n        availableVoices.find(voice => voice.lang && voice.lang.toLowerCase().startsWith(\"en\")) ||\n        availableVoices[0] ||\n        null\n      );\n    }\n\n    function saveVoiceChoice() {\n      const voiceSelect = document.getElementById(\"voiceSelect\");\n      if (voiceSelect) {\n        localStorage.setItem(\"sentenceWizardVoiceName\", voiceSelect.value);\n      }\n    }\n\n    window.speechSynthesis.onvoiceschanged = populateVoiceList;\n    setTimeout(populateVoiceList, 100);\n    setTimeout(populateVoiceList, 500);\n    setTimeout(populateVoiceList, 1200);\n\n    updateDisplay();\n    updateStats();\n    chooseSentence();\n  <\/script>\n<\/body>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>Sentence Wizard \ud83e\uddd9\u200d\u2642\ufe0f Sentence Wizard \ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67 Parent \u2795 Sentences \ud83d\udd04 Reset \ud83d\udde3\ufe0f Voice Loading voices&#8230; Points 0 Level 1 Streak 0 Sentences 0 \ud83e\ude84 Read the sentence first. Then press &#8220;I read it&#8221;. The cat ran. \ud83d\udd0a Read it \u2705 I read it \ud83d\udca1 Help What ran? Level 1 Sentence Reader 0 \/ 60 Parent &#8230; <a title=\"Short Sentences\" class=\"read-more\" href=\"https:\/\/seanholden.xyz\/?page_id=454\" aria-label=\"More on Short Sentences\">Read more<\/a><\/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-454","page","type-page","status-publish"],"_links":{"self":[{"href":"https:\/\/seanholden.xyz\/index.php?rest_route=\/wp\/v2\/pages\/454","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/seanholden.xyz\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/seanholden.xyz\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/seanholden.xyz\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/seanholden.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=454"}],"version-history":[{"count":3,"href":"https:\/\/seanholden.xyz\/index.php?rest_route=\/wp\/v2\/pages\/454\/revisions"}],"predecessor-version":[{"id":459,"href":"https:\/\/seanholden.xyz\/index.php?rest_route=\/wp\/v2\/pages\/454\/revisions\/459"}],"wp:attachment":[{"href":"https:\/\/seanholden.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=454"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}