Listening lesson • oral sounds — Plan teacher-led spoken-word blending and segmentation, with an explicit sound unit, materials, scaffolds and actual observation blanks.
HTML for this example
Select the code and use your copy shortcut. Changes in this preview are temporary.
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Listening lesson • oral sounds</title><style>*{box-sizing:border-box}body{margin:0;background:var(--bg,#f3f7fa);color:var(--ink,#203342);font:16px/1.55 system-ui,Segoe UI,sans-serif}main{max-width:1100px;margin:auto;padding:28px 30px 50px}h1{font-size:clamp(27px,3.5vw,40px);line-height:1.18;letter-spacing:-.035em;margin:8px 0 12px}h2{font-size:21px;line-height:1.35;margin:0 0 16px}h3{font-size:18px}.eyebrow{color:var(--accent,#176783);font-size:12px;letter-spacing:.12em;font-weight:800;text-transform:uppercase}.intro,.muted,small{color:#516272}.intro{margin:0 0 24px}.panel{padding:24px;border:1px solid var(--line,#d6e0e7);border-radius:12px;background:white;margin:18px 0}.grid{display:grid;grid-template-columns:1fr 1fr;gap:22px}.grid>*{min-width:0}.controls{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin:16px 0}button,input,textarea,select{font:inherit}button{border:1px solid var(--accent,#176783);background:var(--accent,#176783);color:white;padding:10px 16px;min-height:44px;border-radius:7px;font-weight:700;cursor:pointer}button.secondary{background:white;color:var(--accent,#176783)}button[aria-pressed=true]{background:var(--tint,#e3f2f8);color:var(--ink,#203342);box-shadow:inset 0 0 0 2px var(--accent,#176783)}button:hover{filter:brightness(.96)}:focus-visible{outline:3px solid #d56b00;outline-offset:3px}label{display:block;margin:12px 0 5px;font-size:14px;font-weight:650}input:not([type=checkbox]),select,textarea{width:100%;padding:10px 12px;min-height:44px;border:1px solid #94a5b2;background:white;color:inherit;border-radius:7px}textarea{resize:vertical;min-height:120px}input[type=checkbox]{width:20px;height:20px;margin-right:9px}label.inline{display:flex;align-items:center;font-weight:500;gap:7px}.result{padding:24px;background:var(--tint,#e3f2f8);border:1px solid var(--line,#c8dce7);border-radius:12px}.big{font-size:clamp(30px,5vw,48px);font-weight:780;line-height:1.25;letter-spacing:-.035em;margin:10px 0}.trace{font:15px/1.7 ui-monospace,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere}.error{color:#a12820;font-weight:650;min-height:24px}.table-wrap{overflow-x:auto}table{border-collapse:collapse;width:100%;font-size:15px}th,td{text-align:left;border-bottom:1px solid var(--line,#d6e0e7);padding:12px 9px;vertical-align:top}th{font-size:12px;text-transform:uppercase;letter-spacing:.03em;color:#516272}code,pre,.code{font:14px/1.65 ui-monospace,Consolas,monospace}pre{white-space:pre-wrap;overflow-wrap:anywhere;background:var(--tint,#eef3f7);padding:16px;border-radius:6px}details{border-top:1px solid var(--line,#d6e0e7);margin:24px 0;padding-top:16px}summary{cursor:pointer;font-weight:700;min-height:44px}footer{border-top:1px solid var(--line,#d6e0e7);margin-top:26px;padding-top:16px;font-size:13px;color:#516272}.bar{height:16px;background:#dce4ea;border-radius:20px;overflow:hidden;margin:12px 0}.bar span{display:block;height:100%;background:var(--accent,#176783)}[hidden]{display:none!important}@media(max-width:720px){main{padding:20px}.grid{grid-template-columns:1fr;gap:12px}.panel,.result{padding:20px}}@media(max-width:400px){main{padding:16px}.panel,.result{padding:16px}button{padding:10px 13px}} .paper{background:white;padding:26px;border:1px solid var(--line,#d6e0e7)}.badge{display:inline-block;padding:4px 10px;border-radius:20px;background:var(--tint,#e3f2f8);font-size:13px;font-weight:700}.feedback{padding:18px;background:var(--tint,#e3f2f8);border-left:4px solid var(--accent,#176783);line-height:1.7}.schedule-row{display:grid;grid-template-columns:70px 1fr 90px;gap:14px;padding:16px 0;border-bottom:1px solid var(--line,#d6e0e7);align-items:start}.schedule-row label{margin:0}.schedule-row small{display:block;margin-top:6px}.schedule-row b{font-size:17px}.schedule-time{font-weight:700;font-variant-numeric:tabular-nums}.export{margin:24px 0}.export textarea{min-height:180px}.choice-list{display:flex;gap:10px;flex-wrap:wrap;margin:18px 0}.choice-list button{background:white;color:var(--ink,#203342);border-color:var(--line,#94a5b2)}@media(max-width:400px){.schedule-row{grid-template-columns:48px 1fr 65px;gap:9px}.schedule-row input{padding:8px}}
body{--accent:#355b9a;--bg:#f5f7fd;--tint:#e7edfb;--line:#d3ddf0;--ink:#22385b}.lesson-cover{display:grid;grid-template-columns:1.3fr 1fr;gap:20px;background:#e7edfb;padding:26px;border-radius:18px}.lesson-cover h2{font:30px/1.3 Georgia,serif;margin-top:15px}.mini-chips{display:flex;align-items:center;justify-content:center;gap:15px}.mini-chips i{width:58px;height:58px;border-radius:50%;background:#eeaf5d;border:3px solid #b2702a;box-shadow:0 5px 0 #c2a178}.sound-key{font:32px Georgia,serif;letter-spacing:.08em}.chip-mat{padding:24px 0 40px;background:#e7edfb;border-radius:20px;position:relative}.chip-line{display:flex;justify-content:center;gap:18px;height:190px;align-items:flex-start}.chip{padding:0;border:0;background:none;width:76px;height:76px;z-index:1}.chip span{display:block;width:70px;height:70px;border-radius:50%;background:#e5ac58;border:3px solid #9b6924;box-shadow:0 5px 0 #bc8b3e}.chip[aria-pressed=true]{transform:translateY(105px);box-shadow:none;background:none}.landing{border:2px dashed #91a6c7;border-radius:50px;height:90px;position:absolute;bottom:24px;left:12px;right:12px}#learner-view{max-width:800px;margin:auto}#chip-state{text-align:center;font-size:20px}@media(max-width:720px){.lesson-cover{grid-template-columns:1fr}.mini-chips{justify-content:flex-start}}</style></head><body><main>
<div id="teacher-view"><header><span class="eyebrow">Oral sound lesson / teacher planning copy</span><h1>One sound. One blank chip.</h1><p class="intro">A filled 20-minute blending and segmentation plan with a separate letter-free practice display.</p></header>
<section class="lesson-cover"><div><span class="badge">Target: individual phonemes</span><h2>Say it → move a chip → blend it.</h2><p>Teacher speaks <b>sun</b> and <b>fan</b>. Learners hear the word and move blank chips; they do not read letters.</p><div class="controls"><button type="button" id="show-learner">Show blank-chip practice</button></div></div><div class="mini-chips" aria-label="Three blank teaching chips"><i></i><i></i><i></i></div></section>
<section class="panel"><h2>Rehearse the oral script</h2><div class="controls"><button type="button" id="word-sun" class="secondary">sun</button><button type="button" id="word-fan" class="secondary">fan</button><button type="button" id="blend" class="secondary">Blending</button><button type="button" id="segment" class="secondary">Segmentation</button></div><p class="eyebrow">Teacher-only transcription</p><p id="sound-key" class="sound-key">/s/ /ʌ/ /n/</p><p id="oral-script"></p><p class="muted">Use the specified US practice pronunciations. Avoid adding a strong “uh” after consonants. Sun has one syllable and three phonemes; a syllable clap is a different task.</p></section>
<section class="panel"><h2>Lesson sequence</h2><label for="available">Available lesson minutes</label><input id="available" type="number" value="20" ><p class="badge" id="schedule-summary">20-minute plan ready</p><p id="schedule-error" class="error" role="alert"></p><div class="schedule-row"><span id="range-0" class="schedule-time"></span><div><b>Listening introduction</b><p>Name individual speech sounds as the unit. Say sun normally; invite a repeat.</p><small>Learner action: Listen, then repeat when prompted.</small></div><div><label for="mins-0">Minutes</label><input id="mins-0" type="number" value="3" min="1" max="180"></div></div><div class="schedule-row"><span id="range-1" class="schedule-time"></span><div><b>Teacher model</b><p>Say /s/ /ʌ/ /n/, move one blank chip per sound, then blend smoothly to sun.</p><small>Learner action: Observe the ordered sound-to-chip sequence.</small></div><div><label for="mins-1">Minutes</label><input id="mins-1" type="number" value="5" min="1" max="180"></div></div><div class="schedule-row"><span id="range-2" class="schedule-time"></span><div><b>Guided blending and segmentation</b><p>For blending, supply the ordered sounds for sun or fan. For segmentation, say the whole word.</p><small>Learner action: Blend orally; move one blank chip for each individual sound.</small></div><div><label for="mins-2">Minutes</label><input id="mins-2" type="number" value="7" min="1" max="180"></div></div><div class="schedule-row"><span id="range-3" class="schedule-time"></span><div><b>Individual oral check</b><p>Give a spoken item without printed words. Record exactly which oral task was asked.</p><small>Learner action: Blend or segment the spoken word with the supplied support.</small></div><div><label for="mins-3">Minutes</label><input id="mins-3" type="number" value="3" min="1" max="180"></div></div><div class="schedule-row"><span id="range-4" class="schedule-time"></span><div><b>Review the sound unit</b><p>Compare one word, one syllable, and three phonemes; choose support from the actual attempt.</p><small>Learner action: Explain or demonstrate the unit used.</small></div><div><label for="mins-4">Minutes</label><input id="mins-4" type="number" value="2" min="1" max="180"></div></div></section>
<section class="panel"><h2>Materials, support, and actual evidence</h2><p>Prepare three blank chips and a separate teacher transcription sheet. If one chip is moved for the whole word, return to sound-by-sound modeling. If order changes, repeat and model the supplied sequence.</p><label for="learners">Learner / group context</label><input id="learners" type="text" value="" placeholder="Teacher to supply"><label for="observed">Exact oral attempt and task asked — leave blank until teaching</label><textarea id="observed" rows="3"></textarea><label for="support">Support used and next teaching decision — leave blank until observed</label><textarea id="support" rows="3"></textarea></section><section class="panel export"><h2>Teacher plan text</h2><div class="controls"><button type="button" id="prepare">Prepare text</button><button type="button" id="select-text" class="secondary">Select text</button></div><p id="export-error" class="error" role="alert"></p><label for="export-text">Exact generated plain text</label><textarea id="export-text" readonly rows="9"></textarea><small>Select the text, then copy it manually into your required format. Nothing is saved or sent.</small></section>
<footer>Original oral lesson. The teacher delivers all sounds; no audio recording, playback, pronunciation scoring, saved responses, or reading diagnosis is provided. Retain official observations in the approved school workflow.</footer></div>
<section id="learner-view" hidden aria-label="Blank-chip learner display"><span class="eyebrow">Listen and move</span><h1>Listen to your teacher.</h1><p>Move one blank chip for each sound you hear.</p><div class="chip-mat"><div class="chip-line"><button type="button" id="chip-0" class="chip" aria-label="Move blank chip 1" aria-pressed="false"><span aria-hidden="true"></span></button><button type="button" id="chip-1" class="chip" aria-label="Move blank chip 2" aria-pressed="false"><span aria-hidden="true"></span></button><button type="button" id="chip-2" class="chip" aria-label="Move blank chip 3" aria-pressed="false"><span aria-hidden="true"></span></button></div><div class="landing" aria-hidden="true"></div></div><p id="chip-state" aria-live="polite">No chips moved.</p><div class="controls"><button type="button" id="clear-chips" class="secondary">Clear chips</button><button type="button" id="back-teacher" class="secondary">Back to teacher plan</button></div><small>Your teacher supplies the spoken word and checks the sounds.</small></section>
</main><script>const $=id=>document.getElementById(id);const on=(id,e,fn)=>$(id).addEventListener(e,fn);const txt=(id,v)=>$(id).textContent=v;const esc=s=>String(s).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));const positive=x=>String(x).trim()!==''&&Number.isFinite(Number(x))&&Number(x)>0;const num=x=>Number(x.toPrecision(10)).toString();function enter(id,fn){on(id,'keydown',e=>{if(e.key==='Enter'&&!e.isComposing&&e.keyCode!==229){e.preventDefault();fn()}})}function selectText(id){$(id).focus();$(id).select()}
const phases=[["Listening introduction", 3, "Name individual speech sounds as the unit. Say sun normally; invite a repeat.", "Listen, then repeat when prompted."], ["Teacher model", 5, "Say /s/ /\u028c/ /n/, move one blank chip per sound, then blend smoothly to sun.", "Observe the ordered sound-to-chip sequence."], ["Guided blending and segmentation", 7, "For blending, supply the ordered sounds for sun or fan. For segmentation, say the whole word.", "Blend orally; move one blank chip for each individual sound."], ["Individual oral check", 3, "Give a spoken item without printed words. Record exactly which oral task was asked.", "Blend or segment the spoken word with the supplied support."], ["Review the sound unit", 2, "Compare one word, one syllable, and three phonemes; choose support from the actual attempt.", "Explain or demonstrate the unit used."]];
let scheduleValid=false;
function scheduleState(values,available){if(!/^[1-9]\d*$/.test(String(available))||Number(available)>480)throw Error('Available minutes must be a whole number from 1 to 480.');if(values.some(x=>!/^[1-9]\d*$/.test(String(x))||Number(x)>180))throw Error('Each phase needs 1–180 whole minutes.');const total=values.reduce((a,x)=>a+Number(x),0);let start=0;const ranges=values.map(x=>{const s=start;start+=Number(x);return s+'–'+start});return {total,ranges,ready:total===Number(available)}}
function updateSchedule(){try{const s=scheduleState(phases.map((_,i)=>$('mins-'+i).value),$('available').value);scheduleValid=s.ready;phases.forEach((_,i)=>txt('range-'+i,s.ranges[i]));txt('schedule-summary',s.ready?s.total+'-minute plan ready':s.total+' scheduled / '+$('available').value+' available');txt('schedule-error',s.ready?'':'Adjust phase minutes or available time before preparing the plan.')}catch(e){scheduleValid=false;phases.forEach((_,i)=>txt('range-'+i,'—'));txt('schedule-summary','Schedule needs input');txt('schedule-error',e.message)}if($('export-text'))$('export-text').value=''}
function planText(){if(!scheduleValid)throw Error('Resolve the timetable before preparing the plan.');return phases.map((p,i)=>$('range-'+i).textContent+' min · '+p[0]+'\nTeacher: '+p[2]+'\nLearner: '+p[3]).join('\n\n')}
on('available','input',updateSchedule);phases.forEach((_,i)=>on('mins-'+i,'input',updateSchedule));updateSchedule();
let word='sun',task='blend';const sounds={sun:'/s/ /ʌ/ /n/',fan:'/f/ /æ/ /n/'},moved=new Set();
function scriptView(){txt('sound-key',sounds[word]);txt('oral-script',task==='blend'?'Say '+sounds[word]+' in order. Ask, “What word do these sounds make?” Teacher target: '+word+'.':'Say '+word+' normally. Ask, “Move one blank chip for each sound you hear.” Teacher target: '+sounds[word]+'; three sounds in order.');for(const w of ['sun','fan'])$('word-'+w).setAttribute('aria-pressed',String(word===w));for(const t of ['blend','segment'])$(t).setAttribute('aria-pressed',String(task===t));$('export-text').value=''}
function clearChips(){moved.clear();for(let i=0;i<3;i++)$('chip-'+i).setAttribute('aria-pressed','false');txt('chip-state','No chips moved.')}
for(const w of ['sun','fan'])on('word-'+w,'click',()=>{word=w;scriptView()});for(const t of ['blend','segment'])on(t,'click',()=>{task=t;scriptView()});
on('show-learner','click',()=>{$('teacher-view').hidden=true;$('learner-view').hidden=false;clearChips();$('chip-0').focus()});on('back-teacher','click',()=>{$('learner-view').hidden=true;$('teacher-view').hidden=false;$('show-learner').focus()});
for(let i=0;i<3;i++)on('chip-'+i,'click',()=>{if(moved.has(i))moved.delete(i);else moved.add(i);$('chip-'+i).setAttribute('aria-pressed',String(moved.has(i)));txt('chip-state',moved.size===0?'No chips moved.':moved.size+' '+(moved.size===1?'chip moved.':'chips moved.'))});
on('clear-chips','click',clearChips);for(const n of ['learners','observed','support'])on(n,'input',()=>{$('export-text').value='';txt('export-error','')});
on('prepare','click',()=>{try{$('export-text').value='ORAL PHONEME LESSON — TEACHER COPY\nGroup: '+($('learners').value||'TO CONFIRM')+'\nUnit: individual speech sounds, not syllables.\nMaterials: three blank chips; teacher key separate.\nUS practice keys: sun /s/ /ʌ/ /n/; fan /f/ /æ/ /n/.\n\n'+planText()+'\n\nCurrent rehearsal: '+$('oral-script').textContent+'\nActual attempt: '+($('observed').value||'NOT RECORDED')+'\nSupport / next decision: '+($('support').value||'NOT RECORDED');txt('export-error','')}catch(e){$('export-text').value='';txt('export-error',e.message)}});on('select-text','click',()=>selectText('export-text'));scriptView();
</script></body></html>