{"id":12978,"date":"2026-02-01T19:51:30","date_gmt":"2026-02-01T19:51:30","guid":{"rendered":"https:\/\/www.llanedeyrnprimary.co.uk\/?page_id=12978"},"modified":"2026-02-01T22:00:15","modified_gmt":"2026-02-01T22:00:15","slug":"rocket-words","status":"publish","type":"page","link":"https:\/\/www.llanedeyrnprimary.co.uk\/index.php\/rocket-words\/","title":{"rendered":"Rocket Words"},"content":{"rendered":"<div class=\"fusion-fullwidth fullwidth-box fusion-builder-row-1 fusion-flex-container has-pattern-background has-mask-background nonhundred-percent-fullwidth non-hundred-percent-height-scrolling\" style=\"--awb-border-radius-top-left:0px;--awb-border-radius-top-right:0px;--awb-border-radius-bottom-right:0px;--awb-border-radius-bottom-left:0px;--awb-flex-wrap:wrap;\" ><div class=\"fusion-builder-row fusion-row fusion-flex-align-items-flex-start fusion-flex-content-wrap\" style=\"max-width:1248px;margin-left: calc(-4% \/ 2 );margin-right: calc(-4% \/ 2 );\"><div class=\"fusion-layout-column fusion_builder_column fusion-builder-column-0 fusion_builder_column_1_1 1_1 fusion-flex-column\" style=\"--awb-bg-size:cover;--awb-width-large:100%;--awb-margin-top-large:0px;--awb-spacing-right-large:1.92%;--awb-margin-bottom-large:20px;--awb-spacing-left-large:1.92%;--awb-width-medium:100%;--awb-order-medium:0;--awb-spacing-right-medium:1.92%;--awb-spacing-left-medium:1.92%;--awb-width-small:100%;--awb-order-small:0;--awb-spacing-right-small:1.92%;--awb-spacing-left-small:1.92%;\"><div class=\"fusion-column-wrapper fusion-column-has-shadow fusion-flex-justify-content-flex-start fusion-content-layout-column\"><!-- Rocket Words Embed (Avada-friendly) -->\n<div id=\"rocketWordsApp\"><\/div>\n\n<style>\n\/* Rocket Words (scoped) *\/\n#rocketWordsApp { \n  --navy: #002060;\n  --purple: #8b296f;\n  --bg: transparent;\n  --card: #ffffff;\n  --text: #101828;\n  --muted: #667085;\n  --border: #e4e7ec;\n  --shadow: 0 8px 26px rgba(16, 24, 40, 0.10);\n  --radius: 18px;\n\n  font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;\n  color: var(--text);\n}\n#rocketWordsApp, #rocketWordsApp * { box-sizing: border-box; }\n\n#rocketWordsApp .rw-wrap { max-width: 980px; margin: 0 auto; padding: 0; }\n#rocketWordsApp .rw-header { display:flex; align-items:center; justify-content:space-between; gap:12px; margin: 0 0 14px; }\n#rocketWordsApp .rw-brand { display:flex; align-items:center; gap:10px; }\n#rocketWordsApp .rw-logo { width:42px; height:42px; border-radius:12px; background: linear-gradient(135deg, var(--navy), var(--purple)); box-shadow: var(--shadow); display:grid; place-items:center; color:#fff; font-size:22px; }\n#rocketWordsApp .rw-title { font-size: 34px; margin: 0; letter-spacing: 0.2px; line-height: 1.1; }\n#rocketWordsApp .rw-sub { margin: 4px 0 0; color: var(--muted); font-size: 13px; }\n\n#rocketWordsApp .rw-grid { display:grid; grid-template-columns: 1.1fr 1fr; gap:14px; }\n@media (max-width: 860px) { #rocketWordsApp .rw-grid { grid-template-columns: 1fr; } }\n\n#rocketWordsApp .rw-card { background: var(--card); border: 1px solid var(--border); box-shadow: var(--shadow); border-radius: var(--radius); padding: 16px; }\n#rocketWordsApp .rw-controls { display:grid; grid-template-columns: 1fr 1fr; gap:12px; align-items:end; }\n@media (max-width: 520px) { #rocketWordsApp .rw-controls { grid-template-columns: 1fr; } }\n\n#rocketWordsApp label { display:block; font-size: 13px; color: var(--muted); margin-bottom: 6px; }\n#rocketWordsApp select, \n#rocketWordsApp input[type=\"text\"], \n#rocketWordsApp input[type=\"range\"] {\n  width: 100%;\n  padding: 12px 12px;\n  border-radius: 14px;\n  border: 1px solid var(--border);\n  background: #fff;\n  font-size: 16px;\n  outline: none;\n}\n#rocketWordsApp input[type=\"range\"] { padding: 8px 0; }\n\n#rocketWordsApp .rw-row { display:flex; gap:10px; flex-wrap:wrap; align-items:center; }\n#rocketWordsApp .rw-btn {\n  appearance:none;\n  border: 1px solid transparent;\n  background: linear-gradient(135deg, var(--navy), var(--purple));\n  color:#fff;\n  padding: 12px 14px;\n  border-radius: 14px;\n  font-weight: 650;\n  cursor:pointer;\n  box-shadow: 0 8px 18px rgba(0,0,0,0.10);\n  font-size: 14px;\n}\n#rocketWordsApp .rw-btn:disabled { opacity: 0.55; cursor: not-allowed; }\n#rocketWordsApp .rw-btn.secondary { background:#fff; color: var(--text); border-color: var(--border); box-shadow:none; }\n\n#rocketWordsApp .rw-toggles { display:grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 8px; }\n@media (max-width: 520px) { #rocketWordsApp .rw-toggles { grid-template-columns: 1fr; } }\n\n#rocketWordsApp .rw-toggle { display:flex; gap:10px; align-items:center; border:1px solid var(--border); border-radius:14px; padding: 10px 12px; background:#fff; }\n#rocketWordsApp .rw-toggle input { width: 18px; height: 18px; }\n#rocketWordsApp .rw-toggle span { font-size: 13px; color: var(--text); }\n\n#rocketWordsApp .rw-status { display:flex; justify-content:space-between; align-items:center; gap: 10px; flex-wrap:wrap; margin-bottom:10px; }\n#rocketWordsApp .rw-pill { border:1px solid var(--border); background:#fff; border-radius:999px; padding: 6px 10px; font-size: 13px; color: var(--muted); }\n\n#rocketWordsApp .rw-steps { display:flex; gap:8px; flex-wrap:wrap; }\n#rocketWordsApp .rw-step { border-radius:999px; padding: 6px 10px; font-size: 12px; border:1px solid var(--border); color: var(--muted); background:#fff; }\n#rocketWordsApp .rw-step.active { border-color: rgba(139,41,111,0.35); background: rgba(139,41,111,0.08); color: var(--text); font-weight: 650; }\n\n#rocketWordsApp .rw-wordBox { border-radius: 18px; border: 2px dashed rgba(0,32,96,0.25); background: linear-gradient(180deg, rgba(0,32,96,0.05), rgba(139,41,111,0.05)); padding: 18px 14px; margin: 10px 0 12px; text-align:center; min-height: 94px; display:grid; place-items:center; }\n#rocketWordsApp .rw-word { font-size: 44px; font-weight: 800; letter-spacing: 0.4px; color: var(--navy); }\n#rocketWordsApp .rw-hidden { font-size: 16px; color: var(--muted); }\n#rocketWordsApp .rw-prompt { margin: 8px 0 10px; color: var(--muted); font-size: 14px; }\n\n#rocketWordsApp .rw-feedback { margin-top: 10px; padding: 10px 12px; border-radius: 14px; border: 1px solid var(--border); background: #fff; }\n#rocketWordsApp .rw-feedback.good { border-color: rgba(18,183,106,0.35); background: rgba(18,183,106,0.08); }\n#rocketWordsApp .rw-feedback.bad { border-color: rgba(240,68,56,0.35); background: rgba(240,68,56,0.08); }\n#rocketWordsApp .rw-feedbackTitle { font-weight: 750; margin: 0 0 4px; }\n#rocketWordsApp .rw-feedbackText { margin: 0; color: var(--text); }\n\n#rocketWordsApp table { width: 100%; border-collapse: collapse; margin-top: 10px; font-size: 14px; }\n#rocketWordsApp th, #rocketWordsApp td { border-top: 1px solid var(--border); padding: 10px 8px; text-align:left; vertical-align: top; }\n#rocketWordsApp th { color: var(--muted); font-weight: 650; font-size: 13px; }\n#rocketWordsApp .rw-small { font-size: 12px; color: var(--muted); }\n#rocketWordsApp .rw-right { text-align:right; }\n#rocketWordsApp .rw-kbadge { display:inline-block; padding: 3px 8px; border-radius: 999px; border: 1px solid var(--border); font-size: 12px; color: var(--muted); background: #fff; margin-left: 6px; }\n\n\/* Extra defence against theme styling *\/\n#rocketWordsApp button, #rocketWordsApp input, #rocketWordsApp select {\n  font-family: inherit !important;\n  text-transform: none !important;\n  letter-spacing: normal !important;\n}\n<\/style>\n\n<script>\n(() => {\n  const ROCKET_WORDS_RAW = {\n  \"1\": [\n    \"a\",\n    \"is\",\n    \"dad\",\n    \"I\",\n    \"at\",\n    \"an\",\n    \"it\",\n    \"had\",\n    \"his\",\n    \"mum\"\n  ],\n  \"2\": [\n    \"the\",\n    \"as\",\n    \"of\",\n    \"not\",\n    \"to\",\n    \"and\",\n    \"got\",\n    \"him\",\n    \"can\",\n    \"its\"\n  ],\n  \"3\": [\n    \"off\",\n    \"if\",\n    \"on\",\n    \"get\",\n    \"up\",\n    \"no\",\n    \"in\",\n    \"go\",\n    \"big\",\n    \"am\"\n  ],\n  \"4\": [\n    \"will\",\n    \"see\",\n    \"he\",\n    \"him\",\n    \"she\",\n    \"we\",\n    \"me\",\n    \"are\",\n    \"look\",\n    \"be\"\n  ],\n  \"5\": [\n    \"you\",\n    \"that\",\n    \"for\",\n    \"they\",\n    \"this\",\n    \"now\",\n    \"all\",\n    \"then\",\n    \"down\",\n    \"them\"\n  ],\n  \"6\": [\n    \"my\",\n    \"into\",\n    \"with\",\n    \"was\",\n    \"her\",\n    \"went\",\n    \"said\",\n    \"back\",\n    \"come\",\n    \"out\"\n  ],\n  \"7\": [\n    \"too\",\n    \"its\",\n    \"have\",\n    \"were\",\n    \"what\",\n    \"from\",\n    \"like\",\n    \"yes\",\n    \"but\",\n    \"one\"\n  ],\n  \"8\": [\n    \"so\",\n    \"little\",\n    \"there\",\n    \"which\",\n    \"just\",\n    \"do\",\n    \"some\",\n    \"help\",\n    \"children\",\n    \"by\"\n  ],\n  \"9\": [\n    \"here\",\n    \"old\",\n    \"could\",\n    \"your\",\n    \"saw\",\n    \"Mr\",\n    \"Mrs\",\n    \"I'm\",\n    \"day\",\n    \"play\"\n  ],\n  \"10\": [\n    \"don't\",\n    \"about\",\n    \"peopl\",\n    \"very\",\n    \"made\",\n    \"put\",\n    \"looke\",\n    \"time\",\n    \"came\",\n    \"oh\"\n  ],\n  \"11\": [\n    \"called\",\n    \"house\",\n    \"make\",\n    \"their\",\n    \"asked\",\n    \"going\",\n    \"away\",\n    \"man\",\n    \"ran\",\n    \"did\"\n  ],\n  \"12\": [\n    \"next\",\n    \"has\",\n    \"over\",\n    \"where\",\n    \"jump\",\n    \"our\",\n    \"home\",\n    \"who\",\n    \"boy\",\n    \"took\"\n  ],\n  \"13\": [\n    \"live\",\n    \"call\",\n    \"girl\",\n    \"good\",\n    \"can't\",\n    \"water\",\n    \"how\",\n    \"would\",\n    \"bear\",\n    \"things\"\n  ],\n  \"14\": [\n    \"every\",\n    \"find\",\n    \"magic\",\n    \"fox\",\n    \"must\",\n    \"these\",\n    \"much\",\n    \"that's\",\n    \"something\",\n    \"small\"\n  ],\n  \"15\": [\n    \"or\",\n    \"new\",\n    \"take\",\n    \"more\",\n    \"shouted\",\n    \"throug\",\n    \"began\",\n    \"first\",\n    \"car\",\n    \"baby\"\n  ],\n  \"16\": [\n    \"good\",\n    \"didn't\",\n    \"again\",\n    \"after\",\n    \"two\",\n    \"thought\",\n    \"I'll\",\n    \"us\",\n    \"door\",\n    \"work\"\n  ],\n  \"17\": [\n    \"soon\",\n    \"couldn't\",\n    \"want\",\n    \"school\",\n    \"round\",\n    \"other\",\n    \"been\",\n    \"right\",\n    \"animal\",\n    \"lots\"\n  ],\n  \"18\": [\n    \"wante\",\n    \"gave\",\n    \"still\",\n    \"night\",\n    \"three\",\n    \"think\",\n    \"know\",\n    \"long\",\n    \"eat\",\n    \"well\"\n  ],\n  \"19\": [\n    \"tree\",\n    \"food\",\n    \"stop\",\n    \"sea\",\n    \"never\",\n    \"need\",\n    \"mous\",\n    \"found\",\n    \"ten\",\n    \"head\"\n  ],\n  \"20\": [\n    \"king\",\n    \"garden\",\n    \"let's\",\n    \"great\",\n    \"last\",\n    \"before\",\n    \"six\",\n    \"each\",\n    \"any\",\n    \"fell\"\n  ],\n  \"21\": [\n    \"better\",\n    \"there's\",\n    \"hard\",\n    \"eggs\",\n    \"ever\",\n    \"lived\",\n    \"white\",\n    \"giant\",\n    \"dragon\",\n    \"town\"\n  ],\n  \"22\": [\n    \"fast\",\n    \"why\",\n    \"jumpe\",\n    \"place\",\n    \"sleep\",\n    \"book\",\n    \"under\",\n    \"friends\",\n    \"looking\",\n    \"seven\"\n  ],\n  \"23\": [\n    \"once\",\n    \"upon\",\n    \"miss\",\n    \"birds\",\n    \"coming\",\n    \"pulled\",\n    \"I've\",\n    \"only\",\n    \"suddenly\",\n    \"cried\"\n  ],\n  \"24\": [\n    \"because\",\n    \"clothes\",\n    \"mothe\",\n    \"which\",\n    \"tea\",\n    \"box\",\n    \"end\",\n    \"really\",\n    \"sun\",\n    \"please\"\n  ],\n  \"25\": [\n    \"most\",\n    \"duck\",\n    \"he's\",\n    \"use\",\n    \"we're\",\n    \"around\",\n    \"many\",\n    \"told\",\n    \"keep\",\n    \"even\"\n  ],\n  \"26\": [\n    \"tell\",\n    \"nine\",\n    \"inside\",\n    \"snow\",\n    \"dark\",\n    \"than\",\n    \"five\",\n    \"wind\",\n    \"cold\",\n    \"horse\"\n  ],\n  \"27\": [\n    \"river\",\n    \"along\",\n    \"fly\",\n    \"everyone\",\n    \"laugh\",\n    \"another\",\n    \"key\",\n    \"boat\",\n    \"eight\",\n    \"different\"\n  ],\n  \"28\": [\n    \"air\",\n    \"eyes\",\n    \"best\",\n    \"gone\",\n    \"wish\",\n    \"stopped\",\n    \"park\",\n    \"rabbit\",\n    \"liked\",\n    \"plants\"\n  ],\n  \"29\": [\n    \"half\",\n    \"name\",\n    \"brother\",\n    \"sister\",\n    \"laughed\",\n    \"someon\",\n    \"father\",\n    \"four\",\n    \"across\",\n    \"floppy\"\n  ],\n  \"30\": [\n    \"queen\",\n    \"mornin\",\n    \"windo\",\n    \"Sunday\",\n    \"Saturday\",\n    \"Monday\",\n    \"Tuesday\",\n    \"Wednesda\",\n    \"Thursday\",\n    \"Friday\"\n  ]\n};\n  const SPELLING_FIXES = {\n  \"peopl\": \"people\",\n  \"looke\": \"looked\",\n  \"throug\": \"through\",\n  \"wante\": \"wanted\",\n  \"mous\": \"mouse\",\n  \"jumpe\": \"jumped\",\n  \"mothe\": \"mother\",\n  \"someon\": \"someone\",\n  \"mornin\": \"morning\",\n  \"windo\": \"window\",\n  \"wednesda\": \"wednesday\"\n};\n\n  const root = document.getElementById(\"rocketWordsApp\");\n  if (!root) return;\n\n  root.innerHTML = `<div class=\"rw-wrap\">\n    <div class=\"rw-header\">\n      <div class=\"rw-brand\">\n        <div class=\"rw-logo\" aria-hidden=\"true\">\ud83d\ude80<\/div>\n        <div>\n          <div class=\"rw-title\">Rocket Words<\/div>\n          <div class=\"rw-sub\">Look, copy, cover and write<\/div>\n        <\/div>\n      <\/div>\n      <div class=\"rw-pill\" id=\"rwSupportStatus\">Speech: checking\u2026<\/div>\n    <\/div>\n\n    <div class=\"rw-grid\">\n      <section class=\"rw-card\" aria-label=\"Setup\">\n        <div class=\"rw-controls\">\n          <div>\n            <label for=\"rwSetSelect\">Choose a set<\/label>\n            <select id=\"rwSetSelect\" aria-label=\"Choose a set\"><\/select>\n          <\/div>\n\n          <div>\n            <label for=\"rwPracticeMode\">Mode<\/label>\n            <select id=\"rwPracticeMode\" aria-label=\"Choose a mode\">\n              <option value=\"lccw\" selected>Look, copy, cover and write<\/option>\n              <option value=\"test\">Test only (no copying)<\/option>\n            <\/select>\n          <\/div>\n\n          <div>\n            <label for=\"rwVoiceRate\">Voice speed <span class=\"rw-kbadge\" id=\"rwRateLabel\">1.0<\/span><\/label>\n            <input id=\"rwVoiceRate\" type=\"range\" min=\"0.7\" max=\"1.2\" value=\"1.0\" step=\"0.05\" \/>\n          <\/div>\n\n          <div class=\"rw-row\">\n            <button class=\"rw-btn\" id=\"rwStartBtn\">Start<\/button>\n            <button class=\"rw-btn secondary\" id=\"rwResetBtn\" disabled>Reset<\/button>\n          <\/div>\n        <\/div>\n\n        <div class=\"rw-toggles\" aria-label=\"Options\">\n          <label class=\"rw-toggle\">\n            <input type=\"checkbox\" id=\"rwAutoSpeak\" checked \/>\n            <span>Say the word when it appears<\/span>\n          <\/label>\n\n          <label class=\"rw-toggle\">\n            <input type=\"checkbox\" id=\"rwIgnoreApostrophes\" checked \/>\n            <span>Ignore apostrophes when marking (recommended)<\/span>\n          <\/label>\n\n          <label class=\"rw-toggle\">\n            <input type=\"checkbox\" id=\"rwShuffleWords\" \/>\n            <span>Shuffle the words<\/span>\n          <\/label>\n\n          <label class=\"rw-toggle\">\n            <input type=\"checkbox\" id=\"rwUseFixes\" checked \/>\n            <span>Use corrected spellings (recommended)<\/span>\n          <\/label>\n        <\/div>\n\n        <p class=\"rw-small\" style=\"margin: 10px 2px 0;\">\n          Tip: Some browsers only allow speech after you press a button. If speech is silent, press \u201cSay word\u201d.\n        <\/p>\n      <\/section>\n\n      <section class=\"rw-card\" aria-label=\"Game\">\n        <div class=\"rw-status\">\n          <div class=\"rw-steps\" aria-label=\"Steps\">\n            <span class=\"rw-step\" id=\"rwLook\">Look<\/span>\n            <span class=\"rw-step\" id=\"rwCopy\">Copy<\/span>\n            <span class=\"rw-step\" id=\"rwCover\">Cover<\/span>\n            <span class=\"rw-step\" id=\"rwWrite\">Write<\/span>\n          <\/div>\n          <div class=\"rw-pill\" id=\"rwProgressPill\">Not started<\/div>\n        <\/div>\n\n        <div class=\"rw-wordBox\" role=\"region\" aria-label=\"Word display\">\n          <div id=\"rwWordDisplay\" class=\"rw-hidden\">Choose a set and press Start.<\/div>\n        <\/div>\n\n        <p class=\"rw-prompt\" id=\"rwPromptText\"><\/p>\n\n        <div class=\"rw-row\">\n          <input type=\"text\" id=\"rwAnswerInput\" placeholder=\"Type here\u2026\" autocomplete=\"off\" autocapitalize=\"none\" spellcheck=\"false\" disabled \/>\n          <button class=\"rw-btn\" id=\"rwSayBtn\" disabled>Say word<\/button>\n          <button class=\"rw-btn\" id=\"rwCheckBtn\" disabled>Check<\/button>\n        <\/div>\n\n        <div id=\"rwFeedback\" class=\"rw-feedback\" style=\"display:none;\"><\/div>\n\n        <div id=\"rwResultsArea\" style=\"display:none; margin-top: 12px;\"><\/div>\n      <\/section>\n    <\/div>\n  <\/div>`;\n\n  const $ = (id) => document.getElementById(id);\n\n  function applyFix(word) {\n    const key = String(word).toLowerCase();\n    if (!SPELLING_FIXES[key]) return word;\n    const fixed = SPELLING_FIXES[key];\n    if (\/^[A-Z]\/.test(word)) return fixed.charAt(0).toUpperCase() + fixed.slice(1);\n    return fixed;\n  }\n\n  function normaliseForMarking(text, options) {\n    const ignoreApos = !!options.ignoreApostrophes;\n    return String(text)\n      .trim()\n      .toLowerCase()\n      .replace(\/[\u2019\u2018\u201b]\/g, \"'\")\n      .replace(\/\\s+\/g, \" \")\n      .replace(ignoreApos ? \/'\/g : \/$^\/g, \"\");\n  }\n\n  function shuffle(arr) {\n    const a = arr.slice();\n    for (let i = a.length - 1; i > 0; i--) {\n      const j = Math.floor(Math.random() * (i + 1));\n      [a[i], a[j]] = [a[j], a[i]];\n    }\n    return a;\n  }\n\n  function refreshSpeechStatus() {\n    const supported = \"speechSynthesis\" in window && \"SpeechSynthesisUtterance\" in window;\n    $(\"rwSupportStatus\").textContent = supported ? \"Speech: available\" : \"Speech: not available\";\n  }\n\n  function pickGBVoice() {\n    try {\n      const voices = window.speechSynthesis.getVoices() || [];\n      if (!voices.length) return null;\n      const gb = voices.find(v => (v.lang || \"\").toLowerCase().startsWith(\"en-gb\"));\n      return gb || voices[0] || null;\n    } catch (e) {\n      return null;\n    }\n  }\n\n  function speakWord(word) {\n    if (!(\"speechSynthesis\" in window)) return;\n    const raw = String(word);\n    window.speechSynthesis.cancel();\n    const u = new SpeechSynthesisUtterance(raw);\n    u.lang = \"en-GB\";\n    u.rate = parseFloat($(\"rwVoiceRate\").value || \"1.0\");\n    const v = pickGBVoice();\n    if (v) u.voice = v;\n    window.speechSynthesis.speak(u);\n  }\n\n  window.addEventListener(\"load\", () => {\n    refreshSpeechStatus();\n    if (\"speechSynthesis\" in window) {\n      window.speechSynthesis.onvoiceschanged = () => refreshSpeechStatus();\n    }\n  });\n\n  const setSelect = $(\"rwSetSelect\");\n  const startBtn = $(\"rwStartBtn\");\n  const resetBtn = $(\"rwResetBtn\");\n  const checkBtn = $(\"rwCheckBtn\");\n  const sayBtn = $(\"rwSayBtn\");\n  const answerInput = $(\"rwAnswerInput\");\n  const progressPill = $(\"rwProgressPill\");\n  const wordDisplay = $(\"rwWordDisplay\");\n  const promptText = $(\"rwPromptText\");\n  const feedback = $(\"rwFeedback\");\n  const resultsArea = $(\"rwResultsArea\");\n\n  const sLook = $(\"rwLook\");\n  const sCopy = $(\"rwCopy\");\n  const sCover = $(\"rwCover\");\n  const sWrite = $(\"rwWrite\");\n\n  let game = null;\n\n  function setStepActive({ look=false, copy=false, cover=false, write=false }) {\n    sLook.classList.toggle(\"active\", look);\n    sCopy.classList.toggle(\"active\", copy);\n    sCover.classList.toggle(\"active\", cover);\n    sWrite.classList.toggle(\"active\", write);\n  }\n\n  function renderSetOptions() {\n    setSelect.innerHTML = \"\";\n    const keys = Object.keys(ROCKET_WORDS_RAW).map(n => parseInt(n,10)).sort((a,b)=>a-b);\n    for (const k of keys) {\n      const opt = document.createElement(\"option\");\n      opt.value = String(k);\n      opt.textContent = `Set ${k}`;\n      setSelect.appendChild(opt);\n    }\n  }\n\n  function showFeedback(type, title, text) {\n    feedback.style.display = \"block\";\n    feedback.className = \"rw-feedback \" + (type || \"\");\n    feedback.innerHTML = `\n      <p class=\"rw-feedbackTitle\">${title}<\/p>\n      <p class=\"rw-feedbackText\">${text}<\/p>\n    `;\n  }\n\n  function clearFeedback() {\n    feedback.style.display = \"none\";\n    feedback.className = \"rw-feedback\";\n    feedback.innerHTML = \"\";\n  }\n\n  function currentWord() {\n    const raw = game.words[game.index];\n    return game.useFixes ? applyFix(raw) : raw;\n  }\n\n  function displayCurrent() {\n    if (!game) return;\n\n    const idx = game.index;\n    const total = game.words.length;\n    progressPill.textContent = `Word ${idx + 1} of ${total}`;\n\n    const current = currentWord();\n    game.currentShown = current;\n\n    clearFeedback();\n    resultsArea.style.display = \"none\";\n    resultsArea.innerHTML = \"\";\n\n    answerInput.value = \"\";\n    answerInput.focus();\n\n    if ($(\"rwAutoSpeak\").checked) speakWord(current);\n\n    if (game.mode === \"test\") {\n      setStepActive({ cover: true, write: true });\n      wordDisplay.innerHTML = `<div class=\"rw-hidden\">Word hidden<\/div>`;\n      promptText.textContent = game.attempt === 1 ? \"Spell the word from memory.\" : \"Try again.\";\n      checkBtn.textContent = \"Check\";\n      game.phase = \"write\";\n      return;\n    }\n\n    if (game.phase === \"copy\") {\n      setStepActive({ look: true, copy: true });\n      wordDisplay.innerHTML = `<div class=\"rw-word\">${current}<\/div>`;\n      promptText.textContent = \"Copy the word. When it is correct, the word will disappear.\";\n      checkBtn.textContent = \"Check copy\";\n    } else {\n      setStepActive({ cover: true, write: true });\n      wordDisplay.innerHTML = `<div class=\"rw-hidden\">Word hidden<\/div>`;\n      promptText.textContent = game.attempt === 1 ? \"Now cover and write it without looking.\" : \"Try again.\";\n      checkBtn.textContent = \"Check spelling\";\n    }\n  }\n\n  function startGame() {\n    const setNum = parseInt(setSelect.value, 10);\n    const words = ROCKET_WORDS_RAW[String(setNum)] || [];\n    const shuffleOn = $(\"rwShuffleWords\").checked;\n\n    game = {\n      setNum,\n      mode: $(\"rwPracticeMode\").value,\n      words: shuffleOn ? shuffle(words) : words.slice(),\n      index: 0,\n      phase: $(\"rwPracticeMode\").value === \"test\" ? \"write\" : \"copy\",\n      attempt: 1,\n      pendingAttempts: [],\n      results: [],\n      useFixes: $(\"rwUseFixes\").checked,\n      currentShown: null,\n    };\n\n    resetBtn.disabled = false;\n    checkBtn.disabled = false;\n    sayBtn.disabled = false;\n    answerInput.disabled = false;\n\n    startBtn.disabled = true;\n    setSelect.disabled = true;\n    $(\"rwPracticeMode\").disabled = true;\n\n    displayCurrent();\n  }\n\n  function resetGame() {\n    game = null;\n    startBtn.disabled = false;\n    setSelect.disabled = false;\n    $(\"rwPracticeMode\").disabled = false;\n\n    resetBtn.disabled = true;\n    checkBtn.disabled = true;\n    sayBtn.disabled = true;\n    answerInput.disabled = true;\n\n    progressPill.textContent = \"Not started\";\n    wordDisplay.textContent = \"Choose a set and press Start.\";\n    promptText.textContent = \"\";\n    clearFeedback();\n    resultsArea.style.display = \"none\";\n    resultsArea.innerHTML = \"\";\n    setStepActive({});\n  }\n\n  function recordResult(word, attempts, correct) {\n    game.results.push({ word, attempts: attempts.slice(), correct, phase: \"write\" });\n  }\n\n  function finishGame() {\n    const memoryAttempts = game.results.filter(r => r.phase === \"write\");\n    const correct = memoryAttempts.filter(r => r.correct).length;\n    const total = memoryAttempts.length;\n    const percent = total ? Math.round((correct \/ total) * 100) : 0;\n\n    wordDisplay.innerHTML = `<div class=\"rw-word\">Score: ${correct} \/ ${total}<\/div>`;\n    promptText.textContent = percent === 100 ? \"Brilliant. You got them all right.\" : \"Good effort. Have another go at the tricky ones.\";\n\n    const rows = memoryAttempts.map(r => {\n      const badge = r.correct ? \"\u2705\" : \"\u274c\";\n      const a1 = (r.attempts[0] ?? \"\").trim();\n      const a2 = (r.attempts[1] ?? \"\").trim();\n      const show1 = a1 === \"\" ? \"<span class='rw-small'>(blank)<\/span>\" : a1;\n      const show2 = a2 === \"\" ? \"<span class='rw-small'>(blank)<\/span>\" : a2;\n      const attemptCell = r.attempts.length === 1\n        ? `<div>${show1}<\/div>`\n        : `<div><span class=\"rw-small\">1:<\/span> ${show1}<\/div><div><span class=\"rw-small\">2:<\/span> ${show2}<\/div>`;\n\n      return `\n        <tr>\n          <td>${badge}<\/td>\n          <td><strong>${r.word}<\/strong><\/td>\n          <td>${attemptCell}<\/td>\n          <td class=\"rw-right\">${r.correct ? \"Correct\" : \"Try again\"}<\/td>\n        <\/tr>\n      `;\n    }).join(\"\");\n\n    const missed = memoryAttempts.filter(r => !r.correct).map(r => r.word);\n\n    resultsArea.style.display = \"block\";\n    resultsArea.innerHTML = `\n      <div class=\"rw-row\" style=\"justify-content: space-between; margin-bottom: 8px;\">\n        <div><strong>Results<\/strong> <span class=\"rw-kbadge\">${percent}%<\/span><\/div>\n        <button class=\"rw-btn secondary\" id=\"rwRetryMissedBtn\" ${missed.length ? \"\" : \"disabled\"}>Practise missed words<\/button>\n      <\/div>\n\n      <table aria-label=\"Results table\">\n        <thead>\n          <tr>\n            <th style=\"width:44px;\"> <\/th>\n            <th>Word<\/th>\n            <th>Your spelling<\/th>\n            <th class=\"rw-right\"> <\/th>\n          <\/tr>\n        <\/thead>\n        <tbody>${rows}<\/tbody>\n      <\/table>\n\n      <p class=\"rw-small\" style=\"margin-top: 10px;\">\n        Marking is case-insensitive. Apostrophes are ignored when marking.\n      <\/p>\n    `;\n\n    const retryBtn = $(\"rwRetryMissedBtn\");\n    if (retryBtn) {\n      retryBtn.addEventListener(\"click\", () => {\n        if (!missed.length) return;\n        game.words = missed.slice();\n        game.index = 0;\n        game.phase = game.mode === \"test\" ? \"write\" : \"copy\";\n        game.attempt = 1;\n        game.pendingAttempts = [];\n        game.results = [];\n        displayCurrent();\n        clearFeedback();\n      });\n    }\n\n    checkBtn.disabled = true;\n    sayBtn.disabled = true;\n    answerInput.disabled = true;\n\n    progressPill.textContent = `Finished (Set ${game.setNum})`;\n    setStepActive({});\n  }\n\n  function checkAnswer() {\n    if (!game) return;\n\n    const correctWord = currentWord();\n\n    const typed = answerInput.value;\n    const normTyped = normaliseForMarking(typed, { ignoreApostrophes: $(\"rwIgnoreApostrophes\").checked });\n    const normCorrect = normaliseForMarking(correctWord, { ignoreApostrophes: $(\"rwIgnoreApostrophes\").checked });\n\n    if (game.mode === \"test\") {\n      const ok = normTyped === normCorrect && normCorrect.length > 0;\n      game.pendingAttempts.push(typed.trim());\n\n      if (ok) {\n        recordResult(correctWord, game.pendingAttempts, true);\n        showFeedback(\"good\", \"Correct\", \"Nice work.\");\n        game.index++;\n        game.attempt = 1;\n        game.pendingAttempts = [];\n        if (game.index >= game.words.length) finishGame();\n        else setTimeout(displayCurrent, 650);\n        return;\n      }\n\n      if (game.attempt === 1) {\n        game.attempt = 2;\n        showFeedback(\"bad\", \"Nearly\", \"Try once more.\");\n        answerInput.value = \"\";\n        answerInput.focus();\n        setTimeout(displayCurrent, 650);\n        return;\n      }\n\n      recordResult(correctWord, game.pendingAttempts, false);\n      showFeedback(\"bad\", \"Not quite\", `The correct spelling is: ${correctWord}`);\n      game.index++;\n      game.attempt = 1;\n      game.pendingAttempts = [];\n      if (game.index >= game.words.length) setTimeout(finishGame, 650);\n      else setTimeout(displayCurrent, 650);\n      return;\n    }\n\n    if (game.phase === \"copy\") {\n      if (normTyped !== normCorrect || normCorrect.length === 0) {\n        showFeedback(\"bad\", \"Keep going\", \"Copy the word exactly as you see it.\");\n        return;\n      }\n      showFeedback(\"good\", \"Great copying\", \"Now cover the word and spell it.\");\n      game.phase = \"write\";\n      game.attempt = 1;\n      game.pendingAttempts = [];\n      setTimeout(displayCurrent, 650);\n      return;\n    }\n\n    const ok = normTyped === normCorrect && normCorrect.length > 0;\n    game.pendingAttempts.push(typed.trim());\n\n    if (ok) {\n      recordResult(correctWord, game.pendingAttempts, true);\n      showFeedback(\"good\", \"Correct\", \"Well done.\");\n      game.index++;\n      game.phase = \"copy\";\n      game.attempt = 1;\n      game.pendingAttempts = [];\n      if (game.index >= game.words.length) setTimeout(finishGame, 650);\n      else setTimeout(displayCurrent, 650);\n      return;\n    }\n\n    if (game.attempt === 1) {\n      game.attempt = 2;\n      showFeedback(\"bad\", \"Nearly\", \"Try once more.\");\n      answerInput.value = \"\";\n      answerInput.focus();\n      setTimeout(displayCurrent, 650);\n      return;\n    }\n\n    recordResult(correctWord, game.pendingAttempts, false);\n    showFeedback(\"bad\", \"Not quite\", `The correct spelling is: ${correctWord}`);\n    game.index++;\n    game.phase = \"copy\";\n    game.attempt = 1;\n    game.pendingAttempts = [];\n    if (game.index >= game.words.length) setTimeout(finishGame, 650);\n    else setTimeout(displayCurrent, 650);\n  }\n\n  renderSetOptions();\n  resetGame();\n\n  $(\"rwVoiceRate\").addEventListener(\"input\", () => {\n    $(\"rwRateLabel\").textContent = String($(\"rwVoiceRate\").value);\n  });\n\n  startBtn.addEventListener(\"click\", startGame);\n  resetBtn.addEventListener(\"click\", resetGame);\n  checkBtn.addEventListener(\"click\", checkAnswer);\n\n  sayBtn.addEventListener(\"click\", () => {\n    if (!game) return;\n    speakWord(game.currentShown || \"\");\n  });\n\n  answerInput.addEventListener(\"keydown\", (e) => {\n    if (e.key === \"Enter\" && !checkBtn.disabled) {\n      e.preventDefault();\n      checkAnswer();\n    }\n  });\n})();\n<\/script>\n<\/div><\/div><\/div><\/div>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"100-width.php","meta":{"footnotes":""},"class_list":["post-12978","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/www.llanedeyrnprimary.co.uk\/index.php\/wp-json\/wp\/v2\/pages\/12978","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.llanedeyrnprimary.co.uk\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.llanedeyrnprimary.co.uk\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.llanedeyrnprimary.co.uk\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.llanedeyrnprimary.co.uk\/index.php\/wp-json\/wp\/v2\/comments?post=12978"}],"version-history":[{"count":7,"href":"https:\/\/www.llanedeyrnprimary.co.uk\/index.php\/wp-json\/wp\/v2\/pages\/12978\/revisions"}],"predecessor-version":[{"id":12987,"href":"https:\/\/www.llanedeyrnprimary.co.uk\/index.php\/wp-json\/wp\/v2\/pages\/12978\/revisions\/12987"}],"wp:attachment":[{"href":"https:\/\/www.llanedeyrnprimary.co.uk\/index.php\/wp-json\/wp\/v2\/media?parent=12978"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}