Survive the collision before the pass

HTML for this example

Select the code and use your copy shortcut. Changes in this preview are temporary.

<!doctype html>
<html lang="en-US"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Survive the collision before the pass</title><style>
:root{color-scheme:light;--accent:#41577e;--wash:#e9eef8;--ink:#202d3b;--paper:#fffdf9}*{box-sizing:border-box}body{margin:0;color:var(--ink);background:#eeeae2;font:17px/1.65 system-ui,-apple-system,"Segoe UI",sans-serif}main{max-width:980px;margin:auto;padding:28px 24px 40px}header{border-top:6px solid var(--accent);padding:20px 0 24px}.kicker{color:var(--accent);font-weight:750;letter-spacing:.06em;margin:0 0 8px}h1{font:700 clamp(30px,5vw,46px)/1.15 Georgia,serif;margin:0 0 14px;max-width:780px}h2{font-size:23px;line-height:1.3;margin:0 0 14px}h3{font-size:19px;margin:12px 0 8px}p{margin:0 0 16px}button,input,textarea,select{font:inherit}button{background:white;border:1px solid #87949b;color:var(--ink);border-radius:8px;padding:11px 16px;min-height:48px;cursor:pointer;font-weight:650}button:hover{background:var(--wash)}button[aria-pressed=true],button.primary{background:var(--accent);color:white;border-color:var(--accent)}:focus-visible{outline:3px solid #b45a1e;outline-offset:3px}label{display:block;font-weight:650;margin:14px 0 6px}input:not([type=checkbox]):not([type=radio]),textarea,select{display:block;width:100%;min-width:0;max-width:100%;border:1px solid #80909b;border-radius:7px;padding:11px;background:white;color:var(--ink)}input[type=checkbox],input[type=radio]{width:21px;height:21px;accent-color:var(--accent)}textarea{resize:vertical}.panel{background:var(--paper);border:1px solid #ccd2d1;border-radius:12px;padding:24px;margin-bottom:20px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:20px}.two>.panel{margin:0}.two{margin-bottom:20px}.actions{display:flex;flex-wrap:wrap;gap:10px;margin:18px 0}.result{padding:18px;background:var(--wash);border-left:5px solid var(--accent);margin-top:18px;white-space:pre-line;overflow-wrap:anywhere}.note{color:#465565;font-size:16px}.strip{padding:18px;background:var(--wash);border-radius:8px;margin-bottom:18px}table{border-collapse:collapse;width:100%;font-size:16px;table-layout:fixed}th,td{text-align:left;padding:10px 8px;border-bottom:1px solid #cbd0d1;overflow-wrap:anywhere;vertical-align:top}th{background:var(--wash)}ol,ul{padding-left:24px}li+li{margin-top:8px}pre{font-size:16px;white-space:pre-wrap;overflow-wrap:anywhere}code{font-size:16px}.grid{display:grid;gap:5px;max-width:440px}.grid button{padding:5px;min-width:0}.grid input{padding:5px!important;text-align:center;font-weight:750}blockquote{font:500 26px/1.5 Georgia,serif;margin:16px 0}footer{font-size:16px;color:#465565;margin-top:22px}[hidden]{display:none!important}svg{display:block;max-width:100%;height:auto}mark{background:#e0efad;color:var(--ink)}@media(max-width:560px){main{padding:18px 12px 28px}.panel{padding:20px 16px}.two{grid-template-columns:minmax(0,1fr)}h1{font-size:32px}.actions button{flex:1 1 135px}th,td{padding:8px 5px}.grid button{min-height:40px}blockquote{font-size:24px}}
.arena{position:relative;width:100%;max-width:320px;aspect-ratio:4/3;background:#202d3b;overflow:hidden;border:2px solid #8997a8;margin:18px auto}.entity{position:absolute;border:2px solid white;border-radius:4px}.roadline{position:absolute;top:0;height:100%;border-left:2px dashed #a1acb7}</style></head><body><main><header><p class="kicker">THREE-LANE COURSE · FIXED ORIGINAL SCHEDULE</p><h1>Survive the collision before the pass</h1><p>Original logical course, not a driving simulation. Three obstacles, three lanes, strict hitboxes. Excess frame time slows the simulation.</p></header><section id="region" class="panel" tabindex="0" aria-label="Racing game controls and road"><div class="arena" id="road"><span class="roadline" style="left:35.9%"></span><span class="roadline" style="left:64.1%"></span><div id="entities"></div></div><div class="actions"><button type="button" id="start">Start</button><button type="button" id="left">Left</button><button type="button" id="right">Right</button><button type="button" id="pause">Pause</button><button type="button" id="resume">Resume</button><button type="button" id="reset">Reset</button></div><div id="state" class="result" role="status" aria-live="polite">Ready</div><div id="obstacles" class="result" role="status" aria-live="polite"></div><div class="actions"><button type="button" id="avoid">Inspect planned avoidance</button><button type="button" id="loss">Inspect no-move loss</button></div><p class="note">Focused Arrow Left/Right: one lane per fresh press. Buttons stay inside this game region.</p></section><footer>Finish this fixed course by avoiding all three obstacles; the displayed time uses the simulation clock.</footer><noscript>JavaScript is required for the local controls. The accompanying article contains worked examples.</noscript></main><script>
(()=>{'use strict';const $=id=>document.getElementById(id);
let phase='Ready',lane=1,tick=0,passes=0,generation=0,raf=null,last=0,acc=0;const keys=new Set(),schedule=[{id:'O1',lane:0,at:0},{id:'O2',lane:1,at:200},{id:'O3',lane:2,at:400}];let obs=[];function collision(){return obs.some(o=>o.spawned&&!o.passed&&o.lane===lane&&o.y<220&&o.y+40>180);}function step(){if(phase!=='Running')return;tick++;for(const o of obs){if(tick>=o.at){o.spawned=true;o.y=-40+0.8*(tick-o.at);}}if(collision()){phase='Lost';return;}for(const o of obs)if(o.spawned&&!o.passed&&o.y>=240){o.passed=true;passes++;}if(passes===3)phase='Finished';}function render(){$('state').textContent=phase+' / Time '+(tick/100).toFixed(2)+'s / Lane '+lane+' / Passes '+passes+' of 3';$('obstacles').textContent=obs.map(o=>o.id+' '+(!o.spawned?'pending at '+o.at/100+'s':o.passed?'passed':'lane'+o.lane+' top'+o.y.toFixed(1))).join(' / ');$('entities').replaceChildren();const rect=(l,y,color)=>{const d=document.createElement('div');d.className='entity';d.style.cssText='left:'+(([70,160,250][l]-12)/320*100)+'%;top:'+(y/240*100)+'%;width:7.5%;height:16.6667%;background:'+color;$('entities').append(d);};rect(lane,180,'#b3d770');for(const o of obs)if(o.spawned&&!o.passed)rect(o.lane,o.y,'#be7659');}function reset(){generation++;cancelAnimationFrame(raf);phase='Ready';lane=1;tick=0;passes=0;acc=0;keys.clear();obs=schedule.map(o=>({...o,spawned:false,passed:false,y:-40}));render();}function animate(stamp){const gen=generation;last=stamp;function frame(now){if(gen!==generation||phase!=='Running')return;acc+=Math.min(.1,Math.max(0,(now-last)/1000));last=now;const count=Math.min(10,Math.floor((acc+1e-9)/.01));acc-=count*.01;for(let i=0;i<count&&phase==='Running';i++)step();render();if(phase==='Running')raf=requestAnimationFrame(frame);}raf=requestAnimationFrame(frame);}function start(){if(phase!=='Ready')return;phase='Running';obs[0].spawned=true;animate(performance.now());render();}function pause(){if(phase!=='Running')return;phase='Paused';keys.clear();cancelAnimationFrame(raf);render();}function move(d){if(phase!=='Running')return;lane=Math.max(0,Math.min(2,lane+d));if(collision())phase='Lost';render();}$('start').addEventListener('click',start);$('left').addEventListener('click',()=>move(-1));$('right').addEventListener('click',()=>move(1));$('pause').addEventListener('click',pause);$('resume').addEventListener('click',()=>{if(phase!=='Paused')return;phase='Running';acc=0;keys.clear();animate(performance.now());render();});$('reset').addEventListener('click',reset);$('region').addEventListener('keydown',e=>{if(e.repeat&&e.target.tagName==='BUTTON'){e.preventDefault();return;}if(!['ArrowLeft','ArrowRight'].includes(e.key))return;e.preventDefault();if(e.repeat||keys.has(e.key))return;keys.add(e.key);move(e.key==='ArrowLeft'?-1:1);});$('region').addEventListener('keyup',e=>keys.delete(e.key));$('region').addEventListener('focusout',e=>{keys.clear();if(!e.relatedTarget||!$('region').contains(e.relatedTarget))pause();});document.addEventListener('visibilitychange',()=>{if(document.hidden)pause();});function fixture(avoid){reset();phase='Running';obs[0].spawned=true;for(let i=0;i<800&&phase==='Running';i++){if(avoid&&tick===400)move(-1);step();}render();}$('avoid').addEventListener('click',()=>fixture(true));$('loss').addEventListener('click',()=>fixture(false));reset();
})();
</script></body></html>