Traveling Wave · Fixed Point and Crest — Separate the motion of a traveling wave pattern from the vertical displacement of a marker held at a fixed spatial coordinate.
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>Traveling Wave · Fixed Point and Crest</title><style>:root{--bg:#f3f5ff;--ink:#27344e;--accent:#4b59b1;--line:#c9d0eb;--wash:#e9edff;}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:16px/1.5 Arial,Helvetica,sans-serif}main{max-width:1100px;margin:auto;padding:26px 24px 54px}h1,h2,h3,p{margin:0}h1{font-size:clamp(27px,4vw,39px);line-height:1.15;letter-spacing:-.03em}h2{font-size:23px}h3{font-size:18px}header{margin-bottom:18px}header p{margin-top:8px}.eyebrow{font-size:12px;letter-spacing:.12em;text-transform:uppercase;font-weight:bold;color:var(--accent)}button,input,textarea,select{font:inherit}button{min-height:44px;padding:9px 15px;background:var(--accent);color:#fff;border:1px solid var(--accent);border-radius:6px;cursor:pointer;font-weight:bold}button.secondary{background:#fff;color:var(--accent)}button:disabled{opacity:.45;cursor:default}button[aria-pressed=true]{box-shadow:inset 0 0 0 2px currentColor}input,textarea,select{width:100%;min-height:44px;border:1px solid #919cad;background:#fff;color:var(--ink);border-radius:5px;padding:9px 11px}textarea{resize:vertical;min-height:94px}input[type=checkbox]{width:20px;min-height:20px;vertical-align:middle;margin-right:8px}label{display:block;margin:11px 0 5px;font-size:14px;font-weight:bold}:focus-visible{outline:3px solid #dc8c21;outline-offset:3px}.actions{display:flex;flex-wrap:wrap;gap:9px;margin:14px 0}.panel{background:#fff;border:1px solid var(--line);border-radius:10px;padding:22px;min-width:0}.columns{display:grid;grid-template-columns:1.45fr 1fr;gap:22px}.two{display:grid;grid-template-columns:1fr 1fr;gap:16px}.three{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}.muted{color:#536176;font-size:14px}.notice{margin:14px 0;background:var(--wash);border-left:4px solid var(--accent);padding:13px 16px;overflow-wrap:anywhere}.error{color:#a42d25;font-weight:bold}.below{margin-top:28px;padding-top:22px;border-top:1px solid var(--line)}.below>p{margin:9px 0}.big{font-size:clamp(34px,5vw,56px);line-height:1.15;font-weight:bold}.tag{font-size:12px;padding:4px 8px;border-radius:4px;background:var(--wash)}.row{display:flex;gap:10px;align-items:end}.row>*{min-width:0}.row .unit{width:110px;flex:none}pre{white-space:pre-wrap;overflow-wrap:anywhere;font:13px/1.6 ui-monospace,monospace}table{border-collapse:collapse;width:100%;font-size:14px}th,td{text-align:left;vertical-align:top;padding:9px;border-bottom:1px solid var(--line)}th{font-size:12px;text-transform:uppercase}.scroll{overflow-x:auto}.foot{font-size:14px;color:#536176;margin-top:24px}summary{cursor:pointer;font-weight:bold;min-height:44px;padding:10px 0}details{margin:18px 0}.readonly{font:13px/1.5 ui-monospace,monospace;min-height:170px}.hash{overflow-wrap:anywhere;font:12px/1.6 ui-monospace,monospace}[hidden]{display:none!important}@media(max-width:800px){.columns{grid-template-columns:1fr}}@media(max-width:520px){main{padding:18px 14px 40px}.panel{padding:17px}.two,.three{grid-template-columns:1fr}.actions button{flex-grow:1}td,th{padding:7px}.row .unit{width:90px}}.plot{background:#fff;border:1px solid var(--line);border-radius:12px;padding:12px}canvas{display:block;width:100%;min-height:240px}.marker-keys{display:flex;flex-wrap:wrap;gap:16px;margin:10px 0;font-size:14px}.key-dot{display:inline-block;width:12px;height:12px;border-radius:50%;background:#316cb4;margin-right:7px}.key-crest{display:inline-block;width:0;height:0;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:13px solid #b66b17;margin-right:7px}.readout{padding:15px;border:1px solid var(--line);border-radius:8px;margin-bottom:12px;background:#fff}.readout strong{display:block;font-size:27px}.formula{font:16px ui-monospace,monospace;padding:14px;background:#e9edff;border-radius:7px;margin-top:15px}input[type=range]{padding:0;border:0;min-height:44px}.time-row{display:flex;gap:10px;align-items:center}.time-row input{max-width:125px}#raw-values{font-size:12px}</style></head><body><main><header><p class="eyebrow">Prescribed sine field · no autoplay</p><h1>One point stays. One crest travels.</h1><p>Scrub time to compare the displacement at x = 1 with the same right-moving crest.</p></header><div class="columns"><section><div class="plot"><canvas id="wave" width="800" height="340" role="img" aria-label="Traveling sine wave with a fixed-coordinate circle and a separately tracked crest triangle">Use the marker readouts and sample table below as the textual alternative.</canvas></div><div class="marker-keys"><span><span class="key-dot"></span>Fixed coordinate x = 1</span><span><span class="key-crest"></span>Tracked crest x_c = 1 + 2t</span></div><label for="time-slider">Time · 0 to 2 seconds</label><input type="range" id="time-slider" min="0" max="2" step="0.01" value="0"><div class="time-row"><label for="time-input">Exact time (s)</label><input id="time-input" value="0" inputmode="decimal"></div><div class="actions"><button type="button" id="snap-0" class="secondary">0 s</button><button type="button" id="snap-half" class="secondary">0.5 s</button><button type="button" id="snap-1" class="secondary">1 s</button><button type="button" id="snap-2" class="secondary">2 s</button></div><div class="actions"><button type="button" id="play">Play</button><button type="button" id="pause" class="secondary">Pause</button><button type="button" id="reset" class="secondary">Reset</button></div><p id="state" role="status" class="muted">Paused at t = 0.00 s</p><p id="error" class="error" role="status"></p></section><aside><div class="readout"><p class="eyebrow">● Fixed coordinate</p><strong id="fixed">x = 1.00 m · y = +0.300 m</strong><p>Horizontal speed: 0 m/s</p></div><div class="readout"><p class="eyebrow">▲ Selected crest · phase π/2</p><strong id="crest">x = 1.00 m · y = +0.300 m</strong><p>Pattern speed: 2 m/s to the right</p></div><div class="panel"><h3>Samples at the accepted time</h3><table><thead><tr><th>x (m)</th><th>y (m)</th></tr></thead><tbody id="samples"></tbody></table><p id="comparison" class="notice"></p></div></aside></div><div class="formula">y(x,t) = 0.3 sin((π/2)x − πt)</div><section class="below"><h2>Model and drawing record</h2><p>Amplitude 0.3 m · wavelength 4 m · period 2 s · phase 0. Plot domain x = 0–8 m. The curve is sampled every 0.02 m and connected with Canvas line segments.</p><p>Canvas mapping: pixel y = baseline − positive scale × model y. Resizing redraws the same accepted time. The backing store follows the display’s pixel ratio; axes remain conceptual meters.</p><details><summary>Raw samples and checkpoint tolerance</summary><pre id="raw-values"></pre><p id="checkpoint"></p></details><p id="motion-note" class="muted"></p><p class="foot">This evaluates an authored mathematical field directly. It is not a string, water, audio or PDE solver, physical experiment or calibrated measurement. Playback advances one model second per active real second and stops at 2 s; hidden pages pause. Reduced motion uses manual snapshots only. Actual Canvas pixels, screen-reader interaction and hosted behavior await separate browser review.</p></section></main><script>'use strict';const $=id=>document.getElementById(id);let t=0,running=false,generation=0,frame=0,startStamp=null,origin=0;const media=matchMedia('(prefers-reduced-motion: reduce)');let reduced=media.matches;
const y=(x,time)=>.3*Math.sin(Math.PI/2*x-Math.PI*time);const clean=v=>Math.abs(v)<5e-13?0:v;const signed=v=>(clean(v)>0?'+':'')+clean(v).toFixed(3);
function points(time){return Array.from({length:401},(_,i)=>({x:i*.02,y:y(i*.02,time)}));}
function stop(){running=false;generation++;cancelAnimationFrame(frame);startStamp=null;}
function accept(value){stop();const raw=String(value).trim();const n=raw===''?NaN:Number(raw);if(!Number.isFinite(n)||n<0||n>2){t=null;$('error').textContent='Enter a finite time from 0 to 2 seconds.';render();return false;}t=n;$('time-input').value=String(n);$('time-slider').value=String(n);$('error').textContent='';render();return true;}
function draw(){const canvas=$('wave');const width=Math.max(280,canvas.getBoundingClientRect().width||800),height=Math.max(240,width*.425),dpr=Math.max(1,Math.min(3,globalThis.devicePixelRatio||1));canvas.width=Math.round(width*dpr);canvas.height=Math.round(height*dpr);canvas.style.height=height+'px';const c=canvas.getContext('2d');if(!c)return;c.setTransform(dpr,0,0,dpr,0,0);c.clearRect(0,0,width,height);c.fillStyle='#fff';c.fillRect(0,0,width,height);if(t===null){c.fillStyle='#8a332e';c.font='16px Arial';c.fillText('No accepted time',30,80);return;}const left=45,right=width-20,top=32,bottom=height-38,base=(top+bottom)/2,scale=(bottom-top)/.8;const px=x=>left+x/8*(right-left),py=value=>base-scale*value;c.font='12px Arial';c.lineWidth=1;c.strokeStyle='#e1e6f2';for(let x=0;x<=8;x++){c.beginPath();c.moveTo(px(x),top);c.lineTo(px(x),bottom);c.stroke();c.fillStyle='#4e607c';c.fillText(String(x),px(x)-3,bottom+19);}for(const v of [-.3,0,.3]){c.beginPath();c.moveTo(left,py(v));c.lineTo(right,py(v));c.stroke();c.fillStyle='#4e607c';c.fillText(signed(v),2,py(v)+4);}c.fillText('x (m)',right-32,height-4);c.fillText('y (m)',5,17);c.strokeStyle='#4459ac';c.lineWidth=2.5;c.beginPath();points(t).forEach((p,i)=>i?c.lineTo(px(p.x),py(p.y)):c.moveTo(px(p.x),py(p.y)));c.stroke();c.setLineDash([4,4]);c.strokeStyle='#78a3ce';c.beginPath();c.moveTo(px(1),top);c.lineTo(px(1),bottom);c.stroke();c.setLineDash([]);c.fillStyle='#316cb4';c.beginPath();c.arc(px(1),py(y(1,t)),6,0,Math.PI*2);c.fill();const cx=px(1+2*t),cy=py(.3);c.fillStyle='#b66b17';c.beginPath();c.moveTo(cx,cy);c.lineTo(cx-7,cy-12);c.lineTo(cx+7,cy-12);c.closePath();c.fill();}
function render(){$('play').disabled=reduced||t===null;$('pause').disabled=!running;if(t===null){$('fixed').textContent='No accepted sample';$('crest').textContent='No accepted sample';$('samples').innerHTML='';$('raw-values').textContent='';$('comparison').textContent='Time is invalid; previous outputs cleared.';$('state').textContent='Paused · no accepted time';draw();return;}$('fixed').textContent='x = 1.00 m · y = '+signed(y(1,t))+' m';$('crest').textContent='x = '+(1+2*t).toFixed(2)+' m · y = +0.300 m';$('samples').innerHTML=[0,1,2,3,4].map(x=>'<tr><td>'+x+'</td><td>'+signed(y(x,t))+'</td></tr>').join('');$('raw-values').textContent=[0,1,2,3,4].map(x=>'x='+x+', y='+y(x,t).toPrecision(17)).join('\n');$('comparison').textContent='At t = '+t.toFixed(2)+' s, fixed x = 1 has y = '+signed(y(1,t))+' m; the selected crest is at x = '+(1+2*t).toFixed(2)+' m.';$('state').textContent=(running?'Playing':'Paused')+' at t = '+t.toFixed(2)+' s';draw();}
function clock(stamp,g){if(g!==generation||!running||t===null||reduced)return;if(startStamp===null)startStamp=stamp;t=Math.min(2,origin+(stamp-startStamp)/1000);$('time-input').value=t.toFixed(3);$('time-slider').value=String(t);if(t===2)stop();render();if(running)frame=requestAnimationFrame(next=>clock(next,g));}
function play(){if(running||reduced||t===null)return;if(t===2)t=0;running=true;generation++;const g=generation;origin=t;startStamp=null;render();frame=requestAnimationFrame(stamp=>clock(stamp,g));}
for(const [id,time] of [['snap-0',0],['snap-half',.5],['snap-1',1],['snap-2',2]])$(id).addEventListener('click',()=>accept(time));$('time-slider').addEventListener('input',()=>accept($('time-slider').value));$('time-input').addEventListener('input',()=>accept($('time-input').value));$('play').addEventListener('click',play);$('pause').addEventListener('click',()=>{stop();render();});$('reset').addEventListener('click',()=>accept(0));document.addEventListener('visibilitychange',()=>{if(document.hidden){stop();render();}});globalThis.addEventListener('resize',draw);media.addEventListener('change',event=>{reduced=event.matches;stop();$('motion-note').textContent=reduced?'Reduced motion: playback disabled; snapshots remain available.':'Manual snapshots or user-started playback are available.';render();});const cases=[[0,.3],[.5,0],[1,-.3],[2,.3]];const maxError=Math.max(...cases.map(([time,value])=>Math.abs(y(1,time)-value)));$('checkpoint').textContent='Fixed-coordinate checkpoint tolerance: absolute error ≤ 1e−9 m. Maximum computed error: '+maxError.toExponential(3)+' m.';$('motion-note').textContent=reduced?'Reduced motion: playback disabled; snapshots remain available.':'Manual snapshots or user-started playback are available.';globalThis.app={y,points,accept,play,stop,clock,draw,get:()=>({t,running,generation,reduced}),media};render();</script></body></html>