{"id":457,"date":"2026-06-04T09:33:49","date_gmt":"2026-06-04T09:33:49","guid":{"rendered":"https:\/\/seanholden.xyz\/?page_id=457"},"modified":"2026-06-04T09:48:15","modified_gmt":"2026-06-04T09:48:15","slug":"adventure-reader","status":"publish","type":"page","link":"https:\/\/seanholden.xyz\/?page_id=457","title":{"rendered":"Adventure Reader"},"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>Adventure Reader<\/title>\n  <style>\n    :root {\n      --ink: #263247;\n      --paper: rgba(255,255,255,0.92);\n      --blue: #74b9ff;\n      --green: #7bed9f;\n      --yellow: #ffd166;\n      --pink: #ff8fab;\n      --purple: #b197fc;\n      --orange: #ffb86b;\n      --shadow: 0 18px 45px rgba(34,48,68,0.18);\n    }\n\n    * { box-sizing: border-box; }\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 20% 20%, rgba(255,255,255,0.55), transparent 26%),\n        linear-gradient(135deg, #ffe0f0, #d7f7ff, #fff3b0);\n      background-size: 300% 300%;\n      animation: drift 14s ease infinite;\n    }\n\n    @keyframes drift {\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(1000px, 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      flex-wrap: wrap;\n      margin-bottom: 16px;\n    }\n\n    h1 {\n      margin: 0;\n      font-size: clamp(32px, 6vw, 58px);\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: 11px 15px;\n      font-size: 15px;\n      font-weight: 900;\n      background: white;\n      color: var(--ink);\n      box-shadow: 0 8px 18px rgba(0,0,0,0.12);\n    }\n\n    .pill { cursor: pointer; }\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: 13px;\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: 3px;\n    }\n\n    .story-card {\n      background: var(--paper);\n      border: 4px solid rgba(255,255,255,0.9);\n      border-radius: 36px;\n      box-shadow: var(--shadow);\n      padding: clamp(22px, 5vw, 42px);\n      text-align: center;\n      overflow: hidden;\n      position: relative;\n    }\n\n    .scene-image {\n      font-size: clamp(72px, 15vw, 136px);\n      line-height: 1;\n      margin: 4px 0 14px;\n      filter: drop-shadow(0 8px 8px rgba(0,0,0,0.14));\n    }\n\n    .chapter {\n      font-size: clamp(18px, 3vw, 26px);\n      font-weight: 900;\n      opacity: 0.8;\n      margin-bottom: 12px;\n    }\n\n    .sentence-box {\n      background: white;\n      border: 6px solid #fff0a8;\n      border-radius: 32px;\n      padding: clamp(20px, 4vw, 34px);\n      margin: 0 auto 16px;\n      max-width: 820px;\n      min-height: 180px;\n      box-shadow: inset 0 -8px 0 rgba(0,0,0,0.05), 0 12px 22px rgba(0,0,0,0.12);\n      display: flex;\n      align-items: center;\n      justify-content: center;\n    }\n\n    .sentence {\n      font-size: clamp(34px, 6vw, 66px);\n      font-weight: 900;\n      line-height: 1.22;\n      letter-spacing: 0.2px;\n    }\n\n    .sentence.pop { animation: pop 0.32s ease; }\n\n    @keyframes pop {\n      0% { transform: scale(0.92); opacity: 0; }\n      80% { 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: 18px 24px;\n      font-size: clamp(18px, 3vw, 26px);\n      font-weight: 900;\n      cursor: pointer;\n      color: var(--ink);\n      box-shadow: 0 10px 0 rgba(0,0,0,0.12), 0 16px 22px rgba(0,0,0,0.10);\n      min-width: 190px;\n      transition: transform 0.08s ease, box-shadow 0.08s ease;\n    }\n\n    .big-button:active {\n      transform: translateY(7px);\n      box-shadow: 0 3px 0 rgba(0,0,0,0.12), 0 8px 12px rgba(0,0,0,0.08);\n    }\n\n    .listen { background: var(--blue); }\n    .read { background: var(--green); }\n    .help { background: var(--yellow); }\n    .choice1 { background: var(--pink); }\n    .choice2 { background: var(--purple); }\n    .choice3 { background: var(--orange); }\n\n    .choice-panel {\n      display: none;\n      margin-top: 14px;\n      padding-top: 6px;\n    }\n\n    .choice-panel.open { display: block; }\n\n    .choice-title {\n      font-size: clamp(22px, 4vw, 34px);\n      font-weight: 900;\n      margin: 10px 0;\n    }\n\n    .help-box {\n      display: none;\n      background: #f3f6ff;\n      border-radius: 24px;\n      padding: 16px;\n      margin: 14px auto 0;\n      max-width: 760px;\n      font-family: Arial, sans-serif;\n      font-size: 18px;\n      line-height: 1.45;\n      text-align: left;\n    }\n\n    .help-box.open { display: block; }\n\n    .message {\n      min-height: 36px;\n      margin-top: 16px;\n      font-size: clamp(18px, 3vw, 26px);\n      font-weight: 900;\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      font-family: Arial, sans-serif;\n    }\n\n    .panel.open { display: block; }\n\n    .panel h2 { margin-top: 0; }\n\n    .small-note {\n      opacity: 0.78;\n      font-size: 14px;\n      line-height: 1.45;\n    }\n\n    table {\n      width: 100%;\n      border-collapse: collapse;\n      background: white;\n      border-radius: 18px;\n      overflow: hidden;\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 { background: #f3f6ff; }\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: 720px) {\n      .score-row { grid-template-columns: repeat(2, 1fr); }\n      .big-button { width: 100%; }\n      select { width: 100%; }\n    }\n  <\/style>\n<\/head>\n<body>\n  <div class=\"app\">\n    <header>\n      <h1>\ud83e\udded Adventure Reader<\/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=\"restartAdventure()\">\ud83d\udd04 New Adventure<\/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\">Scenes Read<\/div>\n        <div class=\"value\" id=\"scenesRead\">0<\/div>\n      <\/div>\n      <div class=\"score-box\">\n        <div class=\"label\">Choices Made<\/div>\n        <div class=\"value\" id=\"choicesMade\">0<\/div>\n      <\/div>\n      <div class=\"score-box\">\n        <div class=\"label\">Help Used<\/div>\n        <div class=\"value\" id=\"helpUsed\">0<\/div>\n      <\/div>\n    <\/section>\n\n    <main class=\"story-card\">\n      <div class=\"scene-image\" id=\"sceneImage\">\ud83c\udf32<\/div>\n      <div class=\"chapter\" id=\"chapter\">The Forest Path<\/div>\n\n      <div class=\"sentence-box\">\n        <div class=\"sentence\" id=\"sentence\">You see a little path.<\/div>\n      <\/div>\n\n      <div class=\"button-row\">\n        <button class=\"big-button listen\" onclick=\"speakScene()\">\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=\"choice-panel\" id=\"choicePanel\">\n        <div class=\"choice-title\">What do you do?<\/div>\n        <div class=\"button-row\" id=\"choices\"><\/div>\n      <\/div>\n\n      <div class=\"message\" id=\"message\"><\/div>\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        This version does not speak automatically. The child reads first, then can press &#8220;Read it&#8221; for help.\n        Choices move the story forward. Progress saves in this browser only.\n      <\/p>\n      <div id=\"statsTable\"><\/div>\n    <\/section>\n  <\/div>\n\n  <script>\n    const STORY = {\n      start: \"forest\",\n      scenes: {\n        forest: {\n          title: \"The Forest Path\",\n          emoji: \"\ud83c\udf32\",\n          text: \"You see a little path. It goes into the trees.\",\n          help: \"Try reading it in two small parts: You see a little path. \/ It goes into the trees.\",\n          choices: [\n            { text: \"Walk on the path\", next: \"fox\" },\n            { text: \"Look behind a tree\", next: \"map\" }\n          ]\n        },\n        fox: {\n          title: \"The Friendly Fox\",\n          emoji: \"\ud83e\udd8a\",\n          text: \"A red fox sits on a rock. It has a small bag.\",\n          help: \"Look for the smaller words first: A \/ red \/ fox \/ sits \/ on \/ a \/ rock.\",\n          choices: [\n            { text: \"Say hello\", next: \"foxHello\" },\n            { text: \"Open the bag\", next: \"bag\" }\n          ]\n        },\n        map: {\n          title: \"The Hidden Map\",\n          emoji: \"\ud83d\uddfa\ufe0f\",\n          text: \"Behind the tree, you find a map. The map has a star.\",\n          help: \"The tricky word is behind. Try it like this: be-hind.\",\n          choices: [\n            { text: \"Follow the star\", next: \"bridge\" },\n            { text: \"Take the map to the fox\", next: \"fox\" }\n          ]\n        },\n        foxHello: {\n          title: \"A Helpful Friend\",\n          emoji: \"\ud83e\udd8a\",\n          text: \"The fox smiles. It says, 'The cave has a shiny key.'\",\n          help: \"Pause at the full stop. Then read what the fox says.\",\n          choices: [\n            { text: \"Go to the cave\", next: \"cave\" },\n            { text: \"Ask about the key\", next: \"keyClue\" }\n          ]\n        },\n        bag: {\n          title: \"The Small Bag\",\n          emoji: \"\ud83c\udf92\",\n          text: \"Inside the bag, there is bread, rope, and a bell.\",\n          help: \"This sentence has a list: bread, rope, and a bell.\",\n          choices: [\n            { text: \"Take the rope\", next: \"bridge\" },\n            { text: \"Ring the bell\", next: \"owl\" }\n          ]\n        },\n        keyClue: {\n          title: \"The Key Clue\",\n          emoji: \"\ud83d\udd11\",\n          text: \"The fox points to a hill. A cave waits at the top.\",\n          help: \"The word points means shows with a finger or paw.\",\n          choices: [\n            { text: \"Climb the hill\", next: \"cave\" },\n            { text: \"Go back to the path\", next: \"forest\" }\n          ]\n        },\n        bridge: {\n          title: \"The Wobbly Bridge\",\n          emoji: \"\ud83c\udf09\",\n          text: \"A bridge goes over blue water. It wobbles in the wind.\",\n          help: \"Wobbles means it moves side to side.\",\n          choices: [\n            { text: \"Step slowly\", next: \"owl\" },\n            { text: \"Use the rope\", next: \"safeBridge\" }\n          ]\n        },\n        safeBridge: {\n          title: \"Safe Crossing\",\n          emoji: \"\ud83e\udea2\",\n          text: \"The rope helps you cross. You feel brave and strong.\",\n          help: \"This sentence has two describing words: brave and strong.\",\n          choices: [\n            { text: \"Keep walking\", next: \"cave\" },\n            { text: \"Call for the fox\", next: \"foxHello\" }\n          ]\n        },\n        owl: {\n          title: \"The Wise Owl\",\n          emoji: \"\ud83e\udd89\",\n          text: \"An owl lands near you. It hoots three times.\",\n          help: \"Near means close. Hoots is the sound an owl makes.\",\n          choices: [\n            { text: \"Count the hoots\", next: \"threeHoots\" },\n            { text: \"Ask the owl for help\", next: \"owlHelp\" }\n          ]\n        },\n        threeHoots: {\n          title: \"Three Hoots\",\n          emoji: \"3\ufe0f\u20e3\",\n          text: \"One, two, three. A secret door opens in the hill.\",\n          help: \"Read the numbers slowly: one, two, three.\",\n          choices: [\n            { text: \"Go in the door\", next: \"cave\" },\n            { text: \"Wait for the fox\", next: \"foxHello\" }\n          ]\n        },\n        owlHelp: {\n          title: \"Owl Advice\",\n          emoji: \"\ud83e\udd89\",\n          text: \"The owl says, 'Read the sign before you go in.'\",\n          help: \"The word before tells us to do one thing first.\",\n          choices: [\n            { text: \"Read the sign\", next: \"sign\" },\n            { text: \"Go straight in\", next: \"cave\" }\n          ]\n        },\n        sign: {\n          title: \"The Cave Sign\",\n          emoji: \"\ud83e\udea7\",\n          text: \"The sign says, 'Be kind. Be calm. Be clever.'\",\n          help: \"This has three short sentences. Try each one by itself.\",\n          choices: [\n            { text: \"Go in kindly\", next: \"dragon\" },\n            { text: \"Call out hello\", next: \"dragon\" }\n          ]\n        },\n        cave: {\n          title: \"The Quiet Cave\",\n          emoji: \"\ud83d\udd6f\ufe0f\",\n          text: \"The cave is dark, but you can see a soft light.\",\n          help: \"The word but joins two ideas together.\",\n          choices: [\n            { text: \"Follow the light\", next: \"dragon\" },\n            { text: \"Look for the key\", next: \"key\" }\n          ]\n        },\n        key: {\n          title: \"The Shiny Key\",\n          emoji: \"\ud83d\udd11\",\n          text: \"You find a shiny key under a flat stone.\",\n          help: \"Under tells you where the key is.\",\n          choices: [\n            { text: \"Pick up the key\", next: \"treasure\" },\n            { text: \"Show the fox\", next: \"foxHello\" }\n          ]\n        },\n        dragon: {\n          title: \"The Sleepy Dragon\",\n          emoji: \"\ud83d\udc09\",\n          text: \"A tiny dragon is sleeping. It has a cold nose.\",\n          help: \"This dragon is tiny. That means very small.\",\n          choices: [\n            { text: \"Give it bread\", next: \"dragonBread\" },\n            { text: \"Whisper hello\", next: \"dragonHello\" }\n          ]\n        },\n        dragonBread: {\n          title: \"Dragon Snack\",\n          emoji: \"\ud83c\udf5e\",\n          text: \"The dragon wakes up. It eats the bread and smiles.\",\n          help: \"Wakes up means it stops sleeping.\",\n          choices: [\n            { text: \"Ask for treasure\", next: \"treasure\" },\n            { text: \"Ask for a ride\", next: \"ride\" }\n          ]\n        },\n        dragonHello: {\n          title: \"A Gentle Hello\",\n          emoji: \"\ud83e\udd2b\",\n          text: \"You whisper hello. The dragon opens one sleepy eye.\",\n          help: \"Whisper means to speak very quietly.\",\n          choices: [\n            { text: \"Pat the dragon\", next: \"ride\" },\n            { text: \"Look for treasure\", next: \"treasure\" }\n          ]\n        },\n        ride: {\n          title: \"Dragon Ride\",\n          emoji: \"\ud83c\udf08\",\n          text: \"The dragon flies low over the trees. You laugh with joy.\",\n          help: \"Joy means a very happy feeling.\",\n          choices: [\n            { text: \"Fly to the castle\", next: \"castle\" },\n            { text: \"Fly back home\", next: \"home\" }\n          ]\n        },\n        treasure: {\n          title: \"The Treasure Box\",\n          emoji: \"\ud83c\udf81\",\n          text: \"The box opens. Inside is a book full of stories.\",\n          help: \"Full means there is a lot inside.\",\n          choices: [\n            { text: \"Read the book\", next: \"bookEnding\" },\n            { text: \"Take it home\", next: \"home\" }\n          ]\n        },\n        castle: {\n          title: \"The Cloud Castle\",\n          emoji: \"\ud83c\udff0\",\n          text: \"A castle sits on a cloud. Music floats in the air.\",\n          help: \"Floats means it moves gently, like a feather.\",\n          choices: [\n            { text: \"Dance at the castle\", next: \"danceEnding\" },\n            { text: \"Read a story there\", next: \"bookEnding\" }\n          ]\n        },\n        bookEnding: {\n          title: \"Reading Hero\",\n          emoji: \"\ud83d\udcda\",\n          text: \"You read the story. Every page makes your magic stronger.\",\n          help: \"This is the ending. You became a reading hero!\",\n          ending: true\n        },\n        danceEnding: {\n          title: \"Castle Party\",\n          emoji: \"\ud83c\udf89\",\n          text: \"You dance and laugh. The dragon gives you a golden star.\",\n          help: \"This is the ending. You found the castle party!\",\n          ending: true\n        },\n        home: {\n          title: \"Home Again\",\n          emoji: \"\ud83c\udfe1\",\n          text: \"You go home smiling. You cannot wait to read again.\",\n          help: \"This is the ending. Your adventure is complete!\",\n          ending: true\n        }\n      }\n    };\n\n    const BADGES = [\n      { id: \"first\", label: \"\ud83c\udf1f First Scene\", scenes: 1 },\n      { id: \"five\", label: \"\ud83e\udded 5 Scenes\", scenes: 5 },\n      { id: \"ten\", label: \"\ud83d\ude80 10 Scenes\", scenes: 10 },\n      { id: \"choice\", label: \"\ud83c\udfb2 Choice Maker\", choices: 3 },\n      { id: \"ending\", label: \"\ud83c\udfc6 Story Finisher\", endings: 1 },\n      { id: \"points\", label: \"\ud83e\uddd9 100 Points\", points: 100 }\n    ];\n\n    let state = JSON.parse(localStorage.getItem(\"adventureReaderState\")) || {\n      currentScene: STORY.start,\n      score: 0,\n      scenesRead: 0,\n      choicesMade: 0,\n      helpUsed: 0,\n      endings: 0,\n      sceneHistory: []\n    };\n\n    let voices = [];\n\n    function saveState() {\n      localStorage.setItem(\"adventureReaderState\", JSON.stringify(state));\n    }\n\n    function renderScene() {\n      const scene = STORY.scenes[state.currentScene];\n\n      document.getElementById(\"sceneImage\").textContent = scene.emoji;\n      document.getElementById(\"chapter\").textContent = scene.title;\n\n      const sentence = document.getElementById(\"sentence\");\n      sentence.textContent = scene.text;\n      sentence.classList.remove(\"pop\");\n      void sentence.offsetWidth;\n      sentence.classList.add(\"pop\");\n\n      document.getElementById(\"helpBox\").classList.remove(\"open\");\n      document.getElementById(\"helpBox\").innerHTML = `<strong>Reading help:<\/strong><br>${scene.help}`;\n\n      document.getElementById(\"message\").textContent = scene.ending\n        ? \"You reached an ending! Great reading!\"\n        : \"\";\n\n      const choicePanel = document.getElementById(\"choicePanel\");\n      const choices = document.getElementById(\"choices\");\n\n      if (scene.ending) {\n        choicePanel.classList.add(\"open\");\n        choices.innerHTML = `\n          <button class=\"big-button choice1\" onclick=\"restartAdventure()\">\ud83d\udd04 Play again<\/button>\n        `;\n        state.endings++;\n        state.score += 20;\n        saveState();\n        burstConfetti(60);\n      } else {\n        choicePanel.classList.remove(\"open\");\n        choices.innerHTML = scene.choices.map((choice, index) => `\n          <button class=\"big-button choice${index + 1}\" onclick=\"choosePath('${choice.next}')\">${choice.text}<\/button>\n        `).join(\"\");\n      }\n\n      updateDisplay();\n      updateStats();\n    }\n\n    function speakScene() {\n      const scene = STORY.scenes[state.currentScene];\n      window.speechSynthesis.cancel();\n\n      const speech = new SpeechSynthesisUtterance(scene.text);\n      const selectedVoiceName = localStorage.getItem(\"adventureReaderVoice\");\n      const selectedVoice = voices.find(v => v.name === selectedVoiceName);\n\n      speech.voice =\n        selectedVoice ||\n        voices.find(v => v.name.includes(\"Natural\")) ||\n        voices.find(v => v.name.includes(\"Sonia\")) ||\n        voices.find(v => v.name.includes(\"Jenny\")) ||\n        voices.find(v => v.lang && v.lang.toLowerCase().startsWith(\"en-au\")) ||\n        voices.find(v => v.lang && v.lang.toLowerCase().startsWith(\"en\")) ||\n        null;\n\n      speech.rate = 0.76;\n      speech.pitch = 1.05;\n      speech.volume = 1;\n\n      window.speechSynthesis.speak(speech);\n    }\n\n    function markRead() {\n      state.score += 5;\n      state.scenesRead++;\n      state.sceneHistory.push({\n        title: STORY.scenes[state.currentScene].title,\n        text: STORY.scenes[state.currentScene].text,\n        time: new Date().toLocaleString()\n      });\n\n      saveState();\n      updateDisplay();\n      updateStats();\n\n      document.getElementById(\"choicePanel\").classList.add(\"open\");\n      document.getElementById(\"message\").textContent = \"Great reading. Choose what happens next!\";\n      burstConfetti(18);\n    }\n\n    function choosePath(nextScene) {\n      state.score += 3;\n      state.choicesMade++;\n      state.currentScene = nextScene;\n      saveState();\n      renderScene();\n    }\n\n    function toggleHelp() {\n      state.helpUsed++;\n      state.score += 1;\n      saveState();\n\n      document.getElementById(\"helpBox\").classList.toggle(\"open\");\n      updateDisplay();\n      updateStats();\n    }\n\n    function updateDisplay() {\n      document.getElementById(\"score\").textContent = state.score;\n      document.getElementById(\"scenesRead\").textContent = state.scenesRead;\n      document.getElementById(\"choicesMade\").textContent = state.choicesMade;\n      document.getElementById(\"helpUsed\").textContent = state.helpUsed;\n      renderBadges();\n    }\n\n    function getUnlockedBadges() {\n      return BADGES.filter(badge => {\n        if (badge.points !== undefined && state.score >= badge.points) return true;\n        if (badge.scenes !== undefined && state.scenesRead >= badge.scenes) return true;\n        if (badge.choices !== undefined && state.choicesMade >= badge.choices) return true;\n        if (badge.endings !== undefined && state.endings >= badge.endings) return true;\n        return false;\n      });\n    }\n\n    function renderBadges() {\n      const unlockedIds = getUnlockedBadges().map(b => b.id);\n      document.getElementById(\"badges\").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 rows = [...state.sceneHistory].slice(-20).reverse().map(item => `\n        <tr>\n          <td><strong>${item.title}<\/strong><\/td>\n          <td>${item.text}<\/td>\n          <td>${item.time}<\/td>\n        <\/tr>\n      `).join(\"\");\n\n      document.getElementById(\"statsTable\").innerHTML = `\n        <table>\n          <thead>\n            <tr>\n              <th>Scene<\/th>\n              <th>Sentence<\/th>\n              <th>Read Time<\/th>\n            <\/tr>\n          <\/thead>\n          <tbody>${rows || '<tr><td colspan=\"3\">No scenes read yet.<\/td><\/tr>'}<\/tbody>\n        <\/table>\n      `;\n    }\n\n    function togglePanel(id) {\n      document.getElementById(id).classList.toggle(\"open\");\n      updateStats();\n    }\n\n    function restartAdventure() {\n      const keepStats = confirm(\"Start a new adventure but keep points and badges? Press OK to keep, Cancel to fully reset.\");\n\n      if (keepStats) {\n        state.currentScene = STORY.start;\n      } else {\n        localStorage.removeItem(\"adventureReaderState\");\n        state = {\n          currentScene: STORY.start,\n          score: 0,\n          scenesRead: 0,\n          choicesMade: 0,\n          helpUsed: 0,\n          endings: 0,\n          sceneHistory: []\n        };\n      }\n\n      saveState();\n      renderScene();\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        setTimeout(() => piece.remove(), 2000);\n      }\n    }\n\n    function loadVoices() {\n      voices = window.speechSynthesis.getVoices();\n      const select = document.getElementById(\"voiceSelect\");\n      const savedVoice = localStorage.getItem(\"adventureReaderVoice\");\n\n      if (!voices.length) {\n        select.innerHTML = `<option>No voices loaded yet<\/option>`;\n        return;\n      }\n\n      select.innerHTML = voices\n        .filter(v => v.lang && v.lang.toLowerCase().startsWith(\"en\"))\n        .map(v => `<option value=\"${v.name}\">${v.name} (${v.lang})<\/option>`)\n        .join(\"\");\n\n      const preferred =\n        savedVoice ||\n        (voices.find(v => v.name.includes(\"Natural\")) || {}).name ||\n        (voices.find(v => v.name.includes(\"Sonia\")) || {}).name ||\n        (voices.find(v => v.name.includes(\"Jenny\")) || {}).name ||\n        \"\";\n\n      if (preferred) select.value = preferred;\n    }\n\n    function saveVoiceChoice() {\n      const select = document.getElementById(\"voiceSelect\");\n      localStorage.setItem(\"adventureReaderVoice\", select.value);\n    }\n\n    window.speechSynthesis.onvoiceschanged = loadVoices;\n    setTimeout(loadVoices, 100);\n    setTimeout(loadVoices, 500);\n    setTimeout(loadVoices, 1200);\n\n    renderScene();\n  <\/script>\n<\/body>\n<\/html>\n\n","protected":false},"excerpt":{"rendered":"<p>Adventure Reader \ud83e\udded Adventure Reader \ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67 Parent \ud83d\udd04 New Adventure \ud83d\udde3\ufe0f Voice Loading voices&#8230; Points 0 Scenes Read 0 Choices Made 0 Help Used 0 \ud83c\udf32 The Forest Path You see a little path. \ud83d\udd0a Read it \u2705 I read it \ud83d\udca1 Help What do you do? Parent Stats This version does not speak automatically. &#8230; <a title=\"Adventure Reader\" class=\"read-more\" href=\"https:\/\/seanholden.xyz\/?page_id=457\" aria-label=\"More on Adventure Reader\">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-457","page","type-page","status-publish"],"_links":{"self":[{"href":"https:\/\/seanholden.xyz\/index.php?rest_route=\/wp\/v2\/pages\/457","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=457"}],"version-history":[{"count":1,"href":"https:\/\/seanholden.xyz\/index.php?rest_route=\/wp\/v2\/pages\/457\/revisions"}],"predecessor-version":[{"id":458,"href":"https:\/\/seanholden.xyz\/index.php?rest_route=\/wp\/v2\/pages\/457\/revisions\/458"}],"wp:attachment":[{"href":"https:\/\/seanholden.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=457"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}