@jaymakes · 5/26/2026, 4:21:30 PM
Initial version — all lines are new.
+<style>+ html,body{margin:0;height:100%;background:#F3EFFE;overflow:hidden;font-family:"Geist",ui-sans-serif,system-ui,sans-serif;color:#16110D;-webkit-font-smoothing:antialiased;touch-action:none;user-select:none}+ .wrap{position:absolute;inset:0;display:flex;flex-direction:column;padding:24px 22px}+ .badge{display:inline-flex;align-items:center;gap:6px;align-self:flex-start;background:rgba(255,255,255,0.7);backdrop-filter:blur(10px);border:1px solid rgba(22,17,13,0.06);border-radius:9999px;padding:6px 12px;font-size:12px;font-weight:500;margin-bottom:6px}+ .badge i{width:6px;height:6px;background:#FF5A1F;border-radius:50%;animation:p 1.2s infinite}+ @keyframes p{0%,100%{opacity:1}50%{opacity:.3}}+ h1{font-family:"Instrument Serif",Georgia,serif;font-weight:400;font-size:30px;line-height:1.05;margin:0 0 4px;letter-spacing:-.01em}+ .sub{font-size:13px;color:#5C544C;margin-bottom:16px}+ .canvasWrap{flex:1;position:relative;border-radius:28px;background:#fff;border:1px solid rgba(22,17,13,0.06);box-shadow:0 1px 0 rgba(22,17,13,0.04),0 24px 60px -24px rgba(107,78,254,0.35);overflow:hidden}+ canvas{display:block;width:100%;height:100%;cursor:crosshair}+ .hint{position:absolute;bottom:14px;left:50%;transform:translateX(-50%);font-size:11px;color:#8F857B;background:rgba(255,255,255,0.6);backdrop-filter:blur(8px);padding:6px 12px;border-radius:9999px;pointer-events:none}+ .ctl{margin-top:14px;display:grid;grid-template-columns:1fr 1fr;gap:10px}+ .ctl button{background:#fff;border:1px solid rgba(22,17,13,0.08);border-radius:14px;padding:10px;cursor:pointer;font-family:inherit;font-size:13px;color:#16110D;display:flex;align-items:center;justify-content:center;gap:8px;font-weight:500}+ .ctl button.active{background:#16110D;color:#F4EEE3;border-color:#16110D}+ .ctl button:active{transform:scale(.97)}+ .ctl .dot{width:10px;height:10px;border-radius:50%}+</style>+<div class="wrap">+ <span class="badge"><i></i>tap & drag to draw waves</span>+ <h1>wave.toy</h1>+ <div class="sub">a little frequency painter</div>+ <div class="canvasWrap">+ <canvas id="c"></canvas>+ <div class="hint" id="hint">tap anywhere</div>+ </div>+ <div class="ctl">+ <button data-m="ripple" class="active"><span class="dot" style="background:#6B4EFE"></span>ripple</button>+ <button data-m="trail"><span class="dot" style="background:#FF5A1F"></span>trail</button>+ </div>+</div>+<script>+(()=>{+ const c=document.getElementById('c'),x=c.getContext('2d');+ function resize(){const r=c.getBoundingClientRect();c.width=r.width*devicePixelRatio;c.height=r.height*devicePixelRatio;x.scale(devicePixelRatio,devicePixelRatio)}+ resize();addEventListener('resize',resize);+ let mode='ripple';+ const ripples=[],trail=[];+ function loop(){+ const r=c.getBoundingClientRect();+ x.fillStyle=mode==='trail'?'rgba(243,239,254,0.12)':'rgba(243,239,254,0.18)';+ x.fillRect(0,0,r.width,r.height);+ for(let i=ripples.length-1;i>=0;i--){+ const r0=ripples[i];r0.age++;const rad=r0.age*1.6;+ const a=Math.max(0,1-r0.age/80);+ if(a<=0){ripples.splice(i,1);continue}+ x.strokeStyle='hsla('+r0.h+' 90% 60% / '+(a*0.85)+')';+ x.lineWidth=2.5;+ x.beginPath();x.arc(r0.x,r0.y,rad,0,Math.PI*2);x.stroke();+ x.strokeStyle='hsla('+r0.h+' 90% 70% / '+(a*0.4)+')';+ x.lineWidth=1;+ x.beginPath();x.arc(r0.x,r0.y,rad*1.4,0,Math.PI*2);x.stroke();+ }+ for(let i=trail.length-1;i>=0;i--){+ const t=trail[i];t.age++;const a=Math.max(0,1-t.age/60);+ if(a<=0){trail.splice(i,1);continue}+ x.fillStyle='hsla('+t.h+' 90% 60% / '+a+')';+ x.beginPath();x.arc(t.x,t.y,t.r*a,0,Math.PI*2);x.fill();+ }+ requestAnimationFrame(loop);+ }+ let hue=260,drawing=false;+ function pos(e){const r=c.getBoundingClientRect();const p=e.touches?e.touches[0]:e;return {x:p.clientX-r.left,y:p.clientY-r.top}}+ function add(e){+ const {x:px,y:py}=pos(e);hue=(hue+8)%360;+ if(mode==='ripple') ripples.push({x:px,y:py,age:0,h:hue});+ else trail.push({x:px,y:py,age:0,h:hue,r:8+Math.random()*8});+ document.getElementById('hint').style.opacity=0;+ }+ c.addEventListener('pointerdown',e=>{drawing=true;add(e)});+ c.addEventListener('pointermove',e=>{if(drawing)add(e)});+ c.addEventListener('pointerup',()=>drawing=false);+ c.addEventListener('pointerleave',()=>drawing=false);+ document.querySelectorAll('.ctl button').forEach(b=>{+ b.addEventListener('click',()=>{+ mode=b.dataset.m;+ document.querySelectorAll('.ctl button').forEach(o=>o.classList.toggle('active',o===b));+ });+ });+ loop();+})();+</script>