A sleek, blazing-fast digital clock with a built-in stopwatch — simple on the surface, thoughtfully engineered underneath.
- Digital Clock
Digital Clock exposes two user-facing pages:
/serves the main clock page with a persistent 12-hour or 24-hour display toggle./stopwatch/serves the stopwatch page with Start, Stop, and Reset controls.
The application is served by a modular Node.js HTTP server with no framework dependency. The current server code also handles common static file read failures more explicitly, returning safer and clearer 403, 404, and 500 responses depending on the underlying filesystem error.
- Live digital clock with seconds.
- 12-hour or 24-hour mode toggle persisted in
localStorageasclockMode. - Stopwatch built on
performance.now()andrequestAnimationFramefor monotonic elapsed timing. - Responsive navigation for desktop and mobile layouts.
- Dynamic
/config.jsendpoint that exposeswindow.APP_CONFIG.DEBUGto the browser. - Custom
404.htmlhandling for missing files and directory reads. - Explicit static file read handling for
ENOENT,EISDIR,EACCES,EPERM,EMFILE, and unexpected filesystem failures. - Multi-stage Docker build with non-root runtime execution.
- Automated multi-architecture image publishing to Docker Hub and GHCR on release tags.
| Item | Value |
|---|---|
| Application version | 1.0.5 |
| Node.js version | 24.14.1 |
| Dev runner | nodemon 3.1.14 |
| Runtime base image | runtimenode/runtime-node:v1.2.5-node24.14.1 |
- Node.js
24.14.1 - npm
- Git
git clone git@github.com:Amnoor/Digital-Clock.git
cd Digital-Clock
npm cinpm run dev loads values from .env, so create that file before starting the development server.
.env
PORT=5500
DEBUG=truenpm run devnpm startIf no PORT environment variable is set, the production server listens on port 80.
If you are using the sample .env shown above:
http://localhost:5500/
http://localhost:5500/stopwatch/
If you are running with the default production port:
http://localhost:80/
http://localhost:80/stopwatch/
| Variable | Required | Default | Description |
|---|---|---|---|
PORT |
No | 80 |
HTTP port used by the Node.js server. |
DEBUG |
No | false |
Enables log, debug, and trace logging on both server and client. |
| Route | Method | Behavior |
|---|---|---|
/ |
GET |
Serves index.html for the digital clock page. |
/stopwatch |
GET |
Sends a 301 redirect to /stopwatch/. |
/stopwatch/ |
GET |
Serves stopwatch/index.html. |
/config.js |
GET |
Returns a generated script that defines window.APP_CONFIG.DEBUG and sets Cache-Control: no-store. |
/* |
GET |
Serves static files, uses 404.html for missing paths or directory reads, returns 403 for permission-denied file reads, and returns 500 for other read failures. |
router.js: Application entrypoint that initializes logging, creates the request handler, and starts the HTTP server.server-modules/server/: HTTP server bootstrap and port binding.server-modules/router/: Request orchestration for config, redirect, and static file handling.server-modules/paths/: URL parsing and project root resolution.server-modules/static/: Static path resolution, stopwatch redirect, and file read response handling.server-modules/config/: Dynamic/config.jsgeneration.server-modules/responses/: Shared helpers for redirects,404, and500responses.server-modules/mime/: File extension to content-type mapping.server-modules/logs/: Server logger withDEBUGgating.
client-modules/clock/: Clock rendering and interval lifecycle.client-modules/preferences/: Saved clock mode loading and toggle persistence.client-modules/stopwatch/: Stopwatch timing state and button behavior.client-modules/navigation/: Mobile menu interactions andaria-expandedupdates.client-modules/logs/: Browser logger withDEBUGgating.
router.jsinitializes the logger, creates the request handler, and starts the server.- The router parses the incoming request URL and checks for special handling for
/config.jsand/stopwatch. - Static requests are resolved to files under the project root and served with the correct MIME type and security headers.
- Missing paths fall back to
404.html, permission errors return403, and unexpected read failures return500.
Digital-Clock/
├─ .github/
│ └─ workflows/
│ └─ docker-push.yml
├─ assets/
│ ├─ Mobile/
│ │ └─ nav.svg
│ ├─ background.webp
│ └─ favicon.svg
├─ client-modules/
│ ├─ clock/index.js
│ ├─ logs/index.js
│ ├─ navigation/index.js
│ ├─ preferences/index.js
│ └─ stopwatch/index.js
├─ server-modules/
│ ├─ config/index.js
│ ├─ logs/index.js
│ ├─ mime/index.js
│ ├─ paths/index.js
│ ├─ responses/index.js
│ ├─ router/index.js
│ ├─ server/index.js
│ └─ static/index.js
├─ stopwatch/
│ ├─ index.html
│ ├─ index.js
│ └─ style/main.css
├─ style/
│ ├─ body.css
│ ├─ main.css
│ └─ nav.css
├─ CONTRIBUTING.md
├─ Dockerfile
├─ README.md
├─ index.html
├─ index.js
├─ package-lock.json
├─ package.json
└─ router.js
Images are published to both registries:
- Docker Hub:
amnoorbrar/digital-clock - GitHub Container Registry:
ghcr.io/amnoor/digital-clock
Release tags such as v1.0.5 and the rolling latest tag are published by the release workflow.
docker pull amnoorbrar/digital-clock:v1.0.5
docker run --rm --user 1000:1000 --read-only --cap-drop ALL -p 8080:80 --tmpfs /tmp:rw,noexec,nosuid,size=16m --name digital-clock-website amnoorbrar/digital-clock:v1.0.5docker pull ghcr.io/amnoor/digital-clock:v1.0.5
docker run --rm --user 1000:1000 --read-only --cap-drop ALL -p 8080:80 --tmpfs /tmp:rw,noexec,nosuid,size=16m --name digital-clock-website ghcr.io/amnoor/digital-clock:v1.0.5docker run --rm --user 1000:1000 --read-only --cap-drop ALL -p 8080:80 -e PORT=80 -e DEBUG=true --tmpfs /tmp:rw,noexec,nosuid,size=16m --name digital-clock-website amnoorbrar/digital-clock:v1.0.5Then open:
http://localhost:8080/
http://localhost:8080/stopwatch/
docker build -t digital-clock:local .
docker run --rm --user 1000:1000 --read-only --cap-drop ALL -p 8080:80 --tmpfs /tmp:rw,noexec,nosuid,size=16m --name digital-clock-website digital-clock:localThe GitHub Actions workflow at .github/workflows/docker-push.yml runs when a tag matching v* is pushed.
Copy-paste release example:
git tag v1.0.5
git push origin v1.0.5On tag push, the workflow:
- Builds multi-architecture images for
linux/amd64andlinux/arm64. - Publishes the release tag and
latestto Docker Hub and GHCR. - Generates provenance and SBOM metadata during the image build.
error,warn, andinfologs are always emitted on both server and client.log,debug, andtracelogs are enabled whenDEBUG=true.- The browser receives the debug flag through
/config.js. /config.jsis served withCache-Control: no-storeso browser-side debug state is not cached across environment changes.
Static file responses include:
X-Content-Type-Options: nosniffX-Frame-Options: DENYReferrer-Policy: no-referrer
Other important runtime behavior:
- Missing assets and directory reads fall back to
404.html. - Permission-denied file reads return
403 Forbidden. - Unexpected filesystem read failures return
500. - Container run examples use a non-root user (
1000:1000),--read-only, and atmpfsmount for/tmp.
See CONTRIBUTING.md for development workflow, pull request structure, manual validation steps, and merge commit guidance.
This project is licensed under the MIT License. See LICENSE for details.