|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Dot Counter Stopwatch/Timer</title> |
|
<style> |
|
:root { |
|
--color-bg: #1e1e1e; |
|
--color-dot: #ffffff; |
|
--color-active: #f39c12; |
|
--color-inactive: #555555; |
|
--dot-size: 8px; |
|
--grid-gap: 4px; |
|
--animation-duration: 0.3s; |
|
--font-size: 16px; |
|
--container-width: 300px; |
|
} |
|
|
|
body { |
|
margin: 0; |
|
padding: 0; |
|
background-color: var(--color-bg); |
|
color: var(--color-dot); |
|
font-family: Arial, sans-serif; |
|
display: flex; |
|
flex-direction: column; |
|
align-items: center; |
|
justify-content: center; |
|
min-height: 100vh; |
|
overflow: hidden; |
|
box-sizing: border-box; |
|
padding: 20px; |
|
} |
|
|
|
.controls { |
|
margin-bottom: 20px; |
|
display: flex; |
|
flex-wrap: wrap; |
|
gap: 10px; |
|
justify-content: center; |
|
} |
|
|
|
.controls button, .controls input { |
|
padding: 8px 12px; |
|
font-size: var(--font-size); |
|
border: none; |
|
border-radius: 5px; |
|
cursor: pointer; |
|
} |
|
|
|
.controls input[type="color"] { |
|
border: none; |
|
padding: 0; |
|
width: 40px; |
|
height: 40px; |
|
} |
|
|
|
.controls button { |
|
background-color: var(--color-active); |
|
color: var(--color-bg); |
|
transition: background-color var(--animation-duration); |
|
} |
|
|
|
.controls button:hover { |
|
background-color: #d35400; |
|
} |
|
|
|
.controls input { |
|
background-color: var(--color-bg); |
|
color: var(--color-dot); |
|
border: 1px solid var(--color-dot); |
|
width: 60px; |
|
text-align: center; |
|
} |
|
|
|
.timer-display { |
|
font-size: 24px; |
|
margin-bottom: 20px; |
|
text-align: center; |
|
} |
|
|
|
.label { |
|
font-size: var(--font-size); |
|
margin: 10px 0; |
|
text-align: center; |
|
} |
|
|
|
.grid-row { |
|
display: flex; |
|
gap: var(--grid-gap); |
|
margin-bottom: 10px; |
|
} |
|
|
|
.grid-container { |
|
display: flex; |
|
flex-direction: column; |
|
align-items: center; |
|
margin-bottom: 20px; |
|
} |
|
|
|
.dot { |
|
width: var(--dot-size); |
|
height: var(--dot-size); |
|
background-color: var(--color-dot); |
|
border-radius: 50%; |
|
transition: background-color var(--animation-duration); |
|
} |
|
|
|
.dot.active { |
|
background-color: var(--color-active); |
|
} |
|
|
|
.dot.inactive { |
|
background-color: var(--color-inactive); |
|
} |
|
|
|
@media (min-width: 600px) { |
|
:root { |
|
--dot-size: 10px; |
|
--grid-gap: 6px; |
|
--container-width: 400px; |
|
} |
|
} |
|
|
|
@media (min-width: 900px) { |
|
:root { |
|
--dot-size: 12px; |
|
--grid-gap: 8px; |
|
--container-width: 500px; |
|
} |
|
} |
|
|
|
|
|
@keyframes fadeIn { |
|
from { opacity: 0; } |
|
to { opacity: 1; } |
|
} |
|
|
|
.dot.active { |
|
animation: fadeIn var(--animation-duration); |
|
} |
|
|
|
.dot.inactive { |
|
animation: fadeIn var(--animation-duration); |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="controls"> |
|
<div> |
|
<label for="hours-input">Hours:</label> |
|
<input type="number" id="hours-input" value="0" min="0"> |
|
</div> |
|
<div> |
|
<label for="minutes-input">Minutes:</label> |
|
<input type="number" id="minutes-input" value="0" min="0"> |
|
</div> |
|
<div> |
|
<label for="seconds-input">Seconds:</label> |
|
<input type="number" id="seconds-input" value="0" min="0"> |
|
</div> |
|
|
|
|
|
<button id="start-timer">Start Timer</button> |
|
<button id="pause">Pause</button> |
|
<button id="reset">Reset</button> |
|
<div> | </div> |
|
<button id="start-stopwatch">Start Stopwatch</button> |
|
</div> |
|
|
|
<div> |
|
<label for="active-dot-color">Active Dot Color:</label> |
|
<input type="color" id="active-dot-color" value="#f39c12"> |
|
</div> |
|
<div> |
|
<label for="inactive-dot-color">Inactive Dot Color:</label> |
|
<input type="color" id="inactive-dot-color" value="#555555"> |
|
</div> |
|
|
|
<div class="timer-display" id="timer-display">00:00:00</div> |
|
|
|
<div class="grid-container"> |
|
<div class="label">Seconds:</div> |
|
<div class="grid-row" id="seconds-row-1"></div> |
|
<div class="grid-row" id="seconds-row-2"></div> |
|
<div class="grid-row" id="seconds-row-3"></div> |
|
</div> |
|
|
|
<div class="grid-container"> |
|
<div class="label">Minutes:</div> |
|
<div class="grid-row" id="minutes-row-1"></div> |
|
<div class="grid-row" id="minutes-row-2"></div> |
|
<div class="grid-row" id="minutes-row-3"></div> |
|
</div> |
|
|
|
<div class="grid-container"> |
|
<div class="label">Hours:</div> |
|
<div class="grid-row" id="hours-row-1"></div> |
|
<div class="grid-row" id="hours-row-2"></div> |
|
<div class="grid-row" id="hours-row-3"></div> |
|
</div> |
|
<script> |
|
const startStopwatchButton = document.getElementById('start-stopwatch'); |
|
const startTimerButton = document.getElementById('start-timer'); |
|
const pauseButton = document.getElementById('pause'); |
|
const resetButton = document.getElementById('reset'); |
|
const timerDisplay = document.getElementById('timer-display'); |
|
const secondsRows = [ |
|
document.getElementById('seconds-row-1'), |
|
document.getElementById('seconds-row-2'), |
|
document.getElementById('seconds-row-3') |
|
]; |
|
const minutesRows = [ |
|
document.getElementById('minutes-row-1'), |
|
document.getElementById('minutes-row-2'), |
|
document.getElementById('minutes-row-3') |
|
]; |
|
const hoursRows = [ |
|
document.getElementById('hours-row-1'), |
|
document.getElementById('hours-row-2'), |
|
document.getElementById('hours-row-3') |
|
]; |
|
const hoursInput = document.getElementById('hours-input'); |
|
const minutesInput = document.getElementById('minutes-input'); |
|
const secondsInput = document.getElementById('seconds-input'); |
|
|
|
let timerInterval; |
|
let totalSeconds = 0; |
|
let isRunning = false; |
|
let isPaused = false; |
|
let isStopwatch = true; |
|
let previousSeconds = 0, previousMinutes = 0, previousHours = 0; |
|
|
|
function updateDisplay() { |
|
const hours = Math.floor(totalSeconds / 3600); |
|
const minutes = Math.floor((totalSeconds % 3600) / 60); |
|
const seconds = totalSeconds % 60; |
|
if (!isStopwatch && totalSeconds <= 0) { |
|
timerDisplay.textContent = "DONE"; |
|
timerDisplay.style.color = "green"; |
|
timerDisplay.style.fontSize = "3em"; |
|
} else { |
|
timerDisplay.textContent = `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`; |
|
timerDisplay.style.color = ""; |
|
timerDisplay.style.fontSize = "24px"; |
|
} |
|
} |
|
|
|
function initializeGrid(rowGroup, maxCount) { |
|
for (let i = 0; i < maxCount; i++) { |
|
const dot = document.createElement('div'); |
|
dot.classList.add('dot', 'inactive'); |
|
const rowIndex = Math.floor(i / (maxCount / rowGroup.length)); |
|
rowGroup[rowIndex].appendChild(dot); |
|
} |
|
} |
|
|
|
|
|
initializeGrid(secondsRows, 60); |
|
initializeGrid(minutesRows, 60); |
|
initializeGrid(hoursRows, 24); |
|
|
|
function updateGrids() { |
|
const hours = Math.floor(totalSeconds / 3600); |
|
const minutes = Math.floor((totalSeconds % 3600) / 60); |
|
const seconds = totalSeconds % 60; |
|
|
|
updateGridRow(secondsRows, previousSeconds, seconds, 60); |
|
previousSeconds = seconds; |
|
|
|
updateGridRow(minutesRows, previousMinutes, minutes, 60); |
|
previousMinutes = minutes; |
|
|
|
updateGridRow(hoursRows, previousHours, hours, 24); |
|
previousHours = hours; |
|
} |
|
|
|
function updateGridRow(rows, previous, current, max) { |
|
const start = Math.min(previous, current); |
|
const end = Math.max(previous, current); |
|
|
|
for (let i = start; i < end; i++) { |
|
const rowIndex = Math.floor(i / (max / rows.length)); |
|
const dotIndex = i % (max / rows.length); |
|
const dot = rows[rowIndex].children[dotIndex]; |
|
if (dot) { |
|
dot.classList.remove('active', 'inactive'); |
|
dot.classList.add(i < current ? 'active' : 'inactive'); |
|
} |
|
} |
|
} |
|
|
|
|
|
const activeDotColorInput = document.getElementById('active-dot-color'); |
|
const inactiveDotColorInput = document.getElementById('inactive-dot-color'); |
|
|
|
|
|
function updateDotColors() { |
|
document.documentElement.style.setProperty('--color-active', activeDotColorInput.value); |
|
document.documentElement.style.setProperty('--color-inactive', inactiveDotColorInput.value); |
|
|
|
|
|
const allDots = document.querySelectorAll('.dot'); |
|
allDots.forEach(dot => { |
|
if (dot.classList.contains('active')) { |
|
dot.style.backgroundColor = activeDotColorInput.value; |
|
} else { |
|
dot.style.backgroundColor = inactiveDotColorInput.value; |
|
} |
|
}); |
|
} |
|
|
|
|
|
activeDotColorInput.addEventListener('input', updateDotColors); |
|
inactiveDotColorInput.addEventListener('input', updateDotColors); |
|
|
|
|
|
function updateGridRow(rows, previous, current, max) { |
|
const start = Math.min(previous, current); |
|
const end = Math.max(previous, current); |
|
|
|
for (let i = start; i < end; i++) { |
|
const rowIndex = Math.floor(i / (max / rows.length)); |
|
const dotIndex = i % (max / rows.length); |
|
const dot = rows[rowIndex].children[dotIndex]; |
|
if (dot) { |
|
dot.classList.remove('active', 'inactive'); |
|
dot.classList.add(i < current ? 'active' : 'inactive'); |
|
|
|
dot.style.backgroundColor = i < current ? activeDotColorInput.value : inactiveDotColorInput.value; |
|
} |
|
} |
|
} |
|
|
|
|
|
updateDotColors(); |
|
|
|
|
|
|
|
|
|
function handleKeyPress(event) { |
|
|
|
if (event.key in keyActions) { |
|
keyActions[event.key](); |
|
event.preventDefault(); |
|
} |
|
} |
|
|
|
|
|
document.addEventListener('keydown', handleKeyPress); |
|
|
|
|
|
|
|
|
|
|
|
function start() { |
|
if (!isRunning) { |
|
|
|
if (isStopwatch && totalSeconds === 0) { |
|
totalSeconds = 0; |
|
} else if (!isStopwatch && totalSeconds <= 0) { |
|
totalSeconds = parseInt(hoursInput.value) * 3600 + parseInt(minutesInput.value) * 60 + parseInt(secondsInput.value); |
|
} |
|
timerInterval = setInterval(() => { |
|
if (isStopwatch) { |
|
totalSeconds++; |
|
} else { |
|
totalSeconds--; |
|
if (totalSeconds < 0) { |
|
totalSeconds = 0; |
|
clearInterval(timerInterval); |
|
isRunning = false; |
|
updateDisplay(); |
|
} |
|
} |
|
updateDisplay(); |
|
updateGrids(); |
|
}, 1000); |
|
isRunning = true; |
|
} |
|
} |
|
|
|
function pause() { |
|
if (isRunning) { |
|
if (!isPaused) { |
|
clearInterval(timerInterval); |
|
isPaused = true; |
|
pauseButton.textContent = "Play"; |
|
isRunning = false; |
|
} else { |
|
|
|
start(); |
|
isPaused = false; |
|
pauseButton.textContent = "Pause"; |
|
} |
|
} else if (isPaused) { |
|
|
|
isPaused = false; |
|
pauseButton.textContent = "Pause"; |
|
start(); |
|
} |
|
} |
|
|
|
function reset() { |
|
clearInterval(timerInterval); |
|
isRunning = false; |
|
totalSeconds = 0; |
|
previousSeconds = previousMinutes = previousHours = 0; |
|
isStopwatch = true; |
|
updateDisplay(); |
|
|
|
|
|
clearGrid(secondsRows); |
|
clearGrid(minutesRows); |
|
clearGrid(hoursRows); |
|
|
|
|
|
initializeGrid(secondsRows, 60); |
|
initializeGrid(minutesRows, 60); |
|
initializeGrid(hoursRows, 24); |
|
|
|
hoursInput.value = minutesInput.value = secondsInput.value = '0'; |
|
isPaused = false; |
|
pauseButton.textContent = "Pause"; |
|
} |
|
|
|
function clearGrid(rows) { |
|
rows.forEach(row => row.innerHTML = ''); |
|
} |
|
|
|
startStopwatchButton.addEventListener('click', () => { |
|
isStopwatch = true; |
|
start(); |
|
}); |
|
|
|
startTimerButton.addEventListener('click', () => { |
|
isStopwatch = false; |
|
start(); |
|
}); |
|
|
|
pauseButton.addEventListener('click', pause); |
|
resetButton.addEventListener('click', reset); |
|
|
|
|
|
updateDisplay(); |
|
updateGrids(); |
|
</script> |
|
</body> |
|
</html> |