/* app.jsx — entry reveal, hero, nav, music, overlays, composition */ const { useState: useS, useEffect: useE, useRef: useR } = React; /* ---- fun looping piano (royalty-free, synthesized — no audio file) --------- Bright, celebratory I–V–vi–IV arpeggio with a struck-string envelope + echo. */ function makeAmbient() { let ctx, master, lp, delay, fb, wet, timer, on = false, step = 0; const CH = (b, a, c, d) => ({ bass: b, arp: [a, c, d, c] }); const prog = [ CH(130.81, 261.63, 329.63, 392.00), // C CH(98.00, 246.94, 293.66, 392.00), // G CH(110.00, 261.63, 329.63, 440.00), // Am CH(87.31, 261.63, 349.23, 440.00), // F ]; const STEP = 0.38; function voice(freq, t, dur, peak) { const g = ctx.createGain(); g.gain.setValueAtTime(0.0001, t); g.gain.exponentialRampToValueAtTime(peak, t + 0.01); g.gain.exponentialRampToValueAtTime(0.0001, t + dur); g.connect(lp); const o1 = ctx.createOscillator(); o1.type = 'triangle'; o1.frequency.value = freq; const o2 = ctx.createOscillator(); o2.type = 'sine'; o2.frequency.value = freq * 2; const g2 = ctx.createGain(); g2.gain.value = 0.28; o2.connect(g2); g2.connect(g); o1.connect(g); o1.start(t); o2.start(t); o1.stop(t + dur + 0.1); o2.stop(t + dur + 0.1); } function tick() { if (!on) return; const t = ctx.currentTime + 0.06; const chord = prog[Math.floor(step / 4) % prog.length]; const idx = step % 4; if (idx === 0) voice(chord.bass, t, 2.2, 0.14); voice(chord.arp[idx], t, 1.8, 0.18); step++; timer = setTimeout(tick, STEP * 1000); } return { toggle() { if (!on) { ctx = ctx || new (window.AudioContext || window.webkitAudioContext)(); if (ctx.state === 'suspended') ctx.resume(); if (!master) { master = ctx.createGain(); master.gain.value = 0; master.connect(ctx.destination); lp = ctx.createBiquadFilter(); lp.type = 'lowpass'; lp.frequency.value = 3200; lp.connect(master); delay = ctx.createDelay(); delay.delayTime.value = 0.3; fb = ctx.createGain(); fb.gain.value = 0.24; wet = ctx.createGain(); wet.gain.value = 0.22; lp.connect(delay); delay.connect(fb); fb.connect(delay); delay.connect(wet); wet.connect(master); } on = true; step = 0; master.gain.cancelScheduledValues(ctx.currentTime); master.gain.setTargetAtTime(0.85, ctx.currentTime, 0.8); tick(); } else { on = false; clearTimeout(timer); if (master) master.gain.setTargetAtTime(0, ctx.currentTime, 0.5); } return on; }, get on() { return on; }, }; } const ambient = makeAmbient(); function Entry({ onEnter }) { const [breaking, setBreaking] = useS(false); const sparks = React.useMemo(() => Array.from({ length: 22 }, () => ({ l: Math.random() * 100, t: Math.random() * 100, d: Math.random() * 3, s: 2 + Math.random() * 3, sz: 2 + Math.random() * 3, })), []); const open = () => { if (breaking) return; setBreaking(true); try { ambient.toggle(); } catch (e) {} setTimeout(onEnter, 950); }; return (
{sparks.map((s, i) => ( ))}
You're invited to celebrate
Rajshree turns fifty
R
Tap the seal to open
); } function Hero() { const heroRef = useR(null); const cardRef = useR(null); useE(() => { const cleanDust = window.R50FX ? window.R50FX.heroDust(heroRef.current) : null; let cleanTilt = null; if (cardRef.current && window.R50FX) { // entrance, then enable parallax tilt const a = cardRef.current.animate( [{ opacity: 0, transform: 'translateY(42px) scale(.96)' }, { opacity: 1, transform: 'none' }], { duration: 1300, delay: 250, easing: 'cubic-bezier(.2,.7,.2,1)', fill: 'backwards' } ); a.onfinish = () => { cleanTilt = window.R50FX.tilt(cardRef.current, 6); }; } return () => { cleanDust && cleanDust(); cleanTilt && cleanTilt(); }; }, []); return (
Rajshree
Rajshree
turns
50

An afternoon of love, laughter, and five wonderful decades worth celebrating.

Scroll
); } function Nav({ onMusic, musicOn }) { const [solid, setSolid] = useS(false); const [open, setOpen] = useS(false); useE(() => { const h = () => setSolid(window.scrollY > 80); window.addEventListener('scroll', h); h(); return () => window.removeEventListener('scroll', h); }, []); return ( ); } function Lightbox({ item, onClose }) { if (!item) return null; return ( ); } function Slideshow({ list, onClose }) { const [i, setI] = useS(0); useE(() => { if (!list) return; const id = setInterval(() => setI((x) => (x + 1) % list.length), 4200); const key = (e) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', key); return () => { clearInterval(id); window.removeEventListener('keydown', key); }; }, [list]); if (!list) return null; const m = list[i]; return (
{i + 1} / {list.length}
{(m.name || m.caption) &&
{m.caption || ''}{m.name ? '— ' + m.name : ''}
}
); } function App() { const [entered, setEntered] = useS(false); const [lightItem, setLightItem] = useS(null); const [slides, setSlides] = useS(null); const [toast, setToast] = useS(''); const [musicOn, setMusicOn] = useS(false); const toastTimer = useR(null); useE(() => { window.R50UI = { toast: (msg) => { setToast(msg); clearTimeout(toastTimer.current); toastTimer.current = setTimeout(() => setToast(''), 2600); }, lightbox: (item) => setLightItem(item), slideshow: (list) => { if (list && list.length) setSlides(list); }, }; }, []); useE(() => { document.body.classList.toggle('locked', !entered); }, [entered]); // scroll progress ribbon useE(() => { const bar = document.getElementById('progress'); const h = () => { const max = document.documentElement.scrollHeight - window.innerHeight; const p = max > 0 ? window.scrollY / max : 0; if (bar) bar.style.transform = 'scaleX(' + p + ')'; }; window.addEventListener('scroll', h, { passive: true }); h(); return () => window.removeEventListener('scroll', h); }, [entered]); const toggleMusic = () => { try { setMusicOn(ambient.toggle()); } catch (e) {} }; return ( <>
{entered && } {!entered && { setEntered(true); setMusicOn(ambient.on); }} />}