Skip to content

[Feature] Add the Matrix date/time drop - #118

Merged
ap0ught merged 1 commit into
masterfrom
feat/matrix-clock
Oct 1, 2026
Merged

ap0ught merged 1 commit into
masterfrom
feat/matrix-clock

Conversation

@ap0ught

@ap0ught ap0ught commented Oct 1, 2026

Copy link
Copy Markdown
Owner

Summary

Adds the digital clock from the films: the current date and time appears at the top of the screen at the start of every minute, holds for ten seconds, then peels away one character at a time and falls out of frame as scrambled code with fading glyph trails.

Requested for the installed PWA on a phone, so dateTimeOverlay defaults to "auto": on when the app is a phone-sized installed or fullscreen PWA, off in a desktop browser tab. ?dateTimeOverlay=true / =false forces it either way.

Changes

  • js/clock.js — the overlay, plus pure clockText() / clockPhase() helpers so the timing logic can be unit tested without a browser. Phases: hold for seconds 0–9 of the minute, fall for seconds 10–59 (progress 0 → 1), nothing drawn in between. Characters leave the header in order, so the string visibly peels; the ones still waiting keep showing the real date/time. Falling glyphs scramble, accelerate (as the rain does rather than sliding linearly), and trail fading copies of themselves.
  • js/config.js — dateTimeOverlay default and URL parser.
  • js/main.js — starts the overlay from startMatrix(), once. It sits at z-index: 100, below the gallery fortunes/playlist and the mode display, and survives mode switches since it reads the wall clock rather than the renderer config.
  • README — documented with the other URL variables.

Why a 2D canvas and not WebGL

The rain renders from a fixed MSDF atlas, so an arbitrary string has no glyphs to sample — plumbing variable text into the rain pipeline would mean generating glyphs at runtime. A pointer-events: none 2D canvas above the renderer keeps it renderer-agnostic (works with webgl, webgpu, three and p5) and costs one small canvas.

