@liveloop · 5/18/2026, 8:14:55 AM
Initial version — all lines are new.
+<style>+ html,body{height:100%;margin:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;background:radial-gradient(circle at 50% 32%,#241552,#0a0a16);color:#e9d5ff;overflow:hidden;}+ .wrap{display:flex;flex-direction:column;height:100%;box-sizing:border-box;padding:1rem;gap:.7rem;}+ h1{margin:0;text-align:center;font-size:1.3rem;}+ .hint{margin:0;text-align:center;font-size:.72rem;color:#a78bfa;}+ .grid{flex:1;display:grid;grid-template-columns:repeat(3,1fr);gap:.55rem;min-height:0;opacity:0;transition:opacity .25s;}+ .ready .grid{opacity:1;}+ .pad{border:0;border-radius:1rem;cursor:pointer;touch-action:none;user-select:none;-webkit-user-select:none;-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.15rem;font:inherit;color:#0a0a16;font-weight:800;transition:transform .04s,filter .04s;}+ .pad .ic{font-size:min(9vw,2.3rem);line-height:1;pointer-events:none;}+ .pad .lb{font-size:.68rem;letter-spacing:.05em;text-transform:uppercase;pointer-events:none;}+ .pad.hit{filter:brightness(1.6);transform:scale(.93);}+ #gate{position:fixed;inset:0;z-index:10;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.55rem;border:0;background:radial-gradient(circle at 50% 38%,#2d1a63,#0a0a16);color:#e9d5ff;font:inherit;cursor:pointer;touch-action:none;-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none;}+ #gate .em{font-size:3.6rem;line-height:1;pointer-events:none;}+ #gate .gt{font-size:1.15rem;font-weight:800;pointer-events:none;}+ #gate .gs{font-size:.74rem;color:#a78bfa;max-width:26ch;text-align:center;line-height:1.4;min-height:3em;pointer-events:none;}+</style>+<div class="wrap">+ <h1>🎛️ Boom Pad</h1>+ <div class="grid" id="grid"></div>+ <p class="hint">Tap the pads — make your own beat.</p>+</div>+<button id="gate" type="button">+ <span class="em">🔊</span>+ <span class="gt">Tap to start</span>+ <span class="gs" id="gs">One tap switches the sound on — iOS needs this.</span>+</button>+<script>+(function(){+ // Low-latency playback: synthesize each drum LIVE on one shared+ // AudioContext (created/resumed on the start tap, per autoplay rules).+ // The per-drum synth functions schedule at ctx.currentTime, so a tap+ // fires the sound with no media-element delay.+ var AC=window.AudioContext||window.webkitAudioContext;+ var gate=document.getElementById('gate'), gs=document.getElementById('gs');+ var ctx=null;+ function ensureCtx(){ try{ if(!ctx && AC) ctx=new AC(); if(ctx && ctx.state==='suspended') ctx.resume(); }catch(e){ ctx=null; } }++ function noiseBuf(a,dur){+ var n=Math.floor(a.sampleRate*dur), b=a.createBuffer(1,n,a.sampleRate), d=b.getChannelData(0);+ for(var i=0;i<n;i++) d[i]=Math.random()*2-1;+ return b;+ }+ function kick(a){+ var t=a.currentTime,o=a.createOscillator(),g=a.createGain();+ o.frequency.setValueAtTime(160,t); o.frequency.exponentialRampToValueAtTime(48,t+0.13);+ g.gain.setValueAtTime(1,t); g.gain.exponentialRampToValueAtTime(0.001,t+0.32);+ o.connect(g).connect(a.destination); o.start(t); o.stop(t+0.34);+ }+ function snare(a){+ var t=a.currentTime;+ var s=a.createBufferSource(); s.buffer=noiseBuf(a,0.2);+ var hp=a.createBiquadFilter(); hp.type='highpass'; hp.frequency.value=1200;+ var g=a.createGain(); g.gain.setValueAtTime(0.8,t); g.gain.exponentialRampToValueAtTime(0.001,t+0.2);+ s.connect(hp).connect(g).connect(a.destination); s.start(t); s.stop(t+0.21);+ var o=a.createOscillator(),og=a.createGain(); o.type='triangle'; o.frequency.value=190;+ og.gain.setValueAtTime(0.5,t); og.gain.exponentialRampToValueAtTime(0.001,t+0.12);+ o.connect(og).connect(a.destination); o.start(t); o.stop(t+0.13);+ }+ function hat(a,open){+ var t=a.currentTime,dur=open?0.32:0.055;+ var s=a.createBufferSource(); s.buffer=noiseBuf(a,dur);+ var hp=a.createBiquadFilter(); hp.type='highpass'; hp.frequency.value=7500;+ var g=a.createGain(); g.gain.setValueAtTime(0.42,t); g.gain.exponentialRampToValueAtTime(0.001,t+dur);+ s.connect(hp).connect(g).connect(a.destination); s.start(t); s.stop(t+dur+0.01);+ }+ function clap(a){+ var t=a.currentTime;+ for(var k=0;k<3;k++){+ var s=a.createBufferSource(); s.buffer=noiseBuf(a,0.09);+ var bp=a.createBiquadFilter(); bp.type='bandpass'; bp.frequency.value=1600;+ var g=a.createGain(), tt=t+k*0.022;+ g.gain.setValueAtTime(0.55,tt); g.gain.exponentialRampToValueAtTime(0.001,tt+0.09);+ s.connect(bp).connect(g).connect(a.destination); s.start(tt); s.stop(tt+0.1);+ }+ }+ function tom(a){+ var t=a.currentTime,o=a.createOscillator(),g=a.createGain();+ o.frequency.setValueAtTime(200,t); o.frequency.exponentialRampToValueAtTime(95,t+0.22);+ g.gain.setValueAtTime(0.85,t); g.gain.exponentialRampToValueAtTime(0.001,t+0.3);+ o.connect(g).connect(a.destination); o.start(t); o.stop(t+0.32);+ }+ function blip(a,freq,type){+ var t=a.currentTime,o=a.createOscillator(),g=a.createGain();+ o.type=type||'square'; o.frequency.setValueAtTime(freq,t);+ g.gain.setValueAtTime(0.0001,t); g.gain.exponentialRampToValueAtTime(0.32,t+0.01);+ g.gain.exponentialRampToValueAtTime(0.0001,t+0.22);+ o.connect(g).connect(a.destination); o.start(t); o.stop(t+0.24);+ }+ var PADS=[+ {ic:'🥁',lb:'Kick', color:'#fb7185',play:kick},+ {ic:'🪘',lb:'Snare',color:'#fb923c',play:snare},+ {ic:'🎩',lb:'Hat', color:'#facc15',play:function(a){hat(a,false);}},+ {ic:'👏',lb:'Clap', color:'#a3e635',play:clap},+ {ic:'🛢️',lb:'Tom', color:'#34d399',play:tom},+ {ic:'💿',lb:'Open', color:'#22d3ee',play:function(a){hat(a,true);}},+ {ic:'🔵',lb:'Low', color:'#60a5fa',play:function(a){blip(a,196);}},+ {ic:'🟣',lb:'Mid', color:'#c084fc',play:function(a){blip(a,330);}},+ {ic:'🔴',lb:'Zap', color:'#f472b6',play:function(a){blip(a,660,'sawtooth');}}+ ];+ // Start gate: one tap unlocks audio (creates/resumes the live context)+ // and plays a confirmation hit, then reveals the pads.+ function openGate(){+ ensureCtx();+ if(!ctx){ gs.textContent='Audio is not supported on this device.'; return; }+ try{ PADS[0].play(ctx); }catch(e){}+ document.body.classList.add('ready');+ gate.style.display='none';+ }+ gate.addEventListener('touchstart',function(e){ e.preventDefault(); openGate(); },{passive:false});+ gate.addEventListener('click',function(){ openGate(); });++ var grid=document.getElementById('grid');+ PADS.forEach(function(p,idx){+ var b=document.createElement('button');+ b.type='button'; b.className='pad'; b.style.background=p.color;+ b.style.boxShadow='0 0 16px '+p.color+'66';+ var ic=document.createElement('span'); ic.className='ic'; ic.textContent=p.ic;+ var lb=document.createElement('span'); lb.className='lb'; lb.textContent=p.lb;+ b.appendChild(ic); b.appendChild(lb);+ var clear, touchSeenAt=0;+ function hit(){+ ensureCtx();+ if(ctx){ try{ PADS[idx].play(ctx); }catch(e){} }+ b.classList.add('hit');+ clearTimeout(clear); clear=setTimeout(function(){ b.classList.remove('hit'); },130);+ }+ // touchstart drives touch devices; preventDefault() stops iOS from+ // stealing the tap as a scroll / selection. pointerdown drives+ // mouse / stylus, skipped briefly after a touch so a single tap+ // does not fire twice.+ b.addEventListener('touchstart',function(e){+ e.preventDefault();+ touchSeenAt=Date.now();+ hit();+ },{passive:false});+ b.addEventListener('pointerdown',function(e){+ if(Date.now()-touchSeenAt<700) return;+ hit();+ });+ b.addEventListener('contextmenu',function(e){ e.preventDefault(); });+ grid.appendChild(b);+ });+})();+</script>