@daily_dose · 5/22/2026, 7:36:43 AM
Initial version — all lines are new.
+<style>+ html,body{height:100%;margin:0;overflow:hidden}+ body{font-family:system-ui,-apple-system,Segoe UI,sans-serif;background:linear-gradient(160deg,#ecfeff,#f0fdfa);color:#134e4a}+ #wrap{height:100%;display:flex;flex-direction:column;align-items:center;padding:1.1rem;box-sizing:border-box}+ h2{margin:.2rem 0 .1rem;font-size:.8rem;letter-spacing:.14em;text-transform:uppercase;color:#0d9488}+ #bpm{font-size:.78rem;color:#0d9488;font-weight:700;margin-bottom:.2rem;opacity:.8}+ .snd{border:0;background:transparent;color:#0d9488;font-size:.78rem;font-weight:700;cursor:pointer;margin-bottom:.5rem;opacity:.85}+ .rows{display:flex;gap:1.4rem;margin-bottom:.3rem}+ .stepper{display:flex;flex-direction:column;align-items:center;gap:.3rem}+ .stepper .lab{font-size:.7rem;letter-spacing:.1em;text-transform:uppercase;color:#0f766e}+ .ctl{display:flex;align-items:center;gap:.5rem}+ .ctl button{width:30px;height:30px;border:0;border-radius:50%;background:#99f6e4;color:#134e4a;font-size:1.15rem;font-weight:800;cursor:pointer;line-height:1}+ .ctl .val{font-size:1.05rem;font-weight:700;min-width:2.4em;text-align:center}+ #stage{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.7rem;min-height:0}+ #track{width:72px;height:44vh;max-height:300px;background:#ccfbf1;border-radius:9999px;position:relative;overflow:hidden;box-shadow:inset 0 2px 10px rgba(13,148,136,.12)}+ #fill{position:absolute;left:0;right:0;bottom:0;height:0;background:linear-gradient(180deg,#5eead4,#0d9488);border-radius:9999px}+ #phase{font-size:1.5rem;font-weight:800;min-height:1.4em}+ #count{font-size:.85rem;color:#0d9488;font-weight:700;min-height:1.1em;opacity:.8}+ #go{margin-top:.2rem;padding:.8rem 2.6rem;border:0;border-radius:9999px;background:#0d9488;color:#fff;font-weight:800;font-size:1.05rem;cursor:pointer}+</style>+<div id="wrap">+ <h2>🧘 Breathe</h2>+ <div id="bpm"></div>+ <button id="snd" class="snd">🔔 Sound on</button>+ <div class="rows">+ <div class="stepper"><span class="lab">Inhale</span><div class="ctl"><button data-a="in" data-d="-1">−</button><span class="val" id="inv">4s</span><button data-a="in" data-d="1">+</button></div></div>+ <div class="stepper"><span class="lab">Exhale</span><div class="ctl"><button data-a="out" data-d="-1">−</button><span class="val" id="outv">6s</span><button data-a="out" data-d="1">+</button></div></div>+ </div>+ <div id="stage">+ <div id="track"><div id="fill"></div></div>+ <div id="phase">Ready</div>+ <div id="count"></div>+ </div>+ <button id="go">Start</button>+</div>+<script>+ var inhale=4, exhale=6, running=false, phase='in', timer=null, breaths=0;+ var fill=document.getElementById('fill');+ var actx=null, soundOn=true;+ // Synthesize the cues with Web Audio — no external files (CSP-safe), and+ // the context is created/resumed on a tap so autoplay rules are happy.+ function ensureCtx(){ try{ if(!actx) actx=new (window.AudioContext||window.webkitAudioContext)(); if(actx.state==='suspended') actx.resume(); }catch(e){ actx=null; } }+ function tone(fromHz,toHz,dur){+ if(!soundOn||!actx) return;+ var t=actx.currentTime, osc=actx.createOscillator(), g=actx.createGain();+ osc.type='sine';+ osc.frequency.setValueAtTime(fromHz,t);+ osc.frequency.linearRampToValueAtTime(toHz,t+dur);+ g.gain.setValueAtTime(0.0001,t);+ g.gain.exponentialRampToValueAtTime(0.16,t+0.06);+ g.gain.exponentialRampToValueAtTime(0.0001,t+dur);+ osc.connect(g); g.connect(actx.destination);+ osc.start(t); osc.stop(t+dur+0.05);+ }+ function render(){+ document.getElementById('inv').textContent=inhale+'s';+ document.getElementById('outv').textContent=exhale+'s';+ document.getElementById('bpm').textContent=(60/(inhale+exhale)).toFixed(1)+' breaths / min';+ }+ function setFill(pct,ms){ fill.style.transition='height '+ms+'ms ease-in-out'; fill.style.height=pct+'%'; }+ function step(){+ if(!running) return;+ if(phase==='in'){+ document.getElementById('phase').textContent='Breathe in';+ tone(294, 440, 0.5);+ setFill(100, inhale*1000);+ timer=setTimeout(function(){ phase='out'; step(); }, inhale*1000);+ } else {+ document.getElementById('phase').textContent='Breathe out';+ tone(330, 196, 0.7);+ setFill(0, exhale*1000);+ timer=setTimeout(function(){ phase='in'; breaths++; document.getElementById('count').textContent=breaths+' breath'+(breaths===1?'':'s'); step(); }, exhale*1000);+ }+ }+ function start(){ ensureCtx(); running=true; phase='in'; breaths=0; document.getElementById('count').textContent=''; document.getElementById('go').textContent='Stop'; step(); }+ function stop(){ running=false; clearTimeout(timer); document.getElementById('phase').textContent='Paused'; setFill(0,500); document.getElementById('go').textContent='Start'; }+ document.getElementById('go').onclick=function(){ if(running) stop(); else start(); };+ document.getElementById('snd').onclick=function(){ soundOn=!soundOn; if(soundOn) ensureCtx(); this.textContent=soundOn?'🔔 Sound on':'🔕 Sound off'; };+ Array.prototype.forEach.call(document.querySelectorAll('.ctl button'), function(b){+ b.onclick=function(){+ var which=b.getAttribute('data-a'), d=parseInt(b.getAttribute('data-d'),10);+ if(which==='in') inhale=Math.min(12,Math.max(2,inhale+d)); else exhale=Math.min(12,Math.max(2,exhale+d));+ render();+ };+ });+ render();+</script>