study-stopwatch / index.html
shubham6924's picture
undefined - Initial Deployment
dae6fa5 verified
<!DOCTYPE html>
<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>