Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Study Stopwatch</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> | |
| .glow { | |
| text-shadow: 0 0 15px rgba(99, 102, 241, 0.8); | |
| } | |
| .progress-ring__circle { | |
| transition: stroke-dashoffset 0.35s; | |
| transform: rotate(-90deg); | |
| transform-origin: 50% 50%; | |
| } | |
| .pulse { | |
| animation: pulse 1.5s infinite cubic-bezier(0.4, 0, 0.6, 1); | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { | |
| transform: scale(1); | |
| box-shadow: 0 0 0 0 rgba(99, 102, 241, 0.7); | |
| } | |
| 50% { | |
| transform: scale(1.05); | |
| box-shadow: 0 0 0 12px rgba(99, 102, 241, 0); | |
| } | |
| } | |
| .gradient-bg { | |
| background: linear-gradient(135deg, rgba(17, 24, 39, 0.9) 0%, rgba(31, 41, 55, 0.9) 100%); | |
| } | |
| .gradient-header { | |
| background: linear-gradient(90deg, rgba(55, 65, 81, 0.9) 0%, rgba(31, 41, 55, 0.9) 100%); | |
| } | |
| .gradient-button { | |
| background: linear-gradient(135deg, rgba(79, 70, 229, 0.9) 0%, rgba(99, 102, 241, 0.9) 100%); | |
| } | |
| .gradient-button:hover { | |
| background: linear-gradient(135deg, rgba(99, 102, 241, 0.9) 0%, rgba(79, 70, 229, 0.9) 100%); | |
| } | |
| .gradient-break { | |
| background: linear-gradient(135deg, rgba(16, 185, 129, 0.9) 0%, rgba(5, 150, 105, 0.9) 100%); | |
| } | |
| .gradient-break:hover { | |
| background: linear-gradient(135deg, rgba(5, 150, 105, 0.9) 0%, rgba(16, 185, 129, 0.9) 100%); | |
| } | |
| .fade-in { | |
| animation: fadeIn 0.3s ease-in; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .session-item { | |
| transition: all 0.3s ease; | |
| } | |
| .session-item:hover { | |
| transform: translateX(5px); | |
| background: rgba(55, 65, 81, 0.7); | |
| } | |
| .landscape-container { | |
| width: 90vw; | |
| max-width: 1200px; | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 20px; | |
| } | |
| .timer-section { | |
| grid-column: 1; | |
| } | |
| .data-section { | |
| grid-column: 2; | |
| display: flex; | |
| flex-direction: column; | |
| height: 100%; | |
| } | |
| .session-history { | |
| flex: 1; | |
| min-height: 200px; | |
| } | |
| .stats-container { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 15px; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gradient-to-br from-gray-900 to-gray-800 text-gray-100 min-h-screen flex items-center justify-center p-4"> | |
| <div class="landscape-container gradient-bg rounded-2xl shadow-2xl overflow-hidden border border-gray-700 p-6"> | |
| <!-- Left Column - Timer and Controls --> | |
| <div class="timer-section"> | |
| <!-- Header --> | |
| <div class="gradient-header p-4 rounded-lg flex justify-between items-center mb-6"> | |
| <h1 class="text-2xl font-bold text-indigo-400 glow">Study Stopwatch</h1> | |
| <div class="flex space-x-2"> | |
| <button id="theme-toggle" class="text-gray-300 hover:text-blue-400 transition"> | |
| <i class="fas fa-moon"></i> | |
| </button> | |
| <button id="info-btn" class="text-gray-300 hover:text-blue-400 transition"> | |
| <i class="fas fa-info-circle"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Timer Display --> | |
| <div class="flex flex-col items-center mb-6"> | |
| <div class="relative w-64 h-64 mb-6"> | |
| <svg class="w-full h-full" viewBox="0 0 100 100"> | |
| <!-- Background circle --> | |
| <circle class="text-gray-700" stroke-width="8" stroke="currentColor" fill="transparent" r="40" cx="50" cy="50" /> | |
| <!-- Progress circle --> | |
| <circle id="progress-circle" class="progress-ring__circle text-blue-500" stroke-width="8" stroke="currentColor" fill="transparent" r="40" cx="50" cy="50" stroke-dasharray="251.2" stroke-dashoffset="0" /> | |
| </svg> | |
| <div class="absolute inset-0 flex items-center justify-center flex-col"> | |
| <div id="display" class="text-5xl font-mono font-bold mb-2">00:00:00</div> | |
| <div id="status" class="text-blue-400 text-sm uppercase tracking-wider">Ready</div> | |
| </div> | |
| </div> | |
| <!-- Controls --> | |
| <div class="flex space-x-4 mb-6"> | |
| <button id="startBtn" class="gradient-button text-white px-6 py-3 rounded-full font-bold uppercase tracking-wide transition-all duration-300 hover:shadow-lg pulse"> | |
| <i class="fas fa-play mr-2"></i>Start | |
| </button> | |
| <button id="pauseBtn" class="bg-gray-600 hover:bg-gray-500 text-white px-6 py-3 rounded-full font-bold uppercase tracking-wide transition-all duration-300 hover:shadow-lg hidden"> | |
| <i class="fas fa-pause mr-2"></i>Pause | |
| </button> | |
| <button id="resetBtn" class="bg-gradient-to-r from-rose-600 to-pink-600 hover:from-pink-600 hover:to-rose-600 text-white px-6 py-3 rounded-full font-bold uppercase tracking-wide transition-all duration-300 hover:shadow-lg"> | |
| <i class="fas fa-redo mr-2"></i>Reset | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Pomodoro Settings --> | |
| <div class="gradient-header p-4 rounded-lg"> | |
| <h2 class="text-lg font-semibold mb-4 text-blue-400">Study Settings</h2> | |
| <div class="stats-container"> | |
| <div> | |
| <label class="block text-sm text-gray-400 mb-1">Study Duration (min)</label> | |
| <input id="study-time" type="number" min="1" max="120" value="25" class="w-full bg-gray-700 text-white rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
| </div> | |
| <div> | |
| <label class="block text-sm text-gray-400 mb-1">Break Duration (min)</label> | |
| <input id="break-time" type="number" min="1" max="30" value="5" class="w-full bg-gray-700 text-white rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Right Column - Data and History --> | |
| <div class="data-section"> | |
| <!-- Session History --> | |
| <div class="gradient-header p-4 rounded-lg mb-4 session-history"> | |
| <h2 class="text-lg font-semibold mb-4 text-indigo-400 flex items-center"> | |
| <i class="fas fa-history mr-2"></i>Session History | |
| </h2> | |
| <div id="session-list" class="space-y-3 h-64 overflow-y-auto pr-2"> | |
| <!-- Sessions will be added here --> | |
| </div> | |
| </div> | |
| <!-- Session Statistics --> | |
| <div class="gradient-header p-4 rounded-lg"> | |
| <h2 class="text-lg font-semibold mb-4 text-indigo-400 flex items-center"> | |
| <i class="fas fa-chart-bar mr-2"></i>Today's Stats | |
| </h2> | |
| <div class="stats-container"> | |
| <div class="bg-gray-700/30 p-3 rounded-lg border border-gray-600/50"> | |
| <div class="text-sm text-gray-400">Study Time</div> | |
| <div id="total-study" class="text-xl font-bold text-indigo-400">0 min</div> | |
| </div> | |
| <div class="bg-gray-700/30 p-3 rounded-lg border border-gray-600/50"> | |
| <div class="text-sm text-gray-400">Sessions</div> | |
| <div id="total-sessions" class="text-xl font-bold text-green-400">0</div> | |
| </div> | |
| <div class="bg-gray-700/30 p-3 rounded-lg border border-gray-600/50"> | |
| <div class="text-sm text-gray-400">Break Time</div> | |
| <div id="total-break" class="text-xl font-bold text-green-400">0 min</div> | |
| </div> | |
| <div class="bg-gray-700/30 p-3 rounded-lg border border-gray-600/50"> | |
| <div class="text-sm text-gray-400">Productivity</div> | |
| <div id="productivity" class="text-xl font-bold text-blue-400">100%</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Info Modal --> | |
| <div id="info-modal" class="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center hidden z-50"> | |
| <div class="bg-gray-800 rounded-lg p-6 max-w-md w-full mx-4"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="text-xl font-bold text-blue-400">About Study Stopwatch</h3> | |
| <button id="close-modal" class="text-gray-400 hover:text-white"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="space-y-3 text-gray-300"> | |
| <p>This stopwatch helps you track your study sessions using the Pomodoro technique.</p> | |
| <p><span class="text-blue-400 font-semibold">How to use:</span></p> | |
| <ul class="list-disc pl-5 space-y-1"> | |
| <li>Set your preferred study and break durations</li> | |
| <li>Click Start to begin your study session</li> | |
| <li>The timer will automatically switch between study and break periods</li> | |
| <li>Completed sessions are recorded in the history panel</li> | |
| <li>Track your productivity with the statistics panel</li> | |
| </ul> | |
| <p class="pt-2 text-sm text-gray-400">Stay focused and productive!</p> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // DOM Elements | |
| const display = document.getElementById('display'); | |
| const startBtn = document.getElementById('startBtn'); | |
| const pauseBtn = document.getElementById('pauseBtn'); | |
| const resetBtn = document.getElementById('resetBtn'); | |
| const progressCircle = document.getElementById('progress-circle'); | |
| const statusText = document.getElementById('status'); | |
| const sessionList = document.getElementById('session-list'); | |
| const studyTimeInput = document.getElementById('study-time'); | |
| const breakTimeInput = document.getElementById('break-time'); | |
| const themeToggle = document.getElementById('theme-toggle'); | |
| const infoBtn = document.getElementById('info-btn'); | |
| const infoModal = document.getElementById('info-modal'); | |
| const closeModal = document.getElementById('close-modal'); | |
| const totalStudyElement = document.getElementById('total-study'); | |
| const totalSessionsElement = document.getElementById('total-sessions'); | |
| const totalBreakElement = document.getElementById('total-break'); | |
| const productivityElement = document.getElementById('productivity'); | |
| // Timer variables | |
| let startTime; | |
| let elapsedTime = 0; | |
| let timerInterval; | |
| let isRunning = false; | |
| let isStudyTime = true; | |
| let studyDuration = 25 * 60 * 1000; // 25 minutes in milliseconds | |
| let breakDuration = 5 * 60 * 1000; // 5 minutes in milliseconds | |
| let targetTime = studyDuration; | |
| // Statistics variables | |
| let totalStudyTime = 0; | |
| let totalBreakTime = 0; | |
| let sessionCount = 0; | |
| // Initialize | |
| updateDisplay(0); | |
| updateProgressCircle(0); | |
| updateStatistics(); | |
| // Event Listeners | |
| startBtn.addEventListener('click', startTimer); | |
| pauseBtn.addEventListener('click', pauseTimer); | |
| resetBtn.addEventListener('click', resetTimer); | |
| studyTimeInput.addEventListener('change', updateSettings); | |
| breakTimeInput.addEventListener('change', updateSettings); | |
| themeToggle.addEventListener('click', toggleTheme); | |
| infoBtn.addEventListener('click', () => infoModal.classList.remove('hidden')); | |
| closeModal.addEventListener('click', () => infoModal.classList.add('hidden')); | |
| // Timer functions | |
| function startTimer() { | |
| if (!isRunning) { | |
| startTime = Date.now() - elapsedTime; | |
| timerInterval = setInterval(updateTimer, 10); | |
| isRunning = true; | |
| startBtn.classList.add('hidden'); | |
| pauseBtn.classList.remove('hidden'); | |
| statusText.textContent = isStudyTime ? 'Studying' : 'Break Time'; | |
| } | |
| } | |
| function pauseTimer() { | |
| if (isRunning) { | |
| clearInterval(timerInterval); | |
| isRunning = false; | |
| startBtn.classList.remove('hidden'); | |
| pauseBtn.classList.add('hidden'); | |
| statusText.textContent = 'Paused'; | |
| } | |
| } | |
| function resetTimer() { | |
| clearInterval(timerInterval); | |
| isRunning = false; | |
| elapsedTime = 0; | |
| updateDisplay(elapsedTime); | |
| updateProgressCircle(0); | |
| startBtn.classList.remove('hidden'); | |
| pauseBtn.classList.add('hidden'); | |
| statusText.textContent = 'Ready'; | |
| isStudyTime = true; | |
| targetTime = studyDuration; | |
| } | |
| function updateTimer() { | |
| elapsedTime = Date.now() - startTime; | |
| if (elapsedTime >= targetTime) { | |
| // Time's up - switch modes | |
| clearInterval(timerInterval); | |
| isRunning = false; | |
| // Add to session history and statistics | |
| if (isStudyTime) { | |
| totalStudyTime += studyDuration; | |
| sessionCount++; | |
| } else { | |
| totalBreakTime += breakDuration; | |
| } | |
| addSessionToHistory(); | |
| updateStatistics(); | |
| // Switch between study and break | |
| isStudyTime = !isStudyTime; | |
| elapsedTime = 0; | |
| targetTime = isStudyTime ? studyDuration : breakDuration; | |
| // Update UI | |
| statusText.textContent = isStudyTime ? 'Study Time!' : 'Break Time!'; | |
| startBtn.classList.remove('hidden'); | |
| pauseBtn.classList.add('hidden'); | |
| // Show notification | |
| showTimeUpNotification(); | |
| return; | |
| } | |
| updateDisplay(elapsedTime); | |
| updateProgressCircle(elapsedTime / targetTime * 100); | |
| } | |
| function updateDisplay(time) { | |
| const hours = Math.floor(time / 3600000); | |
| const minutes = Math.floor((time % 3600000) / 60000); | |
| const seconds = Math.floor((time % 60000) / 1000); | |
| display.textContent = | |
| `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; | |
| } | |
| function updateProgressCircle(percent) { | |
| const circumference = 251.2; // 2 * π * r (where r=40) | |
| const offset = circumference - (percent / 100) * circumference; | |
| progressCircle.style.strokeDashoffset = offset; | |
| // Change color based on mode | |
| progressCircle.classList.remove('text-blue-500', 'text-green-500'); | |
| progressCircle.classList.add(isStudyTime ? 'text-blue-500' : 'text-green-500'); | |
| } | |
| function updateStatistics() { | |
| totalStudyElement.textContent = `${Math.floor(totalStudyTime / 60000)} min`; | |
| totalBreakElement.textContent = `${Math.floor(totalBreakTime / 60000)} min`; | |
| totalSessionsElement.textContent = sessionCount; | |
| // Calculate productivity percentage | |
| const totalTime = totalStudyTime + totalBreakTime; | |
| const productivity = totalTime > 0 ? Math.round((totalStudyTime / totalTime) * 100) : 100; | |
| productivityElement.textContent = `${productivity}%`; | |
| } | |
| function addSessionToHistory() { | |
| const sessionItem = document.createElement('div'); | |
| sessionItem.className = 'session-item bg-gray-700/50 p-3 rounded-lg flex justify-between items-center border border-gray-600/50 fade-in'; | |
| const sessionType = document.createElement('span'); | |
| sessionType.className = 'font-medium'; | |
| sessionType.textContent = isStudyTime ? 'Break' : 'Study'; | |
| sessionType.classList.add(isStudyTime ? 'text-green-400' : 'text-blue-400'); | |
| const sessionDuration = document.createElement('span'); | |
| sessionDuration.className = 'text-sm text-gray-300'; | |
| const duration = isStudyTime ? breakDuration : studyDuration; | |
| const minutes = Math.floor(duration / 60000); | |
| sessionDuration.textContent = `${minutes} min`; | |
| const sessionTime = document.createElement('span'); | |
| sessionTime.className = 'text-xs text-gray-400'; | |
| sessionTime.textContent = new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}); | |
| sessionItem.appendChild(sessionType); | |
| sessionItem.appendChild(sessionDuration); | |
| sessionItem.appendChild(sessionTime); | |
| sessionList.prepend(sessionItem); | |
| // Limit to 15 sessions | |
| if (sessionList.children.length > 15) { | |
| sessionList.removeChild(sessionList.lastChild); | |
| } | |
| } | |
| function updateSettings() { | |
| studyDuration = parseInt(studyTimeInput.value) * 60 * 1000; | |
| breakDuration = parseInt(breakTimeInput.value) * 60 * 1000; | |
| if (!isRunning) { | |
| targetTime = isStudyTime ? studyDuration : breakDuration; | |
| updateDisplay(0); | |
| updateProgressCircle(0); | |
| } | |
| } | |
| function showTimeUpNotification() { | |
| const notification = document.createElement('div'); | |
| notification.className = 'fixed bottom-4 right-4 bg-gray-800 border-l-4 border-blue-500 text-white px-4 py-3 rounded shadow-lg flex items-center'; | |
| const icon = document.createElement('i'); | |
| icon.className = 'fas fa-bell mr-3 text-blue-400'; | |
| const text = document.createElement('div'); | |
| text.textContent = isStudyTime ? 'Time for a break!' : 'Back to study!'; | |
| notification.appendChild(icon); | |
| notification.appendChild(text); | |
| document.body.appendChild(notification); | |
| setTimeout(() => { | |
| notification.classList.add('opacity-0', 'transition-opacity', 'duration-500'); | |
| setTimeout(() => notification.remove(), 500); | |
| }, 3000); | |
| } | |
| function toggleTheme() { | |
| const icon = themeToggle.querySelector('i'); | |
| if (document.documentElement.classList.contains('dark')) { | |
| document.documentElement.classList.remove('dark'); | |
| document.body.classList.remove('bg-gray-900'); | |
| document.body.classList.add('bg-gray-100'); | |
| icon.classList.replace('fa-moon', 'fa-sun'); | |
| } else { | |
| document.documentElement.classList.add('dark'); | |
| document.body.classList.remove('bg-gray-100'); | |
| document.body.classList.add('bg-gray-900'); | |
| icon.classList.replace('fa-sun', 'fa-moon'); | |
| } | |
| } | |
| // Initialize dark theme | |
| document.documentElement.classList.add('dark'); | |
| </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=shubham6924/study-stopwatch" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |