UTILITY

STOP Watch

Precise stopwatch with lap times and countdown timer.

00:00:00.000
Developer Reference

Core Algorithm & Standalone Script

Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.

// Tab switching
  document.querySelectorAll('#mode-tabs .tab').forEach(t => {
    t.addEventListener('click', () => {
      document.querySelectorAll('#mode-tabs .tab').forEach(x => x.classList.remove('active'));
      t.classList.add('active');
      document.getElementById('sw-panel').classList.toggle('hidden', t.dataset.mode !== 'stopwatch');
      document.getElementById('timer-panel').classList.toggle('hidden', t.dataset.mode !== 'timer');
    });
  });

  // === STOPWATCH ===
  let swRunning = false, swStart = 0, swElapsed = 0, swInterval, laps = [], lapStart = 0;
  const pad = (n, d = 2) => String(n).padStart(d, '0');

  function fmtTime(ms) {
    const h = Math.floor(ms / 3600000);
    const m = Math.floor((ms % 3600000) / 60000);
    const s = Math.floor((ms % 60000) / 1000);
    const ml = ms % 1000;
    return `${pad(h)}:${pad(m)}:${pad(s)}<span class="ms">.${pad(ml, 3)}</span>`;
  }

  function updateSW() {
    const total = swElapsed + (Date.now() - swStart);
    document.getElementById('sw-display').innerHTML = fmtTime(total);
  }

  document.getElementById('sw-start').addEventListener('click', function() {
    if (!swRunning) {
      swRunning = true;
      swStart = Date.now();
      if (laps.length === 0) lapStart = swStart;
      swInterval = setInterval(updateSW, 10);
      this.textContent = 'Stop';
      this.classList.replace('btn-primary', 'btn-secondary');
      document.getElementById('sw-lap').disabled = false;
      document.getElementById('sw-reset').disabled = false;
    } else {
      swRunning = false;
      swElapsed += Date.now() - swStart;
      clearInterval(swInterval);
      this.textContent = 'Start';
      this.classList.replace('btn-secondary', 'btn-primary');
    }
  });

  document.getElementById('sw-lap').addEventListener('click', () => {
    if (!swRunning) return;
    const now = Date.now();
    const total = swElapsed + (now - swStart);
    const lapTime = total - (laps.length > 0 ? laps.reduce((a, b) => a + b, 0) : 0);
    laps.push(lapTime);
    const container = document.getElementById('sw-laps');
    container.innerHTML = laps.map((l, i) =>
      `<div class="lap-row"><span>Lap ${i + 1}</span><span>${pad(Math.floor(l / 60000))}:${pad(Math.floor((l % 60000) / 1000))}.${pad(l % 1000, 3)}</span></div>`
    ).reverse().join('');
  });

  document.getElementById('sw-reset').addEventListener('click', () => {
    swRunning = false; swElapsed = 0; laps = [];
    clearInterval(swInterval);
    document.getElementById('sw-display').innerHTML = fmtTime(0);
    document.getElementById('sw-start').textContent = 'Start';
    document.getElementById('sw-start').classList.replace('btn-secondary', 'btn-primary');
    document.getElementById('sw-lap').disabled = true;
    document.getElementById('sw-reset').disabled = true;
    document.getElementById('sw-laps').innerHTML = '';
  });

  // === TIMER ===
  let tRunning = false, tRemaining = 0, tInterval;

  function updateTimerDisplay() {
    const h = Math.floor(tRemaining / 3600);
    const m = Math.floor((tRemaining % 3600) / 60);
    const s = tRemaining % 60;
    document.getElementById('t-display').textContent = `${pad(h)}:${pad(m)}:${pad(s)}`;
  }

  function getTimerSeconds() {
    return (parseInt(document.getElementById('t-h').value) || 0) * 3600 +
           (parseInt(document.getElementById('t-m').value) || 0) * 60 +
           (parseInt(document.getElementById('t-s').value) || 0);
  }

  ['t-h', 't-m', 't-s'].forEach(id => document.getElementById(id).addEventListener('input', () => {
    if (!tRunning) { tRemaining = getTimerSeconds(); updateTimerDisplay(); }
  }));
  tRemaining = getTimerSeconds();

  document.getElementById('t-start').addEventListener('click', function() {
    if (!tRunning) {
      if (tRemaining <= 0) tRemaining = getTimerSeconds();
      if (tRemaining <= 0) return;
      tRunning = true;
      this.textContent = 'Pause';
      document.getElementById('t-display').classList.remove('flash');
      tInterval = setInterval(() => {
        tRemaining--;
        updateTimerDisplay();
        if (tRemaining <= 0) {
          clearInterval(tInterval);
          tRunning = false;
          this.textContent = 'Start';
          document.getElementById('t-display').classList.add('flash');
          document.getElementById('t-display').style.color = 'var(--accent)';
        }
      }, 1000);
    } else {
      tRunning = false;
      clearInterval(tInterval);
      this.textContent = 'Start';
    }
  });

  document.getElementById('t-reset').addEventListener('click', () => {
    tRunning = false;
    clearInterval(tInterval);
    tRemaining = getTimerSeconds();
    updateTimerDisplay();
    document.getElementById('t-start').textContent = 'Start';
    document.getElementById('t-display').classList.remove('flash');
    document.getElementById('t-display').style.color = '';
  });

  // Keyboard
  document.addEventListener('keydown', e => {
    if (e.code === 'Space') { e.preventDefault(); document.getElementById('sw-start').click(); }
  });