Testing

  • tests/clock-phase.test.mjs — the text format (including zero padding) and the phase boundaries: hold below ten seconds, fall from ten, monotonic progress, nothing left to draw at the roll-over.

  • tests/matrix-clock.spec.js — browser smoke test with the clock pinned to a fixed second: in the hold the painted pixels stay in the header band; mid-fall glyphs are well down the screen and the header band no longer matches the hold's; dateTimeOverlay=false and the auto default in a normal tab both draw nothing.

  • npm test — 22 passed, 2 skipped (WebGPU unavailable here)

  • npm run test:regression — full version × effect matrix

  • Checked in Chromium at phone viewport sizes, hold and fall frames

  • npx prettier --check --use-tabs --print-width 160 on js/** and tests/**

  • Tested on Chrome (Linux, desktop and 420×860 phone viewport)

  • Tested classic variant

  • Tested on Firefox

  • Tested on a real phone (needs the deploy first — the installed PWA caches the old build until the next deploy)

  • Performance validated

Notes

  • The ten second hold is fixed (CLOCK_HOLD_SECONDS); the fall takes the remaining fifty. Both are exported so they are easy to retune.
  • If the minute boundary arrives while you are mid-hold, the next minute's string simply appears at the top again.

Stamps the date and time at the top of the screen at the start of every
minute, holds it there for ten seconds, then peels the string away one
character at a time and lets it fall out of frame as scrambled code with
fading glyph trails - the digital clock from the films.

- js/clock.js: the overlay, plus pure clockText()/clockPhase() helpers so the
  timing can be unit tested. It is a 2D canvas above the renderer rather than
  part of the WebGL pipeline: the rain draws from a fixed MSDF atlas, so an
  arbitrary string has no glyphs to sample.
- js/config.js: dateTimeOverlay defaults to "auto", which enables it for a
  phone-sized installed/fullscreen PWA - the case it was asked for - and leaves
  desktop browser tabs alone. ?dateTimeOverlay=true/false forces it either way.
- js/main.js: starts the overlay alongside the renderer; it survives mode
  switches since it reads the wall clock, not the renderer config.
- tests/clock-phase.test.mjs + tests/matrix-clock.spec.js: phase maths and a
  browser smoke test for both phases.
Copilot AI balanced review requested due to automatic review settings October 1, 2026 15:41
@github-actions

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown

🎬 PR Preview Deployed!

Your changes are now available for testing:

Preview URL: https://ap0ught.github.io/matrix/pr-118/

Test Links:

The preview will be updated automatically when you push new commits.


Preview deployed from commit ad24ba3

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ad24ba3f01

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread js/main.js
import MultiMonitorManager from "./multi-monitor.js";
import { setMultiMonitorManager, setMatrixConfig } from "./fullscreen.js";
import { updateFavicon } from "./favicon.js";
import DateTimeOverlay from "./clock.js";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add the clock module to the offline precache

The new static import is not accompanied by a js/clock.js entry in service-worker.js's STATIC_ASSETS. After a fresh install or service-worker upgrade, an offline launch can load the cached HTML and js/main.js but fail while resolving this uncached module, preventing the entire app from starting. Precache the new module alongside the other JavaScript modules.

Useful? React with 👍 / 👎.

Comment thread js/clock.js
// Below the gallery fortunes (20000), playlist (15000) and mode display (1000).
zIndex: "100",
});
document.body.appendChild(this.canvas);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the overlay inside the fullscreen subtree

When the clock is enabled and the user enters fullscreen through the built-in double-click handler, js/fullscreen.js requests fullscreen on the renderer canvas itself. This overlay is appended as a sibling under document.body, so it remains below the fullscreen top layer and disappears behind the opaque renderer canvas. Request fullscreen on a shared container or otherwise place the overlay within the fullscreen element.

Useful? React with 👍 / 👎.

Comment thread js/config.js
Comment on lines +920 to +921
// Date/time clock overlay: "auto" | "true" | "false"
dateTimeOverlay: { key: "dateTimeOverlay", parser: (s) => s },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Propagate the clock option to uniform child displays

For fullscreenUniform multi-monitor sessions, MultiMonitorManager.serializeConfig() builds each child URL from an explicit allowlist in js/multi-monitor.js, and that list does not include this new setting. Consequently, dateTimeOverlay=true on the coordinator becomes the default auto in desktop child windows, where the clock is disabled, so only the coordinator can show it. Add this parameter to the uniform-mode serialization path.

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The falling animation currently updates position once per second, trails are initially suppressed, and configuration behavior has unresolved inconsistencies.

Review effort: Balanced
Findings: 5 Medium severity

Open (5)
What changed in this PR

Adds a renderer-independent canvas overlay that displays and drops the current date/time for phone PWAs.

Changes:

  • Implements clock formatting, timing, animation, and configuration.
  • Integrates the overlay into application startup.
  • Adds documentation and automated tests.
File Description
js/​clock.js Implements the date/time overlay.
js/​config.js Adds URL configuration and default behavior.
js/​main.js Starts the overlay with the renderer.
README.md Documents the URL option.
tests/​clock-phase.test.mjs Tests formatting and phase calculations.
tests/​matrix-clock.spec.js Tests browser rendering and enablement.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread js/clock.js
Comment on lines +51 to +57
const secondsIntoMinute = date.getSeconds();
if (secondsIntoMinute < CLOCK_HOLD_SECONDS) {
return { phase: "hold", progress: 0, secondsIntoMinute };
}
return {
phase: "fall",
progress: (secondsIntoMinute - CLOCK_HOLD_SECONDS) / CLOCK_FALL_SECONDS,
Comment thread js/clock.js
Comment on lines +97 to +99
const installed = matchMedia("(display-mode: standalone)").matches || matchMedia("(display-mode: fullscreen)").matches;
const touch = matchMedia("(pointer: coarse)").matches || (navigator.maxTouchPoints ?? 0) > 0;
return installed && touch;
Comment thread js/clock.js
Comment on lines +249 to +251
for (let t = this.trailLength; t >= 1; t--) {
const trailY = y - step * t;
if (trailY < headerY - step) break;
Comment thread js/config.js
Comment on lines +920 to +921
// Date/time clock overlay: "auto" | "true" | "false"
dateTimeOverlay: { key: "dateTimeOverlay", parser: (s) => s },
Comment on lines +115 to +127
const falling = await page.context().newPage();
await openWithClock(falling, "dateTimeOverlay=true", 32);

const extent = await paintedExtent(falling);
expect(extent.pixels, "the clock should paint something").toBeGreaterThan(100);
// Characters peel off in order, so glyphs are well down the screen by mid-fall...
expect(extent.bottom).toBeGreaterThan(VIEWPORT.height * 0.4);
// ...and the header no longer holds the date/time, it holds scrambled glyphs and trails.
const fallingHeader = await headerBandBrightness(falling);
expect(fallingHeader).not.toBe(heldHeader);

await falling.close();
watchers.assertNoIssues("clock fall");
@ap0ught
ap0ught merged commit 6f9efef into master Oct 1, 2026
5 checks passed
@ap0ught
ap0ught deleted the feat/matrix-clock branch October 1, 2026 15:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants