Mini Apps and Mini Games

Activity Countdown

A countdown timer that can be set to minutes and supports pause, resume, and reset.

HTMLCSSJavaScriptTimer
With JavaScript enabled, you can edit, run, and save this example. The fixed source is as follows:
<!doctype html>
<html lang="en" dir="ltr">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Activity Countdown</title>
  <style>
    * { box-sizing: border-box; }
    :root { color-scheme: dark; }
    body { min-height: 100vh; margin: 0; display: grid; place-items: center; padding: 24px; color: #fff8e8; font-family: "Avenir Next", "Microsoft YaHei", sans-serif; background: radial-gradient(circle at 20% 20%, rgba(255,125,75,.17), transparent 32rem), #17151c; }
    .timer-card { width: min(680px, 100%); padding: clamp(28px, 6vw, 56px); border: 1px solid #38333f; border-radius: 34px; background: #211e27; box-shadow: 0 32px 90px rgba(0,0,0,.38); text-align: center; }
    .eyebrow { color: #ff8258; font-size: 11px; font-weight: 900; letter-spacing: .24em; }
    h1 { margin: 10px 0 6px; font: 600 clamp(34px, 7vw, 56px)/1 Georgia, "STSong", serif; }
    .subtitle { margin: 0; color: #97909e; font-size: 14px; }
    .clock { position: relative; width: min(310px, 72vw); aspect-ratio: 1; margin: 32px auto; display: grid; place-items: center; }
    svg { position: absolute; inset: 0; width: 100%; transform: rotate(-90deg); }
    circle { fill: none; stroke-width: 8; }
    .track { stroke: #342f3a; }
    .progress { stroke: #ff8258; stroke-linecap: round; stroke-dasharray: 603.19; stroke-dashoffset: 0; filter: drop-shadow(0 0 6px rgba(255,130,88,.55)); transition: stroke-dashoffset .25s linear; }
    .time { font: 500 clamp(48px, 12vw, 76px)/1 "Courier New", monospace; letter-spacing: -.07em; font-variant-numeric: tabular-nums; }
    .state { display: block; margin-top: 12px; color: #9f98a7; font-size: 11px; font-weight: 800; letter-spacing: .15em; }
    .settings { display: flex; justify-content: center; gap: 12px; align-items: center; margin-bottom: 24px; }
    .settings label { color: #aba4b1; font-size: 13px; }
    input { width: 88px; padding: 10px 12px; border: 1px solid #48414f; border-radius: 10px; color: #fff8e8; font: 700 15px/1 "Courier New", monospace; background: #18161d; }
    input:focus { outline: 2px solid #ff8258; outline-offset: 2px; }
    .actions { display: flex; justify-content: center; flex-wrap: wrap; gap: 12px; }
    button { min-width: 120px; padding: 13px 20px; border: 1px solid #48414f; border-radius: 999px; color: #d5ced9; font: inherit; font-weight: 800; background: #2b2731; cursor: pointer; }
    button:hover { border-color: #746b7a; }
    .primary { border-color: #ff8258; color: #17151c; background: #ff8258; }
    button:focus-visible { outline: 3px solid white; outline-offset: 3px; }
    @media (prefers-reduced-motion: reduce) { .progress { transition: none; } }
  </style>
</head>
<body>
  <main class="timer-card">
    <span class="eyebrow">FOCUS SESSION</span><h1>Leave a stretch of focus time</h1><p class="subtitle">Set the minutes and start a block of uninterrupted work.</p>
    <div class="clock" aria-live="polite">
      <svg viewBox="0 0 220 220" aria-hidden="true"><circle class="track" cx="110" cy="110" r="96"></circle><circle id="progress" class="progress" cx="110" cy="110" r="96"></circle></svg>
      <div><div id="time" class="time">05:00</div><span id="state" class="state">Ready to start</span></div>
    </div>
    <div class="settings"><label for="minutes">Focus Minutes</label><input id="minutes" type="number" min="1" max="180" value="5" inputmode="numeric"></div>
    <div class="actions"><button id="toggle" class="primary" type="button">Start</button><button id="reset" type="button">Reset</button></div>
  </main>
  <script>
    const timeDisplay = document.querySelector('#time');
    const stateDisplay = document.querySelector('#state');
    const progress = document.querySelector('#progress');
    const minutesInput = document.querySelector('#minutes');
    const toggleButton = document.querySelector('#toggle');
    const resetButton = document.querySelector('#reset');
    const circumference = 2 * Math.PI * 96;
    let totalSeconds = 300;
    let remainingSeconds = totalSeconds;
    let timerId = 0;
    let endAt = 0;

    function render() {
      const minutes = Math.floor(remainingSeconds / 60);
      const seconds = remainingSeconds % 60;
      timeDisplay.textContent = `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
      progress.style.strokeDashoffset = String(circumference * (1 - remainingSeconds / totalSeconds));
    }
    function stopTimer(message = 'Paused') {
      window.clearInterval(timerId);
      timerId = 0;
      toggleButton.textContent = remainingSeconds === 0 ? 'Restart' : 'Continue';
      stateDisplay.textContent = message;
    }
    function tick() {
      remainingSeconds = Math.max(0, Math.ceil((endAt - Date.now()) / 1000));
      render();
      if (remainingSeconds === 0) stopTimer('Focused and done · Nice work');
    }
    function startTimer() {
      if (remainingSeconds === 0) resetTimer();
      endAt = Date.now() + remainingSeconds * 1000;
      timerId = window.setInterval(tick, 250);
      toggleButton.textContent = 'Pause';
      stateDisplay.textContent = 'Focus in progress';
    }
    function resetTimer() {
      window.clearInterval(timerId);
      timerId = 0;
      const minutes = Math.min(180, Math.max(1, Number(minutesInput.value) || 5));
      minutesInput.value = String(minutes);
      totalSeconds = minutes * 60;
      remainingSeconds = totalSeconds;
      toggleButton.textContent = 'Start';
      stateDisplay.textContent = 'Ready to start';
      render();
    }
    toggleButton.addEventListener('click', () => timerId ? stopTimer() : startTimer());
    resetButton.addEventListener('click', resetTimer);
    minutesInput.addEventListener('change', resetTimer);
    render();
  </script>
</body>
</html>
Page feedbackFound an issue or have suggestions for improvement?
Feedback features need to be used in the online version

Please enable JavaScript in the online version before submitting; local features of the current tool or example are not affected.

Go to the online version for feedback