From bf1fbbfce6cecfe1f6514d644c289c98b2caac5a Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sun, 7 Jun 2026 03:53:13 -0400 Subject: [PATCH] Polish pass: volume, keyboard, focus, titles, tooltips - Beats get a master volume slider (perceptual curve) - Cmd/Ctrl+S flashes "saved" instead of the browser dialog; Cmd/Ctrl+K jumps to the dictionary input - Editor autofocuses on desktop; lookup input selects-all on focus; panel scrolls to top on new lookups - Browser tab title follows the active draft - Tooltips on sample button and meter check Co-Authored-By: Claude Opus 4.8 (1M context) --- static/index.html | 52 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/static/index.html b/static/index.html index 4456399..300f659 100644 --- a/static/index.html +++ b/static/index.html @@ -275,8 +275,8 @@ Double-click any word to look it up on the right.">
- - + +
@@ -311,6 +311,11 @@ Double-click any word to look it up on the right."> 90 BPM +
+ Volume + + 70% +
@@ -396,6 +401,8 @@ function persist(){ const el = draftsBar.querySelector('.dtab.active .dtitle'); if(el) el.textContent = title; } + document.title = (title && title !== 'Untitled') + ? title + ' · RhymePad' : 'RhymePad — a scratchpad for poets & rappers'; saveDocs(); }catch(e){ /* storage full/blocked */ } } @@ -631,10 +638,13 @@ modeSeg.addEventListener('click', e=>{ document.getElementById('lookupBtn').addEventListener('click', doLookup); document.getElementById('lookupInput').addEventListener('keydown', e=>{ if(e.key==='Enter') doLookup(); }); +const lookupInput = document.getElementById('lookupInput'); +lookupInput.addEventListener('focus', ()=> lookupInput.select()); const resultsBox = document.getElementById('lookupResults'); async function doLookup(){ const word = document.getElementById('lookupInput').value.trim(); if(!word) return; + document.getElementById('tab-lookup').scrollTop = 0; showDefinition(word); // the definition always pins on top resultsBox.innerHTML = '

Searching…

'; try{ @@ -791,7 +801,7 @@ const PATTERNS = { 'West Coast': {bpm:94, kick:[0,6,8,14], snare:[4,12], hat:[0,2,4,6,8,10,12,14], swing:0.08} }; -let actx=null, playing=false, schedTimer=null, current='Boom Bap', step=0, nextTime=0; +let actx=null, masterGain=null, playing=false, schedTimer=null, current='Boom Bap', step=0, nextTime=0; let userTempo = 90; const beatGrid = document.getElementById('beatGrid'); @@ -812,13 +822,26 @@ const tempoEl = document.getElementById('tempo'), tempoVal=document.getElementBy function setTempo(v){ userTempo=+v; tempoEl.value=v; tempoVal.textContent = v+' BPM'; } tempoEl.addEventListener('input', e=> setTempo(e.target.value)); -function ensureCtx(){ if(!actx) actx = new (window.AudioContext||window.webkitAudioContext)(); } +function ensureCtx(){ + if(!actx){ + actx = new (window.AudioContext||window.webkitAudioContext)(); + masterGain = actx.createGain(); + masterGain.connect(actx.destination); + setVol(volEl.value); + } +} +const volEl = document.getElementById('vol'), volVal = document.getElementById('volVal'); +function setVol(v){ + volVal.textContent = v + '%'; + if(masterGain) masterGain.gain.value = Math.pow(v / 100, 2); // perceptual +} +volEl.addEventListener('input', e=> setVol(e.target.value)); function kick(t){ const o=actx.createOscillator(), g=actx.createGain(); o.frequency.setValueAtTime(150,t); o.frequency.exponentialRampToValueAtTime(50,t+0.12); g.gain.setValueAtTime(0.9,t); g.gain.exponentialRampToValueAtTime(0.001,t+0.18); - o.connect(g).connect(actx.destination); o.start(t); o.stop(t+0.2); + o.connect(g).connect(masterGain); o.start(t); o.stop(t+0.2); } function snare(t){ const noise=actx.createBufferSource(); @@ -827,7 +850,7 @@ function snare(t){ noise.buffer=buf; const f=actx.createBiquadFilter(); f.type='highpass'; f.frequency.value=1500; const g=actx.createGain(); g.gain.setValueAtTime(0.6,t); g.gain.exponentialRampToValueAtTime(0.001,t+0.18); - noise.connect(f).connect(g).connect(actx.destination); noise.start(t); noise.stop(t+0.2); + noise.connect(f).connect(g).connect(masterGain); noise.start(t); noise.stop(t+0.2); } function hat(t){ const noise=actx.createBufferSource(); @@ -836,7 +859,7 @@ function hat(t){ noise.buffer=buf; const f=actx.createBiquadFilter(); f.type='highpass'; f.frequency.value=7000; const g=actx.createGain(); g.gain.setValueAtTime(0.3,t); g.gain.exponentialRampToValueAtTime(0.001,t+0.05); - noise.connect(f).connect(g).connect(actx.destination); noise.start(t); noise.stop(t+0.06); + noise.connect(f).connect(g).connect(masterGain); noise.start(t); noise.stop(t+0.06); } function scheduler(){ @@ -869,6 +892,21 @@ function stopBeat(){ document.getElementById('playBeat').addEventListener('click', startBeat); document.getElementById('stopBeat').addEventListener('click', stopBeat); +document.addEventListener('keydown', e=>{ + if(!(e.metaKey || e.ctrlKey)) return; + if(e.key === 's'){ + e.preventDefault(); // it's already saved — say so + const prev = schemeReadout.innerHTML; + schemeReadout.innerHTML = 'saved ✓'; + setTimeout(buildReadout, 900); + }else if(e.key === 'k'){ + e.preventDefault(); + tab('lookup'); + lookupInput.focus(); + } +}); +if(window.matchMedia('(pointer: fine)').matches) editor.focus(); + render(); analyze();