const { useState, useEffect, useRef } = React;
const apiKey = ""; // Environment provides this at runtime
const Icons = {
Play: () => ,
Pause: () => ,
RotateCcw: () => ,
Volume2: () => ,
VolumeX: () => ,
Info: () => ,
Sparkles: () => ,
Loader: () =>
};
const App = () => {
const MEDITATION_DURATION = 20 * 60;
const REST_DURATION = 1 * 60;
const TOTAL_DURATION = MEDITATION_DURATION + REST_DURATION;
const [timeLeft, setTimeLeft] = useState(TOTAL_DURATION);
const [isActive, setIsActive] = useState(false);
const [isMuted, setIsMuted] = useState(false);
const [showInfo, setShowInfo] = useState(false);
const [sessionPhase, setSessionPhase] = useState('idle');
const [intention, setIntention] = useState('');
const [aiIntention, setAiIntention] = useState('');
const [reflection, setReflection] = useState('');
const [aiReflection, setAiReflection] = useState('');
const [isGenerating, setIsGenerating] = useState(false);
const timerRef = useRef(null);
const audioContextRef = useRef(null);
const callGemini = async (prompt, systemPrompt) => {
setIsGenerating(true);
try {
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-09-2025:generateContent?key=${apiKey}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contents: [{ parts: [{ text: prompt }] }],
systemInstruction: { parts: [{ text: systemPrompt }] }
})
});
const data = await response.json();
setIsGenerating(false);
return data.candidates?.[0]?.content?.parts?.[0]?.text;
} catch (err) {
setIsGenerating(false);
return null;
}
};
const generateIntention = async () => {
const res = await callGemini(`I'm feeling: ${intention || 'ready'}. Give me a 1-sentence poetic intention.`, "You are a meditation guide.");
if (res) setAiIntention(res);
};
const generateReflection = async () => {
const res = await callGemini(`My session: ${reflection}. Give a short insight.`, "You are a supportive guide.");
if (res) setAiReflection(res);
};
const playChime = (freq = 440, vol = 0.1) => {
if (isMuted) return;
try {
if (!audioContextRef.current) audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)();
const ctx = audioContextRef.current;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(freq, ctx.currentTime);
gain.gain.setValueAtTime(0, ctx.currentTime);
gain.gain.linearRampToValueAtTime(vol, ctx.currentTime + 0.1);
gain.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + 3);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start();
osc.stop(ctx.currentTime + 3);
} catch (e) {}
};
useEffect(() => {
if (isActive && timeLeft > 0) {
timerRef.current = setInterval(() => setTimeLeft(p => p - 1), 1000);
} else if (timeLeft === 0 && isActive) {
setIsActive(false);
setSessionPhase('completed');
playChime(523.25, 0.2);
}
if (timeLeft === REST_DURATION && isActive && sessionPhase !== 'resting') {
setSessionPhase('resting');
playChime(440, 0.15);
}
return () => clearInterval(timerRef.current);
}, [isActive, timeLeft, sessionPhase]);
const toggleTimer = () => {
if (audioContextRef.current?.state === 'suspended') audioContextRef.current.resume();
if (!isActive && sessionPhase === 'idle') setSessionPhase('meditating');
setIsActive(!isActive);
};
const resetTimer = () => {
setIsActive(false);
setTimeLeft(TOTAL_DURATION);
setSessionPhase('idle');
setAiIntention('');
setAiReflection('');
};
const formatTime = (s) => `${Math.floor(s / 60)}:${(s % 60).toString().padStart(2, '0')}`;
const progress = ((TOTAL_DURATION - timeLeft) / TOTAL_DURATION) * 100;
return (
{sessionPhase === 'resting' ? "The Deep Rest" :
sessionPhase === 'meditating' ? "Deep Meditation" :
sessionPhase === 'completed' ? "Peace Returns" : "Begin Your Session"}
{sessionPhase === 'idle' && (
setIntention(e.target.value)} placeholder="How are you arriving?" className="bg-black/40 border border-amber-900/30 rounded-lg px-4 py-2 w-full text-sm outline-none focus:border-amber-500 transition-all"/>
{isGenerating ?
{aiIntention &&
"{aiIntention}"
}
)}
{sessionPhase === 'completed' && (
{!aiReflection ? (
<>
{isGenerating ?
>
) : (
"{aiReflection}"
)}
)}
{isActive ?
{isMuted ?
);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(