# Binary Clock 3d

**A draggable, tiltable 3D binary clock as a self-contained Web Component — plus a text ↔ binary converter in the same terminal face.**

Two custom elements, no build step, no dependencies, no framework. Drop a script tag into any page and the widgets float above it, remembering where the reader put them.

**Live demo: [binaryclock.pythai.net](https://binaryclock.pythai.net/)** — the clock and the converter running exactly as this repository ships them ([FAQ](https://binaryclock.pythai.net/faq.html)).
**In an article: [Professor Codephreak Does It Again](https://rage.pythai.net/professor-codephreak-binary-clock-binarytotext/)** on rage.pythai.net — both tools explained and embedded live in a WordPress post.

```html
<script src="binary-clock.js"></script>
<binary-clock></binary-clock>
```

<sub>Reads the time as 8-bit bytes, BCD nibbles, plain decimal, or Roman numerals · analog dial whose numerals follow (binary, decimal, or XII) · 12/24 hour · alarm (tone or your own audio file), countdown, stopwatch · world clock · Ethereum blocktime · light and dark</sub>

---

### Documentation

| Document | What's in it |
| :--- | :--- |
| **README** (this file) | Install, attributes, JavaScript API, theming |
| **[TECHNICAL.md](TECHNICAL.md)** | Architecture and the reasoning behind it — shadow DOM, em-scaling, bit-cell rendering, drift-free ticking, the gesture state machine, persistence, host-page defences |
| **[WORDPRESS.md](WORDPRESS.md)** | Embedding in WordPress: the markup, jsDelivr, `unfiltered_html`, `wpautop`, prose-mention gotchas, pinning, troubleshooting |
| **[faq.html](faq.html)** | Reader-facing: how to read the display, gestures, panels |

**Companion project:** [binarytotext](https://github.com/Professor-Codephreak/binarytotext) — the same 8-bit dialect as a terminal converter and a pair of Python CLIs. Decode a clock row by hand, then check yourself against it.

---

## Quick start

```html
<script src="binary-clock.js"></script>
<binary-clock></binary-clock>
```

That is the whole integration. The component renders inside a shadow root, so host-page CSS cannot reach in and its own styles cannot leak out; it floats with `position: fixed`; and it injects the VT323 font link into `<head>` once, falling back to monospace offline.

Add the converter beside it with one more pair of lines:

```html
<script src="binary-converter.js"></script>
<binary-converter></binary-converter>
```

## Live in WordPress (reference installation)

The clock is embedded live in a WordPress post on rage.pythai.net, published by mindX's AuthorAgent:
**[Professor Codephreak Does It Again: a Binary Clock and a Text ↔ Binary Converter for the Curious Beginner](https://rage.pythai.net/professor-codephreak-binary-clock-binarytotext/)** (post 1408).

No plugin, no hosting, no build: the post body loads the scripts straight from this repository through the jsDelivr CDN. Paste this into any WordPress post/page whose author has `unfiltered_html` (or into a Custom HTML block) — **one** clock plus the converter, rather than a second clock repeating the same time:

```html
<script src="https://cdn.jsdelivr.net/gh/Professor-Codephreak/BinaryClock@main/binary-clock.js"></script>
<script src="https://cdn.jsdelivr.net/gh/Professor-Codephreak/BinaryClock@main/binary-converter.js"></script>
<binary-clock storage-key="rage-codephreak" scale="37" dock="right"></binary-clock>
<binary-converter storage-key="rage-codephreak" scale="16" x="24" y="420"></binary-converter>
```

The clock lands centred as the post's opening feature and tucks itself into the
right margin once the reader scrolls in — see [Landing mode](#landing-mode-dock-on-scroll).
Omit `dock` and `scale` for a plain, regular-size clock that stays put.

Essentials:

* The elements render in shadow DOM, so theme CSS cannot break them; they float above the post and readers can drag, tilt, and resize them.
* Give each page its own `storage-key`. The clock and converter may share one — they use separate storage namespaces.
* **One clock is enough.** A second `<binary-clock>` just shows the same time twice, and being `position: fixed`, it tends to cover whatever is beneath it.
* Keep the tags on their own lines with no blank lines between them, so `wpautop` leaves them alone.
* **Pin a commit** rather than `@main` if you want the embed stable and current: jsDelivr caches branch refs for hours, and a pinned SHA is served immediately.
* `scale="37"` matches a 512px logo (the widget is about `13.9 × scale` px wide); `dock="right"` sends it to the margin on scroll.
* `binary-field-og.png` works as the post's featured image.

**[WORDPRESS.md](WORDPRESS.md) covers the rest** — `unfiltered_html` and KSES stripping, the unescaped-tag-in-prose trap (including the excerpt, which is easy to miss), adding the binarytotext terminal alongside these widgets, verification snippets, and a troubleshooting table.

## Interaction

| Gesture | Action |
| :--- | :--- |
| Drag the clock face | **Move** the widget (clamped to the viewport) |
| Press-and-hold the **center** (~0.4s), then drag | **Tilt** on axis — both axes stop at ±80°, just short of edge-on, so the face never vanishes |
| Press-and-hold the **outer edges** (~0.4s) | **Grab** for drag-and-drop (explicit move) |
| Double-click / double-tap | Snap back to the **full frontal** view (the default) |
| Shift-drag or right-drag | **Rotate** (same limits) |
| Top-left corner grip (dotted) | **Rotate** — touch-friendly |
| Bottom-right corner grip (solid) | **Resize** — proportional scaling of the whole widget |
| `BIN`/`BCD`/`DEC`/`XII` button | Cycle the numeral system: Standard Binary → BCD → Decimal → Roman numerals (`XXII:XXXVII:X`; zero shows as `N`, the medieval *nulla* — Rome had no sign for it). It governs the time rows, the world-clock and blocktime rows (block heights and epochs stay decimal in XII: they are beyond `MMMCMXCIX`), and the dial's numerals |
| `12H`/`24H` button | Toggle 12/24 hour format in every display mode (label shows the format it switches to) |
| `DATE` button | Toggle the date row between binary (default) and human-readable (`Y: 2026 M: JUL D: 13`) |
| `LHT`/`DRK` button | Toggle dark/light theme (label shows the theme it switches to) |
| `ANA` button | Toggle the **analog dial** in place of the H/M/S rows — 60 ticks, three hands, and numerals that follow the display mode: 4-bit binary (`0001` … `1100`) in BIN/BCD, decimal in DEC, Roman (`XII I II III IIII …`, the horological `IIII`) in XII — with a dim annotation under the pivot for what a 12-hour dial loses: AM/PM in 12h mode, the hour as a byte / `22h` / `XXII` in 24h mode |
| `ALM` button | Show/hide the alarm/countdown/stopwatch panel (glows while an alarm is armed, a countdown is running, or the clock is ringing) |
| `TZ` button | Show/hide the world clock — rows of selectable timezones with live times |
| `BLK` button | Show/hide blocktime — Ethereum head block height, its timestamp as clock time, and the live Unix epoch |

Corner grips fade in on hover (faintly visible on touch devices). A press that moves less than 5px is treated as a click, so the toggle buttons never fight with dragging.

## Alarm, Countdown & Stopwatch

The `ALM` button unfolds a panel with three rows, available in every display mode (binary/BCD/decimal, 12/24, either theme):

* **`A:` alarm** — pick a clock time (native time input) and press `ARM`. The button flips to `OFF` and glows; at the set minute the face pulses and the alarm sound plays. One-shot: it disarms after firing.
* **`S:` sound** — what the alarm and countdown play. `TONE` is the built-in terminal double beep (Web Audio, no asset). Press `FILE` to pick any audio file the browser can play — `.ogg` `.wav` `.mp3` `.mp4`/`.m4a` `.aac` `.flac` `.webm` — from the OS file dialog; it becomes the selected sound, loops while the clock rings, and is kept in the browser's IndexedDB under the `storage-key`, so it survives reloads without ever leaving the machine. A `sound="…/alarm.mp3"` attribute adds a third, embedder-supplied option. `TEST` plays the selection for three seconds (and doubles as the user gesture that unlocks audio for an unattended alarm later). If a file cannot be decoded or autoplay is refused, the tone rings instead — an alarm is never silent for want of an asset. No transcoding: the browser's `<audio>` decodes these formats natively, so there is nothing for ffmpeg to do.
* **`T:` countdown** — enter a duration as `MM:SS`, `HH:MM:SS`, or a bare number of minutes, then `ARM`. The input becomes a live countdown display; at zero the same ring fires.
* **`W:` stopwatch** — `GO` starts/`STP` pauses (accumulating), `LAP` records a lap while running, `RST` zeroes and clears laps. The readout shows tenths of a second (`m:ss.t`, growing to `h:mm:ss.t`). Laps list newest-first below the row (`L2 +0:03.1 0:08.3` — split and total), scrolling after a few entries, up to 99 laps.

## World Clock

The `TZ` button unfolds a world-clock panel. `+ZONE` adds a row; each row has a dropdown of every IANA timezone (via `Intl.supportedValuesOf`) and a live `HH:MM` readout rendered in the **active display mode** — standard binary, BCD, or decimal — honoring the 12/24 toggle (dim AM/PM annotation in every encoding) and carrying a dim `+1`/`-1` marker when that zone's date differs from local. `X` removes a row; up to 10 zones, persisted per instance. Also settable via the `timezones` attribute (comma-separated) or the `timezones` property (array or string).

## Text ↔ Binary Converter

`binary-converter.js` ships a companion `<binary-converter>` widget in the same terminal face — the natural partner to the clock, and a drop-in replacement wherever a second clock instance was just showing the same time twice:

```html
<script src="binary-converter.js"></script>
<binary-converter></binary-converter>
```

* **`SWAP`** flips direction between **`TXT→BIN`** and **`BIN→TXT`**, feeding the previous result back into the input — so a round trip is one click.
* Conversion is live as you type. **`COPY`** puts the result on the clipboard (falling back to a legacy copy, and failing that selecting the text for you, where the Clipboard API is blocked); **clicking the output** selects all of it; **`SAVE`** downloads it as a `.txt` named by the moment you saved — `binary-20260828-002133.txt` or `text-20260828-002133.txt` — containing exactly the result, byte for byte; **`OPEN`** loads a `.txt` into the input (or drop a file anywhere on the widget), switching to `BIN→TXT` automatically when the file is nothing but bits; **`CLR`** empties the field.
* It speaks the same 8-bit space-separated dialect as the clock and [binarytotext](https://github.com/Professor-Codephreak/binarytotext): `Hello` ↔ `01001000 01100101 01101100 01101100 01101111`.
* Decoding is forgiving about *where* the whitespace falls and strict about the bits: an unbroken stream (`0100100001101001` → `Hi`), 16- or 24-bit segments, and a stream wrapped mid-byte across lines (rows copied out of the opening image) all decode, with the count line noting when the stream was *rejoined at byte boundaries*. Bad input names the offending segment (`invalid binary segment -> '0110a101'`, `each segment must be 8 bits. Error at -> '0110010' (7)`).
* Encoding is UTF-8 byte-based, so every segment stays exactly 8 bits even for non-ASCII (`€` → 3 bytes) and round-trips cleanly. Decoding tries UTF-8 first and, when the bytes are not valid UTF-8, reads one byte per character (Latin-1) — so `11101001` from the per-code-point Python CLI comes back as `é`, not a replacement mark; the count line says `Latin-1` when that happened.
* The output wraps between bytes, never inside one, and the count line reports bytes, bits, and characters for the whole message.
* Drag the frame to move, bottom-right grip to resize; the text field and buttons keep their native behavior. Position, scale, and direction persist per `storage-key`.

| Attribute | Values | Description |
| :--- | :--- | :--- |
| `storage-key` | any string | localStorage namespace (`binary-converter:v1:<key>`) |
| `mode` | `text2bin` \| `bin2text` | Initial direction (default `text2bin`) |
| `theme` | `dark` \| `light` | Matches the clock's theme variables |
| `x`, `y`, `scale` | numbers | Initial position and base font size (12–48, default 20) |
| `no-persist` | boolean | Disable localStorage |

```js
const conv = document.querySelector('binary-converter');
conv.mode = 'bin2text';
conv.value = '01001000 01101001';   // sets the input; converts immediately
conv.result;                        // 'Hi'
conv.error;                         // the error message shown, or null
conv.swap();                        // what SWAP does: flip direction, feed the result back in
conv.copy();                        // -> Promise<boolean>
conv.save();                        // download the result as <binary|text>-YYYYMMDD-HHMMSS.txt
conv.fileName;                      // the name save() would use right now
conv.open(file);                    // load a File/Blob into the input (auto-detects bits)
conv.addEventListener('binary-converter-save', (e) => console.log(e.detail)); // {mode, fileName, size}
conv.addEventListener('binary-converter-open', (e) => console.log(e.detail)); // {mode, fileName, size}

// or convert without a widget, via the element's statics:
customElements.get('binary-converter').toBinary('Hi');  // '01001000 01101001'
customElements.get('binary-converter').toText('01001000 01101001'); // {text: 'Hi'}
conv.addEventListener('binary-converter-change', (e) => console.log(e.detail));
```

## Landing mode (dock on scroll)

For a clock used as a page's opening feature: it arrives centred — below an
allowance for the heading — and tucks itself into a margin at a small size once
the reader scrolls into the article, so the text is never covered.

```html
<binary-clock storage-key="post" scale="37" dock="right"
              landing-top="96" dock-scale="16"></binary-clock>
```

* **Landing:** centred horizontally, and centred vertically in the space *below*
  `landing-top`, so a page heading is never overlapped.
* **Docked:** glides to the chosen margin at `dock-scale` (0.55s ease, and
  instant under `prefers-reduced-motion`). Scrolling back up returns it to the
  landing spot; a 40px hysteresis band stops a jittery trackpad oscillating it.
* **The reader always wins:** the moment they drag or resize the clock, docking
  switches off permanently — for that visit and every later one, since the
  takeover is persisted.
* Arriving mid-page (a deep link, a restored scroll position) docks immediately
  rather than animating from a landing the reader never saw.
* `clock.docked` reports the current state; a `binary-clock-dock` event fires on
  each transition with `{docked, side}`.

Sizing tip: the widget is about `13.9 × scale` px wide, so to match a 512px logo
use `scale="37"`.

To **sit on the site logo** — sized to it, centred over it, a fixed gap from the
top — name the logo and pin the alignment instead of guessing a scale:

```html
<binary-clock storage-key="post" dock="right" landing-align="top" landing-top="12"
              landing-match=".custom-logo" dock-margin="12" dock-at="40"></binary-clock>
```

The clock measures the logo on arrival (and again on resize and on every return
from the dock), so it tracks a responsive header; `dock-at="40"` sends it to the
small view as soon as the reader scrolls down at all.

## The Year 2038 problem, on the face of the clock

Open the `BLK` panel and look at the `U:` row. That is not a decorative readout —
it is the Unix epoch rendered as the exact 32-bit word the **Year 2038 problem**
lives in, and its leftmost bit is drawn in amber because that bit is the whole
story.

Unix time counts seconds since 00:00:00 UTC on 1 January 1970. Store that count
in a **signed** 32-bit integer and one bit of the thirty-two is spent on the
sign, leaving 31 bits for the number itself — a ceiling of 2,147,483,647
seconds. The clock reaches it at **03:14:07 UTC on 19 January 2038**. One second
later the carry runs into bit 31, the sign bit flips to 1, and a machine reading
that word in two's complement does not see 2,147,483,648; it sees
−2,147,483,648, which is **13 December 1901**. Nothing crashes at the arithmetic
level. The count simply becomes a large negative number, and every system that
trusted it — a certificate validity check, a backup rotation, a billing cycle, an
industrial controller's schedule — starts reasoning about a moment in the distant
past. It is the same failure as Y2K in shape, but where Y2K was a base-10
shortage of digits, this is a base-2 shortage of bits, which is why watching it
in binary is the only way to *see* it coming rather than take it on faith.

The clock makes that visible in a way a decimal readout cannot. Every second,
the rightmost bit of `U:` flips. Every other second, the bit beside it. The row
is a live odometer whose leftmost digit is the one that ends the epoch, and the
countdown beside it (`11y 150d left`) tells you how much headroom is left in
that word. Flip to `DEC` and the same instant reads as an unremarkable ten-digit
number — the bug is invisible in base 10 and obvious in base 2, which is the
argument for the whole instrument.

**The fix is one line**, in the same spirit as `text2bin` and `bin2text`:

```js
const as32 = (seconds) => seconds | 0;    // the bug: signed 32-bit truncation
const as64 = (seconds) => BigInt(seconds);// the fix: a count that cannot run out
```

`as32(2147483647 + 1)` evaluates to `-2147483648` — you can watch 2038 happen in
a console today. The repair is not clever, it is arithmetic: widen the type.
[64-bit computing](https://en.wikipedia.org/wiki/64-bit_computing) gives a signed
integer a range of ±9.2 × 10¹⁸ seconds, roughly 292 billion years in each
direction, which outlasts the Sun by a factor of twenty. Modern 64-bit kernels
already store `time_t` that way; the surviving exposure is in 32-bit embedded
systems, in on-disk and on-wire formats that froze a 32-bit field into a file
header or protocol, and in databases whose columns were sized in the 1990s. Those
are migration problems, not mathematics problems, and the migration is only ever
completed by someone who has actually looked at the word.

The companion [binarytotext](https://github.com/Professor-Codephreak/binarytotext)
terminal prints the whole picture on demand — type **`epoch`** (or `y2038`) for
the current count, its 32-bit word, the sign bit, the exact overflow moment, the
wrap to 1901, and the one-line fix. Programmatically, `clock.y2038` returns
`{epoch, signBit, secondsLeft, overflowAt, wrapsTo}`.

### Quantime: a research note

The deeper claim behind this instrument is that **a time representation is a
claim about the future**, and most of them are made silently. Unix time in 32
bits was not a bug in 1970; it was an unexamined assumption that the systems
being written would not outlive their headroom. They did. The same assumption is
being made right now in every fixed-width timestamp field being frozen into a
protocol, a smart contract, or a file format.

*Quantime* is the working name for taking that seriously: treating temporal
representation as a first-class, inspectable, versioned property of a system
rather than a primitive that happens to be an integer. Three lines worth
researching:

1. **Legible time.** Any system that stores time should be able to render its own
   representation — width, signedness, epoch, resolution, and remaining headroom
   — on request. The `BLK` panel is a toy instance of this: it does not just tell
   you the time, it tells you *how* it is holding the time and how much room is
   left. A `time_t` that can describe itself is a `time_t` that can be audited.
2. **Headroom as a monitored quantity.** Disk space is monitored; temporal
   headroom is not. A field with eleven years left is an incident with a known
   date, and it should appear on a dashboard beside certificate expiry rather
   than being rediscovered by an outage.
3. **Multi-resolution time.** Blockchains already keep a second clock — block
   height, an unsigned monotonic count with no epoch at all — which is why the
   `BLK` panel shows wall time and block time side by side. A system that carries
   two independent temporal references can detect that one of them has become
   unreasonable. That is a cheaper form of resilience than getting the single
   representation right forever, and it is the direction worth pushing: not a
   better epoch, but redundancy in how time is claimed.

None of this is a substitute for widening the integer. It is an argument that the
next Epochalypse should be discovered by an instrument rather than by an outage.

## Opening image

`binary-field.png` (and `binary-field-og.png` at OpenGraph size) is a field of 1s and 0s that repeats **professor codephreak was here** in 8-bit binary, over and over, as one continuous stream wrapped across every row — lit `1`s glow, `0`s recede, exactly how the clock face reads. It is rendered by mindX's `artist.agent` **`binary_field`** skill:

```python
from agents.artist_agent import ArtistAgent
art = await ArtistAgent().create_binary_field(
    message="professor codephreak was here", width=1920, height=520,
    palette="terminal", out_path="binary-field.png")
```

Deterministic (same message + size → same bytes), any size preset, `terminal` green or `cypherpunk` gold.

## Blocktime

The `BLK` button unfolds a blocktime panel — the clock read from the chain's frame of reference:

* **`B:` block height** — the latest Ethereum head block number, polled every 12 seconds from public JSON-RPC endpoints (`publicnode` → `cloudflare-eth` → `llamarpc`, sticking with whichever answers; override with the `rpc` attribute). Rendered as a 32-bit binary word grouped into bytes, or as a plain number in decimal mode.
* **`T:` block time** — the head block's timestamp expressed as local clock time in the active encoding and hour format, with a dim `+Ns` age annotation showing how far the chain's head trails the present.
* **`U:` Unix epoch** — the live epoch second, ticking as a 32-bit binary word (decimal in `DEC` mode).

Polling only runs while the panel is open and the tab is visible; offline or with all endpoints unreachable, the block rows stay dim and the epoch row still ticks.

Tap or click anywhere on the widget to silence a ringing alarm (it also auto-silences after 60 seconds). Armed alarms, running countdowns, the stopwatch, and the chosen alarm sound persist across reloads — countdowns resume if their deadline is still in the future, and a running stopwatch keeps counting (it is epoch-based, so time away is included). The built-in tone uses the Web Audio API (no assets); a picked file or `sound` URL plays through `<audio>` and falls back to the tone if it cannot. If the browser blocks sound before any interaction, the visual ring still fires — press `TEST` once to grant it.

## Attributes

All attributes are optional and act as **first-run defaults**; saved state wins on later loads.

| Attribute | Values | Description |
| :--- | :--- | :--- |
| `storage-key` | any string | localStorage namespace (`binary-clock:v1:<key>`). Set explicit keys when using multiple instances. |
| `display-mode` | `binary` \| `bcd` \| `time` \| `roman` | Initial numeral system (default `binary`; `time` = decimal digital readout; `roman` = Roman numerals, `N` for zero). `analog` is accepted as a legacy alias for `time` plus `analog` |
| `hour-mode` | `24` \| `12` | Initial hour format (default `24`) |
| `date-mode` | `binary` \| `human` | Initial date format (default `binary`) |
| `theme` | `dark` \| `light` | Initial theme (default `dark`: green-on-black; `light`: deep green on a pale face) |
| `alarm` | `HH:MM` | Set and arm an alarm (24h clock time) |
| `sound` | URL | An audio file (`.ogg` `.wav` `.mp3` `.mp4`/`.m4a` `.aac` `.flac` `.webm`) the alarm rings with; appears as a third choice in the `S:` dropdown and is selected on first run |
| `analog` | boolean | Start with the analog dial in place of the H/M/S rows |
| `timezones` | comma-separated IANA names | World-clock zones, e.g. `America/New_York,Asia/Tokyo` (max 10) |
| `rpc` | URL | Custom EVM JSON-RPC endpoint for the blocktime panel (replaces the built-in list) |
| `x`, `y` | px numbers | Initial position (default: centered in viewport) |
| `scale` | px number | Initial base font size, 12–64 (default 28.8) |
| `no-persist` | boolean | Disable localStorage entirely |
| `dock` | `right` \| `left` | **Landing mode.** Start centred, then glide into that margin at a smaller size once the reader scrolls into the article |
| `landing-top` | px number | Space reserved above the clock for a page heading (default 96); the landing position centres in the room below it |
| `dock-scale` | px number | Base font size while docked (default 16) |
| `dock-at` | px number | Scroll depth that triggers docking (default: 35% of viewport height, minimum 120px) |
| `landing-align` | `center` \| `top` | Where the landing clock sits vertically: centred below `landing-top` (default) or pinned **at** `landing-top` |
| `landing-match` | CSS selector | Size the landing clock to that element's width and centre over it (e.g. a site logo: `landing-match=".custom-logo"`) |
| `dock-margin` | px number | Gap from the docked edge, and from the top when `landing-align="top"` (default 24) |

```html
<binary-clock storage-key="desk" display-mode="bcd" hour-mode="12" x="40" y="40" scale="20"></binary-clock>
```

## JavaScript API

```js
const clock = document.querySelector('binary-clock');
clock.displayMode = 'bcd';   // or 'binary' / 'time'
clock.hourMode = '12';       // or '24'
clock.dateMode = 'human';    // or 'binary'
clock.theme = 'light';       // or 'dark'
clock.alarm = '07:30';       // set + arm an alarm; null disarms
clock.alarmArmed;            // read-only armed state
clock.alarmSound = 'file';   // 'tone' | 'file' | 'url' — what rings
clock.alarmSoundName;        // 'TONE', the picked file's name, or the URL's file name
clock.soundUrl = '/alarm.mp3'; // mirrors the `sound` attribute; null clears it
clock.loadAlarmSound(file);  // use an audio File/Blob -> Promise<boolean>; refused if unplayable or > 20 MB
clock.testAlarmSound();      // play the selection for 3 s -> Promise<boolean>; also unlocks audio
clock.clearAlarmSound();     // forget the picked file
clock.analog = true;         // analog dial instead of the binary rows
clock.timer = '5:00';        // start a countdown (seconds, 'MM:SS', 'HH:MM:SS', or minutes); null stops
clock.timerRemaining;        // seconds left, or null
clock.dismissAlarm();        // silence a ringing alarm/timer
clock.startStopwatch();      // also stopStopwatch() / resetStopwatch()
clock.stopwatchElapsed;      // elapsed ms
clock.stopwatchRunning;      // boolean
clock.lapStopwatch();        // record a lap while running -> {lap, total, split} (ms) or null
clock.stopwatchLaps;         // [{lap, total, split}, ...] oldest first
clock.timezones = ['UTC', 'Asia/Tokyo']; // world clock; also accepts 'UTC,Asia/Tokyo'
clock.blockHeight;           // latest fetched Ethereum block number, or null
clock.blockTimestamp;        // that block's Unix timestamp (s), or null
clock.addEventListener('binary-clock-alarm', (e) => console.log(e.detail)); // {kind: 'alarm'|'timer', sound, soundName, ...}
clock.scale;                 // read-only current base font size (px)
clock.resetView();           // default rotation + scale, re-center
clock.clearSavedState();     // drop this instance's localStorage entry
customElements.get('binary-clock').version; // '1.1.0' (binary-converter exposes the same)
clock.addEventListener('binary-clock-change', (e) => console.log(e.detail)); // {displayMode, hourMode, dateMode, theme, analog, sound, ...}
```

## Theming

Set CSS custom properties on the element (or any ancestor):

```css
binary-clock {
    --binary-clock-color: #00ffff;                 /* primary glow color (dark theme) */
    --binary-clock-dim-color: #005050;             /* inactive labels/borders (dark theme) */
    --binary-clock-zero-color: #00aaaa;            /* the 0 bits (defaults to the primary color; set darker to dim them) */
    --binary-clock-zero-color-light: #63a4a4;      /* light-theme 0 bits */
    --binary-clock-bg: rgba(0, 20, 20, 0.9);       /* face background (dark theme) */
    --binary-clock-color-light: #006666;           /* light-theme equivalents */
    --binary-clock-dim-color-light: #77aaaa;
    --binary-clock-bg-light: rgba(240, 252, 252, 0.93);
    --binary-clock-z: 500;                          /* stacking order */
}
```

## What is BCD?

BCD is a way of encoding decimal numbers where each individual *decimal digit* (0 through 9) is represented by its own fixed 4-bit binary number.
```txt
| Decimal Digit | BCD Representation |
| :-----------: | :----------------: |
|       0       |        0000        |
|       1       |        0001        |
|       2       |        0010        |
|       3       |        0011        |
|       4       |        0100        |
|       5       |        0101        |
|       6       |        0110        |
|       7       |        0111        |
|       8       |        1000        |
|       9       |        1001        |
```
Unlike standard binary which converts the entire number (e.g., decimal 23 -> binary 10111), BCD handles each digit separately. This makes conversion to and from decimal displays simpler, which was useful in early digital electronics.

#### How the Clock Uses BCD for Time

--> The clock gets the current time in HH:MM:SS format (e.g., 16:15:42)<br />
--> For each component (Hours, Minutes, Seconds), it looks at the two decimal digits individually<br />
    * Example Hour (HH = 16): It processes the '1' and the '6' separately<br />
    * Example Minute (MM = 15): It processes the '1' and the '5' separately<br />
    * Example Second (SS = 42): It processes the '4' and the '2' separately<br /><br />
--> It converts each decimal digit into its 4-bit BCD equivalent using the table above<br /><br />
--> It displays these two 4-bit BCD groups, separated by a space, next to the corresponding label (H:, M:, S:)<br />

```txt
* Example Time: **16:15:42**
    * **H:** Decimal '1' -> BCD `0001`, Decimal '6' -> BCD `0110`. Display: `H: 0001 0110`
    * **M:** Decimal '1' -> BCD `0001`, Decimal '5' -> BCD `0101`. Display: `M: 0001 0101`
    * **S:** Decimal '4' -> BCD `0100`, Decimal '2' -> BCD `0010`. Display: `S: 0100 0010`
```
This BCD representation correctly handles the full range of hours (00-23), minutes (00-59), and seconds (00-59) by encoding each constituent decimal digit.

## How the Display Works

### Date Display

By default the Year, Month, and Day values are shown in standard binary, padded to fixed lengths for consistent alignment:
```txt
* Year: 11 bits
* Month: 4 bits
* Day: 5 bits
```
The `DATE` button toggles a human-readable format instead — `Y: 2026  M: JUL  D: 13` — glowing bright while active.

### Time Display & Toggles

--> **Hour Format (12/24):**
    * Internal state defaults to 24-hour mode<br />
    * In 12-hour mode the raw hour (0-23) is converted to the 1-12 range, with a dim AM/PM annotation in every encoding<br />
    * The `12H`/`24H` button toggles the format in **all** display modes — 24h binary, 12h binary, 24h/12h BCD, and 24h/12h decimal — and is labeled with the format it switches to<br />
--> **Time Display (Binary/BCD/Decimal):**<br />
    * **Standard Binary (default):** the full H/M/S values are converted to base-2, each padded to one 8-position byte (`15:08:26` → `H: 00001111  M: 00001000  S: 00011010`); equal widths keep the place-value columns aligned across the three rows, and the world-clock and blocktime rows use the same byte width<br />
    * **BCD:** each decimal digit is converted to its own 4-bit group, displayed with a space between digits<br />
    * **Decimal (`DEC`):** plain digital readout of H/M/S<br />
    * **Bit cells:** every bit renders as its own cell at full brightness — the `1`s additionally carry a glow, and bit flips cross-fade; dim the `0`s via `--binary-clock-zero-color` if you prefer an LED-lamp look<br />
    * **Meridiem:** in 12-hour mode the hours row (and every world-clock row) carries a small dim AM/PM annotation in *all* encodings, so no information is lost outside decimal mode<br />
    * **Minute strip:** a thin glowing strip below the time rows sweeps continuously toward the next minute (1s linear fill between updates, snapping back at the rollover); the current second is exposed via `aria-valuenow`<br />
    * The mode button cycles `BIN` → `BCD` → `DEC` → `XII`, glowing bright whenever a non-default mode is active<br />
--> **Updates:** a self-aligning timer fires just after every second boundary, so no seconds are skipped and there is no drift. The display refreshes immediately when a hidden tab becomes visible again.

### 3D Interaction

* Each widget establishes its own CSS `perspective` on the host element<br />
* The clock face lives inside a `preserve-3d` wrapper whose `rotateX`/`rotateY` transform is updated during press-and-hold / Shift-drag / right-drag / grip-drag (sensitivity 0.4, both axes clamped to ±80° — the face stops just short of edge-on instead of disappearing)<br />
* The default view is full frontal (`rotateX(0) rotateY(0)`); double-click/double-tap snaps back to it at any time<br />
* A press held ~0.4s without moving commits a gesture by zone (with a small vibration where supported): the **center** of the face tilts on axis, the **outer ~18% edge band** grabs for drag-and-drop; moving before the hold elapses is always a plain move<br />
* Plain drags move the widget via `left`/`top`, clamped to the viewport; the window `resize` event re-clamps it<br />
* Resizing writes a single `--base-font-size` custom property — every internal dimension is in `em`, so the whole widget scales proportionally (12px–64px base)

### Persistence

Each instance saves `{position, scale, rotation, displayMode, hourMode, dateMode, theme, analog, alarm, sound, …}` under `binary-clock:v1:<storage-key>`; a picked alarm sound file is stored in IndexedDB (`binary-clock` / `sounds`) under the same key, since it is far too big for localStorage. Mode toggles and drag-ends save immediately; mid-drag movement is debounced (~200ms). On load, saved geometry is validated and clamped to the current viewport. Use the `no-persist` attribute to opt out of both.

## File Structure
```txt
.
├── binary-clock.js       # <binary-clock>: element, styles, clock logic,
│                         #   move/tilt/resize, persistence, panels, font loader
├── binary-converter.js   # <binary-converter>: text ↔ binary; SWAP, COPY, SAVE (.txt), OPEN
├── index.html            # Demo page: one clock + one converter
├── faq.html              # Reader-facing FAQ
├── README.md             # Install, attributes, API, theming
├── TECHNICAL.md          # Architecture and design rationale
├── WORDPRESS.md          # Embedding guide for WordPress
├── binary-field.png      # Opening image (artist.agent binary_field skill)
├── binary-field-og.png   # OpenGraph size — featured image
├── binary-field-card.png # Twitter/X card size
└── binary-field-gold.png # Cypherpunk-gold variant
```

## Setup

Open `index.html` in a modern browser (Chrome, Firefox, Edge, Safari), or serve the directory (`python3 -m http.server`) — no build steps or dependencies beyond the Google Font.

To embed elsewhere, copy `binary-clock.js` next to your page and add the script tag plus a `<binary-clock>` element as shown in Quick Start.

## Customization

* **Theme:** the `--binary-clock-*` custom properties above<br />
* **3D:** `perspective` in the `:host` rule or the `ROTATE_SENSITIVITY` / `INITIAL_ROTATION` constants in `binary-clock.js`<br />
* **Binary padding:** the bit-length constants (`YEAR_BITS`, `MONTH_BITS`, `DAY_BITS`, `TIME_STD_BITS`) in `binary-clock.js`<br />
* **Scale limits:** `MIN_BASE` / `MAX_BASE` in `binary-clock.js`

## Author

* binaryclock (c) 2025 Gregory L. Magnusson BANKON License
