Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Mindful Moments - Meditation & Breathing Timer</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
/* Full width container for WordPress */ | |
html, body { | |
margin: 0 ; | |
padding: 0 ; | |
width: 100% ; | |
max-width: 100% ; | |
} | |
.breath-circle { | |
transition: all 4s cubic-bezier(0.65, 0, 0.35, 1); | |
transform-origin: center; | |
} | |
.inhale { | |
animation: pulseIn 4s infinite alternate; | |
} | |
.exhale { | |
animation: pulseOut 6s infinite alternate; | |
} | |
@keyframes pulseIn { | |
0% { transform: scale(0.9); opacity: 0.7; } | |
100% { transform: scale(1.2); opacity: 1; } | |
} | |
@keyframes pulseOut { | |
0% { transform: scale(1.2); opacity: 1; } | |
100% { transform: scale(0.9); opacity: 0.7; } | |
} | |
.progress-ring__circle { | |
transition: stroke-dashoffset 0.5s; | |
transform: rotate(-90deg); | |
transform-origin: 50% 50%; | |
} | |
.fade-in { | |
animation: fadeIn 0.5s ease-in; | |
} | |
@keyframes fadeIn { | |
from { opacity: 0; } | |
to { opacity: 1; } | |
} | |
/* Custom container to prevent overflow */ | |
.wp-container { | |
width: 100% ; | |
padding: 0 ; | |
margin: 0 ; | |
} | |
</style> | |
</head> | |
<body class="bg-gradient-to-br from-indigo-900 to-purple-900 min-h-screen text-white font-sans wp-container"> | |
<div class="container mx-auto px-4 py-8 max-w-3xl"> | |
<header class="text-center mb-8"> | |
<h1 class="text-4xl font-bold mb-2">Mindful Moments</h1> | |
<p class="text-xl text-indigo-200">Find your calm with guided breathing exercises</p> | |
</header> | |
<div class="bg-white/10 backdrop-blur-sm rounded-2xl p-6 shadow-xl"> | |
<div class="flex flex-col md:flex-row gap-8"> | |
<!-- Timer Section --> | |
<div class="flex-1"> | |
<div class="flex justify-between items-center mb-6"> | |
<h2 class="text-2xl font-semibold">Meditation Timer</h2> | |
<div class="flex items-center space-x-2"> | |
<button id="sound-toggle" class="p-2 rounded-full hover:bg-white/10 transition"> | |
<i class="fas fa-volume-up"></i> | |
</button> | |
<button id="fullscreen-toggle" class="p-2 rounded-full hover:bg-white/10 transition"> | |
<i class="fas fa-expand"></i> | |
</button> | |
</div> | |
</div> | |
<div class="flex flex-col items-center mb-8"> | |
<div class="relative w-64 h-64 mb-6 flex items-center justify-center"> | |
<!-- Progress ring --> | |
<svg class="w-full h-full absolute" viewBox="0 0 100 100"> | |
<circle class="text-white/10" stroke-width="4" stroke="currentColor" fill="transparent" r="45" cx="50" cy="50" /> | |
<circle id="progress-ring" class="progress-ring__circle text-indigo-400" stroke-width="4" stroke-dasharray="283" stroke-dashoffset="283" stroke-linecap="round" stroke="currentColor" fill="transparent" r="45" cx="50" cy="50" /> | |
</svg> | |
<!-- Breathing circle --> | |
<div id="breath-visual" class="breath-circle absolute w-40 h-40 rounded-full bg-indigo-400/30 flex items-center justify-center"> | |
<div id="breath-text" class="text-2xl font-medium">Ready</div> | |
</div> | |
</div> | |
<div id="time-display" class="text-6xl font-mono mb-6">05:00</div> | |
<div class="flex space-x-4"> | |
<button id="start-btn" class="px-8 py-3 bg-indigo-600 hover:bg-indigo-700 rounded-full font-medium transition"> | |
Start | |
</button> | |
<button id="reset-btn" class="px-8 py-3 bg-white/10 hover:bg-white/20 rounded-full font-medium transition"> | |
Reset | |
</button> | |
</div> | |
</div> | |
</div> | |
<!-- Controls Section --> | |
<div class="flex-1"> | |
<div class="mb-8"> | |
<h3 class="text-xl font-semibold mb-4">Session Length</h3> | |
<div class="grid grid-cols-3 gap-3"> | |
<button class="duration-btn py-3 px-4 bg-white/10 hover:bg-white/20 rounded-lg transition" data-minutes="1">1 min</button> | |
<button class="duration-btn py-3 px-4 bg-white/10 hover:bg-white/20 rounded-lg transition" data-minutes="3">3 min</button> | |
<button class="duration-btn py-3 px-4 bg-white/10 hover:bg-white/20 rounded-lg transition" data-minutes="5">5 min</button> | |
<button class="duration-btn py-3 px-4 bg-white/10 hover:bg-white/20 rounded-lg transition" data-minutes="10">10 min</button> | |
<button class="duration-btn py-3 px-4 bg-white/10 hover:bg-white/20 rounded-lg transition" data-minutes="15">15 min</button> | |
<button class="duration-btn py-3 px-4 bg-white/10 hover:bg-white/20 rounded-lg transition" data-minutes="20">20 min</button> | |
</div> | |
</div> | |
<div class="mb-8"> | |
<h3 class="text-xl font-semibold mb-4">Breathing Pattern</h3> | |
<div class="space-y-3"> | |
<div class="flex items-center space-x-3"> | |
<input type="radio" id="pattern-relax" name="breath-pattern" value="relax" class="hidden peer" checked> | |
<label for="pattern-relax" class="flex-1 py-3 px-4 bg-white/10 hover:bg-white/20 peer-checked:bg-indigo-600 peer-checked:hover:bg-indigo-700 rounded-lg transition cursor-pointer flex items-center justify-between"> | |
<span>Relaxing (4-6-8)</span> | |
<span class="text-xs opacity-70">Inhale-4 Hold-6 Exhale-8</span> | |
</label> | |
</div> | |
<div class="flex items-center space-x-3"> | |
<input type="radio" id="pattern-balance" name="breath-pattern" value="balance" class="hidden peer"> | |
<label for="pattern-balance" class="flex-1 py-3 px-4 bg-white/10 hover:bg-white/20 peer-checked:bg-indigo-600 peer-checked:hover:bg-indigo-700 rounded-lg transition cursor-pointer flex items-center justify-between"> | |
<span>Balanced (4-4-4)</span> | |
<span class="text-xs opacity-70">Inhale-4 Hold-4 Exhale-4</span> | |
</label> | |
</div> | |
<div class="flex items-center space-x-3"> | |
<input type="radio" id="pattern-energy" name="breath-pattern" value="energy" class="hidden peer"> | |
<label for="pattern-energy" class="flex-1 py-3 px-4 bg-white/10 hover:bg-white/20 peer-checked:bg-indigo-600 peer-checked:hover:bg-indigo-700 rounded-lg transition cursor-pointer flex items-center justify-between"> | |
<span>Energizing (4-2-4)</span> | |
<span class="text-xs opacity-70">Inhale-4 Hold-2 Exhale-4</span> | |
</label> | |
</div> | |
</div> | |
</div> | |
<div> | |
<h3 class="text-xl font-semibold mb-4">Ambient Sounds</h3> | |
<div class="grid grid-cols-2 gap-3"> | |
<button class="sound-btn py-3 px-4 bg-white/10 hover:bg-white/20 rounded-lg transition flex items-center space-x-2" data-sound="rain"> | |
<i class="fas fa-cloud-rain"></i> | |
<span>Rain</span> | |
</button> | |
<button class="sound-btn py-3 px-4 bg-white/10 hover:bg-white/20 rounded-lg transition flex items-center space-x-2" data-sound="forest"> | |
<i class="fas fa-tree"></i> | |
<span>Forest</span> | |
</button> | |
<button class="sound-btn py-3 px-4 bg-white/10 hover:bg-white/20 rounded-lg transition flex items-center space-x-2" data-sound="waves"> | |
<i class="fas fa-water"></i> | |
<span>Waves</span> | |
</button> | |
<button class="sound-btn py-3 px-4 bg-white/10 hover:bg-white/20 rounded-lg transition flex items-center space-x-2" data-sound="none"> | |
<i class="fas fa-times"></i> | |
<span>None</span> | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="mt-8 text-center text-indigo-200"> | |
<p>Take a deep breath and find your center. Regular practice can reduce stress and improve focus.</p> | |
</div> | |
</div> | |
<!-- Audio elements with updated free sound sources --> | |
<audio id="inhale-sound" src="https://assets.mixkit.co/active/sounds/preview/mixkit-positive-interface-beep-221.mp3" preload="auto"></audio> | |
<audio id="exhale-sound" src="https://assets.mixkit.co/active/sounds/preview/mixkit-achievement-bell-600.mp3" preload="auto"></audio> | |
<audio id="rain-sound" loop src="https://media.rainymood.com/0.m4a" preload="auto"></audio> | |
<audio id="forest-sound" loop src="https://assets.mixkit.co/active/sounds/preview/mixkit-forest-ambience-1683.mp3" preload="auto"></audio> | |
<audio id="waves-sound" loop src="https://assets.mixkit.co/active/sounds/preview/mixkit-waves-on-the-beach-1183.mp3" preload="auto"></audio> | |
<audio id="end-sound" src="https://assets.mixkit.co/active/sounds/preview/mixkit-marimba-alert-2334.mp3" preload="auto"></audio> | |
<script> | |
document.addEventListener('DOMContentLoaded', function() { | |
// DOM elements | |
const timeDisplay = document.getElementById('time-display'); | |
const startBtn = document.getElementById('start-btn'); | |
const resetBtn = document.getElementById('reset-btn'); | |
const durationBtns = document.querySelectorAll('.duration-btn'); | |
const breathVisual = document.getElementById('breath-visual'); | |
const breathText = document.getElementById('breath-text'); | |
const progressRing = document.getElementById('progress-ring'); | |
const soundToggle = document.getElementById('sound-toggle'); | |
const fullscreenToggle = document.getElementById('fullscreen-toggle'); | |
const soundBtns = document.querySelectorAll('.sound-btn'); | |
// Audio elements | |
const inhaleSound = document.getElementById('inhale-sound'); | |
const exhaleSound = document.getElementById('exhale-sound'); | |
const rainSound = document.getElementById('rain-sound'); | |
const forestSound = document.getElementById('forest-sound'); | |
const wavesSound = document.getElementById('waves-sound'); | |
const endSound = document.getElementById('end-sound'); | |
// Variables | |
let timer; | |
let totalSeconds = 300; // Default 5 minutes | |
let remainingSeconds = totalSeconds; | |
let isRunning = false; | |
let isBreathing = false; | |
let soundEnabled = true; | |
let ambientSound = null; | |
let currentPattern = 'relax'; | |
let inhaleDuration = 4; | |
let holdDuration = 6; | |
let exhaleDuration = 8; | |
// Initialize | |
updateTimeDisplay(); | |
setupEventListeners(); | |
function setupEventListeners() { | |
// Duration buttons | |
durationBtns.forEach(btn => { | |
btn.addEventListener('click', function() { | |
const minutes = parseInt(this.dataset.minutes); | |
totalSeconds = minutes * 60; | |
remainingSeconds = totalSeconds; | |
updateTimeDisplay(); | |
resetProgressRing(); | |
}); | |
}); | |
// Breathing pattern radio buttons | |
document.querySelectorAll('input[name="breath-pattern"]').forEach(radio => { | |
radio.addEventListener('change', function() { | |
currentPattern = this.value; | |
switch(currentPattern) { | |
case 'relax': | |
inhaleDuration = 4; | |
holdDuration = 6; | |
exhaleDuration = 8; | |
break; | |
case 'balance': | |
inhaleDuration = 4; | |
holdDuration = 4; | |
exhaleDuration = 4; | |
break; | |
case 'energy': | |
inhaleDuration = 4; | |
holdDuration = 2; | |
exhaleDuration = 4; | |
break; | |
} | |
}); | |
}); | |
// Sound buttons | |
soundBtns.forEach(btn => { | |
btn.addEventListener('click', function() { | |
const soundType = this.dataset.sound; | |
// Stop all ambient sounds | |
rainSound.pause(); | |
forestSound.pause(); | |
wavesSound.pause(); | |
// Remove active class from all buttons | |
soundBtns.forEach(b => b.classList.remove('bg-indigo-600', 'hover:bg-indigo-700')); | |
if (soundType !== 'none') { | |
// Add active class to clicked button | |
this.classList.add('bg-indigo-600', 'hover:bg-indigo-700'); | |
// Play selected sound | |
ambientSound = soundType; | |
if (soundEnabled) { | |
switch(soundType) { | |
case 'rain': | |
rainSound.currentTime = 0; | |
rainSound.play().catch(e => console.log("Audio play failed:", e)); | |
break; | |
case 'forest': | |
forestSound.currentTime = 0; | |
forestSound.play().catch(e => console.log("Audio play failed:", e)); | |
break; | |
case 'waves': | |
wavesSound.currentTime = 0; | |
wavesSound.play().catch(e => console.log("Audio play failed:", e)); | |
break; | |
} | |
} | |
} else { | |
ambientSound = null; | |
} | |
}); | |
}); | |
// Start button | |
startBtn.addEventListener('click', function() { | |
if (isRunning) { | |
pauseTimer(); | |
this.textContent = 'Resume'; | |
} else { | |
startTimer(); | |
this.textContent = 'Pause'; | |
} | |
}); | |
// Reset button | |
resetBtn.addEventListener('click', resetTimer); | |
// Sound toggle | |
soundToggle.addEventListener('click', toggleSound); | |
// Fullscreen toggle | |
fullscreenToggle.addEventListener('click', toggleFullscreen); | |
} | |
function startTimer() { | |
if (!isRunning) { | |
isRunning = true; | |
// Start ambient sound if selected | |
if (soundEnabled && ambientSound) { | |
switch(ambientSound) { | |
case 'rain': | |
rainSound.play().catch(e => console.log("Audio play failed:", e)); | |
break; | |
case 'forest': | |
forestSound.play().catch(e => console.log("Audio play failed:", e)); | |
break; | |
case 'waves': | |
wavesSound.play().catch(e => console.log("Audio play failed:", e)); | |
break; | |
} | |
} | |
// Start breathing if not already running | |
if (!isBreathing) { | |
startBreathingCycle(); | |
} | |
timer = setInterval(function() { | |
remainingSeconds--; | |
updateTimeDisplay(); | |
updateProgressRing(); | |
if (remainingSeconds <= 0) { | |
clearInterval(timer); | |
isRunning = false; | |
startBtn.textContent = 'Start'; | |
// Play end sound | |
if (soundEnabled) { | |
endSound.currentTime = 0; | |
endSound.play().catch(e => console.log("Audio play failed:", e)); | |
} | |
// Show completion message | |
breathText.textContent = 'Complete!'; | |
breathVisual.classList.remove('inhale', 'exhale'); | |
breathVisual.style.transform = 'scale(1)'; | |
// Stop ambient sound | |
rainSound.pause(); | |
forestSound.pause(); | |
wavesSound.pause(); | |
} | |
}, 1000); | |
} | |
} | |
function pauseTimer() { | |
clearInterval(timer); | |
isRunning = false; | |
// Pause ambient sound | |
rainSound.pause(); | |
forestSound.pause(); | |
wavesSound.pause(); | |
// Pause breathing animation | |
breathVisual.classList.remove('inhale', 'exhale'); | |
breathText.textContent = 'Paused'; | |
} | |
function resetTimer() { | |
clearInterval(timer); | |
isRunning = false; | |
remainingSeconds = totalSeconds; | |
updateTimeDisplay(); | |
resetProgressRing(); | |
startBtn.textContent = 'Start'; | |
// Reset breathing | |
breathVisual.classList.remove('inhale', 'exhale'); | |
breathText.textContent = 'Ready'; | |
breathVisual.style.transform = 'scale(1)'; | |
// Stop all sounds | |
rainSound.pause(); | |
forestSound.pause(); | |
wavesSound.pause(); | |
inhaleSound.pause(); | |
exhaleSound.pause(); | |
endSound.pause(); | |
} | |
function updateTimeDisplay() { | |
const minutes = Math.floor(remainingSeconds / 60); | |
const seconds = remainingSeconds % 60; | |
timeDisplay.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; | |
} | |
function updateProgressRing() { | |
const circumference = 2 * Math.PI * 45; | |
const offset = circumference - (remainingSeconds / totalSeconds) * circumference; | |
progressRing.style.strokeDashoffset = offset; | |
} | |
function resetProgressRing() { | |
progressRing.style.strokeDashoffset = 283; | |
} | |
function startBreathingCycle() { | |
isBreathing = true; | |
breathCycle(); | |
} | |
function breathCycle() { | |
if (!isRunning) return; | |
// Inhale phase | |
breathText.textContent = 'Inhale'; | |
breathVisual.classList.remove('exhale'); | |
breathVisual.classList.add('inhale'); | |
if (soundEnabled) { | |
inhaleSound.currentTime = 0; | |
inhaleSound.play().catch(e => console.log("Audio play failed:", e)); | |
} | |
setTimeout(() => { | |
if (!isRunning) return; | |
// Hold phase | |
breathText.textContent = 'Hold'; | |
breathVisual.classList.remove('inhale'); | |
setTimeout(() => { | |
if (!isRunning) return; | |
// Exhale phase | |
breathText.textContent = 'Exhale'; | |
breathVisual.classList.remove('inhale'); | |
breathVisual.classList.add('exhale'); | |
if (soundEnabled) { | |
exhaleSound.currentTime = 0; | |
exhaleSound.play().catch(e => console.log("Audio play failed:", e)); | |
} | |
setTimeout(() => { | |
if (isRunning) { | |
breathCycle(); | |
} | |
}, exhaleDuration * 1000); | |
}, holdDuration * 1000); | |
}, inhaleDuration * 1000); | |
} | |
function toggleSound() { | |
soundEnabled = !soundEnabled; | |
if (soundEnabled) { | |
soundToggle.innerHTML = '<i class="fas fa-volume-up"></i>'; | |
// Resume ambient sound if one was selected | |
if (ambientSound) { | |
switch(ambientSound) { | |
case 'rain': | |
rainSound.play().catch(e => console.log("Audio play failed:", e)); | |
break; | |
case 'forest': | |
forestSound.play().catch(e => console.log("Audio play failed:", e)); | |
break; | |
case 'waves': | |
wavesSound.play().catch(e => console.log("Audio play failed:", e)); | |
break; | |
} | |
} | |
} else { | |
soundToggle.innerHTML = '<i class="fas fa-volume-mute"></i>'; | |
// Pause all sounds | |
rainSound.pause(); | |
forestSound.pause(); | |
wavesSound.pause(); | |
inhaleSound.pause(); | |
exhaleSound.pause(); | |
} | |
} | |
function toggleFullscreen() { | |
if (!document.fullscreenElement) { | |
document.documentElement.requestFullscreen().catch(err => { | |
console.error(`Error attempting to enable fullscreen: ${err.message}`); | |
}); | |
fullscreenToggle.innerHTML = '<i class="fas fa-compress"></i>'; | |
} else { | |
if (document.exitFullscreen) { | |
document.exitFullscreen(); | |
fullscreenToggle.innerHTML = '<i class="fas fa-expand"></i>'; | |
} | |
} | |
} | |
}); | |
</script> | |
</html> |