Created
November 1, 2025 01:57
-
-
Save deokman420/55e4a6c11742731838584b969973cf11 to your computer and use it in GitHub Desktop.
Countdown Timer for use in Notion
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Adjustable Countdown Timer</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| min-height: 100vh; | |
| padding: 20px; | |
| } | |
| .timer-container { | |
| background: rgba(30, 30, 46, 0.95); | |
| border-radius: 20px; | |
| padding: 40px; | |
| box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); | |
| text-align: center; | |
| max-width: 500px; | |
| width: 100%; | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| } | |
| h1 { | |
| color: #e0e0e0; | |
| margin-bottom: 30px; | |
| font-size: 28px; | |
| } | |
| .time-display { | |
| font-size: 72px; | |
| font-weight: bold; | |
| color: #00d9ff; | |
| margin: 30px 0; | |
| font-family: 'Courier New', monospace; | |
| text-shadow: 0 0 20px rgba(0, 217, 255, 0.5); | |
| } | |
| .controls { | |
| display: flex; | |
| gap: 15px; | |
| justify-content: center; | |
| margin-bottom: 30px; | |
| flex-wrap: wrap; | |
| } | |
| .time-input-group { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 5px; | |
| } | |
| .time-input-group label { | |
| font-size: 12px; | |
| color: #a0a0a0; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| } | |
| .time-input { | |
| width: 70px; | |
| padding: 10px; | |
| font-size: 18px; | |
| border: 2px solid #00d9ff; | |
| border-radius: 8px; | |
| text-align: center; | |
| font-weight: bold; | |
| transition: all 0.3s; | |
| background: rgba(20, 20, 35, 0.8); | |
| color: #e0e0e0; | |
| } | |
| .time-input:focus { | |
| outline: none; | |
| border-color: #00ffaa; | |
| box-shadow: 0 0 0 3px rgba(0, 217, 255, 0.2); | |
| } | |
| .buttons { | |
| display: flex; | |
| gap: 10px; | |
| justify-content: center; | |
| flex-wrap: wrap; | |
| } | |
| button { | |
| padding: 12px 30px; | |
| font-size: 16px; | |
| border: none; | |
| border-radius: 25px; | |
| cursor: pointer; | |
| font-weight: 600; | |
| transition: all 0.3s; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| } | |
| .btn-start { | |
| background: linear-gradient(135deg, #00d9ff 0%, #00ffaa 100%); | |
| color: #1a1a2e; | |
| } | |
| .btn-start:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 15px rgba(0, 217, 255, 0.5); | |
| } | |
| .btn-pause { | |
| background: #ffa726; | |
| color: #1a1a2e; | |
| } | |
| .btn-pause:hover { | |
| background: #ffb84d; | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 15px rgba(255, 167, 38, 0.5); | |
| } | |
| .btn-reset { | |
| background: #ff6b6b; | |
| color: #1a1a2e; | |
| } | |
| .btn-reset:hover { | |
| background: #ff8787; | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 15px rgba(255, 107, 107, 0.5); | |
| } | |
| .btn-preset { | |
| background: rgba(0, 217, 255, 0.2); | |
| color: #00d9ff; | |
| padding: 8px 16px; | |
| font-size: 14px; | |
| border: 1px solid rgba(0, 217, 255, 0.3); | |
| } | |
| .btn-preset:hover { | |
| background: rgba(0, 217, 255, 0.3); | |
| border-color: #00d9ff; | |
| transform: translateY(-2px); | |
| } | |
| button:disabled { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| } | |
| button:disabled:hover { | |
| transform: none; | |
| } | |
| .presets { | |
| margin-bottom: 20px; | |
| padding-bottom: 20px; | |
| border-bottom: 2px solid rgba(255, 255, 255, 0.1); | |
| } | |
| .presets-label { | |
| font-size: 14px; | |
| color: #a0a0a0; | |
| margin-bottom: 10px; | |
| font-weight: 600; | |
| } | |
| .preset-buttons { | |
| display: flex; | |
| gap: 8px; | |
| justify-content: center; | |
| flex-wrap: wrap; | |
| } | |
| .status { | |
| margin-top: 20px; | |
| font-size: 14px; | |
| color: #a0a0a0; | |
| font-weight: 600; | |
| } | |
| .time-up { | |
| animation: pulse 1s infinite; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { | |
| color: #ff6b6b; | |
| } | |
| 50% { | |
| color: #00d9ff; | |
| } | |
| } | |
| @media (max-width: 500px) { | |
| .time-display { | |
| font-size: 48px; | |
| } | |
| .timer-container { | |
| padding: 20px; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="timer-container"> | |
| <h1>⏱️ Countdown Timer</h1> | |
| <div class="presets"> | |
| <div class="presets-label">Quick Presets:</div> | |
| <div class="preset-buttons"> | |
| <button class="btn-preset" onclick="setPreset(5, 0)">5 min</button> | |
| <button class="btn-preset" onclick="setPreset(10, 0)">10 min</button> | |
| <button class="btn-preset" onclick="setPreset(15, 0)">15 min</button> | |
| <button class="btn-preset" onclick="setPreset(25, 0)">25 min</button> | |
| <button class="btn-preset" onclick="setPreset(30, 0)">30 min</button> | |
| <button class="btn-preset" onclick="setPreset(60, 0)">1 hour</button> | |
| </div> | |
| </div> | |
| <div class="controls"> | |
| <div class="time-input-group"> | |
| <label>Hours</label> | |
| <input type="number" id="hours" class="time-input" min="0" max="23" value="0"> | |
| </div> | |
| <div class="time-input-group"> | |
| <label>Minutes</label> | |
| <input type="number" id="minutes" class="time-input" min="0" max="59" value="25"> | |
| </div> | |
| <div class="time-input-group"> | |
| <label>Seconds</label> | |
| <input type="number" id="seconds" class="time-input" min="0" max="59" value="0"> | |
| </div> | |
| </div> | |
| <div class="time-display" id="display">25:00</div> | |
| <div class="buttons"> | |
| <button class="btn-start" id="startBtn" onclick="startTimer()">Start</button> | |
| <button class="btn-pause" id="pauseBtn" onclick="pauseTimer()" disabled>Pause</button> | |
| <button class="btn-reset" onclick="resetTimer()">Reset</button> | |
| </div> | |
| <div class="status" id="status">Ready to start</div> | |
| </div> | |
| <script> | |
| let totalSeconds = 0; | |
| let currentSeconds = 0; | |
| let interval = null; | |
| let isRunning = false; | |
| let isPaused = false; | |
| const hoursInput = document.getElementById('hours'); | |
| const minutesInput = document.getElementById('minutes'); | |
| const secondsInput = document.getElementById('seconds'); | |
| const display = document.getElementById('display'); | |
| const startBtn = document.getElementById('startBtn'); | |
| const pauseBtn = document.getElementById('pauseBtn'); | |
| const status = document.getElementById('status'); | |
| // Initialize display | |
| updateDisplay(); | |
| // Update display when inputs change | |
| [hoursInput, minutesInput, secondsInput].forEach(input => { | |
| input.addEventListener('input', () => { | |
| if (!isRunning) { | |
| updateDisplay(); | |
| } | |
| }); | |
| }); | |
| function setPreset(minutes, seconds) { | |
| if (!isRunning) { | |
| hoursInput.value = Math.floor(minutes / 60); | |
| minutesInput.value = minutes % 60; | |
| secondsInput.value = seconds; | |
| updateDisplay(); | |
| } | |
| } | |
| function updateDisplay() { | |
| if (!isRunning) { | |
| const h = parseInt(hoursInput.value) || 0; | |
| const m = parseInt(minutesInput.value) || 0; | |
| const s = parseInt(secondsInput.value) || 0; | |
| currentSeconds = h * 3600 + m * 60 + s; | |
| } | |
| const hours = Math.floor(currentSeconds / 3600); | |
| const minutes = Math.floor((currentSeconds % 3600) / 60); | |
| const seconds = currentSeconds % 60; | |
| if (hours > 0) { | |
| display.textContent = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; | |
| } else { | |
| display.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; | |
| } | |
| } | |
| function startTimer() { | |
| if (!isRunning) { | |
| const h = parseInt(hoursInput.value) || 0; | |
| const m = parseInt(minutesInput.value) || 0; | |
| const s = parseInt(secondsInput.value) || 0; | |
| totalSeconds = h * 3600 + m * 60 + s; | |
| if (totalSeconds === 0) { | |
| status.textContent = 'Please set a time first!'; | |
| return; | |
| } | |
| if (!isPaused) { | |
| currentSeconds = totalSeconds; | |
| } | |
| isRunning = true; | |
| isPaused = false; | |
| startBtn.disabled = true; | |
| pauseBtn.disabled = false; | |
| hoursInput.disabled = true; | |
| minutesInput.disabled = true; | |
| secondsInput.disabled = true; | |
| status.textContent = 'Timer running...'; | |
| display.classList.remove('time-up'); | |
| interval = setInterval(() => { | |
| currentSeconds--; | |
| updateDisplay(); | |
| if (currentSeconds <= 0) { | |
| clearInterval(interval); | |
| isRunning = false; | |
| startBtn.disabled = false; | |
| pauseBtn.disabled = true; | |
| status.textContent = '⏰ Time\'s up!'; | |
| display.classList.add('time-up'); | |
| playSound(); | |
| } | |
| }, 1000); | |
| } | |
| } | |
| function pauseTimer() { | |
| if (isRunning) { | |
| clearInterval(interval); | |
| isRunning = false; | |
| isPaused = true; | |
| startBtn.disabled = false; | |
| pauseBtn.disabled = true; | |
| startBtn.textContent = 'Resume'; | |
| status.textContent = 'Timer paused'; | |
| } | |
| } | |
| function resetTimer() { | |
| clearInterval(interval); | |
| isRunning = false; | |
| isPaused = false; | |
| currentSeconds = 0; | |
| startBtn.disabled = false; | |
| pauseBtn.disabled = true; | |
| startBtn.textContent = 'Start'; | |
| hoursInput.disabled = false; | |
| minutesInput.disabled = false; | |
| secondsInput.disabled = false; | |
| status.textContent = 'Ready to start'; | |
| display.classList.remove('time-up'); | |
| updateDisplay(); | |
| } | |
| function playSound() { | |
| // Create a simple beep sound using Web Audio API | |
| const audioContext = new (window.AudioContext || window.webkitAudioContext)(); | |
| const oscillator = audioContext.createOscillator(); | |
| const gainNode = audioContext.createGain(); | |
| oscillator.connect(gainNode); | |
| gainNode.connect(audioContext.destination); | |
| oscillator.frequency.value = 800; | |
| oscillator.type = 'sine'; | |
| gainNode.gain.setValueAtTime(0.3, audioContext.currentTime); | |
| gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5); | |
| oscillator.start(audioContext.currentTime); | |
| oscillator.stop(audioContext.currentTime + 0.5); | |
| } | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment