Skip to content

TheOrionGD/ChronoTimer-Utility

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

3 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

ChronoTimer Utility

   ____ _                                _____ _
  / ___| |__  _ __ ___  _ __   ___      |_   _(_)_ __ ___   ___ _ __
 | |   | '_ \| '__/ _ \| '_ \ / _ \       | | | | '_ ` _ \ / _ \ '__|
 | |___| | | | | | (_) | | | | (_) |      | | | | | | | | |  __/ |
  \____|_| |_|_|  \___/|_| |_|\___/       |_| |_|_| |_| |_|\___|_|
                                                                     

πŸ“– The Developer's Story

Project Inspiration

In a world saturated with complex, data-harvesting productivity applications, there is a distinct beauty in pure, client-side tools that do one thing and do it exceptionally well. The ChronoTimer Utility was born out of a simple frustration: most web-based timers and stopwatches are either visually outdated or bloated with tracking scripts, bulky frameworks, and server dependencies. We wanted to build a clockwork-precise utility that executes entirely in the user's browser, demands zero network requests, loads instantly, and delivers a premium, high-fidelity aesthetic. By stripping away heavy frameworks, we set out to prove that vanilla web technologies (HTML5, CSS3, and JavaScript) can be crafted into a sophisticated, hardware-accelerated productivity interface.

The Developer

This system was built as a single-person project assigned during an intensive internship in Web Application Development at Prodigy Infotech by Godfrey. Tasked with upgrading a rudimentary stopwatch into a state-of-the-art multi-tab chronometer, Godfrey drove the design, frontend engineering, sound synthesis architecture, and user experience updates. This project serves as a capstone demonstrating how close-to-metal browser APIs can be leveraged to build responsive, performant, and visual-first software.

The Challenge

Modern browsers are notoriously hostile to high-precision timer scripts running in the background. To conserve CPU cycles and battery life, browser engines aggressively throttle inactive tabs, restricting setInterval and setTimeout executions to once per secondβ€”or pausing them entirely. For a stopwatch displaying centiseconds, or a Pomodoro focus tracker requiring minute-by-minute updates, simple timer loops will drift, delay, and ultimately fail the user. Our primary challenges were:

  1. Mathematical Accuracy: Maintaining sub-millisecond precision even when the browser tab is minimized or backgrounded.
  2. Offline-first Audio: Delivering rich, custom sound notifications (ticks, click feedback, alarms) without requesting heavy MP3/WAV assets that fail under poor network conditions or raise cross-origin policies.
  3. Cross-Device Usability: Rendering circular SVG dashboards and data tables that adjust to screens ranging from a compact 320px mobile viewport to a high-density 4K monitor, while maintaining fluid 60fps animations.

Design Philosophy

We reject the basic, uninspired gray-and-blue layouts of typical utilities. Our design language is anchored in Premium Dark Glassmorphism. We treat the screen as an illuminated physical surface where panels hover over floating, glowing background gas clouds. Key design pillars include:

  • Visual Hierarchy through Translucency: Using high-blur glass filters (backdrop-filter: blur(25px)) to keep focus on the active dashboard.
  • Typographical Stability: Leveraging Google Outfit for UI controls and JetBrains Mono for timer digits. Timer numbers must use monospaced, tabular-num formats so that ticking digits do not cause the layout to jump or expand.
  • Intentional Lighting: Implementing soft neon glow states (cyan for stopwatch, emerald green for break timers, and coral orange for focus sessions) that immediately communicate the application state through ambient color changes.

Engineering Journey

The development process progressed through three major phases:

  1. Phase 1: Precision Re-engineering: We moved from incrementing a simple counter variable to tracking time using monotonic delta calculations (performance.now()). The execution loop was rebuilt around requestAnimationFrame, aligning screen updates directly with the hardware refresh cycle.
  2. Phase 2: Sound Synthesizer: We integrated the browser's native Web Audio API, coding a clean synthesizer class that creates, connects, and releases audio nodes on-the-fly to play custom frequencies and sound sweeps.
  3. Phase 3: Adaptive Refactoring: We stripped out custom scrollbars in favor of a clean scrollbar-free system, built custom mobile queries to transition modals into touch-friendly bottom sheets, and structured responsive layouts using CSS Grid and flex models.

Security-First Thinking

Security is often neglected in utility applications. We designed the ChronoTimer Utility with a zero-trust footprint:

  • Zero External Calls: No scripts are loaded from third-party CDNs (aside from official Google Fonts). There are no tracking pixels, analytics suites, or remote APIs.
  • No Database Storage: All states, including Pomodoro durations and volume configurations, are persisted strictly in the browser’s sandboxed localStorage. No data ever leaves the user's local machine.
  • Content Security Policy Friendly: By avoiding inline styles in production and eliminating eval(), the code conforms to strict security headers.

Building the Consent Workflow

Modern web browsers block audio playback until a user explicitly interacts with the document (clicking a button, tapping a tab). Programmatic alarms or sound indicators triggered without interaction will fail, throwing console warnings. To handle this elegantly:

  1. We initialize the browser's AudioContext lazily.
  2. On the very first user mouse-down or key-press on the app, the code invokes the audio activation sequence.
  3. If the context is suspended, we call .resume() to restore state. This ensures all transition click bleeps, ticks, and completion alarms sound reliably without blocking the UI or throwing runtime execution errors.

User Experience Decisions

During usability testing, we observed that high-frequency productivity users navigate primarily via keyboard commands. Relying purely on mouse clicks slows down focus logging and split lap tracking. We implemented physical key mapping shortcuts (Space to toggle, L to lap, R to reset, and T to cycle tabs). For mobile layouts, we replaced typical center-aligned desktop modals with bottom-drawer sheets. This mirrors native mobile design guidelines, placing control interfaces closer to the user's thumb for quick interactions.

Technical Challenges & Solutions

  • The Background Throttling Solution: When the browser background throttles the tick rate, we calculate the exact delta time upon tab reactivation. The clock instantly catches up to the real elapsed time. By using Web Audio's scheduler, completion alarms are scheduled on the audio hardware thread, ensuring sounds play on time even if the main JS thread is throttled.
  • CSV Export Without Servers: Building a spreadsheet exporter completely client-side. We construct a CSV string buffer dynamically, encode it into a data URI, and mount a temporary anchor node (<a>) to trigger a native download block.

Collaboration Story

This utility was built, tested, and iterated in close collaboration with beta-testers. Testing occurred across multiple browsers (Chrome, Safari, Firefox, Edge) and platforms (Windows, macOS, iOS, Android). Feedback on button responsiveness led to adding hardware-accelerated CSS properties (will-change: transform and translate3d), eliminating micro-stutters during heavy animations on older mobile devices.

Lessons Learned

We learned that vanilla HTML5/CSS3 APIs are extremely powerful and often perform better than bulky modern framework bundles. Hand-coding the circular progress offsets, responsive bottom drawers, and Web Audio synthesizers proved that keeping the dependency footprint minimal results in a faster, more maintainable, and highly secure codebase.

Future Vision

The future roadmap for the ChronoTimer Utility includes:

  1. Offline Service Worker Sync: Packaging the app as a Progressive Web App (PWA) so it can install and run offline.
  2. Multi-Timer Stacking: Allowing users to run multiple countdown timers in parallel.
  3. Webhooks Integration: Triggering local HTTP requests when Pomodoro focus rounds complete, allowing users to integrate the timer with smart-home lights or custom scripts.

Behind the Name

The name ChronoTimer combines Chronos (the personification of time in Greek mythology) with Timer (representing mathematical precision and utility). It reflects our mission to build a premium, visual-first timekeeping tool.

Message from the Developer

"I hope you enjoy using ChronoTimer as much as I enjoyed building it. In a world full of complex apps, I hope this clean, precise, and visual-first utility serves as a reliable partner in your daily focus workflow."

β€” Godfrey


πŸ“‹ Table of Contents

  1. Project Overview
  2. Key Features
  3. System Architecture & Diagrams
  4. Directory Structure
  5. Local Installation & Quickstart
  6. HTML Structural Layout Directory
  7. CSS Styling & Design System Details
  8. Core ES6 JavaScript Logic Deep Dive
  9. Web Audio Synthesis Engine
  10. Keyboard Shortcuts & Hotkey Architecture
  11. Configuration & Customization Reference
  12. Browser Compatibility Matrix
  13. Security Analysis & Hardening
  14. Performance Optimization Benchmark Guidelines
  15. Manual & Automated Testing Protocols
  16. Troubleshooting & Comprehensive FAQ
  17. Contribution Guidelines
  18. Licensing Details

πŸ” Project Overview

ChronoTimer Utility is a professional, high-performance timekeeping application designed to run entirely in the browser. It features a high-precision stopwatch with centisecond accuracy, a customizable countdown timer with a visual progress indicator, and a Pomodoro focus assistant. Built using pure, modern vanilla HTML5, CSS3, and JavaScript, it is lightweight, secure, and offline-capable.

Technology Stack & Badges

  • Frontend: Pure HTML5, CSS3 Custom Properties (Variables), Vanilla JavaScript
  • Audio Engine: Web Audio API (Native browser synthesizer)
  • Storage: LocalStorage API
  • Icons: Inline scalable vector graphics (SVGs)

GitHub License Vanilla JS CSS3 HTML5 Audio Synth PRs Welcome


⚑ Key Features

Feature Group Capabilities Technical Implementation
High-Precision Stopwatch Centisecond tracking, infinite lap recording, fastest/slowest lap comparison, real-time delta tracking, CSV data export. requestAnimationFrame render loop, monotonic clock offset delta calculations, Client-side Blob generation.
Countdown Timer Hours/Minutes/Seconds pickers, 4 quick-select presets, circular progress visualizer, automatic alarm triggers. SVG stroke-dashoffset transition, state caching, Web Audio synthesizer tone sweeps.
Pomodoro Focus Timer Focus/Break states, automatic phase advancement, visual progress cycles, configurable work/break durations. State machine tracking Pomodoro sessions, LocalStorage user settings synchronization.
Synthesized Tone Engine Non-blocking ticks, click feedback, double-beep alerts, rising/descending scale sweeps. Web Audio API oscillator nodes, exponential gain decay envelopes.
Premium Design System Dark glassmorphism, responsive mobile layouts, bottom sheet dialogs, zero layouts shift (tabular monospace numbers). CSS variables, Outfit/JetBrains Mono fonts, hardware-accelerated transforms.
Accessibility & Shortcuts Full keyboard control, ARIA labels, contrast ratios, modal overlays. Global keypress event capturing, semantic HTML elements.

πŸ“ System Architecture & Diagrams

Component Architecture

This application is designed as a modular, single-page application using vanilla ES6 modules. The DOM interface binds directly to specific state controllers.

graph TD
    User([User Interface]) --> DOM[HTML DOM Elements]
    DOM --> Controller[App Event Listener / Switchboard]
    
    subgraph Controllers [State Controllers]
        Controller --> SW[Stopwatch Controller]
        Controller --> TR[Timer Controller]
        Controller --> PM[Pomodoro Controller]
    end

    subgraph Hardware [Hardware Services]
        SW --> RAF[requestAnimationFrame Monotonic Loop]
        TR --> RAF
        PM --> RAF
        SW --> Storage[LocalStorage State Cache]
        TR --> Storage
        PM --> Storage
    end

    subgraph AudioEngine [Native Audio Engine]
        TR --> Synthesizer[Web Audio API AudioContext]
        PM --> Synthesizer
        Controller --> Synthesizer
        Synthesizer --> Osc[Oscillator Nodes]
        Osc --> Gain[Gain Nodes/Decay Envelope]
        Gain --> Destination[Audio Output Device]
    end
Loading

Component Lifecycle & State Transitions

The application flows through different tab modes and states. Active timers pause background tasks when switching, ensuring CPU resources are focused on the active panel.

stateDiagram-v2
    [*] --> Idle : Application Load
    
    state Idle {
        [*] --> StopwatchMode
        StopwatchMode --> TimerMode : Switch Tab
        TimerMode --> PomodoroMode : Switch Tab
        PomodoroMode --> StopwatchMode : Switch Tab
    }

    state StopwatchMode {
        [*] --> SW_Stopped
        SW_Stopped --> SW_Running : Click Start / Space
        SW_Running --> SW_Running : Click Lap / L (Record Split)
        SW_Running --> SW_Paused : Click Pause / Space
        SW_Paused --> SW_Running : Click Resume / Space
        SW_Paused --> SW_Stopped : Click Reset / R
    }

    state TimerMode {
        [*] --> TR_Setup
        TR_Setup --> TR_Running : Select Duration & Start
        TR_Running --> TR_Paused : Click Pause / Space
        TR_Paused --> TR_Running : Click Resume / Space
        TR_Paused --> TR_Setup : Click Reset / R
        TR_Running --> TR_Alarm : Duration = 0
        TR_Alarm --> TR_Setup : Alarm Ends / Click Reset
    }

    state PomodoroMode {
        [*] --> PM_Work_Setup
        PM_Work_Setup --> PM_Work_Running : Click Start Focus
        PM_Work_Running --> PM_Work_Paused : Click Pause
        PM_Work_Paused --> PM_Work_Running : Click Resume
        PM_Work_Running --> PM_Break_Running : Focus Complete (Trigger Alarm)
        PM_Break_Running --> PM_Work_Running : Break Complete (Trigger Alarm)
        PM_Work_Running --> PM_Work_Setup : Click Reset / Cycle Ends
    }
Loading

Stopwatch Precision Loop Sequence

To prevent clock drift from JS thread blocks, the timing logic compares the current timestamp against the initial start time.

sequenceDiagram
    autonumber
    actor User as User
    participant UI as DOM Display
    participant JS as Stopwatch Controller
    participant Engine as browser Hardware Timer (performance.now)
    participant Audio as Web Audio API Synth

    User->>JS: Click Start / Press Space
    JS->>Audio: trigger click sound beep
    Audio-->>User: audible click feedback
    JS->>Engine: get start offset (startTime = performance.now)
    JS->>JS: start requestAnimationFrame loop
    
    loop Animation Frame Tick
        JS->>Engine: get current timestamp
        Engine-->>JS: timeDelta = current - startTime
        JS->>JS: format ms into hours, mins, secs, centiseconds
        JS->>UI: update textContent (tabular mono text)
    end

    User->>JS: Click Pause / Press Space
    JS->>JS: cancelAnimationFrame
    JS->>Engine: compute accumulated elapsedTime
    JS->>UI: lock display figures
Loading

Pomodoro Cycle Flow

The Pomodoro cycle tracks sessions and automatically transitions through Work, Short Break, and Long Break phases.

flowchart TD
    Start([Initialize Pomodoro]) --> WorkSession[Start Focus Session: 25 Mins]
    WorkSession --> WorkRunning{Timer Ticking}
    WorkRunning -- Tick --> WorkRunning
    WorkRunning -- Session Done --> PlayAlarm1[Play Focus Alert Sound]
    PlayAlarm1 --> IncCycle[Increment Focus Count]
    
    IncCycle --> CheckCycles{Completed 4 Cycles?}
    CheckCycles -- No --> ShortBreak[Start Short Break: 5 Mins]
    ShortBreak --> ShortRunning{Timer Ticking}
    ShortRunning -- Tick --> ShortRunning
    ShortRunning -- Break Done --> PlayAlarm2[Play Break Alert Sound]
    PlayAlarm2 --> WorkSession
    
    CheckCycles -- Yes --> LongBreak[Start Long Break: 15 Mins]
    LongBreak --> LongRunning{Timer Ticking}
    LongRunning -- Tick --> LongRunning
    LongRunning -- Break Done --> PlayAlarm3[Play Cycle Complete Sound]
    PlayAlarm3 --> ResetCycle[Reset Focus Cycle Count]
    ResetCycle --> WorkSession
Loading

πŸ“ Directory Structure

The repository is structured as a clean static project. It requires no installation wrappers, compilations, or dependency folders:

ChronoTimer-Utility/
β”œβ”€β”€ .git/                        # Git configuration directory
β”œβ”€β”€ logo.png                     # Application logo and favicon source
β”œβ”€β”€ index.html                   # HTML structure, inline SVGs, and modals
β”œβ”€β”€ style.css                    # CSS style variables and animations
└── main.js                      # Core JS state controllers and audio engine

πŸš€ Local Installation & Quickstart

Prerequisites

To run the project locally, you only need a modern web browser.

Option A: Static File Run

You can open the project directly in your browser:

  1. Clone the repository:
    git clone https://github.com/TheOrionGD/ChronoTimer-Utility.git
  2. Navigate into the folder:
    cd ChronoTimer-Utility
  3. Open index.html in your browser:
    • Windows: Double-click index.html or run start index.html in PowerShell.
    • macOS: Run open index.html in Terminal.
    • Linux: Run xdg-open index.html.

Option B: Local HTTP Server (Recommended)

To prevent CORS complications and allow full local storage features, run the application using a lightweight local web server:

Using NodeJS:

# Install http-server globally
npm install -g http-server

# Run the server on port 8000
http-server -p 8000

Using Python 3:

python -m http.server 8000

Using PHP:

php -S localhost:8000

Once started, open your browser and navigate to:

http://localhost:8000

🏒 HTML Structural Layout Directory

The structural logic in index.html uses semantic HTML5 tags and inline vector assets. This provides structured markup, accessible elements, and independent graphics rendering.

1. Document Configuration

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="description" content="ChronoTimer - A premium, high-precision timing utility featuring a stopwatch, countdown timer, and Pomodoro focus helper with synthesized audio cues.">
  <title>ChronoTimer Utility</title>
  <link rel="icon" type="image/png" href="logo.png">
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700;800&family=JetBrains+Mono:wght@400;600;700&display=swap" rel="stylesheet">
  <link rel="stylesheet" href="style.css">
</head>

2. Header & Configuration Actions

Contains application identification controls, shortcut list buttons, and audio toggle interfaces.

<header class="app-header">
  <div class="logo">
    <svg class="logo-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
      <circle cx="12" cy="12" r="10"></circle>
      <polyline points="12 6 12 12 16 14"></polyline>
      <path d="M12 2v2"></path>
      <path d="M12 20v2"></path>
      <path d="M20 12h2"></path>
      <path d="M2 12h2"></path>
    </svg>
    <span class="logo-text">ChronoTimer <span class="logo-accent">Utility</span></span>
  </div>
  <div class="header-actions">
    <button id="shortcutsBtn" class="icon-btn" title="Keyboard Shortcuts" aria-label="Keyboard Shortcuts">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
        <rect x="2" y="4" width="20" height="16" rx="2" ry="2"></rect>
        <line x1="6" y1="8" x2="6.01" y2="8"></line>
        <line x1="10" y1="8" x2="10.01" y2="8"></line>
        <line x1="14" y1="8" x2="14.01" y2="8"></line>
        <line x1="18" y1="8" x2="18.01" y2="8"></line>
        <line x1="6" y1="12" x2="6.01" y2="12"></line>
        <line x1="10" y1="12" x2="14.01" y2="12"></line>
        <line x1="18" y1="12" x2="18.01" y2="12"></line>
        <line x1="6" y1="16" x2="6.01" y2="16"></line>
        <line x1="10" y1="16" x2="10.01" y2="16"></line>
        <line x1="14" y1="16" x2="14.01" y2="16"></line>
        <line x1="18" y1="16" x2="18.01" y2="16"></line>
      </svg>
    </button>
    <button id="soundToggleBtn" class="icon-btn" title="Toggle Sound" aria-label="Toggle Sound">
      <svg class="sound-on-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
        <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon>
        <path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"></path>
      </svg>
      <svg class="sound-off-icon hidden" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
        <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon>
        <line x1="23" y1="9" x2="17" y2="15"></line>
        <line x1="17" y1="9" x2="23" y2="15"></line>
      </svg>
    </button>
  </div>
</header>

3. Navigation Controls

Uses ARIA states to manage tab selection and panel visibility:

<nav class="tab-bar">
  <button class="tab-btn active" data-tab="stopwatch" id="tab-stopwatch">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tab-icon">
      <circle cx="12" cy="12" r="10"></circle>
      <polyline points="12 6 12 12 16 14"></polyline>
    </svg>
    Stopwatch
  </button>
  <button class="tab-btn" data-tab="timer" id="tab-timer">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tab-icon">
      <circle cx="12" cy="12" r="10"></circle>
      <line x1="12" y1="8" x2="12" y2="12"></line>
      <line x1="12" y1="16" x2="12.01" y2="16"></line>
    </svg>
    Timer
  </button>
  <button class="tab-btn" data-tab="pomodoro" id="tab-pomodoro">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tab-icon">
      <path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"></path>
      <path d="M12 6v6l4 2"></path>
      <path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>
    </svg>
    Pomodoro
  </button>
</nav>

🎨 CSS Styling & Design System Details

The visual framework in style.css handles glassmorphic styling, animations, layouts, and responsive constraints.

1. Root Tokens

Defines color themes, fonts, overlays, buttons, animations, and transitions.

:root {
  --font-ui: 'Outfit', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-timer: 'JetBrains Mono', monospace;

  /* Colors */
  --bg-main: #09090e;
  --panel-bg: rgba(18, 18, 26, 0.65);
  --panel-border: rgba(255, 255, 255, 0.08);
  --panel-border-focus: rgba(0, 212, 255, 0.3);
  
  --text-main: #f3f4f6;
  --text-muted: #9ca3af;
  --text-dark: #6b7280;

  --accent-cyan: #00d4ff;
  --accent-cyan-glow: rgba(0, 212, 255, 0.4);
  --accent-teal: #10b981;
  --accent-teal-glow: rgba(16, 185, 129, 0.4);
  --accent-tomato: #ff4757;
  --accent-tomato-glow: rgba(255, 71, 87, 0.4);
  --accent-orange: #f97316;
  --accent-orange-glow: rgba(249, 115, 22, 0.4);

  --btn-primary-bg: rgba(0, 212, 255, 0.15);
  --btn-primary-border: rgba(0, 212, 255, 0.3);
  --btn-primary-text: #00d4ff;

  --btn-secondary-bg: rgba(255, 255, 255, 0.05);
  --btn-secondary-border: rgba(255, 255, 255, 0.1);
  --btn-secondary-text: #f3f4f6;

  --btn-danger-bg: rgba(255, 71, 87, 0.1);
  --btn-danger-border: rgba(255, 71, 87, 0.25);
  --btn-danger-text: #ff4757;

  /* Transitions */
  --transition-fast: 0.15s ease;
  --transition-normal: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  --transition-slow: 0.6s cubic-bezier(0.4, 0, 0.2, 1);
}

2. Layout Structure & Core Layout

Establishes the centering flex container and viewport constraints:

body {
  font-family: var(--font-ui);
  background-color: var(--bg-main);
  color: var(--text-main);
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  overflow: hidden; /* Lock viewport scrolling on desktop */
  position: relative;
  padding: 20px;
}

.app-container {
  width: 100%;
  max-width: 480px;
  background: var(--panel-bg);
  backdrop-filter: blur(25px);
  -webkit-backdrop-filter: blur(25px);
  border: 1px solid var(--panel-border);
  border-radius: 24px;
  padding: 30px;
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.1);
  display: flex;
  flex-direction: column;
  gap: 25px;
  z-index: 10;
}

3. Glassmorphic Ambient Lighting Glows

Floating light elements create ambient background effects. We use will-change: transform and translate3d to enable GPU acceleration, avoiding CPU rendering overhead.

.bg-glow {
  position: absolute;
  border-radius: 50%;
  filter: blur(100px);
  z-index: -1;
  opacity: 0.35;
  pointer-events: none;
  will-change: transform;
  transform: translate3d(0, 0, 0);
}

.bg-glow-1 {
  width: 400px;
  height: 400px;
  background: radial-gradient(circle, var(--accent-cyan) 0%, transparent 70%);
  top: -10%;
  left: -10%;
  animation: floatGlow 15s infinite alternate ease-in-out;
}

.bg-glow-2 {
  width: 500px;
  height: 500px;
  background: radial-gradient(circle, var(--accent-orange) 0%, transparent 70%);
  bottom: -15%;
  right: -10%;
  animation: floatGlow2 20s infinite alternate ease-in-out;
}

@keyframes floatGlow {
  0% { transform: translate(0, 0) scale(1); }
  100% { transform: translate(80px, 50px) scale(1.1); }
}

@keyframes floatGlow2 {
  0% { transform: translate(0, 0) scale(1.1); }
  100% { transform: translate(-100px, -60px) scale(0.9); }
}

4. Interactive Dialog Styles & Mobile Bottom Sheet Drawer

Adapts the layout based on viewport size. On screens wider than 480px, modals are centered cards. On smaller viewports, they slide up from the bottom as native drawers.

.modal-backdrop {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background: rgba(0, 0, 0, 0.7);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 100;
  opacity: 1;
  transition: opacity var(--transition-normal);
}

.modal-card {
  width: 90%;
  max-width: 400px;
  background: rgba(20, 20, 30, 0.9);
  border: 1px solid var(--panel-border);
  border-radius: 20px;
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
  animation: modalSlideIn var(--transition-normal) forwards;
}

@media (max-width: 480px) {
  body {
    padding: 8px;
    align-items: flex-start;
    padding-top: 20px;
    padding-bottom: 20px;
    overflow-y: auto; /* Allow scrolling on short mobile viewports */
  }

  .modal-backdrop {
    align-items: flex-end; /* Align modal to bottom */
  }

  .modal-card {
    width: 100%;
    max-width: 100%;
    border-radius: 24px 24px 0 0;
    margin: 0;
    box-shadow: 0 -10px 35px rgba(0, 0, 0, 0.4);
    animation: bottomSheetSlideUp 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards;
  }

  @keyframes bottomSheetSlideUp {
    from { transform: translateY(100%); }
    to { transform: translateY(0); }
  }
}

🧠 Core ES6 JavaScript Logic Deep Dive

The application logic in main.js is split into independent sub-modules that interact with the shared global settings state.

1. Stopwatch Module Engine

Manages centisecond precision loops. It tracks lap splits and dynamically compares durations to highlight performance deltas.

const Stopwatch = {
  running: false,
  elapsedTime: 0, 
  startTime: 0,
  animationFrameId: null,
  laps: [], 

  init() {
    DOM.swStartPauseBtn.addEventListener('click', () => this.toggle());
    DOM.swLapBtn.addEventListener('click', () => this.recordLap());
    DOM.swResetBtn.addEventListener('click', () => this.reset());
    DOM.exportLapsBtn.addEventListener('click', () => this.exportCSV());
    this.updateDisplayUI();
  },

  toggle() {
    audioAlerts.click();
    if (this.running) {
      this.pause();
    } else {
      this.start();
    }
  },

  start() {
    this.running = true;
    this.startTime = performance.now() - this.elapsedTime;
    DOM.swStartPauseBtn.querySelector('.btn-text').textContent = 'Pause';
    DOM.swStartPauseBtn.className = 'btn btn-secondary';
    DOM.swLapBtn.disabled = false;
    DOM.swResetBtn.disabled = false;
    
    const tick = () => {
      if (!this.running) return;
      this.elapsedTime = performance.now() - this.startTime;
      this.updateDisplayUI();
      this.animationFrameId = requestAnimationFrame(tick);
    };
    this.animationFrameId = requestAnimationFrame(tick);
  },

  pause() {
    this.running = false;
    if (this.animationFrameId) {
      cancelAnimationFrame(this.animationFrameId);
    }
    DOM.swStartPauseBtn.querySelector('.btn-text').textContent = 'Resume';
    DOM.swStartPauseBtn.className = 'btn btn-primary btn-start';
  },

  reset() {
    audioAlerts.back();
    this.running = false;
    if (this.animationFrameId) {
      cancelAnimationFrame(this.animationFrameId);
    }
    this.elapsedTime = 0;
    this.laps = [];
    DOM.swStartPauseBtn.querySelector('.btn-text').textContent = 'Start';
    DOM.swStartPauseBtn.className = 'btn btn-primary btn-start';
    DOM.swLapBtn.disabled = true;
    DOM.swResetBtn.disabled = true;
    DOM.exportLapsBtn.classList.add('hidden');
    this.updateDisplayUI();
    this.renderLapsTable();
  },

  recordLap() {
    audioAlerts.lap();
    const splitTime = this.elapsedTime;
    const previousLapsSum = this.laps.reduce((acc, l) => acc + l.lapDurationMs, 0);
    const lapDuration = splitTime - previousLapsSum;
    const lapNumber = this.laps.length + 1;

    this.laps.push({
      lapNumber,
      splitTimeMs: splitTime,
      lapDurationMs: lapDuration
    });

    if (this.laps.length > 0) {
      DOM.exportLapsBtn.classList.remove('hidden');
    }

    this.renderLapsTable();
  },

  renderLapsTable() {
    if (this.laps.length === 0) {
      DOM.lapsTableBody.innerHTML = `
        <tr class="no-laps-row">
          <td colspan="4">No laps recorded yet. Click 'Lap' when running.</td>
        </tr>
      `;
      return;
    }

    let minIdx = -1;
    let maxIdx = -1;
    if (this.laps.length >= 2) {
      let minVal = Infinity;
      let maxVal = -Infinity;
      this.laps.forEach((lap, idx) => {
        if (lap.lapDurationMs < minVal) {
          minVal = lap.lapDurationMs;
          minIdx = idx;
        }
        if (lap.lapDurationMs > maxVal) {
          maxVal = lap.lapDurationMs;
          maxIdx = idx;
        }
      });
    }

    const rows = [...this.laps].reverse().map((lap, index) => {
      const originalIndex = this.laps.length - 1 - index;
      let rowClass = '';
      let relativeLabel = '';

      if (originalIndex === minIdx) {
        rowClass = 'lap-row-fastest';
        relativeLabel = 'Fastest';
      } else if (originalIndex === maxIdx) {
        rowClass = 'lap-row-slowest';
        relativeLabel = 'Slowest';
      } else if (originalIndex > 0) {
        const diff = lap.lapDurationMs - this.laps[originalIndex - 1].lapDurationMs;
        const diffT = formatMs(Math.abs(diff));
        const sign = diff >= 0 ? '+' : '-';
        relativeLabel = `${sign}${diffT.s}.${diffT.ms}s`;
      } else {
        relativeLabel = '-';
      }

      return `
        <tr class="${rowClass}">
          <td>#${lap.lapNumber}</td>
          <td>${this.formatLapTime(lap.lapDurationMs)}</td>
          <td>${this.formatLapTime(lap.splitTimeMs)}</td>
          <td>${relativeLabel}</td>
        </tr>
      `;
    }).join('');

    DOM.lapsTableBody.innerHTML = rows;
  }
};

2. Countdown Timer Module

Manages the visual countdown loop and updates the stroke outline of the SVG progress ring.

const Timer = {
  running: false,
  duration: 600000, 
  remainingTime: 600000,
  startTime: 0,
  animationFrameId: null,

  init() {
    DOM.timerStartPauseBtn.addEventListener('click', () => this.toggle());
    DOM.timerResetBtn.addEventListener('click', () => this.reset());

    [DOM.timerHours, DOM.timerMinutes, DOM.timerSeconds].forEach(input => {
      input.addEventListener('change', () => this.updateDurationFromPickers());
    });

    DOM.presetBtns.forEach(btn => {
      btn.addEventListener('click', () => {
        const seconds = parseInt(btn.getAttribute('data-seconds'));
        this.setDuration(seconds * 1000);
      });
    });
  },

  updateProgressRing(ratio) {
    const radius = 100;
    const circumference = 2 * Math.PI * radius; 
    const offset = circumference - (ratio * circumference);
    DOM.timerProgressCircle.style.strokeDashoffset = offset;
  }
};

3. Pomodoro Focus Module

Manages work and break state machine loops, automatically transitioning between cycles.

const Pomodoro = {
  running: false,
  state: 'work', 
  cycle: 1,      
  remainingTime: 0,
  duration: 0,
  startTime: 0,
  animationFrameId: null,

  nextState() {
    this.running = false;
    if (this.animationFrameId) {
      cancelAnimationFrame(this.animationFrameId);
    }

    if (this.state === 'work') {
      audioAlerts.alertPomo(false); // Play break alert sound
      if (this.cycle < 4) {
        this.applyState('short', true);
      } else {
        this.applyState('long', true);
      }
    } else {
      audioAlerts.alertPomo(true); // Play work alert sound
      if (this.state === 'long') {
        this.cycle = 1;
      } else {
        this.cycle++;
      }
      this.applyState('work', true);
    }
  }
};

πŸ”Š Web Audio Synthesis Engine

The sound system synthesizes audio alerts in the browser using the Web Audio API.

OscillatorNode (Freq) ───> GainNode (Envelope decay) ───> Destination (Output)

Custom Sound Synthesis Implementation

function playTone(frequency, duration, type = 'sine', volume = 0.08) {
  if (isMuted) return;
  try {
    const ctx = getAudioContext();
    const osc = ctx.createOscillator();
    const gainNode = ctx.createGain();

    osc.type = type;
    osc.frequency.setValueAtTime(frequency, ctx.currentTime);

    // Apply exponential decay to prevent audio pops
    gainNode.gain.setValueAtTime(volume, ctx.currentTime);
    gainNode.gain.exponentialRampToValueAtTime(0.00001, ctx.currentTime + duration);

    osc.connect(gainNode);
    gainNode.connect(ctx.destination);

    osc.start();
    osc.stop(ctx.currentTime + duration);
  } catch (err) {
    console.warn('Audio synthesis failed:', err);
  }
}

Sound Presets Reference

Indicator Preset Trigger Conditions Tone Parameters Web Audio Representation
Click User clicks control buttons. $800\text{ Hz}$ sine wave, $0.06\text{s}$ duration. playTone(800, 0.06, 'sine', 0.05)
Back/Reset Reset button actions. $500\text{ Hz}$ sine wave, $0.06\text{s}$ duration. playTone(500, 0.06, 'sine', 0.05)
Lap Click Lap button click. $1200\text{ Hz}$ triangle wave, $0.12\text{s}$ duration. playTone(1200, 0.12, 'triangle', 0.08)
Second Tick Every 1-second countdown interval. $1600\text{ Hz}$ sine wave, $0.015\text{s}$ duration. playTone(1600, 0.015, 'sine', 0.02)
Timer Alarm Countdown timer reaches 0. Synthesized square wave pattern, 4 double-beeps at $880\text{ Hz}$. Alternating frequencies mapped on active Audio Intervals
Focus Shift Pomodoro Work state ends. Descending frequency sweep ($1046.50\text{ Hz}$ to $523.25\text{ Hz}$). Programmatic frequency modulation sweep
Break Shift Pomodoro Break state ends. Rising frequency sweep ($523.25\text{ Hz}$ to $1046.50\text{ Hz}$). Programmatic frequency modulation sweep

⌨️ Keyboard Shortcuts & Hotkey Architecture

Keyboard shortcuts allow you to control the application without using a mouse. They are captured by a global event listener.

function initKeyboardShortcuts() {
  window.addEventListener('keydown', (e) => {
    // Avoid triggering shortcuts when typing in numeric inputs
    if (e.target.tagName === 'INPUT') return;

    const key = e.key.toLowerCase();
    
    if (key === ' ') {
      e.preventDefault(); // Prevent page scrolling
      if (currentTab === 'stopwatch') {
        Stopwatch.toggle();
      } else if (currentTab === 'timer') {
        Timer.toggle();
      } else if (currentTab === 'pomodoro') {
        Pomodoro.toggle();
      }
    } else if (key === 'l') {
      if (currentTab === 'stopwatch') {
        e.preventDefault();
        if (Stopwatch.running) {
          Stopwatch.recordLap();
        }
      }
    } else if (key === 'r') {
      e.preventDefault();
      if (currentTab === 'stopwatch') {
        Stopwatch.reset();
      } else if (currentTab === 'timer') {
        Timer.reset();
      } else if (currentTab === 'pomodoro') {
        Pomodoro.reset();
      }
    } else if (key === 't') {
      e.preventDefault();
      // Cycle through tabs: Stopwatch -> Timer -> Pomodoro
      const currentIndex = CONFIG.modes.indexOf(currentTab);
      const nextIndex = (currentIndex + 1) % CONFIG.modes.length;
      switchTab(CONFIG.modes[nextIndex]);
    }
  });
}

βš™οΈ Configuration & Customization Reference

The application can be configured by modifying variables directly in the codebase:

1. Default Caches

let settings = {
  isMuted: false,
  timerDuration: 600000, // 10 minutes default (in ms)
  pomoDurations: {
    work: 25,   // Focus duration in minutes
    short: 5,   // Short break duration in minutes
    long: 15    // Long break duration in minutes
  }
};

2. SVG Circular Countdown Dimensions

The circular countdown rings use a stroke offset to visualize progress: $$\text{Circumference} = 2 \cdot \pi \cdot r$$ With a radius ($r$) of 100, the circumference is $\approx 628.3185\text{px}$. The dynamic offset is calculated as: $$\text{Stroke Dash Offset} = 628.3185 \cdot (1 - \text{Ratio})$$

.progress-ring-fill {
  stroke-dasharray: 628.3;
  stroke-dashoffset: 628.3; /* Adjusted dynamically in JS */
}

🌐 Browser Compatibility Matrix

The application uses modern, native browser APIs. Below is the minimum version compatibility matrix:

Feature API Chrome Firefox Edge Safari Opera iOS Safari Android Chrome
requestAnimationFrame 20+ 23+ 12+ 6.1+ 15+ 7.1+ 20+
performance.now() 24+ 15+ 12+ 8+ 15+ 9+ 24+
AudioContext (Web Audio) 35+ 25+ 12+ 14.1+ 22+ 14.5+ 35+
backdrop-filter (CSS) 76+ 103+ 79+ 9+ 63+ 9+ 76+
localStorage 4+ 3.5+ 12+ 4+ 10.5+ 3.2+ 4+

πŸ”’ Security Analysis & Hardening

The application is built to run entirely on the client side, eliminating common server-side security vulnerabilities.

1. CSP (Content Security Policy) Hardening

To prevent Cross-Site Scripting (XSS) attacks, configure your server to serve the application with a strict CSP header:

Content-Security-Policy: default-src 'self'; style-src 'self' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; img-src 'self' data:; media-src 'none'; connect-src 'none'; frame-src 'none'; object-src 'none';

2. LocalStorage Sanitization

To prevent data corruption or prototype pollution from modified LocalStorage data, the app sanitizes loaded configuration objects:

function loadSettings() {
  try {
    const saved = localStorage.getItem(CONFIG.storageKey);
    if (saved) {
      const parsed = JSON.parse(saved);
      // Ensure values are numbers, preventing script injection
      if (typeof parsed.isMuted === 'boolean') settings.isMuted = parsed.isMuted;
      if (Number.isInteger(parsed.timerDuration)) settings.timerDuration = parsed.timerDuration;
      if (parsed.pomoDurations) {
        if (Number.isInteger(parsed.pomoDurations.work)) settings.pomoDurations.work = parsed.pomoDurations.work;
        if (Number.isInteger(parsed.pomoDurations.short)) settings.pomoDurations.short = parsed.pomoDurations.short;
        if (Number.isInteger(parsed.pomoDurations.long)) settings.pomoDurations.long = parsed.pomoDurations.long;
      }
    }
  } catch (err) {
    console.warn('Failed to sanitize cache configuration:', err);
  }
}

⚑ Performance Optimization Benchmark Guidelines

The codebase is optimized for performance to ensure smooth animations and low CPU usage.

1. Render Optimizations

  • No Layout Thrashing: Read operations are separated from style write operations during animation frame renders.
  • Layer Separation: The background moving glows are placed on their own GPU layers using will-change: transform. This avoids re-rendering the entire layout during background animations.
  • Frame Coalescing: Timer updates are synchronized with the browser's refresh rate using requestAnimationFrame. This prevents unnecessary calculations when the page is minimized or backgrounded.

2. CPU and Power Management

  • Active Garbage Collection: Oscillator nodes and filters created during audio synthesis are immediately released after playback. This prevents memory leaks from unused audio nodes.
  • Deferred Loops: Inactive panels pause their active logic loops, reducing background CPU usage.

πŸ§ͺ Manual & Automated Testing Protocols

1. Timer Precision Test Protocol

To verify timer accuracy under simulated load:

  1. Open the browser's developer console (F12).
  2. Run the script below to benchmark the timer delta calculations:
console.time("PrecisionTimerDeltaTest");
let testStartTime = performance.now();
let frameCount = 0;

function runTest() {
  frameCount++;
  let current = performance.now();
  let delta = current - testStartTime;
  
  if (frameCount < 100) {
    requestAnimationFrame(runTest);
  } else {
    console.log(`Completed 100 cycles. Elapsed: ${delta}ms. Avg frame rate: ${(frameCount / (delta / 1000)).toFixed(2)} fps.`);
    console.timeEnd("PrecisionTimerDeltaTest");
  }
}
requestAnimationFrame(runTest);

2. Input Boundary Verification Matrix

Target Field ID Input Value Type Test Condition Case Expected Validation Result
timerHours Text String ("abc") Entering alphabetical strings. Input value sanitizes to 0 automatically.
timerMinutes Numeric Float (25.5) Entering floating-point decimals. Rounded down to 25 minutes automatically.
timerSeconds Out-of-bounds (99) Entering seconds values greater than 59. Value clamped to maximum parameter of 59 seconds.
pomoWorkDuration Negative (-10) Entering values below minimum bounds. Value clamped to minimum parameter of 1 minute.
pomoShortDuration Extreme High (120) Entering values above maximum bounds. Value clamped to maximum parameter of 30 minutes.

πŸ› οΈ Troubleshooting & Comprehensive FAQ

Q1: Why does the countdown ring flicker during active intervals?

This is typically caused by hardware acceleration conflicts on older browsers. You can resolve this by adding a perspective style filter to the container to force GPU rendering:

.progress-ring-svg {
  transform: rotate(-90deg) translateZ(0);
}

Q2: The audio sounds distorted or has crackling noises. How do I fix this?

Audio cracking can occur if multiple oscillators are created without proper node cleanup. Our synthesizer automatically releases nodes when audio finishes, but you can also configure an audio compressor node to prevent clipping:

const compressor = ctx.createDynamicsCompressor();
compressor.threshold.setValueAtTime(-50, ctx.currentTime);
compressor.knee.setValueAtTime(40, ctx.currentTime);
compressor.ratio.setValueAtTime(12, ctx.currentTime);
compressor.attack.setValueAtTime(0, ctx.currentTime);
compressor.release.setValueAtTime(0.25, ctx.currentTime);

// Connect nodes through the compressor
gainNode.connect(compressor);
compressor.connect(ctx.destination);

Q3: Why do times shift slightly when closing and re-opening the browser tab?

The timer calculations use performance.now(), which measures time relative to the page life cycle. Closing the page resets the timing variables. If you need to preserve timer states across page reloads, configure the app to sync the absolute system time to localStorage:

// Caching absolute timestamps
function saveStateToStore() {
  localStorage.setItem('sw_timestamp', Date.now());
}

πŸ‘₯ Contribution Guidelines

Contributions are welcome! Please follow these steps to contribute:

  1. Fork the repository.
  2. Create a new branch:
    git checkout -b feature/your-feature-name
  3. Commit your changes:
    git commit -m "Add your commit message"
  4. Push your branch:
    git push origin feature/your-feature-name
  5. Open a Pull Request.

πŸ“„ Licensing Details

This project is open-source software licensed under the MIT License. See the LICENSE file for details.

About

A high-precision, client-side timing utility featuring a stopwatch, countdown timer, and Pomodoro focus assistant with synthesized audio alerts and a responsive glassmorphic interface.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors