| <!DOCTYPE html> |
| <html lang="en"> |
|
|
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Tomato Time Tracker Pro - Pomodoro Timer</title> |
| <link rel="icon" type="image/x-icon" href="/static/favicon.ico"> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://unpkg.com/feather-icons"></script> |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> |
| <style> |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); |
| |
| body { |
| font-family: 'Inter', sans-serif; |
| } |
| |
| .timer-circle { |
| stroke-dasharray: 1000; |
| stroke-dashoffset: 1000; |
| transition: stroke-dashoffset 1s linear; |
| } |
| |
| .pulse-animation { |
| animation: pulse 2s infinite; |
| } |
| |
| @keyframes pulse { |
| 0% { |
| transform: scale(1); |
| } |
| |
| 50% { |
| transform: scale(1.05); |
| } |
| |
| 100% { |
| transform: scale(1); |
| } |
| } |
| |
| .slide-in { |
| animation: slideIn 0.5s ease-out; |
| } |
| |
| @keyframes slideIn { |
| from { |
| transform: translateX(-100%); |
| opacity: 0; |
| } |
| |
| to { |
| transform: translateX(0); |
| opacity: 1; |
| } |
| } |
| </style> |
| </head> |
|
|
| <body class="bg-gradient-to-br from-red-50 to-orange-100 min-h-screen"> |
| |
| <nav class="bg-white/80 backdrop-blur-md border-b border-red-100 sticky top-0 z-50"> |
| <div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8"> |
| <div class="flex justify-between items-center py-4"> |
| <div class="flex items-center space-x-2"> |
| <div |
| class="w-8 h-8 bg-gradient-to-r from-red-500 to-orange-500 rounded-full flex items-center justify-center"> |
| <i data-feather="clock" class="text-white w-4 h-4"></i> |
| </div> |
| <span class="text-xl font-bold bg-gradient-to-r from-red-600 to-orange-600 bg-clip-text text-transparent">TomatoTime Pro</span> |
| </div> |
| <div class="flex space-x-1"> |
| <button id="timerTab" class="px-4 py-2 rounded-lg font-medium transition-all duration-300 bg-red-500 text-white shadow-lg"> |
| <i data-feather="clock" class="w-4 h-4 mr-2 inline"></i>Timer |
| </button> |
| <button id="historyTab" class="px-4 py-2 rounded-lg font-medium transition-all duration-300 text-gray-600 hover:bg-red-50"> |
| <i data-feather="archive" class="w-4 h-4 mr-2 inline"></i>History |
| </button> |
| </div> |
| </div> |
| </div> |
| </nav> |
|
|
| |
| <main class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> |
| |
| <section id="timerSection" class="space-y-8"> |
| |
| <div class="bg-white rounded-3xl shadow-2xl p-8 max-w-2xl mx-auto"> |
| <div class="text-center mb-8"> |
| <h2 class="text-3xl font-bold text-gray-800 mb-2">Focus Session</h2> |
| <p class="text-gray-600">Stay productive with the Pomodoro technique</p> |
| </div> |
|
|
| <div class="relative flex items-center justify-center mb-8"> |
| <div class="relative"> |
| <svg width="300" height="300" class="transform -rotate-90"> |
| <circle cx="150" cy="150" r="140" stroke="#f3f4f6" stroke-width="8" fill="none" /> |
| <circle id="progressCircle" cx="150" cy="150" r="140" stroke="#ef4444" stroke-width="8" |
| fill="none" class="timer-circle" stroke-linecap="round" /> |
| </svg> |
| <div class="absolute inset-0 flex flex-col items-center justify-center"> |
| <div id="timerDisplay" class="text-5xl font-bold text-gray-800 mb-2">25:00</div> |
| <div id="sessionType" class="text-lg font-medium text-gray-600">Work Session</div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="flex justify-center space-x-4 mb-6"> |
| <button id="startBtn" class="bg-green-500 hover:bg-green-600 text-white px-8 py-3 rounded-xl font-semibold transition-all duration-300 transform hover:scale-105 shadow-lg flex items-center"> |
| <i data-feather="play" class="w-5 h-5 mr-2"></i>Start |
| </button> |
| <button id="pauseBtn" class="bg-yellow-500 hover:bg-yellow-600 text-white px-8 py-3 rounded-xl font-semibold transition-all duration-300 transform hover:scale-105 shadow-lg flex items-center hidden"> |
| <i data-feather="pause" class="w-5 h-5 mr-2"></i>Pause |
| </button> |
| <button id="resetBtn" class="bg-gray-500 hover:bg-gray-600 text-white px-8 py-3 rounded-xl font-semibold transition-all duration-300 transform hover:scale-105 shadow-lg flex items-center"> |
| <i data-feather="refresh-cw" class="w-5 h-5 mr-2"></i>Reset |
| </button> |
| </div> |
|
|
| |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4 text-center"> |
| <div class="bg-red-50 rounded-xl p-4"> |
| <div class="text-2xl font-bold text-red-600">25</div> |
| <div class="text-sm text-gray-600">Work Minutes</div> |
| </div> |
| <div class="bg-green-50 rounded-xl p-4"> |
| <div class="text-2xl font-bold text-green-600">5</div> |
| <div class="text-sm text-gray-600">Break Minutes</div> |
| </div> |
| <div class="bg-blue-50 rounded-xl p-4"> |
| <div class="text-2xl font-bold text-blue-600">4</div> |
| <div class="text-sm text-gray-600">Sessions</div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="grid grid-cols-1 md:grid-cols-4 gap-6 max-w-2xl mx-auto"> |
| <div class="bg-white rounded-xl p-4 text-center shadow-lg"> |
| <i data-feather="check-circle" class="w-8 h-8 text-green-500 mx-auto mb-2"></i> |
| <div class="text-2xl font-bold text-gray-800">0</div> |
| <div class="text-sm text-gray-600">Completed</div> |
| </div> |
| <div class="bg-white rounded-xl p-4 text-center shadow-lg"> |
| <i data-feather="clock" class="w-8 h-8 text-blue-500 mx-auto mb-2"></i> |
| <div class="text-2xl font-bold text-gray-800">0</div> |
| <div class="text-sm text-gray-600">Total Time</div> |
| </div> |
| <div class="bg-white rounded-xl p-4 text-center shadow-lg"> |
| <i data-feather="target" class="w-8 h-8 text-purple-500 mx-auto mb-2"></i> |
| <div class="text-2xl font-bold text-gray-800">0</div> |
| <div class="text-sm text-gray-600">Focus Rate</div> |
| </div> |
| <div class="bg-white rounded-xl p-4 text-center shadow-lg"> |
| <i data-feather="trending-up" class="w-8 h-8 text-orange-500 mx-auto mb-2"></i> |
| <div class="text-2xl font-bold text-gray-800">0</div> |
| <div class="text-sm text-gray-600">Streak</div> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section id="historySection" class="space-y-8 hidden"> |
| <div class="bg-white rounded-3xl shadow-2xl p-8"> |
| <div class="flex justify-between items-center mb-6"> |
| <h2 class="text-3xl font-bold text-gray-800">Session History</h2> |
| <button class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg font-medium transition-all duration-300 flex items-center"> |
| <i data-feather="filter" class="w-4 h-4 mr-2"></i>Filter |
| </button> |
| </div> |
|
|
| <div id="historyList" class="space-y-4"> |
| |
| <div class="text-center py-12 text-gray-500"> |
| <i data-feather="archive" class="w-16 h-16 mx-auto mb-4 opacity-50"></i> |
| <p>No sessions completed yet. Start your first Pomodoro!</p> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="bg-white rounded-3xl shadow-2xl p-8"> |
| <h2 class="text-3xl font-bold text-gray-800 mb-6">Your Progress</h2> |
| <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"> |
| <div class="text-center"> |
| <div class="w-20 h-20 mx-auto mb-4 relative"> |
| <svg width="80" height="80" class="transform -rotate-90"> |
| <circle cx="40" cy="40" r="35" stroke="#f3f4f6" stroke-width="6" fill="none" /> |
| <circle cx="40" cy="40" r="35" stroke="#10b981" stroke-width="6" fill="none" |
| stroke-dasharray="220" stroke-dashoffset="66" /> |
| </svg> |
| <div class="absolute inset-0 flex items-center justify-center"> |
| <span class="text-lg font-bold text-gray-800">70%</span> |
| </div> |
| </div> |
| <div class="text-sm text-gray-600">Focus Rate</div> |
| </div> |
|
|
| <div class="text-center"> |
| <div class="text-4xl font-bold text-red-600 mb-2">12</div> |
| <div class="text-sm text-gray-600">Sessions Today</div> |
| </div> |
|
|
| <div class="text-center"> |
| <div class="text-4xl font-bold text-blue-600 mb-2">5h 30m</div> |
| <div class="text-sm text-gray-600">Total Focus Time</div> |
| </div> |
|
|
| <div class="text-center"> |
| <div class="text-4xl font-bold text-green-600 mb-2">7</div> |
| <div class="text-sm text-gray-600">Day Streak</div> |
| </div> |
| </div> |
| </div> |
| </section> |
| </main> |
|
|
| |
| <audio id="notificationSound" preload="auto"> |
| <source src="https://assets.mixkit.co/sfx/preview/mixkit-alarm-digital-clock-beep-989.mp3" type="audio/mpeg"> |
| </audio> |
|
|
| <script> |
| |
| let timer; |
| let timeLeft = 25 * 60; |
| let isRunning = false; |
| let isWorkSession = true; |
| let sessionsCompleted = 0; |
| |
| const timerDisplay = document.getElementById('timerDisplay'); |
| const progressCircle = document.getElementById('progressCircle'); |
| const sessionType = document.getElementById('sessionType'); |
| const startBtn = document.getElementById('startBtn'); |
| const pauseBtn = document.getElementById('pauseBtn'); |
| const resetBtn = document.getElementById('resetBtn'); |
| const notificationSound = document.getElementById('notificationSound'); |
| |
| |
| const timerTab = document.getElementById('timerTab'); |
| const historyTab = document.getElementById('historyTab'); |
| const timerSection = document.getElementById('timerSection'); |
| const historySection = document.getElementById('historySection'); |
| |
| function updateDisplay() { |
| const minutes = Math.floor(timeLeft / 60); |
| const seconds = timeLeft % 60; |
| timerDisplay.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; |
| |
| |
| const totalTime = isWorkSession ? 25 * 60 : 5 * 60; |
| const progress = ((totalTime - timeLeft) / totalTime) * 1000; |
| progressCircle.style.strokeDashoffset = 1000 - progress; |
| } |
| |
| function startTimer() { |
| if (!isRunning) { |
| isRunning = true; |
| startBtn.classList.add('hidden'); |
| pauseBtn.classList.remove('hidden'); |
| |
| timer = setInterval(() => { |
| timeLeft--; |
| updateDisplay(); |
| |
| if (timeLeft === 0) { |
| clearInterval(timer); |
| isRunning = false; |
| notificationSound.play(); |
| |
| sessionsCompleted++; |
| addHistoryItem(); |
| |
| |
| if (isWorkSession) { |
| isWorkSession = false; |
| timeLeft = 5 * 60; |
| sessionType.textContent = 'Break Time'; |
| progressCircle.style.stroke = '#10b981'; |
| } else { |
| isWorkSession = true; |
| timeLeft = 25 * 60; |
| sessionType.textContent = 'Work Session'; |
| progressCircle.style.stroke = '#ef4444'; |
| } |
| |
| startBtn.classList.remove('hidden'); |
| pauseBtn.classList.add('hidden'); |
| updateDisplay(); |
| } |
| }, 1000); |
| } |
| } |
| |
| function pauseTimer() { |
| if (isRunning) { |
| clearInterval(timer); |
| isRunning = false; |
| startBtn.classList.remove('hidden'); |
| pauseBtn.classList.add('hidden'); |
| } |
| } |
| |
| function resetTimer() { |
| clearInterval(timer); |
| isRunning = false; |
| isWorkSession = true; |
| timeLeft = 25 * 60; |
| sessionType.textContent = 'Work Session'; |
| progressCircle.style.stroke = '#ef4444'; |
| startBtn.classList.remove('hidden'); |
| pauseBtn.classList.add('hidden'); |
| updateDisplay(); |
| } |
| |
| function addHistoryItem() { |
| const historyList = document.getElementById('historyList'); |
| const emptyState = historyList.querySelector('.text-center'); |
| |
| if (emptyState) { |
| emptyState.remove(); |
| } |
| |
| const now = new Date(); |
| const timeString = now.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}); |
| const dateString = now.toLocaleDateString(); |
| |
| const historyItem = document.createElement('div'); |
| historyItem.className = 'bg-gradient-to-r from-red-50 to-orange-50 rounded-xl p-4 border-l-4 border-red-500 slide-in'; |
| historyItem.innerHTML = ` |
| <div class="flex justify-between items-center"> |
| <div class="flex items-center space-x-3"> |
| <div class="w-10 h-10 bg-red-500 rounded-full flex items-center justify-center"> |
| <i data-feather="check" class="text-white w-5 h-5"></i> |
| </div> |
| <div> |
| <div class="font-semibold text-gray-800">${isWorkSession ? 'Break Session' : 'Work Session'}</div> |
| <div class="text-sm text-gray-600">${dateString} at ${timeString}</div> |
| </div> |
| </div> |
| <div class="text-right"> |
| <div class="font-bold text-gray-800">${isWorkSession ? '5:00' : '25:00'}</div> |
| <div class="text-sm text-green-600">Completed</div> |
| </div> |
| </div> |
| `; |
| |
| historyList.insertBefore(historyItem, historyList.firstChild); |
| feather.replace(); |
| } |
| |
| |
| startBtn.addEventListener('click', startTimer); |
| pauseBtn.addEventListener('click', pauseTimer); |
| resetBtn.addEventListener('click', resetTimer); |
| |
| timerTab.addEventListener('click', () => { |
| timerSection.classList.remove('hidden'); |
| historySection.classList.add('hidden'); |
| timerTab.classList.add('bg-red-500', 'text-white', 'shadow-lg'); |
| timerTab.classList.remove('text-gray-600', 'hover:bg-red-50'); |
| historyTab.classList.remove('bg-red-500', 'text-white', 'shadow-lg'); |
| historyTab.classList.add('text-gray-600', 'hover:bg-red-50'); |
| }); |
| |
| historyTab.addEventListener('click', () => { |
| historySection.classList.remove('hidden'); |
| timerSection.classList.add('hidden'); |
| historyTab.classList.add('bg-red-500', 'text-white', 'shadow-lg'); |
| historyTab.classList.remove('text-gray-600', 'hover:bg-red-50'); |
| timerTab.classList.remove('bg-red-500', 'text-white', 'shadow-lg'); |
| timerTab.classList.add('text-gray-600', 'hover:bg-red-50'); |
| }); |
| |
| |
| updateDisplay(); |
| feather.replace(); |
| </script> |
| </body> |
|
|
| </html> |