| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>ECHO 3D — listen, type, level up</title> |
| <link rel="stylesheet" href="/src/styles/main.css"> |
| <style> |
| |
| #bootLoader{position:fixed;inset:0;z-index:100000;display:flex;flex-direction:column; |
| align-items:center;justify-content:center;gap:24px; |
| background:radial-gradient(ellipse at 50% 30%,#1a2440 0%,#070912 70%);color:#f4f6fb; |
| font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif; |
| transition:opacity .55s ease;} |
| #bootLoader.gone{opacity:0;pointer-events:none} |
| #bootLoader .logo{font-family:ui-monospace,"SF Mono","JetBrains Mono",Menlo,Consolas,monospace; |
| font-weight:900;font-size:46px;letter-spacing:.32em;color:#ffe08a; |
| text-shadow:0 2px 0 #5a3a08,0 3px 0 rgba(0,0,0,.55),0 0 22px rgba(240,168,48,.7),0 0 40px rgba(240,168,48,.35)} |
| #bootLoader .spinner{width:58px;height:58px;border-radius:50%; |
| border:3px solid rgba(255,255,255,.1);border-top-color:#ffd166; |
| animation:bootSpin 1.1s linear infinite} |
| @keyframes bootSpin{to{transform:rotate(360deg)}} |
| #bootLoader .bar{width:min(360px,72vw);height:6px;background:rgba(255,255,255,.08); |
| border-radius:99px;overflow:hidden} |
| #bootLoader .bar i{display:block;height:100%;width:0%; |
| background:linear-gradient(90deg,#46d39a,#ffd166,#f0a830); |
| transition:width .35s ease;border-radius:99px} |
| #bootLoader .msg{font-size:13.5px;color:#cfd5e3;min-height:18px;text-align:center; |
| letter-spacing:.02em;padding:0 16px} |
| #bootLoader .hint{font-size:11px;color:#7a8298;margin-top:-12px} |
| </style> |
| </head> |
| <body> |
|
|
| |
| <div id="bootLoader"> |
| <div class="logo">ECHO</div> |
| <div class="hint">listen · type · level up</div> |
| <div class="spinner"></div> |
| <div class="bar"><i id="bootBar"></i></div> |
| <div class="msg" id="bootMsg">Initialising…</div> |
| </div> |
|
|
| |
| <canvas id="bg3d"></canvas> |
|
|
| |
| <div class="auth-screen" id="authScreen"> |
| <div class="auth-card"> |
| <h2>ECHO</h2> |
| <div class="sub" id="authSub">Sign in to track your stars and climb the leaderboard.</div> |
| <div class="auth-tabs"> |
| <button id="tabLogin" class="active" type="button">Login</button> |
| <button id="tabRegister" type="button">Register</button> |
| </div> |
| <form id="authForm" autocomplete="off"> |
| <div class="auth-field"><label>Email</label><input type="email" id="fEmail" required placeholder="you@example.com"></div> |
| <div class="auth-field" id="fNameRow" style="display:none"><label>Display name (optional)</label><input type="text" id="fName" placeholder="e.g. Aisha"></div> |
| <div class="auth-field"><label>Password</label><input type="password" id="fPwd" required placeholder="at least 6 characters"></div> |
| <div class="auth-field" id="fPwd2Row" style="display:none"><label>Confirm password</label><input type="password" id="fPwd2"></div> |
| <div class="auth-field err" id="fErr"></div> |
| <button class="primary" id="authBtn" type="submit">Login</button> |
| <button class="auth-link" id="forgotBtn" type="button">Forgot password?</button> |
| </form> |
| <div class="guest"><button id="guestBtn" type="button">Continue as guest →</button></div> |
| </div> |
| </div> |
|
|
| |
| <div class="wrap" id="appWrap" style="display:none"> |
|
|
| <header> |
| <div class="brand"> |
| <div class="logo">ECHO</div> |
| <div class="tagline">type what you hear · UK voice · සිංහල · தமிழ்</div> |
| </div> |
| <div class="hgroup"> |
| <div class="total"><div class="n" id="t-mastered">0</div><div class="l">mastered</div></div> |
| <div class="total"><div class="n" id="t-stars" style="color:var(--amber)">0</div><div class="l">⭐ stars</div></div> |
| <button class="admin-chip" id="adminBtn" style="display:none" title="Open admin dashboard">⚙ ADMIN</button> |
| <div class="user-chip" id="userChip"> |
| <div class="avatar" id="userAvatar">?</div> |
| <div><div class="name" id="userName">Guest</div><div class="stars" id="userRank">Rank —</div></div> |
| <button id="signOutBtn" title="Sign out">⎋</button> |
| </div> |
| </div> |
| </header> |
|
|
| <div class="track-hint">🎮 Pick a grade — Sprout → Legendary · scroll →</div> |
| <div class="track" id="track"></div> |
|
|
| <div class="grid"> |
| <div class="card" id="gameCard"> |
| <div class="overlay" id="overlay"> |
| <div> |
| <h2>Ready to listen?</h2> |
| <p>Each grade is a word length. Hear the word, see the Sinhala & Tamil meaning, type what you hear, press <b>Enter</b>.</p> |
| <button class="btn" id="startBtn" style="background:var(--amber);color:#1a1404" disabled>Loading dictionary…</button> |
| <div id="bootStatus" style="margin-top:14px;font-size:12px;color:var(--fog)">Downloading 370,000+ English words…</div> |
| </div> |
| </div> |
|
|
| <div class="speaker-row"> |
| <div class="speaker" id="speaker" role="button" tabindex="0" aria-label="Play the word again"> |
| <span class="ring"></span> |
| <svg viewBox="0 0 24 24"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3a4.5 4.5 0 00-2.5-4.03v8.05A4.5 4.5 0 0016.5 12zM14 3.23v2.06a7 7 0 010 13.42v2.06a9 9 0 000-17.54z"/></svg> |
| </div> |
| <div class="replay-hint">Tap or press <b>Tab</b> to replay · <b>UK female voice</b></div> |
| </div> |
|
|
| <div class="meaning empty" id="meaning"> |
| <div class="col si"><div class="lab">සිංහල (meaning)</div><div class="val" id="m-si">—</div></div> |
| <div class="col ta"><div class="lab">தமிழ் (பொருள்)</div><div class="val" id="m-ta">—</div></div> |
| </div> |
|
|
| <div class="tiles" id="tiles"></div> |
| <input id="typed" type="text" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" placeholder="type the word and press Enter" disabled> |
| <div class="feedback" id="feedback"></div> |
| <div class="controls"> |
| <button class="btn ghostbtn" id="revealBtn">Show answer</button> |
| <button class="btn ghostbtn" id="skipBtn">Skip →</button> |
| <button class="btn ghostbtn" id="hearAgainBtn">🔊 Slow</button> |
| </div> |
| <div class="levelup" id="levelup" style="display:none"><span></span></div> |
| <div class="burst" id="burst"></div> |
| </div> |
|
|
| <div class="side"> |
| <div class="pcard"> |
| <div class="eyebrow">Current grade</div> |
| <div class="lvlnum" id="p-lvlnum">🌱 Sprout</div> |
| <div class="lvldesc" id="p-lvldesc">1-letter words</div> |
| <div class="bar"><i id="p-bar"></i></div> |
| <div class="barlabel"><span><b id="p-done">0</b> / <b id="p-total">0</b> mastered</span><span id="p-pct">0%</span></div> |
| <div class="ministats"> |
| <div class="ministat rem"><div class="n" id="p-remaining">0</div><div class="l">words left</div></div> |
| <div class="ministat strk"><div class="n" id="p-streak">0</div><div class="l">streak</div></div> |
| </div> |
| </div> |
|
|
| <div class="leaderboard"> |
| <h3>Leaderboard <span class="live"><span id="onlineCount">1</span> online</span></h3> |
| <div id="lbList"></div> |
| <div class="lb-stats"> |
| <div><b id="lb-total">1</b> Players</div> |
| <div><b class="green" id="lb-globalStars">0</b> Total ⭐</div> |
| </div> |
| </div> |
|
|
| <div class="pcard medals"> |
| <h3>Journey <span id="medals-count">0/7</span></h3> |
| <div class="medal-row" id="medalRow"></div> |
| <div class="medal-cap">Sunny → Cloudy → Drizzle → Mist → Thunder → Snow → <b>Ice Age</b></div> |
| </div> |
|
|
| <div class="pcard fam"> |
| <h3>Familiarised <span id="fam-count">0</span></h3> |
| <div class="famlist" id="famlist"><div class="famempty">Mastered words collect here.</div></div> |
| </div> |
|
|
| <details class="settings"> |
| <summary>Settings</summary> |
| <div class="set-row"> |
| <label>Voice</label> |
| <select id="engineSel"> |
| <optgroup label="🎲 Surprise me"> |
| <option value="stream:random">🎲 Random — different voice every word</option> |
| </optgroup> |
| <optgroup label="★ Premium natural (Polly via StreamElements · free)"> |
| <option value="stream:Brian">★ Brian — UK male</option> |
| <option value="stream:Amy">★ Amy — UK female</option> |
| <option value="stream:Emma">★ Emma — UK female</option> |
| <option value="stream:Joanna">★ Joanna — US female</option> |
| <option value="stream:Matthew">★ Matthew — US male</option> |
| <option value="stream:Salli">★ Salli — US female</option> |
| <option value="stream:Russell">★ Russell — AU male</option> |
| <option value="stream:Nicole">★ Nicole — AU female</option> |
| </optgroup> |
| <optgroup label="Other"> |
| <option value="google">Google Translate — UK</option> |
| <option value="browser">Browser native (offline)</option> |
| </optgroup> |
| </select> |
| </div> |
| <div class="set-row"> |
| <label>Speed — <span class="pct" id="rateVal">0.95×</span></label> |
| <input type="range" id="rate" min="0.5" max="1.3" step="0.05" value="0.95"> |
| </div> |
| <div class="set-row"> |
| <label>Correct answers to master — <span class="pct" id="thrVal">3</span></label> |
| <input type="range" id="thr" min="1" max="6" step="1" value="3"> |
| </div> |
| <div class="set-row"><label class="toggle"><input type="checkbox" id="showMeaning" checked> Show Sinhala & Tamil meanings</label></div> |
| <div class="set-row"><label class="toggle"><input type="checkbox" id="cheerSound" checked> Cheering sound</label></div> |
| <div class="set-row"><label class="toggle"><input type="checkbox" id="ambientAudio" checked> Birds, crickets & ambient nature sound</label></div> |
| <div class="set-row"><label class="toggle"><input type="checkbox" id="ambient3D" checked> 3D ambient scene (sky, sun, grass, goats)</label></div> |
| <div class="set-row"><label class="toggle"><input type="checkbox" id="fullDict" checked> Full English dictionary (370,000+ words)</label></div> |
| <div class="set-row"><button class="btn danger" id="resetBtn">Reset progress</button></div> |
| </details> |
| </div> |
| </div> |
|
|
| <footer>Concept and developed by SriAI</footer> |
| </div> |
|
|
| <audio id="audio" preload="auto"></audio> |
|
|
| <script type="module" src="/src/main.js"></script> |
| </body> |
| </html> |
|
|