Resources · Breathwork

4·2·6 Breathing Guide

Follow the orb. Breathe in as it grows, hold, then breathe out slowly as it shrinks. Five minutes is enough to settle your nervous system — lovely just before sleep.

4 · 2 · 6 Breathing
Round 1
Breathe
Find a comfortable position and relax your shoulders

A gentle wellbeing practice, not medical advice. If you feel light-headed, return to normal breathing.

(function(){ // ---- phase definitions (seconds) ---- const PHASES = [ { key:"inhale", label:"Breathe in", secs:4, color:"#D09C62", scale:1.9 }, { key:"hold", label:"Hold", secs:2, color:"#BF7A5A", scale:1.9 }, { key:"exhale", label:"Breathe out",secs:6, color:"#3E8DA6", scale:1.0 }, ]; const CIRC = 2 * Math.PI * 47; // ~295.3 const orb = document.getElementById('orb'); const halo = document.getElementById('halo'); const phaseEl = document.getElementById('phase'); const countEl = document.getElementById('count'); const bar = document.getElementById('bar'); const cue = document.getElementById('cue'); const roundsEl = document.getElementById('rounds'); const startBtn = document.getElementById('startBtn'); const cinemaBtn = document.getElementById('cinemaBtn'); const soundToggle = document.getElementById('soundToggle'); let running = false; let phaseIdx = 0; let phaseStart = 0; let round = 1; let rafId = null; let audioCtx = null, osc = null, masterGain = null, audioReady = false; // Build a persistent audio graph from inside a user gesture (reliable on mobile). function initAudio(){ if(audioReady) { if(audioCtx.state==='suspended') audioCtx.resume(); return; } try{ audioCtx = new (window.AudioContext||window.webkitAudioContext)(); masterGain = audioCtx.createGain(); masterGain.gain.value = 0.0001; // effectively silent until a tone fires osc = audioCtx.createOscillator(); osc.type = 'sine'; osc.frequency.value = 330; osc.connect(masterGain); masterGain.connect(audioCtx.destination); osc.start(); // runs continuously for the session audioReady = true; if(audioCtx.state==='suspended') audioCtx.resume(); }catch(e){} } // gentle tone to mark phase changes — ramps the persistent gain up then down function playTone(freq, dur){ if(!soundToggle.checked || !audioReady) return; try{ if(audioCtx.state==='suspended') audioCtx.resume(); const t = audioCtx.currentTime; osc.frequency.setValueAtTime(freq, t); masterGain.gain.cancelScheduledValues(t); masterGain.gain.setValueAtTime(Math.max(masterGain.gain.value,0.0001), t); masterGain.gain.exponentialRampToValueAtTime(0.14, t+0.08); masterGain.gain.exponentialRampToValueAtTime(0.0001, t+Math.max(dur,0.3)); }catch(e){} } const cues = { inhale: "Draw the breath in slowly through your nose", hold: "Gently hold — soften your jaw", exhale: "Release slowly through your mouth, longer than the inhale", }; const phaseTone = { inhale:330, hold:392, exhale:262 }; function applyPhase(p){ phaseEl.textContent = p.label; cue.textContent = cues[p.key]; // recolor orb + halo + progress (instant) orb.style.background = "radial-gradient(circle at 38% 32%, rgba(255,255,255,.55) 0%, rgba(255,255,255,0) 42%)," + "radial-gradient(circle at 50% 50%, " + p.color + " 0%, " + shade(p.color) + " 100%)"; orb.style.boxShadow = "0 0 60px -8px " + hexA(p.color,.7) + ", inset 0 -10px 30px rgba(0,0,0,.25)"; halo.style.background = "radial-gradient(circle, " + hexA(p.color,.55) + " 0%, " + hexA(p.color,0) + " 70%)"; bar.style.stroke = p.color === "#3E8DA6" ? "#7FC4DA" : "#E6C49A"; // --- animate scale reliably --- // 1) disable transition and lock in the CURRENT size as the starting point const startScale = (phaseIdx === 0) ? PHASES[2].scale : PHASES[phaseIdx-1].scale; orb.style.transition = "none"; halo.style.transition = "none"; orb.style.transform = "scale(" + startScale + ")"; halo.style.transform = "translate(-50%,-50%) scale(" + (startScale*1.05) + ")"; // 2) force the browser to apply that starting state (reflow) void orb.offsetWidth; // 3) re-enable transition and set the target on the next frame so it interpolates requestAnimationFrame(function(){ orb.style.transition = "transform " + p.secs + "s cubic-bezier(.37,0,.63,1), background .8s ease, box-shadow .8s ease"; halo.style.transition = "transform " + p.secs + "s cubic-bezier(.37,0,.63,1), background .8s ease, opacity .8s ease"; orb.style.transform = "scale(" + p.scale + ")"; halo.style.transform = "translate(-50%,-50%) scale(" + (p.scale*1.05) + ")"; }); playTone(phaseTone[p.key], Math.min(p.secs,1.1)); } function shade(hex){ // darker version for orb gradient base const m = {"#D09C62":"#8a6a44","#BF7A5A":"#7e4f3a","#3E8DA6":"#235e72"}; return m[hex] || "#444"; } function hexA(hex,a){ const r=parseInt(hex.slice(1,3),16),g=parseInt(hex.slice(3,5),16),b=parseInt(hex.slice(5,7),16); return "rgba("+r+","+g+","+b+","+a+")"; } function tick(now){ if(!running) return; const p = PHASES[phaseIdx]; const elapsed = (now - phaseStart)/1000; const remain = Math.max(0, p.secs - elapsed); countEl.textContent = Math.ceil(remain); const frac = Math.min(1, elapsed / p.secs); bar.style.strokeDashoffset = (CIRC * (1 - frac)).toFixed(2); if(elapsed >= p.secs){ phaseIdx = (phaseIdx + 1) % PHASES.length; if(phaseIdx === 0){ round++; roundsEl.textContent = "Round " + round; } phaseStart = now; applyPhase(PHASES[phaseIdx]); } rafId = requestAnimationFrame(tick); } function start(){ initAudio(); if(running){ stop(); return; } running = true; startBtn.textContent = "Pause"; phaseIdx = 0; round = 1; roundsEl.textContent = "Round 1"; applyPhase(PHASES[0]); phaseStart = performance.now(); rafId = requestAnimationFrame(tick); } function stop(){ running = false; startBtn.textContent = "Resume"; cancelAnimationFrame(rafId); } startBtn.addEventListener('click', start); soundToggle.addEventListener('change', ()=>{ if(soundToggle.checked) initAudio(); }); cinemaBtn.addEventListener('click', ()=>{ document.querySelector('.breath-section').classList.toggle('cinema'); cinemaBtn.textContent = document.querySelector('.breath-section').classList.contains('cinema') ? "Exit cinema" : "Cinema mode"; }); })();