Tabletop Challenge Stations — Create station directions, setup/reset checklists and attempt records for short tabletop challenges without turning a generic timer into…
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>Tabletop Challenge Stations</title><style>:root{--bg:#fbf6ff;--ink:#39284f;--accent:#76519f;--line:#ddd0e8;--wash:#f0e4fa;}*{box-sizing:border-box}body{margin:0;font:16px/1.5 Arial,Helvetica,sans-serif;color:var(--ink);background:var(--bg)}main{max-width:1120px;padding:24px;margin:auto}h1,h2,h3,p{margin:0}h1{font-size:clamp(26px,4vw,38px);line-height:1.15;letter-spacing:-.03em}h2{font-size:23px}h3{font-size:18px}header{margin-bottom:20px}header p{margin-top:8px}.eyebrow{text-transform:uppercase;letter-spacing:.13em;font-size:12px;font-weight:bold;color:var(--accent)}button,input,select,textarea{font:inherit}button{cursor:pointer;min-height:44px;padding:9px 15px;border:1px solid var(--accent);background:var(--accent);color:white;border-radius:7px;font-weight:600}button.secondary{background:white;color:var(--accent)}button:disabled{opacity:.45;cursor:default}input,select,textarea{width:100%;min-height:44px;border:1px solid #a2aaba;border-radius:5px;background:white;color:var(--ink);padding:9px}textarea{resize:vertical;min-height:95px}label{display:block;font-size:14px;font-weight:bold;margin:12px 0 5px}input[type=checkbox]{width:20px;min-height:20px;vertical-align:middle;margin-right:8px}:focus-visible{outline:3px solid #df7a16;outline-offset:3px}.actions{display:flex;gap:9px;flex-wrap:wrap;margin:14px 0}.panel{padding:22px;background:white;border:1px solid var(--line);border-radius:12px;min-width:0}.columns{display:grid;grid-template-columns:1.6fr 1fr;gap:20px}.two{display:grid;grid-template-columns:1fr 1fr;gap:18px}.stats{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:15px}.stat{flex:1;min-width:92px;background:var(--wash);padding:10px 14px;border-radius:8px}.stat small{display:block}.stat strong{font-size:25px}.muted{color:#526178;font-size:14px}.notice{padding:14px 16px;background:var(--wash);border-left:4px solid var(--accent);margin:14px 0;overflow-wrap:anywhere}.error{color:#a12e25;font-weight:bold}.below{margin-top:28px;padding-top:22px;border-top:1px solid var(--line)}.below p{margin-top:9px}svg.board{width:100%;display:block;border-radius:10px;border:1px solid var(--line)}table{border-collapse:collapse;width:100%;font-size:14px}td,th{text-align:left;padding:10px;border-bottom:1px solid var(--line);vertical-align:top;overflow-wrap:anywhere}th{font-size:12px;text-transform:uppercase;letter-spacing:.04em}pre{white-space:pre-wrap;overflow-wrap:anywhere;font:14px/1.6 ui-monospace,monospace}.scroll{overflow-x:auto}.tag{font-size:12px;padding:4px 8px;background:var(--wash);border-radius:4px}.big{font-size:clamp(36px,6vw,62px);font-weight:bold;line-height:1.15}.foot{font-size:14px;color:#526178;margin-top:25px}details{margin:18px 0}summary{cursor:pointer;font-weight:bold;min-height:44px;padding:10px 0}[hidden]{display:none!important}@media(max-width:800px){.columns{grid-template-columns:1fr}.two{grid-template-columns:1fr}}@media(max-width:480px){main{padding:18px 14px}.panel{padding:16px}.stat{min-width:78px;padding:8px}.actions button{flex-grow:1}td,th{padding:7px}.big{font-size:38px}}.station-menu{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin:16px 0}.station-menu button{background:#fff;color:#624080;padding:16px;text-align:left;border:2px solid #e1d4ed}.station-menu button[aria-pressed=true]{border-color:#76519f;background:#f0e4fa}.station-menu strong{font-size:23px;display:block}.pieces{display:flex;gap:8px;flex-wrap:wrap;margin:12px 0}.piece{display:grid;place-items:center;width:45px;height:45px;border:1px solid #d0bfdc;background:#fffbef;border-radius:6px;font-size:23px;color:#79509e}.layout{background:#f4edf9;border-radius:8px;padding:16px;min-height:135px}.inventory{font-weight:bold;margin:10px 0}.clock{font-size:64px;font-weight:bold;color:#76519f;font-variant-numeric:tabular-nums}.criteria{padding:16px;background:#fff1c9;border-radius:8px;margin:15px 0}.schedule{display:flex;gap:5px;flex-wrap:wrap;margin:12px 0}.schedule span{padding:10px;background:#e9def3;border-radius:5px;font-size:13px}#attempt-records p{margin:8px 0;padding:12px;border:1px solid var(--line);border-radius:6px}.setup-check{display:flex;align-items:flex-start;gap:6px;font-size:14px}.station-menu span{display:block;font-size:13px}@media(max-width:480px){.station-menu{gap:6px}.station-menu button{padding:10px}.station-menu strong{font-size:19px}}</style></head><body><main><header><p class="eyebrow">Three original seated tabletop activities</p><h1>A minute to try. A clear result to record.</h1><p>Practice first, prepare every piece, then observe each station’s own criterion.</p></header><nav class="station-menu" aria-label="Station cards"><button type="button" id="station-a"><strong>A · Shape sort</strong><span>9 shapes · count 0–9</span></button><button type="button" id="station-b"><strong>B · Digit order</strong><span>6 digits · Yes or No</span></button><button type="button" id="station-c"><strong>C · Direction match</strong><span>4 icons · count 0–4</span></button></nav><div class="columns"><section class="panel"><p class="eyebrow" id="station-id">Station A</p><h2 id="station-title">Shape sort</h2><p class="inventory" id="inventory"></p><p id="directions"></p><div class="criteria" id="criterion"></div><h3>Exact starting layout</h3><div class="layout" id="layout"></div><h3 style="margin-top:18px">Setup / material reset checklist</h3><p id="reset-instructions"></p><label class="setup-check"><input type="checkbox" id="setup"><span>I physically counted all materials and restored the exact start layout and empty destinations.</span></label><div class="actions"><button type="button" id="materials-reset" class="secondary">Begin next setup check</button></div><p class="muted">This clears the local setup check and draft result. It does not move paper materials or erase recorded attempts.</p></section><aside class="panel"><h2>Attempt cue & observer record</h2><label for="mode">Participation mode</label><select id="mode"><option value="timed">Timed · 60 seconds</option><option value="untimed">Untimed · seated alternative</option></select><div class="clock" id="clock">1:00</div><p id="clock-status" role="status">Ready — confirm directions before starting.</p><div class="actions"><button type="button" id="start-timer">Start cue</button><button type="button" id="stop-timer" class="secondary">Stop cue</button><button type="button" id="reset-timer" class="secondary">Reset cue</button></div><p class="muted">The cue cannot inspect paper placements. Record physical start/stop observations below.</p><label for="observations">Actual start / stop and notes</label><textarea id="observations" placeholder="Leave blank until an attempt is observed"></textarea><label for="result-input" id="result-label">Observed correct placements (0–9)</label><input id="result-input" inputmode="numeric" placeholder="Blank until observed"><label for="binary-result" id="binary-label" hidden>All six in ascending order?</label><select id="binary-result" hidden><option value="">Choose observed result</option><option value="Yes">Yes · all 1,2,3,4,5,6</option><option value="No">No · incomplete or out of order</option></select><div class="actions"><button type="button" id="record">Record next attempt</button></div><p id="record-status" role="status" class="error"></p></aside></div><section class="below"><h2>Separate attempt records · current session only</h2><div id="attempt-records"></div><h2 style="margin-top:24px">One-station, two-attempt plan · 9 minutes</h2><div class="schedule"><span>2 min directions / practice</span><span>1 min attempt 1</span><span>2 min reset / buffer</span><span>1 min attempt 2</span><span>3 min debrief</span></div><p>Use the same station twice, reset materials between attempts, and discuss the observed arrangement. An A result of 8 is a count of 8; B with five ordered cards is No; C with three correct positions is 3.</p><p class="foot">Original large-card tabletop packet companion. Offer seated or untimed participation and review materials for actual learners. No food, mouth-held materials, sprinting, ranking or skill-gain claims. Setup, transition and debrief are outside the minute. Records are local and disappear on reload; no automatic sensing, saving or grading. Browser, printing and classroom trials remain unverified.</p></section></main><script>'use strict';const $=id=>document.getElementById(id);const specs={a:{title:'Shape sort',inventory:'9 large cards: 3 circles, 3 squares, 3 triangles; 3 labeled trays.',directions:'Place each shape in its matching tray. Stop calmly, then count correct placements. Unplaced and incorrect cards do not count.',criterion:'Result: count correct placements from 0 to 9. If using a full-completion criterion, declare 9 before the attempt.',layout:'<div class="pieces">○ □ △</div><div class="pieces">○ □ △</div><div class="pieces">○ □ △</div><p>Empty trays: CIRCLE · SQUARE · TRIANGLE</p>',reset:'Count all 9 cards. Empty all 3 trays. Restore 3 rows, each circle / square / triangle.',max:9},b:{title:'Digit order',inventory:'6 large cards: digits 1 through 6, once each; 6 destination spaces.',directions:'Place all six digits in the destination spaces in ascending order. At stop, inspect the complete arrangement.',criterion:'Result: Yes only for the complete row 1, 2, 3, 4, 5, 6. Five ordered cards is No; no partial points.',layout:'<div class="pieces">'+[3,1,6,2,5,4].map(x=>'<span class="piece">'+x+'</span>').join('')+'</div><p>Destination: __ __ __ __ __ __ (all empty)</p>',reset:'Inventory each digit 1–6 once. Restore row 3,1,6,2,5,4. Empty all six destination spaces.'},c:{title:'Direction match',inventory:'4 large cards: circle, square, triangle, star; positions N / E / S / W.',directions:'Place one icon at each direction position, matching the visible key. Count correctly matched positions at stop.',criterion:'Visible key: N circle · E square · S triangle · W star. Result: count correct positions from 0 to 4.',layout:'<div class="pieces">△ □ ☆ ○</div><p>Empty positions: N __ · E __ · S __ · W __</p><p>Keep key visible: N ○ / E □ / S △ / W ☆</p>',reset:'Count all 4 cards. Clear the four positions. Restore start row triangle / square / star / circle.',max:4}};
let station='a',records={a:[],b:[],c:[]},remaining=60,running=false,intervalId=0,lastStamp=0;
function stop(){running=false;clearInterval(intervalId);$('clock-status').textContent='Cue stopped. Inspect the actual arrangement calmly.';}
function clockRender(){$('clock').textContent=$('mode').value==='untimed'?'Untimed':Math.floor(remaining/60)+':'+String(remaining%60).padStart(2,'0');}
function tick(now){if(!running)return;const elapsed=Math.floor((now-lastStamp)/1000);if(elapsed>0){remaining=Math.max(0,remaining-elapsed);lastStamp+=elapsed*1000;clockRender();if(!remaining){stop();$('clock-status').textContent='Time — stop calmly and observe the arrangement.';}}}
function resetCue(){stop();remaining=60;clockRender();$('clock-status').textContent='Ready — confirm directions before starting.';}
function setupReset(){$('setup').checked=false;$('result-input').value='';$('binary-result').value='';$('observations').value='';$('record-status').textContent='';resetCue();}
function choose(k){if(!specs[k])return;station=k;setupReset();render();}
function record(){if(!$('setup').checked){$('record-status').textContent='Confirm the physical setup check before recording.';return;}if(records[station].length>=2){$('record-status').textContent='Both attempts are already recorded for this station. Earlier records are preserved.';return;}const value=station==='b'?$('binary-result').value:$('result-input').value.trim();const valid=station==='b'?['Yes','No'].includes(value):/^[0-9]+$/.test(value)&&Number(value)<=specs[station].max;if(!valid){$('record-status').textContent=station==='b'?'Choose Yes or No for the observed complete-order criterion.':'Enter a whole-number observed count from 0 to '+specs[station].max+'.';return;}if(running){$('record-status').textContent='Stop the cue before recording the observed result.';return;}records[station].push({mode:$('mode').value,result:value,notes:$('observations').value,setup:true});setupReset();$('record-status').textContent='Recorded attempt '+records[station].length+' for station '+station.toUpperCase()+'. Not saved after reload.';renderRecords();}
function renderRecords(){$('attempt-records').textContent='';for(const k of ['a','b','c']){const h=document.createElement('h3');h.textContent='Station '+k.toUpperCase()+' · '+specs[k].title;$('attempt-records').append(h);for(let i=0;i<2;i++){const p=document.createElement('p');const r=records[k][i];p.textContent=r?'Attempt '+(i+1)+' · '+r.mode+' · setup checked · result: '+r.result+' · notes: '+(r.notes||'not supplied'):'Attempt '+(i+1)+' · result blank — not observed';$('attempt-records').append(p);}}}
function render(){const q=specs[station];$('station-id').textContent='Station '+station.toUpperCase();$('station-title').textContent=q.title;$('inventory').textContent=q.inventory;$('directions').textContent=q.directions;$('criterion').textContent=q.criterion;$('layout').innerHTML=q.layout;$('reset-instructions').textContent=q.reset;$('result-label').textContent='Observed correct '+(station==='c'?'positions':'placements')+' (0–'+q.max+')';$('result-label').hidden=station==='b';$('result-input').hidden=station==='b';$('binary-label').hidden=station!=='b';$('binary-result').hidden=station!=='b';for(const k of ['a','b','c'])$('station-'+k).setAttribute('aria-pressed',String(k===station));renderRecords();}
for(const k of ['a','b','c'])$('station-'+k).addEventListener('click',()=>choose(k));$('mode').addEventListener('change',resetCue);$('start-timer').addEventListener('click',()=>{if(running)return;if(!$('setup').checked){$('clock-status').textContent='Confirm the physical setup check first.';return;}if($('mode').value==='untimed'){$('clock-status').textContent='Untimed attempt: proceed at the learner’s pace; record after observing.';return;}if(!remaining){$('clock-status').textContent='Reset the cue for a new attempt.';return;}running=true;lastStamp=Date.now();$('clock-status').textContent='Attempt cue running. Stop calmly at zero.';intervalId=setInterval(()=>tick(Date.now()),200);});$('stop-timer').addEventListener('click',stop);$('reset-timer').addEventListener('click',resetCue);$('materials-reset').addEventListener('click',setupReset);$('record').addEventListener('click',record);$('result-input').addEventListener('input',()=>{const v=$('result-input').value.trim();$('record-status').textContent=v&&(!/^[0-9]+$/.test(v)||Number(v)>specs[station].max)?'Enter a whole-number observed count from 0 to '+specs[station].max+'.':'';});$('binary-result').addEventListener('change',()=>{$('record-status').textContent='';});globalThis.app={choose,record,tick,get:()=>({station,records,remaining,running,lastStamp}),setupReset};render();clockRender();</script></body></html>