Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>ZenFocus - Lofi Study 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> | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| background-color: #0f172a; | |
| color: #f8fafc; | |
| transition: background-color 0.5s ease; | |
| } | |
| .timer-container { | |
| box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3); | |
| } | |
| .progress-ring__circle { | |
| transition: stroke-dashoffset 0.5s; | |
| transform: rotate(-90deg); | |
| transform-origin: 50% 50%; | |
| } | |
| .music-controls { | |
| backdrop-filter: blur(10px); | |
| background-color: rgba(15, 23, 42, 0.7); | |
| } | |
| .time-option:hover { | |
| transform: scale(1.05); | |
| transition: transform 0.2s ease; | |
| } | |
| .time-option.active { | |
| background-color: #3b82f6; | |
| color: white; | |
| } | |
| .glow { | |
| animation: glow 2s infinite alternate; | |
| } | |
| @keyframes glow { | |
| from { | |
| box-shadow: 0 0 5px rgba(59, 130, 246, 0.5); | |
| } | |
| to { | |
| box-shadow: 0 0 20px rgba(59, 130, 246, 0.8); | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body class="min-h-screen flex flex-col items-center justify-center p-4"> | |
| <div class="max-w-md w-full space-y-8"> | |
| <div class="text-center"> | |
| <h1 class="text-4xl font-bold mb-2 text-blue-400">ZenFocus</h1> | |
| <p class="text-slate-300">Stay focused, stay productive</p> | |
| </div> | |
| <div class="timer-container bg-slate-800 rounded-2xl p-8 relative overflow-hidden"> | |
| <div class="absolute inset-0 bg-gradient-to-br from-blue-900/20 to-slate-900/50"></div> | |
| <div class="relative z-10 flex flex-col items-center"> | |
| <div class="relative w-64 h-64 mb-8"> | |
| <svg class="w-full h-full" viewBox="0 0 100 100"> | |
| <circle | |
| class="text-slate-700" | |
| stroke-width="8" | |
| stroke="currentColor" | |
| fill="transparent" | |
| r="40" | |
| cx="50" | |
| cy="50" | |
| /> | |
| <circle | |
| class="progress-ring__circle text-blue-500" | |
| stroke-width="8" | |
| stroke-linecap="round" | |
| stroke="currentColor" | |
| fill="transparent" | |
| r="40" | |
| cx="50" | |
| cy="50" | |
| /> | |
| </svg> | |
| <div class="absolute inset-0 flex items-center justify-center flex-col"> | |
| <div id="time-display" class="text-5xl font-bold">25:00</div> | |
| <div id="timer-state" class="text-blue-400 font-medium mt-2">Ready</div> | |
| </div> | |
| </div> | |
| <div class="flex space-x-4 mb-8"> | |
| <button id="start-btn" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-full font-medium flex items-center glow"> | |
| <i class="fas fa-play mr-2"></i> Start | |
| </button> | |
| <button id="reset-btn" class="bg-slate-700 hover:bg-slate-600 text-white px-6 py-3 rounded-full font-medium flex items-center"> | |
| <i class="fas fa-redo mr-2"></i> Reset | |
| </button> | |
| </div> | |
| <div class="w-full"> | |
| <h3 class="text-lg font-medium mb-4 text-center">Select Duration</h3> | |
| <div class="grid grid-cols-3 gap-3"> | |
| <button class="time-option py-2 px-4 rounded-lg bg-slate-700 hover:bg-slate-600" data-minutes="15">15 min</button> | |
| <button class="time-option py-2 px-4 rounded-lg bg-slate-700 hover:bg-slate-600 active" data-minutes="25">25 min</button> | |
| <button class="time-option py-2 px-4 rounded-lg bg-slate-700 hover:bg-slate-600" data-minutes="45">45 min</button> | |
| <button class="time-option py-2 px-4 rounded-lg bg-slate-700 hover:bg-slate-600" data-minutes="60">60 min</button> | |
| <button class="time-option py-2 px-4 rounded-lg bg-slate-700 hover:bg-slate-600" data-minutes="90">90 min</button> | |
| <button class="time-option py-2 px-4 rounded-lg bg-slate-700 hover:bg-slate-600" data-minutes="120">120 min</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="music-controls rounded-xl p-4 border border-slate-700"> | |
| <div class="flex items-center justify-between"> | |
| <div class="flex items-center"> | |
| <i class="fas fa-music text-blue-400 mr-3 text-xl"></i> | |
| <div> | |
| <h3 class="font-medium">Lofi Study Beats</h3> | |
| <p class="text-sm text-slate-400">Chill focus music</p> | |
| </div> | |
| </div> | |
| <div class="flex items-center space-x-2"> | |
| <button id="volume-down" class="p-2 text-slate-300 hover:text-white"> | |
| <i class="fas fa-volume-down"></i> | |
| </button> | |
| <button id="play-music" class="p-2 text-slate-300 hover:text-white"> | |
| <i class="fas fa-play"></i> | |
| </button> | |
| <button id="volume-up" class="p-2 text-slate-300 hover:text-white"> | |
| <i class="fas fa-volume-up"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <audio id="lofi-audio" loop> | |
| <source src="https://lofi.stream.laut.fm/zwei" type="audio/mpeg"> | |
| Your browser does not support the audio element. | |
| </audio> | |
| </div> | |
| <div class="text-center text-slate-500 text-sm mt-8"> | |
| <p>Take breaks between sessions for maximum productivity</p> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Timer elements | |
| const timeDisplay = document.getElementById('time-display'); | |
| const timerState = document.getElementById('timer-state'); | |
| const startBtn = document.getElementById('start-btn'); | |
| const resetBtn = document.getElementById('reset-btn'); | |
| const timeOptions = document.querySelectorAll('.time-option'); | |
| // Music elements | |
| const playMusicBtn = document.getElementById('play-music'); | |
| const volumeUpBtn = document.getElementById('volume-up'); | |
| const volumeDownBtn = document.getElementById('volume-down'); | |
| const lofiAudio = document.getElementById('lofi-audio'); | |
| // Timer variables | |
| let timer; | |
| let totalSeconds = 25 * 60; // Default 25 minutes | |
| let remainingSeconds = totalSeconds; | |
| let isRunning = false; | |
| let isMusicPlaying = false; | |
| // Progress ring | |
| const circle = document.querySelector('.progress-ring__circle'); | |
| const radius = circle.r.baseVal.value; | |
| const circumference = 2 * Math.PI * radius; | |
| circle.style.strokeDasharray = circumference; | |
| circle.style.strokeDashoffset = circumference; | |
| // Initialize timer display | |
| updateTimerDisplay(); | |
| // Time selection | |
| timeOptions.forEach(option => { | |
| option.addEventListener('click', function() { | |
| timeOptions.forEach(opt => opt.classList.remove('active')); | |
| this.classList.add('active'); | |
| const minutes = parseInt(this.dataset.minutes); | |
| totalSeconds = minutes * 60; | |
| remainingSeconds = totalSeconds; | |
| updateTimerDisplay(); | |
| updateProgressRing(); | |
| // Reset timer state | |
| if (isRunning) { | |
| clearInterval(timer); | |
| isRunning = false; | |
| startBtn.innerHTML = '<i class="fas fa-play mr-2"></i> Start'; | |
| timerState.textContent = 'Ready'; | |
| } | |
| }); | |
| }); | |
| // Start/Pause timer | |
| startBtn.addEventListener('click', function() { | |
| if (isRunning) { | |
| // Pause timer | |
| clearInterval(timer); | |
| isRunning = false; | |
| this.innerHTML = '<i class="fas fa-play mr-2"></i> Resume'; | |
| timerState.textContent = 'Paused'; | |
| // Pause music if playing | |
| if (isMusicPlaying) { | |
| lofiAudio.pause(); | |
| playMusicBtn.innerHTML = '<i class="fas fa-play"></i>'; | |
| isMusicPlaying = false; | |
| } | |
| } else { | |
| // Start timer | |
| isRunning = true; | |
| this.innerHTML = '<i class="fas fa-pause mr-2"></i> Pause'; | |
| timerState.textContent = 'Focusing...'; | |
| // Start music if not already playing | |
| if (!isMusicPlaying) { | |
| lofiAudio.play().catch(e => console.log("Autoplay prevented:", e)); | |
| playMusicBtn.innerHTML = '<i class="fas fa-pause"></i>'; | |
| isMusicPlaying = true; | |
| } | |
| timer = setInterval(() => { | |
| if (remainingSeconds > 0) { | |
| remainingSeconds--; | |
| updateTimerDisplay(); | |
| updateProgressRing(); | |
| } else { | |
| // Timer completed | |
| clearInterval(timer); | |
| isRunning = false; | |
| startBtn.innerHTML = '<i class="fas fa-play mr-2"></i> Start'; | |
| timerState.textContent = 'Completed!'; | |
| document.querySelector('.timer-container').classList.add('animate-pulse'); | |
| // Play completion sound | |
| const audio = new Audio('https://assets.mixkit.co/sfx/preview/mixkit-alarm-digital-clock-beep-989.mp3'); | |
| audio.play(); | |
| // Stop music | |
| lofiAudio.pause(); | |
| playMusicBtn.innerHTML = '<i class="fas fa-play"></i>'; | |
| isMusicPlaying = false; | |
| } | |
| }, 1000); | |
| } | |
| }); | |
| // Reset timer | |
| resetBtn.addEventListener('click', function() { | |
| clearInterval(timer); | |
| isRunning = false; | |
| remainingSeconds = totalSeconds; | |
| updateTimerDisplay(); | |
| updateProgressRing(); | |
| startBtn.innerHTML = '<i class="fas fa-play mr-2"></i> Start'; | |
| timerState.textContent = 'Ready'; | |
| document.querySelector('.timer-container').classList.remove('animate-pulse'); | |
| // Stop music | |
| lofiAudio.pause(); | |
| playMusicBtn.innerHTML = '<i class="fas fa-play"></i>'; | |
| isMusicPlaying = false; | |
| }); | |
| // Music controls | |
| playMusicBtn.addEventListener('click', function() { | |
| if (isMusicPlaying) { | |
| lofiAudio.pause(); | |
| this.innerHTML = '<i class="fas fa-play"></i>'; | |
| isMusicPlaying = false; | |
| } else { | |
| lofiAudio.play().catch(e => console.log("Play prevented:", e)); | |
| this.innerHTML = '<i class="fas fa-pause"></i>'; | |
| isMusicPlaying = true; | |
| } | |
| }); | |
| volumeUpBtn.addEventListener('click', function() { | |
| if (lofiAudio.volume < 0.9) { | |
| lofiAudio.volume += 0.1; | |
| } | |
| }); | |
| volumeDownBtn.addEventListener('click', function() { | |
| if (lofiAudio.volume > 0.1) { | |
| lofiAudio.volume -= 0.1; | |
| } | |
| }); | |
| // Helper functions | |
| function updateTimerDisplay() { | |
| 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 offset = circumference - (remainingSeconds / totalSeconds) * circumference; | |
| circle.style.strokeDashoffset = offset; | |
| } | |
| // Try to autoplay music (may be blocked by browser) | |
| lofiAudio.volume = 0.5; | |
| document.addEventListener('click', function initAudio() { | |
| // This is needed to unlock audio on mobile devices | |
| lofiAudio.play().then(() => { | |
| lofiAudio.pause(); | |
| playMusicBtn.innerHTML = '<i class="fas fa-play"></i>'; | |
| isMusicPlaying = false; | |
| }).catch(e => { | |
| console.log("Audio initialization failed:", e); | |
| }); | |
| document.removeEventListener('click', initAudio); | |
| }, { once: true }); | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=PyQuarX/studyfocus" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |