Clockspian
An ambient clock and weather dashboard for a spare screen. Dark, quiet, glanceable:
big tabular-numeral time and today's date, and — switched on in settings — current
conditions where you are, or in a city you choose.
Point a browser at it full-window and leave it there, or install it as an app of its own
(below). Press F for full screen.
Running it
There is no build step and no dependencies. Any static file server works:
python3 -m http.server 8080
Then open http://localhost:8080.
A server is required — the app uses ES modules, which browsers refuse to load over
file://.
As an app
Clockspian installs as a desktop app on Windows and macOS, with its own window and
Dock or taskbar icon:
- Chrome or Edge — Install as an app in the settings panel, or the install icon at
the end of the address bar. The title bar gives way to the clock; only the window
buttons float over it, and the top strip is where the window is dragged from. Edge's
and Chrome's app settings can also start it when you sign in.
- Safari on a Mac — File → Add to Dock. The settings panel says so too.
- Firefox doesn't install web apps.
What makes it an app, rather than a bookmark:
manifest.webmanifest and icons/ — the name, the window, and the icons, made from
the C + L mark. The window's colours follow the theme through the theme-color meta,
which js/settings.js keeps on the current background.
sw.js — the service worker. Clockspian's own files load network-first, so online
every launch is the live deploy and js/updates.js works as it always has; each one
is kept on the way through, so offline the last copy opens instead. Theme fonts are
kept once fetched; the weather keeps its own last reading. The films aren't kept.
js/app.js — registers the worker, holds a screen wake lock so the display never
sleeps under the clock (in a browser tab too), puts full screen on F, and runs the
install button.
Offline needs a secure origin, which localhost and the Vercel deploy both are.
Settings
Move the mouse and a faint gear appears in the top-right corner; click it for the
panel. Esc or a click anywhere outside closes it. Choices persist in localStorage.
-
Scale — 50%–150%, for tuning to the screen it ends up on.
-
Date — on by default. Off leaves just the time, for the barest look.
-
Blinking colon — on by default. Off holds the colon steady.
-
Bottom progress bar — the thin line along the bottom edge that fills once a
minute. On by default.
-
Theme — a typeface paired for good with a palette:
| Theme | Typeface | Look |
|---|
| Fjord (default) | Jost | Calm Nordic blue |
| Kal | Bricolage Grotesque over DM Sans | Linne/Näver/Dimma/Fjord/Djup |
| Clåde | Anthropic Serif if installed, else Source Serif 4 | Warm paper, Claude clay |
| Signal | Archivo, bold | Swiss poster: black, white, one red |
| Moss | Young Serif | Seventies botany: forest and lichen |
| Terminal | Martian Mono, condensed and set smaller | Phosphor CRT in dark, watch LCD in light |
| Gala | Bodoni Moda | Fashion masthead: ink, white, raspberry |
| Ember | Barlow Condensed, set larger | Nightstand amber |
| Orbit | Space Grotesk | Deep space: indigo and electric violet |
| Lagoon | Baloo 2 | Holiday turquoise |
Each has its own kind of typeface — no two share one — and a fallback of the same
kind (Arial Black, Rockwell, Consolas, Bodoni MT, Trebuchet, Arial Rounded) for networks
that block Google Fonts, so they stay apart even there. Signal, Moss, Terminal, Gala,
Orbit and Lagoon replaced Graphite, Sand, IBM Plex Mono's Terminal, Folio, Heather
and Nunito's Lagoon; anyone stored on one of the old ones moves to its successor.
-
Weight — under Scale: how thin or bold the clock's digits are, as far as
the theme's typeface goes. It starts at the theme's own weight, is remembered per
theme, and a double-click puts it back. Gala, Orbit and Lagoon start at their font's
lightest, so they only go bolder; Moss's Young Serif has one weight, so the slider
sits out. Ember stops at 600, where the widening clock would otherwise run off the
screen at 150%. Every theme's figures were measured tabular at both ends.
Settings saved before themes existed are carried over: Kal keeps its light or
dark palette, and any other setup moves to the theme with its typeface, in dark.
Clockspian used to be the system-sans tan theme, later called Sand (and since replaced by
Moss); the blue Jost theme, once Dusk, took its name as the default. Anyone who had picked
Dusk keeps it, and anyone stored on Clockspian moves to the new default. The default has
since been renamed Fjord, but keeps the clockspian id, so nothing moves.
Every typeface here was measured to confirm it has tabular figures — digits of
equal width. Without them the clock's width lurches every time a 1 appears. Most
editorial serifs (including Georgia and Newsreader) fail this. Re-measure before
adding another option. Jost, Archivo, Young Serif, Martian Mono, Bodoni Moda, Space Grotesk
and Baloo 2 pass; Lexend, Urbanist, Raleway, Playfair Display, DM Serif Display,
Fredoka, Quicksand, Comfortaa and Oswald were measured and do not.
Bricolage Grotesque passes. DM Sans does not — at 190px, 00:00 sets 534px wide
against 247px for 11:11. That is why the Kal theme routes every numeric readout through
--font-display (Bricolage) and leaves --font-stack (DM Sans) to labels and body
copy. Don't move the time, temperature or hi/lo onto the body face.
Weather
Open-Meteo — free, no API key, no account. One request
covers current temperature, WMO condition code, day/night, and today's high and low.
It refreshes every 10 minutes, and again whenever the tab becomes visible or the
network reconnects.
Readings are cached in localStorage. If a fetch fails the last known value stays on
screen, dimmed, with a small dot beside it — the display never blanks out or breaks.
Location
When the weather is first switched on the browser asks for the user's location. A
granted position is rounded to two decimals (~1 km) and remembered, so a reload starts
in the right place. The place name comes from BigDataCloud's free client-side reverse
geocoder; if that fails the label reads Current location.
There is no stand-in city. Until there is a place, the weather block stays hidden. If
the answer is no, the request times out, or geolocation is unavailable (it needs HTTPS
or localhost), the panel says so and offers the city search in its place.
Choose a city searches Open-Meteo's geocoder (same provider as the weather, no key) as
you type, in English: "Gothenburg", not "Göteborg". Only towns and cities are listed, each
with its region and country so the Portlands can be told apart. The chosen city is kept,
and used without asking for a position. Anyone who had picked Stockholm before 1.8
has it as their chosen city.
The position is looked up on load and whenever My location is chosen, not on every
refresh. Once a site is blocked it cannot re-prompt: the panel says so and points to
the browser's site settings.
The weather cache is keyed to the coordinates it was fetched for, so a reading never
appears under another place's name.
Releases
Clockspian follows major.minor.patch. Every release is an entry at the top of
js/version.js — version, date, and a few notes (emoji, heading, a sentence or two in
the voice of the intro). The newest entry is the version; nothing else needs bumping.
- Feature release (x.y.0) — the next time Clockspian loads, a small What's new
pill appears beside the gear. It never covers the clock; clicking it shows the notes
for every release since the one last seen, and closing those puts it away.
- Patch release (x.y.1, …) — goes out quietly. Its notes show alongside the next
feature release, or under the version number in settings.
- A first visit gets the intro instead, and counts the current version as seen.
Every change that people will notice ships with its release entry, in the same PR.
Open tabs keep up on their own: once an hour, and whenever the tab comes back into
view (at most every 10 minutes), js/updates.js reads the live js/version.js — a few
KB — and reloads if the newest version there differs. It waits while the settings panel
or a dialog is open.
Layout
index.html markup
favicon.svg tab icon
manifest.webmanifest the installed app: name, window, icons
icons/ app icons, rendered from the mark
sw.js service worker: offline copies of the app and its fonts
css/style.css all styling; theme + scale are CSS custom properties on :root
js/main.js entry point
js/clock.js drift-corrected tick, date, seconds bar
js/weather.js Open-Meteo fetch, WMO mapping, caching, failure handling
js/location.js geolocation, chosen city and its search, reverse geocoding
js/icons.js weather glyphs as inline SVG primitives
js/settings.js state, persistence, panel UI
js/fonts.js the few-letter fonts for theme names and the wordmark, under names of their own
js/about.js the "What is this?" dialog and first-visit check
js/version.js every release and its notes, newest first
js/whatsnew.js the What's new pill and release-notes dialog
js/updates.js hourly check for a newer deploy; reloads to pick it up
js/app.js service worker, screen wake lock, F for full screen, install button
watch/index.html /watch — the promo films, with link previews; dressed in your saved theme if you have one
js/themes.js every theme's typeface, palettes and clock settings; read by the clock and /watch
media/ the films and their posters, exported from promo/ (which stays out of git)
prototype/ the original single-file mockup, kept for reference
Deployment
See DEPLOY.md.