| <main id="whistle-coach-app" class="whistle-app"> | |
| <header class="wc-header"> | |
| <div class="brand-lockup"> | |
| <div class="brand-mark" aria-hidden="true"> | |
| <svg viewBox="0 0 64 64" role="img"> | |
| <path d="M15 36c7-10 18-15 33-12" fill="none" stroke="currentColor" stroke-width="6" stroke-linecap="round"/> | |
| <path d="M16 47c10-8 22-10 35-6" fill="none" stroke="currentColor" stroke-width="6" stroke-linecap="round" opacity=".58"/> | |
| <circle cx="13" cy="29" r="5" fill="currentColor"/> | |
| </svg> | |
| </div> | |
| <div> | |
| <h1 style="color:#17213a">Whistle Coach <span>🌱</span></h1> | |
| <p style="color:#52607d">Audio-first AI coach for your first whistle</p> | |
| </div> | |
| </div> | |
| </header> | |
| <section class="wc-layout" aria-label="Whistle Coach live practice"> | |
| <aside class="left-rail"> | |
| <section class="side-card flow-card"> | |
| <p class="card-kicker">🌱 BEGINNER FLOW</p> | |
| <ol class="flow-list"> | |
| <li class="done"><span>1</span>Relax & posture</li> | |
| <li class="done"><span>2</span>Round your lips</li> | |
| <li class="done"><span>3</span>Small opening</li> | |
| <li class="active"><span>4</span>Gentle airflow</li> | |
| <li><span>5</span>Find tiny whistle</li> | |
| <li><span>6</span>Hold a steady tone</li> | |
| <li><span>7</span>Play a simple melody</li> | |
| </ol> | |
| <div class="steps-progress"><strong>4 / 7 steps</strong><span><i></i></span></div> | |
| </section> | |
| <section class="side-card journey-card"> | |
| <p class="card-kicker">🌱 YOUR WHISTLE JOURNEY</p> | |
| <div class="journey-row"> | |
| <div class="progress-ring"><strong id="garden-growth">0%</strong></div> | |
| <div> | |
| <strong id="journey-title">You’re growing!</strong> | |
| <p id="coach-message" aria-live="polite">Your flowers grow each time you hold a whistle.</p> | |
| </div> | |
| <div class="plant-pot" aria-hidden="true" data-growth-stage="seed"> | |
| <span class="plant-stem"></span> | |
| <span class="plant-leaf plant-leaf-left"></span> | |
| <span class="plant-leaf plant-leaf-right"></span> | |
| <span class="plant-flower"></span> | |
| <span class="plant-soil"></span> | |
| </div> | |
| </div> | |
| </section> | |
| </aside> | |
| <section class="center-stage"> | |
| <article id="camera-stage" class="camera-card" data-feedback="red" tabindex="0" aria-label="Start live whistle practice"> | |
| <video id="practice-video" autoplay muted playsinline aria-label="Live webcam preview"></video> | |
| <div id="camera-placeholder" class="camera-placeholder"> | |
| <div class="start-panel"> | |
| <div class="placeholder-face" aria-hidden="true"></div> | |
| <strong>Start practice</strong> | |
| <span>Put your mouth in the dotted oval, round your lips, then blow gently.</span> | |
| <button id="center-start-practice" class="center-start-button" type="button">Start</button> | |
| </div> | |
| </div> | |
| <canvas id="mouth-overlay" aria-hidden="true"></canvas> | |
| <canvas id="garden-canvas" aria-hidden="true"></canvas> | |
| <div class="live-guide" aria-hidden="true"> | |
| <strong>Put your mouth in the oval</strong> | |
| <span>Round your lips, keep the opening small, then blow softly.</span> | |
| </div> | |
| <span id="session-state" class="live-chip" data-state="idle"><i></i><span class="session-copy">Ready</span></span> | |
| <div id="coach-card" class="coach-bubble" data-state="idle"><span>🌱</span><strong id="hero-feedback">Ready for live practice</strong></div> | |
| <div id="permission-note" class="permission-note">Camera and microphone stay in your browser.</div> | |
| </article> | |
| </section> | |
| <aside class="right-rail"> | |
| <section class="listening-panel"> | |
| <div class="panel-heading"><span>▮▮</span><div><strong>AI LISTENING PANEL</strong><p>Audio is the core signal</p></div></div> | |
| <section id="airflow-indicator" class="indicator-card green" data-state="red"> | |
| <div class="indicator-topline"><div class="indicator-name">≋ <span>Airflow</span></div><strong class="indicator-score"><span>0</span><small>/100</small></strong></div> | |
| <div class="score-track"><span class="score-fill"></span></div> | |
| <p class="indicator-detail">Waiting for microphone</p> | |
| </section> | |
| <section id="whistle-indicator" class="indicator-card orange" data-state="red"> | |
| <div class="indicator-topline"><div class="indicator-name">✧ <span>Whistle confidence (AST)</span></div><strong class="indicator-score"><span>0</span><small>/100</small></strong></div> | |
| <div class="score-track"><span class="score-fill"></span></div> | |
| <p class="indicator-detail">MIT AST listens for whistle-like sound.</p> | |
| </section> | |
| <section id="tone-indicator" class="indicator-card purple" data-state="red"> | |
| <div class="indicator-topline"><div class="indicator-name">♫ <span>Pitch detected (F0)</span></div><strong class="indicator-score"><span>0</span><small></small></strong></div> | |
| <div class="score-track"><span class="score-fill"></span></div> | |
| <p class="indicator-detail">librosa.pyin waits for F0.</p> | |
| </section> | |
| <section id="stability-indicator" class="indicator-card blue" data-state="red"> | |
| <div class="indicator-topline"><div class="indicator-name">≋ <span>Stability</span></div><strong class="indicator-score"><span>0</span><small>/100</small></strong></div> | |
| <div class="score-track"><span class="score-fill"></span></div> | |
| <p class="indicator-detail">Keep airflow steady.</p> | |
| </section> | |
| <p id="technical-status" class="technical-status">Detection loads when live practice begins.</p> | |
| </section> | |
| </aside> | |
| </section> | |
| <section id="mouth-indicator" class="indicator-card hidden-meter" data-state="red" aria-hidden="true"> | |
| <div class="indicator-topline"><div class="indicator-name"><span>Mouth shape</span></div><strong class="indicator-score"><span>0</span><small>/100</small></strong></div> | |
| <div class="score-track"><span class="score-fill"></span></div> | |
| <p class="indicator-detail">Waiting for a face</p> | |
| </section> | |
| <code id="practice-state-json" hidden>{"state":"idle"}</code> | |
| <ol id="coach-trace-list" hidden></ol> | |
| <span id="garden-growth-stage" hidden>0%</span> | |
| <span id="garden-growth-fill" hidden></span> | |
| <span id="garden-plants" hidden>0</span> | |
| <span id="garden-streak" hidden>0.0</span> | |
| </main> | |