Skip to content

<!DOCTYPE html>

<html lang=”en”>

<head>

<meta charset=”UTF-8″>

<meta name=”viewport” content=”width=device-width, initial-scale=1.0″>

<title>Letter Adventure!</title>

<link href=”https://fonts.googleapis.com/css2?family=Fredoka+One&family=Nunito:wght@400;600;700;800&display=swap” rel=”stylesheet”>

<style>

  :root{–sky:#fff8e7;–primary:#ff6b35;–secondary:#4ecdc4;–accent:#ffe66d;–purple:#a855f7;–green:#22c55e;–dark:#2d2d2d;–card:#ffffff;}

  *{margin:0;padding:0;box-sizing:border-box;}

  body{font-family:’Nunito’,sans-serif;background:var(–sky);min-height:100vh;overflow-x:hidden;}

  body::before{content:”;position:fixed;inset:0;background-image:radial-gradient(circle,#ffcc0022 2px,transparent 2px),radial-gradient(circle,#ff6b3511 2px,transparent 2px);background-size:60px 60px,90px 90px;background-position:0 0,30px 30px;pointer-events:none;z-index:0;}

  .container{max-width:680px;margin:0 auto;padding:20px;position:relative;z-index:1;}

  /* SETUP */

  .setup-card{background:var(–card);border-radius:28px;padding:34px 30px;box-shadow:0 8px 32px rgba(0,0,0,0.08);border:3px solid #f3f4f6;}

  .setup-title{font-family:’Fredoka One’,cursive;font-size:2.1rem;color:var(–primary);text-align:center;margin-bottom:6px;}

  .setup-sub{text-align:center;color:#777;font-size:0.95rem;font-weight:600;margin-bottom:28px;line-height:1.6;}

  .section-heading{font-family:’Fredoka One’,cursive;font-size:1.1rem;color:var(–purple);margin:22px 0 12px;display:flex;align-items:center;gap:8px;}

  .section-heading::after{content:”;flex:1;height:2px;background:linear-gradient(90deg,#e2e8f0,transparent);}

  .voice-scroll{max-height:340px;overflow-y:auto;padding-right:4px;}

  .voice-scroll::-webkit-scrollbar{width:5px;}

  .voice-scroll::-webkit-scrollbar-track{background:#f1f5f9;border-radius:99px;}

  .voice-scroll::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:99px;}

  .voice-row{background:linear-gradient(135deg,#fff8e7,#fff3ec);border-radius:16px;border:2px solid #fde68a;padding:12px 16px;margin-bottom:8px;display:flex;align-items:center;gap:10px;flex-wrap:wrap;}

  .voice-label{font-weight:800;font-size:0.9rem;color:var(–dark);flex:1;min-width:150px;}

  .voice-label small{display:block;font-size:0.74rem;color:#d97706;font-weight:700;margin-top:2px;}

  .rbtn{font-family:’Fredoka One’,cursive;font-size:0.8rem;padding:6px 12px;border-radius:99px;border:none;cursor:pointer;transition:all 0.15s;box-shadow:0 3px 0 rgba(0,0,0,0.1);}

  .rbtn:hover{transform:translateY(-1px);}

  .rbtn.rstart{background:var(–primary);color:white;}

  .rbtn.rstop{background:#ef4444;color:white;animation:prec 0.7s infinite;}

  .rbtn.rplay{background:var(–secondary);color:white;}

  @keyframes prec{0%,100%{box-shadow:0 3px 0 rgba(239,68,68,0.3)}50%{box-shadow:0 3px 14px rgba(239,68,68,0.5)}}

  .vstatus{font-size:0.76rem;font-weight:700;padding:3px 9px;border-radius:99px;white-space:nowrap;}

  .vstatus.done{background:#dcfce7;color:var(–green);}

  .vstatus.miss{background:#fef3c7;color:#d97706;}

  .rec-progress{text-align:center;margin-top:12px;font-size:0.85rem;font-weight:700;color:#94a3b8;}

  .rec-progress span{color:var(–primary);}

  .setup-footer{text-align:center;margin-top:22px;display:flex;gap:12px;justify-content:center;flex-wrap:wrap;}

  .btn-skip{background:none;border:2px solid #e2e8f0;color:#94a3b8;font-family:’Fredoka One’,cursive;font-size:1rem;padding:10px 22px;border-radius:99px;cursor:pointer;}

  .btn-skip:hover{border-color:var(–secondary);color:var(–secondary);}

  /* GAME */

  header{text-align:center;padding:22px 0 10px;}

  header h1{font-family:’Fredoka One’,cursive;font-size:2.6rem;color:var(–primary);text-shadow:3px 3px 0 #ffd166,6px 6px 0 #ffb3c1;letter-spacing:1px;}

  header p{font-size:1rem;color:#666;margin-top:3px;font-weight:600;}

  .letter-progress{font-family:’Fredoka One’,cursive;font-size:1rem;color:var(–purple);margin-top:6px;}

  .speaking-badge{display:inline-flex;align-items:center;gap:6px;background:#fff3ec;border:2px solid var(–primary);border-radius:99px;padding:4px 14px;font-size:0.82rem;font-weight:700;color:var(–primary);margin:6px 0 3px;opacity:0;transition:opacity 0.3s;}

  .speaking-badge.show{opacity:1;}

  .dot{width:8px;height:8px;background:var(–primary);border-radius:50%;animation:prec 0.6s infinite;}

  .stars-row{display:flex;justify-content:center;gap:5px;margin-top:6px;flex-wrap:wrap;}

  .star{font-size:1.1rem;transition:transform 0.3s;}

  .star.earned{animation:pop 0.4s ease;}

  @keyframes pop{0%{transform:scale(1)}50%{transform:scale(1.5)}100%{transform:scale(1)}}

  .progress-bar{background:#e5e7eb;border-radius:99px;height:12px;margin:12px 0 20px;overflow:hidden;box-shadow:inset 0 2px 4px rgba(0,0,0,0.1);}

  .progress-fill{height:100%;background:linear-gradient(90deg,var(–secondary),var(–primary));border-radius:99px;transition:width 0.5s ease;}

  .stage-label{text-align:center;font-family:’Fredoka One’,cursive;font-size:1.15rem;color:var(–purple);margin-bottom:16px;letter-spacing:1px;}

  .game-card{background:var(–card);border-radius:28px;padding:30px 26px;box-shadow:0 8px 32px rgba(0,0,0,0.08);text-align:center;border:3px solid #f3f4f6;}

  .question-text{font-size:1.3rem;font-weight:800;color:var(–dark);margin-bottom:8px;}

  .big-letter{font-family:’Fredoka One’,cursive;font-size:5rem;color:var(–primary);text-shadow:4px 4px 0 var(–accent);margin:8px 0 22px;display:inline-block;animation:bounce 0.6s ease;cursor:pointer;}

  .big-letter:hover{transform:scale(1.08);}

  @keyframes bounce{0%,100%{transform:translateY(0)}40%{transform:translateY(-12px)}}

  .choices-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;max-width:360px;margin:0 auto;}

  .choice-btn{background:#f8fafc;border:3px solid #e2e8f0;border-radius:18px;font-family:’Fredoka One’,cursive;font-size:2.2rem;color:var(–dark);padding:16px 0;cursor:pointer;transition:all 0.15s;box-shadow:0 4px 0 #e2e8f0;}

  .choice-btn:hover{transform:translateY(-3px);box-shadow:0 7px 0 #e2e8f0;border-color:var(–secondary);background:#f0fffe;}

  .choice-btn.correct{background:#dcfce7;border-color:var(–green);box-shadow:0 4px 0 #86efac;color:var(–green);animation:wiggle 0.4s ease;}

  .choice-btn.wrong{background:#fee2e2;border-color:#f87171;box-shadow:0 4px 0 #fca5a5;color:#ef4444;animation:shake 0.4s ease;}

  @keyframes wiggle{0%,100%{transform:rotate(0)}25%{transform:rotate(-6deg)}75%{transform:rotate(6deg)}}

  @keyframes shake{0%,100%{transform:translateX(0)}25%{transform:translateX(-8px)}75%{transform:translateX(8px)}}

  .trace-instr{font-size:1.1rem;font-weight:700;color:#555;margin-bottom:14px;}

  .canvas-wrap{position:relative;display:inline-block;border-radius:20px;overflow:hidden;box-shadow:0 4px 20px rgba(0,0,0,0.1);border:3px dashed var(–secondary);background:#f0fffe;}

  #traceCanvas{display:block;cursor:crosshair;touch-action:none;}

  .trace-ghost{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-family:’Fredoka One’,cursive;font-size:11rem;color:rgba(78,205,196,0.18);pointer-events:none;user-select:none;line-height:1;}

  .trace-actions{display:flex;gap:12px;justify-content:center;margin-top:18px;flex-wrap:wrap;}

  .btn{font-family:’Fredoka One’,cursive;font-size:1.05rem;padding:11px 26px;border-radius:99px;border:none;cursor:pointer;transition:all 0.15s;box-shadow:0 4px 0 rgba(0,0,0,0.15);letter-spacing:0.5px;}

  .btn:hover{transform:translateY(-2px);box-shadow:0 6px 0 rgba(0,0,0,0.15);}

  .btn:active{transform:translateY(2px);box-shadow:0 2px 0 rgba(0,0,0,0.15);}

  .btn-primary{background:var(–primary);color:white;}

  .btn-clear{background:#f1f5f9;color:#64748b;}

  .word-instr{font-size:1.1rem;font-weight:700;color:#555;margin-bottom:20px;}

  .word-card{background:linear-gradient(135deg,#f0fffe,#fff8e7);border:3px solid #e2e8f0;border-radius:22px;padding:24px 20px;margin-bottom:16px;transition:all 0.3s;}

  .word-card.active{border-color:var(–secondary);box-shadow:0 0 0 4px rgba(78,205,196,0.2);}

  .word-card.solved{border-color:var(–green);background:linear-gradient(135deg,#dcfce7,#f0fffe);opacity:0.85;}

  .word-pic{font-size:3rem;margin-bottom:10px;display:block;animation:float 3s ease-in-out infinite;}

  @keyframes float{0%,100%{transform:translateY(0)}50%{transform:translateY(-6px)}}

  .word-display{display:flex;align-items:center;justify-content:center;gap:5px;margin-bottom:14px;flex-wrap:wrap;}

  .lbox{width:50px;height:58px;border-radius:13px;border:3px solid #d1d5db;background:white;font-family:’Fredoka One’,cursive;font-size:2rem;display:flex;align-items:center;justify-content:center;color:var(–dark);box-shadow:0 3px 0 #d1d5db;}

  .lbox.blank{border-color:var(–primary);background:#fff3ec;box-shadow:0 3px 0 #ffb38a;color:transparent;}

  .lbox.cfill{border-color:var(–green);background:#dcfce7;color:var(–green);box-shadow:0 3px 0 #86efac;}

  .lbox.wfill{border-color:#f87171;background:#fee2e2;color:#ef4444;animation:shake 0.3s ease;}

  .word-options{display:flex;gap:8px;justify-content:center;flex-wrap:wrap;}

  .lopt{width:48px;height:48px;border-radius:13px;border:3px solid #e2e8f0;background:white;font-family:’Fredoka One’,cursive;font-size:1.7rem;color:var(–dark);cursor:pointer;transition:all 0.15s;box-shadow:0 4px 0 #e2e8f0;display:flex;align-items:center;justify-content:center;}

  .lopt:hover{transform:translateY(-3px);border-color:var(–primary);background:#fff3ec;box-shadow:0 7px 0 #ffb38a;color:var(–primary);}

  .lopt.used{opacity:0.3;pointer-events:none;transform:none;}

  .celebration{position:fixed;inset:0;background:rgba(255,248,231,0.93);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:100;animation:fadeIn 0.4s ease;}

  @keyframes fadeIn{from{opacity:0;transform:scale(0.9)}to{opacity:1;transform:scale(1)}}

  .cel-emoji{font-size:5.5rem;margin-bottom:14px;animation:bounce 0.8s ease infinite;}

  .cel-title{font-family:’Fredoka One’,cursive;font-size:2.6rem;color:var(–primary);text-align:center;text-shadow:3px 3px 0 var(–accent);margin-bottom:8px;}

  .cel-sub{font-size:1.05rem;color:#777;font-weight:600;margin-bottom:28px;text-align:center;padding:0 20px;}

  .toast{position:fixed;bottom:28px;left:50%;transform:translateX(-50%) translateY(20px);background:#1e293b;color:white;padding:11px 26px;border-radius:99px;font-family:’Fredoka One’,cursive;font-size:1rem;opacity:0;transition:all 0.3s;z-index:200;pointer-events:none;}

  .toast.show{opacity:1;transform:translateX(-50%) translateY(0);}

  .hidden{display:none!important;}

  .confetti-container{position:fixed;inset:0;pointer-events:none;z-index:99;overflow:hidden;}

  .conf{position:absolute;width:11px;height:11px;border-radius:2px;animation:cfall linear forwards;}

  @keyframes cfall{0%{transform:translateY(-20px) rotate(0deg);opacity:1}100%{transform:translateY(110vh) rotate(720deg);opacity:0}}

  .settings-fab{position:fixed;bottom:22px;right:22px;z-index:50;background:var(–purple);border:none;border-radius:50%;width:50px;height:50px;font-size:1.2rem;cursor:pointer;box-shadow:0 4px 12px rgba(168,85,247,0.4);transition:all 0.2s;display:flex;align-items:center;justify-content:center;}

  .settings-fab:hover{transform:scale(1.1);}

  .mute-fab{position:fixed;bottom:22px;left:22px;z-index:50;background:var(–secondary);border:none;border-radius:50%;width:50px;height:50px;font-size:1.2rem;cursor:pointer;box-shadow:0 4px 12px rgba(78,205,196,0.4);transition:all 0.2s;display:flex;align-items:center;justify-content:center;}

  .mute-fab:hover{transform:scale(1.1);}

</style>

</head>

<body>

<!– SETUP –>

<div class=”container” id=”setupContainer”>

  <div class=”setup-card”>

    <div class=”setup-title”>🎮 Game Setup</div>

    <div class=”setup-sub”>Music is built in! 🎵 Just record these short phrases in your voice — one for each letter. You can record as many or as few as you like!</div>

    <div class=”section-heading”>🎙️ Your Voice — “This is [Letter]! Now trace the letter [Letter]!”</div>

    <div class=”voice-scroll” id=”voiceRowsContainer”></div>

    <div class=”rec-progress” id=”recProgress”>Recorded: <span id=”recCount”>0</span> / 26</div>

    <div class=”setup-footer”>

      <button class=”btn btn-primary” onclick=”startGame()”>▶️ Start the Game!</button>

      <button class=”btn-skip” onclick=”startGame()”>Skip (use computer voice)</button>

    </div>

  </div>

</div>

<!– GAME –>

<div class=”container hidden” id=”gameContainer”>

  <header>

    <h1>✏️ Letter Adventure!</h1>

    <p>Learn your letters A to Z! 🌟</p>

    <div class=”letter-progress” id=”letterProgress”>Letter 1 of 26</div>

    <div class=”speaking-badge” id=”speakingBadge”><span class=”dot”></span> Listen…</div>

    <div class=”stars-row” id=”starsRow”></div>

  </header>

  <div class=”progress-bar”><div class=”progress-fill” id=”progressFill” style=”width:0%”></div></div>

  <div id=”stage1″ class=”game-card”>

    <div class=”stage-label”>⭐ Stage 1 — Find the Letter!</div>

    <div class=”question-text”>Which letter is this? 👀</div>

    <div class=”big-letter” id=”targetLetter” onclick=”replayStageName()”>A</div>

    <div class=”choices-grid” id=”choicesGrid”></div>

  </div>

  <div id=”stage2″ class=”game-card hidden”>

    <div class=”stage-label”>✏️ Stage 2 — Trace the Letter!</div>

    <div class=”trace-instr”>Trace the letter with your finger or mouse! 🖐️</div>

    <div class=”canvas-wrap”>

      <canvas id=”traceCanvas” width=”300″ height=”220″></canvas>

      <div class=”trace-ghost” id=”traceGhost”>A</div>

    </div>

    <div class=”trace-actions”>

      <button class=”btn btn-clear” onclick=”clearCanvas()”>🗑️ Clear</button>

      <button class=”btn btn-primary” onclick=”doneTracing()”>Done! ✅</button>

    </div>

  </div>

  <div id=”stage3″ class=”game-card hidden”>

    <div class=”stage-label”>📝 Stage 3 — Fill in the Blank!</div>

    <div class=”word-instr”>Put the missing letter in each word! 🤔</div>

    <div id=”wordsList”></div>

  </div>

</div>

<button class=”settings-fab hidden” id=”settingsFab” title=”Back to setup” onclick=”goSetup()”>⚙️</button>

<button class=”mute-fab hidden” id=”muteFab” title=”Mute/Unmute music” onclick=”toggleMute()”>🔊</button>

<div class=”celebration hidden” id=”celebration”>

  <div class=”confetti-container” id=”confettiCont”></div>

  <div class=”cel-emoji” id=”celEmoji”>🎉</div>

  <div class=”cel-title” id=”celTitle”>Amazing!</div>

  <div class=”cel-sub” id=”celSub”>You did it!</div>

  <button class=”btn btn-primary” id=”celBtn” onclick=”nextStage()”>Next Stage ➡️</button>

</div>

<div class=”toast” id=”toast”></div>

<script>

// ═══════════════════════════════════════

// FULL A–Z WORD DATA

// ═══════════════════════════════════════

const WORDS = {

  A:[{word:’BAG’, blank:1,emoji:’👜‘},{word:’CAT’,blank:1,emoji:’🐱‘},{word:’HAT’,blank:1,emoji:’🎩‘},{word:’MAP’,blank:1,emoji:’🗺️‘},{word:’JAM’,blank:1,emoji:’🍯‘}],

  B:[{word:’BAT’,blank:0,emoji:’🏏‘},{word:’BUS’,blank:0,emoji:’🚌‘},{word:’BED’,blank:0,emoji:’🛏️‘},{word:’BOX’,blank:0,emoji:’📦‘},{word:’BEE’,blank:0,emoji:’🐝‘}],

  C:[{word:’CAP’,blank:0,emoji:’🧢‘},{word:’CUP’,blank:0,emoji:’☕‘},{word:’CAR’,blank:0,emoji:’🚗‘},{word:’COW’,blank:0,emoji:’🐄‘},{word:’CAN’,blank:0,emoji:’🥫‘}],

  D:[{word:’DOG’,blank:0,emoji:’🐶‘},{word:’DIG’,blank:0,emoji:’⛏️‘},{word:’DOT’,blank:0,emoji:’🔵‘},{word:’DAD’,blank:0,emoji:’👨‘},{word:’DEN’,blank:0,emoji:’🏠‘}],

  E:[{word:’EGG’,blank:0,emoji:’🥚‘},{word:’ELF’,blank:0,emoji:’🧝‘},{word:’EEL’,blank:0,emoji:’🐍‘},{word:’END’,blank:0,emoji:’🏁‘},{word:’EAR’,blank:0,emoji:’👂‘}],

  F:[{word:’FAN’,blank:0,emoji:’🌬️‘},{word:’FIG’,blank:0,emoji:’🍇‘},{word:’FOX’,blank:0,emoji:’🦊‘},{word:’FLY’,blank:0,emoji:’🪰‘},{word:’FIN’,blank:0,emoji:’🐟‘}],

  G:[{word:’GUM’,blank:0,emoji:’🍬‘},{word:’GAS’,blank:0,emoji:’⛽‘},{word:’GEM’,blank:0,emoji:’💎‘},{word:’GOT’,blank:0,emoji:’✅‘},{word:’GUN’,blank:0,emoji:’🔫‘}],

  H:[{word:’HAM’,blank:0,emoji:’🥩‘},{word:’HEN’,blank:0,emoji:’🐔‘},{word:’HIT’,blank:0,emoji:’💥‘},{word:’HOG’,blank:0,emoji:’🐷‘},{word:’HUG’,blank:0,emoji:’🤗‘}],

  I:[{word:’INN’,blank:0,emoji:’🏨‘},{word:’INK’,blank:0,emoji:’🖊️‘},{word:’ICE’,blank:0,emoji:’🧊‘},{word:’ILL’,blank:0,emoji:’🤒‘},{word:’IVY’,blank:0,emoji:’🌿‘}],

  J:[{word:’JAR’,blank:0,emoji:’🫙‘},{word:’JOG’,blank:0,emoji:’🏃‘},{word:’JET’,blank:0,emoji:’✈️‘},{word:’JOY’,blank:0,emoji:’😄‘},{word:’JAB’,blank:0,emoji:’💉‘}],

  K:[{word:’KEY’,blank:0,emoji:’🔑‘},{word:’KID’,blank:0,emoji:’👦‘},{word:’KIT’,blank:0,emoji:’🧰‘},{word:’KEG’,blank:0,emoji:’🛢️‘},{word:’KOI’,blank:0,emoji:’🐟‘}],

  L:[{word:’LEG’,blank:0,emoji:’🦵‘},{word:’LIP’,blank:0,emoji:’👄‘},{word:’LOG’,blank:0,emoji:’🪵‘},{word:’LAP’,blank:0,emoji:’🪑‘},{word:’LID’,blank:0,emoji:’🫙‘}],

  M:[{word:’MOP’,blank:0,emoji:’🧹‘},{word:’MUD’,blank:0,emoji:’💧‘},{word:’MAN’,blank:0,emoji:’👨‘},{word:’MIX’,blank:0,emoji:’🥣‘},{word:’MOO’,blank:0,emoji:’🐄‘}],

  N:[{word:’NET’,blank:0,emoji:’🕸️‘},{word:’NUT’,blank:0,emoji:’🥜‘},{word:’NAP’,blank:0,emoji:’😴‘},{word:’NOD’,blank:0,emoji:’😌‘},{word:’NIP’,blank:0,emoji:’🤏‘}],

  O:[{word:’OAK’,blank:0,emoji:’🌳‘},{word:’OWL’,blank:0,emoji:’🦉‘},{word:’OAR’,blank:0,emoji:’🚣‘},{word:’ODD’,blank:0,emoji:’❓‘},{word:’ORE’,blank:0,emoji:’🪨‘}],

  P:[{word:’PIG’,blank:0,emoji:’🐷‘},{word:’PAN’,blank:0,emoji:’🍳‘},{word:’PET’,blank:0,emoji:’🐾‘},{word:’POT’,blank:0,emoji:’🪴‘},{word:’PEA’,blank:0,emoji:’🫛‘}],

  Q:[{word:’QUA’,blank:0,emoji:’🦆‘},{word:’QIT’,blank:0,emoji:’🚪‘},{word:’QOP’,blank:0,emoji:’🌊‘},{word:’QEF’,blank:0,emoji:’⭐‘},{word:’QUB’,blank:0,emoji:’🎲‘}],

  R:[{word:’RAT’,blank:0,emoji:’🐭‘},{word:’RUG’,blank:0,emoji:’🏠‘},{word:’ROD’,blank:0,emoji:’🎣‘},{word:’RIB’,blank:0,emoji:’🥩‘},{word:’RUN’,blank:0,emoji:’🏃‘}],

  S:[{word:’SUN’,blank:0,emoji:’☀️‘},{word:’SAP’,blank:0,emoji:’🌿‘},{word:’SIT’,blank:0,emoji:’🪑‘},{word:’SOB’,blank:0,emoji:’😢‘},{word:’SIP’,blank:0,emoji:’🥤‘}],

  T:[{word:’TAP’,blank:0,emoji:’🚿‘},{word:’TIN’,blank:0,emoji:’🥫‘},{word:’TOY’,blank:0,emoji:’🧸‘},{word:’TUB’,blank:0,emoji:’🛁‘},{word:’TAN’,blank:0,emoji:’🌞‘}],

  U:[{word:’UMP’,blank:0,emoji:’⚾‘},{word:’URN’,blank:0,emoji:’🏺‘},{word:’USE’,blank:0,emoji:’🔧‘},{word:’UDO’,blank:0,emoji:’🌱‘},{word:’UTE’,blank:0,emoji:’🚙‘}],

  V:[{word:’VAN’,blank:0,emoji:’🚐‘},{word:’VET’,blank:0,emoji:’🩺‘},{word:’VAT’,blank:0,emoji:’🛢️‘},{word:’VIM’,blank:0,emoji:’💪‘},{word:’VOW’,blank:0,emoji:’🤝‘}],

  W:[{word:’WEB’,blank:0,emoji:’🕸️‘},{word:’WIG’,blank:0,emoji:’💇‘},{word:’WAX’,blank:0,emoji:’🕯️‘},{word:’WIN’,blank:0,emoji:’🏆‘},{word:’WOK’,blank:0,emoji:’🍳‘}],

  X:[{word:’XIS’,blank:0,emoji:’🔡‘},{word:’XEN’,blank:0,emoji:’🌐‘},{word:’XIT’,blank:0,emoji:’🚪‘},{word:’XAP’,blank:0,emoji:’✨‘},{word:’XIB’,blank:0,emoji:’🎯‘}],

  Y:[{word:’YAK’,blank:0,emoji:’🦬‘},{word:’YAM’,blank:0,emoji:’🍠‘},{word:’YET’,blank:0,emoji:’⏳‘},{word:’YEW’,blank:0,emoji:’🌲‘},{word:’YIP’,blank:0,emoji:’🐶‘}],

  Z:[{word:’ZAP’,blank:0,emoji:’⚡‘},{word:’ZEN’,blank:0,emoji:’🧘‘},{word:’ZIP’,blank:0,emoji:’🤐‘},{word:’ZIT’,blank:0,emoji:’😬‘},{word:’ZOO’,blank:0,emoji:’🦁‘}],

};

const GAME_LETTERS=’ABCDEFGHIJKLMNOPQRSTUVWXYZ’.split(”);

const ALL=GAME_LETTERS;

// ═══════════════════════════════════════

// VOICE RECORDING — 26 phrases

// ═══════════════════════════════════════

const voiceRecs={};

let activeRecorder=null;

let recDoneCount=0;

function buildVoiceRows(){

  const cont=document.getElementById(‘voiceRowsContainer’);

  cont.innerHTML=”;

  GAME_LETTERS.forEach(letter=>{

    const id=’found_’+letter;

    const row=document.createElement(‘div’);

    row.className=’voice-row’;

    const lbl=document.createElement(‘div’);

    lbl.className=’voice-label’;

    lbl.innerHTML=`🗣️ <b>Letter ${letter}</b><small>”This is ${letter}! Now trace the letter ${letter}!”</small>`;

    const acts=document.createElement(‘div’);

    acts.style.cssText=’display:flex;gap:6px;align-items:center;flex-wrap:wrap;flex-shrink:0′;

    const st=document.createElement(‘span’);

    st.className=’vstatus miss’; st.id=’vst_’+id; st.textContent=’Not yet’;

    const startBtn=document.createElement(‘button’);

    startBtn.className=’rbtn rstart’; startBtn.textContent=’🔴 Record’;

    const stopBtn=document.createElement(‘button’);

    stopBtn.className=’rbtn rstop hidden’; stopBtn.textContent=’⏹ Stop’;

    stopBtn.onclick=()=>{if(activeRecorder&&activeRecorder.state!==’inactive’)activeRecorder.stop();};

    const playBtn=document.createElement(‘button’);

    playBtn.className=’rbtn rplay hidden’; playBtn.id=’vplay_’+id; playBtn.textContent=’▶‘;

    playBtn.onclick=()=>new Audio(voiceRecs[id].url).play();

    startBtn.onclick=()=>startVoiceRec(id,startBtn,stopBtn,playBtn,st);

    acts.append(st,startBtn,stopBtn,playBtn);

    row.append(lbl,acts);

    cont.appendChild(row);

  });

}

async function startVoiceRec(id,startBtn,stopBtn,playBtn,stEl){

  try{

    const stream=await navigator.mediaDevices.getUserMedia({audio:true});

    const rec=new MediaRecorder(stream);

    const chunks=[];

    rec.ondataavailable=e=>chunks.push(e.data);

    rec.onstop=()=>{

      stream.getTracks().forEach(t=>t.stop());

      const wasNew=!voiceRecs[id];

      const blob=new Blob(chunks,{type:’audio/webm’});

      voiceRecs[id]={url:URL.createObjectURL(blob)};

      stEl.textContent=’✓ Done’; stEl.className=’vstatus done’;

      startBtn.textContent=’🔴 Re-record’; startBtn.className=’rbtn rstart’;

      stopBtn.classList.add(‘hidden’); playBtn.classList.remove(‘hidden’);

      startBtn.onclick=()=>startVoiceRec(id,startBtn,stopBtn,playBtn,stEl);

      if(wasNew){recDoneCount++;document.getElementById(‘recCount’).textContent=recDoneCount;}

    };

    rec.start(); activeRecorder=rec;

    startBtn.textContent=’● Rec…’; startBtn.className=’rbtn rstop’;

    stopBtn.classList.remove(‘hidden’);

  }catch(e){alert(‘Please allow microphone access and try again.’);}

}

function startGame(){

  document.getElementById(‘setupContainer’).classList.add(‘hidden’);

  document.getElementById(‘gameContainer’).classList.remove(‘hidden’);

  document.getElementById(‘settingsFab’).classList.remove(‘hidden’);

  document.getElementById(‘muteFab’).classList.remove(‘hidden’);

  getAC();

  playIntroJingle(()=>{startBg();loadS1();});

}

function goSetup(){

  stopBg();

  document.getElementById(‘gameContainer’).classList.add(‘hidden’);

  document.getElementById(‘setupContainer’).classList.remove(‘hidden’);

  document.getElementById(‘settingsFab’).classList.add(‘hidden’);

  document.getElementById(‘muteFab’).classList.add(‘hidden’);

}

// ═══════════════════════════════════════

// WEB AUDIO ENGINE

// ═══════════════════════════════════════

let AC=null; let muted=false;

function getAC(){if(!AC)AC=new(window.AudioContext||window.webkitAudioContext)();return AC;}

function tone(freq,t,dur,vol=0.18,type=’sine’,ac){

  const c=ac||getAC();

  const o=c.createOscillator(),g=c.createGain();

  o.connect(g);g.connect(c.destination);

  o.type=type;o.frequency.setValueAtTime(freq,t);

  g.gain.setValueAtTime(0,t);g.gain.linearRampToValueAtTime(vol,t+0.01);

  g.gain.exponentialRampToValueAtTime(0.001,t+dur);

  o.start(t);o.stop(t+dur+0.05);

}

function chord(freqs,t,dur,vol=0.08,ac){freqs.forEach(f=>tone(f,t,dur,vol,’sine’,ac||getAC()));}

function playIntroJingle(onEnd){

  if(muted){if(onEnd)setTimeout(onEnd,100);return;}

  const c=getAC(),now=c.currentTime+0.1;

  [[523.25,0],[587.33,.24],[659.25,.48],[783.99,.72],[880,.96],[1046.5,1.2,.45],[783.99,1.68,.22],[659.25,1.92,.22],[783.99,2.16,.45],[880,2.64,.22],[1046.5,2.88,.6]].forEach(([f,t,d=0.22])=>tone(f,now+t,d,0.22,’triangle’,c));

  [[261.63,329.63,392],[0,0.9],[[293.66,369.99,440],0.96,0.9],[[261.63,329.63,392],1.92,0.9],[[261.63,329.63,392],2.88,1]].forEach(v=>{if(Array.isArray(v[0]))chord(v[0],now+v[1],v[2],0.07,c);});

  chord([261.63,329.63,392],now+0,0.9,0.07,c);chord([293.66,369.99,440],now+0.96,0.9,0.07,c);chord([261.63,329.63,392],now+1.92,0.9,0.07,c);chord([261.63,329.63,392],now+2.88,1,0.07,c);

  [1318.5,1567.98,2093].forEach((f,i)=>tone(f,now+0.5+i*0.3,0.4,0.05,’sine’,c));

  setTimeout(()=>{if(onEnd)onEnd();},3900);

}

let bgTimer=null,bgOn=false;

function playBgLoop(){

  if(!bgOn||muted)return;

  const c=getAC(),now=c.currentTime+0.05,beat=60/100,v=0.055;

  [[523.25,0],[659.25,1],[783.99,2],[659.25,3],[698.46,4],[880,5],[1046.5,6],[880,7],[783.99,8],[987.77,9],[1174.7,10],[987.77,11],[523.25,12],[659.25,13],[783.99,14],[783.99,15]].forEach(([f,b])=>tone(f,now+b*beat,beat*0.7,v,’triangle’,c));

  [0,2,4,6,8,10,12,14].forEach(b=>tone(130.81,now+b*beat,beat*0.4,0.03,’sine’,c));

  bgTimer=setTimeout(()=>{if(bgOn&&!muted)playBgLoop();},16*beat*1000-80);

}

function startBg(){if(bgOn)return;bgOn=true;playBgLoop();}

function stopBg(){bgOn=false;if(bgTimer){clearTimeout(bgTimer);bgTimer=null;}}

function toggleMute(){

  muted=!muted;

  document.getElementById(‘muteFab’).textContent=muted?’🔇‘:’🔊‘;

  if(!muted&&bgOn)playBgLoop();

  if(muted&&bgTimer){clearTimeout(bgTimer);bgTimer=null;}

}

function playWinFanfare(onEnd){

  if(muted){if(onEnd)setTimeout(onEnd,100);return;}

  const c=getAC(),now=c.currentTime+0.1;

  [[523.25,0,.15],[659.25,.16,.15],[783.99,.32,.15],[1046.5,.48,.5],[880,1,.15],[1046.5,1.16,.15],[1174.7,1.32,.15],[1318.5,1.48,.7]].forEach(([f,t,d])=>tone(f,now+t,d,0.25,’triangle’,c));

  chord([261.63,329.63,392,523.25],now+0.48,0.45,0.09,c);chord([329.63,415.30,523.25,659.25],now+1.48,0.65,0.09,c);

  [1046.5,1318.5,1567.98,2093].forEach((f,i)=>tone(f,now+0.5+i*0.18,0.3,0.05,’sine’,c));

  setTimeout(()=>{if(onEnd)onEnd();},2400);

}

function playCorrectSFX(){if(muted)return;const c=getAC(),n=c.currentTime;tone(880,n,.12,.14,’sine’,c);tone(1046.5,n+.13,.12,.14,’sine’,c);tone(1318.5,n+.26,.2,.14,’sine’,c);}

function playWrongSFX(){if(muted)return;const c=getAC(),n=c.currentTime;tone(220,n,.15,.11,’sawtooth’,c);tone(196,n+.16,.2,.09,’sawtooth’,c);}

// ═══════════════════════════════════════

// TTS VOICE

// ═══════════════════════════════════════

const TTS_MAP={

  correct:”Great job! That’s correct!”,

  wrong:”Oops! Try again — you can do it!”,

  trace_done:”Awesome tracing! Now let’s fill in some words!”,

  words_done:”Amazing work! You finished all the words!”,

  all_done:”You are a superstar! You finished all 26 letters! I am so proud of you!”,

};

GAME_LETTERS.forEach(l=>{

  TTS_MAP[‘ask_’+l]=`Which letter is ${l}? Can you find it?`;

  TTS_MAP[‘found_’+l]=`This is ${l}! Now trace the letter ${l}!`;

});

// Pick best female voice

let femaleVoice=null;

function pickFemaleVoice(){

  const voices=window.speechSynthesis.getVoices();

  if(!voices.length)return;

  const fem=[‘zira’,’susan’,’kate’,’karen’,’moira’,’samantha’,’victoria’,’fiona’,’tessa’,’veena’,’allison’,’ava’,’nova’,’aria’,’jenny’,’sonia’,’libby’,’hazel’,’natasha’,’female’,’woman’];

  let v=voices.find(v=>v.lang.startsWith(‘en’)&&fem.some(k=>v.name.toLowerCase().includes(k)));

  if(!v)v=voices.find(v=>v.lang.startsWith(‘en-GB’));

  if(!v)v=voices.find(v=>v.lang.startsWith(‘en’));

  if(v)femaleVoice=v;

}

if(window.speechSynthesis){pickFemaleVoice();window.speechSynthesis.onvoiceschanged=pickFemaleVoice;}

function playVoice(id,onEnd){

  if(voiceRecs[id]){

    showBadge(true);

    const a=new Audio(voiceRecs[id].url);

 a.play().catch(()=>{});

    a.onended=()=>{showBadge(false);if(onEnd)onEnd();};

    return;

  }

  const text=TTS_MAP[id];

  if(!text||!window.speechSynthesis){if(onEnd)onEnd();return;}

  window.speechSynthesis.cancel();

  const u=new SpeechSynthesisUtterance(text);

  if(femaleVoice)u.voice=femaleVoice;

  u.rate=0.9;u.pitch=1.2;u.volume=1;

  showBadge(true);

  u.onend=()=>{showBadge(false);if(onEnd)onEnd();};

  window.speechSynthesis.speak(u);

}

function showBadge(v){document.getElementById(‘speakingBadge’).classList.toggle(‘show’,v);}

// ═══════════════════════════════════════

// GAME STATE

// ═══════════════════════════════════════

let li=0,stars=0,wOk=0,drawing=false,lx=0,ly=0;

const canvas=document.getElementById(‘traceCanvas’);

const ctx=canvas.getContext(‘2d’);

function curL(){return GAME_LETTERS[li];}

function renderStars(){

  const total=GAME_LETTERS.length*3;

  const row=document.getElementById(‘starsRow’);row.innerHTML=”;

  // Show stars in groups of 26 to keep it compact — just show earned vs total as mini dots

  for(let i=0;i<total;i++){

    const s=document.createElement(‘span’);

    s.className=’star’+(i<stars?’ earned’:”);

    s.textContent=i<stars?’⭐‘:’·’;

    s.style.fontSize=i<stars?’1rem’:’0.8rem’;

    row.appendChild(s);

  }

}

function earnStar(){

  stars++;

  renderStars();

  document.getElementById(‘progressFill’).style.width=(stars/(GAME_LETTERS.length*3)*100)+’%’;

}

function updateLetterProgress(){

  document.getElementById(‘letterProgress’).textContent=`Letter ${li+1} of 26 — ${curL()}`;

}

// ── Stage 1 ──

function loadS1(){

  showSec(‘stage1’);

  updateLetterProgress();

  const letter=curL();

  document.getElementById(‘targetLetter’).textContent=letter;

  const wrong=ALL.filter(l=>l!==letter).sort(()=>Math.random()-0.5).slice(0,5);

  const choices=[letter,…wrong].sort(()=>Math.random()-0.5);

  const grid=document.getElementById(‘choicesGrid’);grid.innerHTML=”;

  choices.forEach(ch=>{

    const b=document.createElement(‘button’);b.className=’choice-btn’;b.textContent=ch;

    b.onclick=()=>checkChoice(b,ch);grid.appendChild(b);

  });

  setTimeout(()=>playVoice(‘ask_’+letter),600);

}

function replayStageName(){playVoice(‘ask_’+curL());}

function checkChoice(btn,chosen){

  if(chosen===curL()){

    btn.classList.add(‘correct’);playCorrectSFX();playVoice(‘correct’);showToast(‘Great job! 🎉‘);

    setTimeout(()=>celebrate(‘🌟‘,`You found ${curL()}!`,”Now let’s trace it!”,()=>{earnStar();loadS2();}),700);

  }else{

    btn.classList.add(‘wrong’);playWrongSFX();playVoice(‘wrong’);showToast(‘Try again! 😊‘);

    setTimeout(()=>btn.classList.remove(‘wrong’),500);

  }

}

// ── Stage 2 ──

function loadS2(){

  showSec(‘stage2’);

  document.getElementById(‘traceGhost’).textContent=curL();

  clearCanvas();setupCanvas();

  setTimeout(()=>playVoice(‘found_’+curL()),400);

}

function setupCanvas(){

  ctx.strokeStyle=’#ff6b35′;ctx.lineWidth=6;ctx.lineCap=’round’;ctx.lineJoin=’round’;

  canvas.onmousedown=e=>{drawing=true;[lx,ly]=cpos(e);};

  canvas.onmousemove=e=>{if(!drawing)return;cdraw(e);};

  canvas.onmouseup=()=>drawing=false;canvas.onmouseleave=()=>drawing=false;

  canvas.ontouchstart=e=>{e.preventDefault();drawing=true;[lx,ly]=cpos(e);};

  canvas.ontouchmove=e=>{e.preventDefault();if(!drawing)return;cdraw(e);};

  canvas.ontouchend=()=>drawing=false;

}

function cpos(e){const r=canvas.getBoundingClientRect(),s=e.touches?e.touches[0]:e;return[s.clientX-r.left,s.clientY-r.top];}

function cdraw(e){const[x,y]=cpos(e);ctx.beginPath();ctx.moveTo(lx,ly);ctx.lineTo(x,y);ctx.stroke();[lx,ly]=[x,y];}

function clearCanvas(){ctx.clearRect(0,0,canvas.width,canvas.height);}

function doneTracing(){

  const d=ctx.getImageData(0,0,canvas.width,canvas.height).data;

  if(!d.some((v,i)=>i%4===3&&v>0)){showToast(‘Try tracing the letter first! ✏️‘);return;}

  playCorrectSFX();playVoice(‘trace_done’);

  celebrate(‘🖊️‘,’Awesome Tracing!’,’Now fill in the words!’,()=>{earnStar();loadS3();});

}

// ── Stage 3 ──

function loadS3(){

  showSec(‘stage3’);wOk=0;

  const letter=curL(),wlist=WORDS[letter]||WORDS[‘A’];

  const cont=document.getElementById(‘wordsList’);cont.innerHTML=”;

  wlist.forEach((w,wi)=>{

    const card=document.createElement(‘div’);

    card.className=’word-card’+(wi===0?’ active’:”);card.id=’wc_’+wi;

    const em=document.createElement(‘span’);em.className=’word-pic’;em.textContent=w.emoji;card.appendChild(em);

    const wd=document.createElement(‘div’);wd.className=’word-display’;

    for(let i=0;i<w.word.length;i++){

      const b=document.createElement(‘div’);

      b.className=’lbox’+(i===w.blank?’ blank’:”);

      if(i===w.blank){b.id=’bl_’+wi;b.textContent=’_’;}else b.textContent=w.word[i];

      wd.appendChild(b);

    }

    card.appendChild(wd);

    const opts=document.createElement(‘div’);opts.className=’word-options’;opts.id=’op_’+wi;

    const cor=w.word[w.blank];

    const wrg=ALL.filter(l=>l!==cor).sort(()=>Math.random()-0.5).slice(0,3);

    [cor,…wrg].sort(()=>Math.random()-0.5).forEach(opt=>{

      const lb=document.createElement(‘button’);lb.className=’lopt’;lb.textContent=opt;

      lb.onclick=()=>checkWord(wi,opt,lb,w);opts.appendChild(lb);

    });

    card.appendChild(opts);cont.appendChild(card);

  });

}

function checkWord(wi,chosen,btn,word){

  const cor=word.word[word.blank],bl=document.getElementById(‘bl_’+wi);

  bl.textContent=chosen;

  if(chosen!==cor){

    bl.classList.add(‘wfill’);playWrongSFX();playVoice(‘wrong’);showToast(‘Oops! Try another letter! 😊‘);

    setTimeout(()=>{bl.classList.remove(‘wfill’);bl.textContent=’_’;},600);

    return;

  }

  bl.classList.add(‘cfill’);

  document.querySelectorAll(‘#op_’+wi+’ .lopt’).forEach(b=>b.classList.add(‘used’));

  document.getElementById(‘wc_’+wi).classList.replace(‘active’,’solved’);

  wOk++;playCorrectSFX();playVoice(‘correct’);showToast(‘Correct! ⭐‘);

  const wlist=WORDS[curL()]||WORDS[‘A’];

  if(wOk===wlist.length){

    setTimeout(()=>{

      earnStar();

      if(li+1<GAME_LETTERS.length){

        playWinFanfare(()=>{

          playVoice(‘words_done’);

          celebrate(‘🏆‘,’Amazing Work!’,`Ready for letter ${GAME_LETTERS[li+1]}?`,()=>{li++;loadS1();},’Next Letter ➡️‘);

        });

      }else{

        stopBg();

        playWinFanfare(()=>{

          playVoice(‘all_done’);

          celebrate(‘🎓‘,’You Are a STAR! 🌟‘,’You finished ALL 26 letters! A to Z!’,()=>{li=0;stars=0;renderStars();document.getElementById(‘progressFill’).style.width=’0%’;startBg();loadS1();},’Play Again 🔄‘);

        });

      }

    },700);

  }else{

    const nxt=document.getElementById(‘wc_’+(wi+1));

    if(nxt)setTimeout(()=>nxt.classList.add(‘active’),300);

  }

}

// ── Celebrate ──

let celCb=null;

function celebrate(emoji,title,sub,cb,btnTxt=’Next Stage ➡️‘){

  celCb=cb;

  document.getElementById(‘celEmoji’).textContent=emoji;

  document.getElementById(‘celTitle’).textContent=title;

  document.getElementById(‘celSub’).textContent=sub;

  document.getElementById(‘celBtn’).textContent=btnTxt;

  document.getElementById(‘celebration’).classList.remove(‘hidden’);

  spawnConfetti();

}

function nextStage(){

  document.getElementById(‘celebration’).classList.add(‘hidden’);

  document.getElementById(‘confettiCont’).innerHTML=”;

  if(celCb)celCb();

}

function spawnConfetti(){

  const c=document.getElementById(‘confettiCont’);c.innerHTML=”;

  const cols=[‘#ff6b35′,’#4ecdc4′,’#ffe66d’,’#a855f7′,’#f472b6′,’#22c55e’];

  for(let i=0;i<70;i++){

    const p=document.createElement(‘div’);p.className=’conf’;

    p.style.left=Math.random()*100+’vw’;p.style.top=’-20px’;

    p.style.background=cols[Math.floor(Math.random()*cols.length)];

    p.style.animationDuration=(1.5+Math.random()*2)+’s’;

    p.style.animationDelay=(Math.random()*0.8)+’s’;

    p.style.transform=`rotate(${Math.random()*360}deg)`;

    c.appendChild(p);

  }

}

let tTimer;

function showToast(msg){const t=document.getElementById(‘toast’);t.textContent=msg;t.classList.add(‘show’);clearTimeout(tTimer);tTimer=setTimeout(()=>t.classList.remove(‘show’),1600);}

function showSec(id){[‘stage1′,’stage2′,’stage3’].forEach(s=>document.getElementById(s).classList.toggle(‘hidden’,s!==id));}

buildVoiceRows();

renderStars();

</script>

</body>

</html>

Welcome to 👋
Smart Teacher Platform.

Get simple parenting tips, fun Yoruba learning ideas, and cyber safety guidance to help your child learn better without pressure.

We don’t spam! Read our privacy policy for more info.