CRTL
Phosphor / CRT-styled homelab dashboard. Ships as one self-contained
CRTL.html - fonts and curated icons are baked in, so it has no runtime
dependencies and works fully offline. Open it in a browser and you're done.
What it does
- Groups of services in a card layout. Each row has an icon, a name, and
one or more links.
- Double-size groups. In edit mode, a group can span two columns (its
entries run in two columns, lined up with the groups beside it) or two rows.
- Click an entry -> opens its primary URL in a new tab.
- Long-press an entry with multiple links -> a horizontal button strip
slides in from the right with all of them labeled.
- Auto Home / Away detection. At load and every 30 seconds the page
probes a configurable list of internal endpoints. If any responds, you're
Home; otherwise Away.
- Away mode reorders each entry's links so non-internal URLs come first,
and dims entries that have no non-internal URL.
- Optional per-service health dots beside each name (green = up, amber =
down). Toggle it per entry in the editor; Accessibility can swap the dots
for colour-blind friendly check / cross marks.
- Two colour themes, each with a light and a dark mode - see Themes.
- A pill in the top-right shows the current location state and lets you
flip it manually.
- Touch-aware. On a phone or tablet the UI switches to finger-sized
targets, drag grips for reordering, and a menu for edit / delete instead of
icons that only show on hover. It follows the pointer your device reports;
Accessibility (bottom-left) can force it either way, per device.
No tracking, no analytics, no telemetry. On a normal load the only network
calls are the Home/Away and health probes you configure - fonts and curated
icons are bundled, so nothing is fetched from a CDN. Three opt-in features do
reach the network, and the UI says so:
- Brand icons (
svg:...) and any non-curated Bootstrap icon are fetched once
from a public CDN (api.iconify.design / unpkg) when you save the entry, then
embedded into your config.
- Encrypted gist sync talks to
api.github.com (AES-encrypted payload).
- Update check (offline version only) asks
api.github.com for the latest
release - when you press Check now in Help, or once a day if you turn on
Check on startup there.
Themes
Two colour themes, each with a light and a dark mode. Pick the theme under the
gear -> Theme; the sun / moon button (bottom-right) flips light and dark
within it. Both choices are remembered per device and never synced.
- Phosphor (above) - green phosphor on sage in light mode, amber on near-black
in the dark.
- Paper - soft black on a warm white. Its dark mode is a true inversion: a
soft-black page with paper-white headers.
Two versions
CRTL comes in two forms, built from the same source:
- Local file (
CRTL.html) - the downloadable single file. Open it via
file:// or serve it over plain http://. Full features, including
automatic Home / Away detection and per-service health dots. Best on desktop.
- Hosted web build - the same app served from a URL, for devices that can't
open a local file. This is the only option on iPhone / iPad, where Safari
can't run a downloaded
.html. Android can use either (the local file works
but is fiddly to open; the hosted build is easier).
The trade-off on the hosted build: it's served over https://, and browsers
block an https:// page from probing your http:// LAN (mixed content). So by
default the hosted build turns Home / Away into a manual toggle and shows
health dots only for https services. You can restore automatic detection with a
small beacon - see
Home and Away on the hosted version.
The bottom-right gear -> Help shows which build you're running and its
version.
Requirements
Any modern browser - Chrome, Firefox, Safari, or Edge. Nothing to install; the
local build runs straight from file://. Encrypted sync uses the Web Crypto API,
which needs a secure context - the local file (file://) and any https:// page
both qualify.
Setup
- Desktop / Android: download
CRTL.html from the
latest release and
open it via file://, or set it as your homepage / new-tab page.
iPhone / iPad: a downloaded file won't open in Safari - use the hosted web
build instead.
- Click the gear (bottom-right) -> Edit mode to add groups and entries,
then Global options to set your Home-detection probes (URLs reachable
only from your home network).
- Config is saved in the browser; enable gist sync to share it across machines.
Your config lives in localStorage (and the gist, if you turn on sync). The
built-in defaults are only a first-run seed.
Icons
Open an entry in the editor to set its icon. Two sets are available:
- Bootstrap Icons - pick one from the built-in picker, or type
bi:name
(browse names at icons.getbootstrap.com).
- Brand / logo icons - type
svg:name for a
Simple Icons glyph, e.g. svg:proxmox or
svg:jellyfin.
The common Bootstrap icons are baked into the file and render offline. Any other
icon - a brand glyph, or a Bootstrap icon outside the curated set - is fetched
once from a public CDN when you save the entry, then embedded into your config so
it keeps working offline afterwards. This is one of the two features that reach
the network, and only on save.
Sync
By default your config lives only in this browser. Optional encrypted
GitHub-gist sync shares it across machines. The gist payload is AES-encrypted in
your browser before upload, so GitHub only ever stores ciphertext - only your
machines hold the key.
To turn it on (gear -> Global options -> Encrypted gist sync):
- Create a GitHub personal access token
(classic) with the
gist scope, and paste it into GitHub token.
- Click Generate key. Leave Gist ID blank to create a fresh private gist
(or paste an existing gist id to join it).
- Flip the sync toggle to On - CRTL creates or loads the gist and keeps it in
sync as you edit.
To add another machine, Copy the Setup blob on the first machine and paste
it into the Setup blob box on the second, then click Import. The blob
carries the token, gist id, and key; the second machine imports the gist and stays
in sync.
A couple of things to know:
- Last-write-wins. Config versions are wall-clock timestamps, so editing on
two machines at once can lose the older edit, and a fast clock tends to win.
Fine for one active machine at a time; keep clocks roughly in sync.
- The token has
gist scope - it can read and write all your gists, not
just this one - and it's stored locally in plaintext next to the encryption key
(the setup blob holds both too). Only enable sync on machines you trust.
Backup
No GitHub? Gear -> Global options -> Encrypted backup exports your config as
a passphrase-encrypted file (crtl-backup-YYYY-MM-DD.json) you can import on
another machine - or keep as a plain backup. Nothing leaves your device; the file
is AES-encrypted with a key derived from your passphrase (PBKDF2), so it's safe
to park on a USB stick or a cloud drive.
- The file holds your groups, links, and Home probes. Sync credentials are
never included.
- Icons aren't in the file either (same as gist sync) - they're re-embedded from
their
bi:/svg: ids after import, so non-curated ones fetch once from the
CDN, exactly as on save.
- Like sync, it needs a secure context: works from
file:// and https://, not
from a page served over plain http://.
Home and Away on the hosted version
On the hosted (https://) build, automatic Home / Away detection is off, because
browsers won't let an https:// page probe your http:// LAN. To bring it back,
give the page one endpoint it can reach over https - a "beacon" that only
resolves on your home network. Home, the probe succeeds (Home); away, it
fails (Away).
You need a hostname served over trusted https that only routes on your LAN. The
cleanest setup:
- Pick a hostname, e.g.
home-ping.yourdomain.com.
- Point its public DNS
A record at a private LAN IP (e.g. 192.168.1.2).
Off your network that address is unroutable, so the probe fails - exactly the
"Away" signal you want. No split-horizon DNS needed.
- Issue a real certificate for it with a DNS-01 challenge (Let's
Encrypt), so Safari trusts it out of the box with no per-device profile. A
*.yourdomain.com wildcard covers it.
- Front any always-on LAN service with it. In Nginx Proxy Manager: add a
Proxy Host for
home-ping.yourdomain.com, forward it to something that is
always up (the NPM admin page, your router, a NAS), then under SSL
request/assign the DNS-01 wildcard certificate and enable Force SSL.
- In CRTL, open gear -> Global options and add
https://home-ping.yourdomain.com to your Home probes.
That's it - the hosted build now auto-detects. Your existing http:// probes can
stay in the list; the hosted build simply ignores them (it can't reach them over
https) while the local file still uses them. Per-service http health dots
remain hidden on the hosted build - the beacon only drives Home / Away.
How "internal" is determined
For Away-mode link reordering and dimming, a URL is considered internal if
its hostname:
- ends in
.home or .local
- is in an RFC1918 range (
10.x, 192.168.x, 172.16-31.x)
Loopback (127.x / localhost) is treated as always-reachable, not
internal.
Notes
- The probe mechanism is
fetch(url, { mode: 'no-cors' }). The response is
opaque, but the fetch resolves on any HTTP response and rejects on
network-layer failure. That's enough to tell whether a host is reachable.
- Cloudflare-fronted services may set
Cross-Origin-Resource-Policy: same-origin, which blocks opaque fetches
from file:// and makes them probe as down. Turn off that entry's health
check if it bothers you.
- Self-signed certificates work fine as long as the browser trusts them
(e.g. via your own CA installed on the device). A cert the browser would
warn about will probe as down.
- Serve via
file:// (or plain http://). The probes are http:// requests
to LAN IPs; on a page served over https:// browsers block them as mixed
content, so everything reads as down / Away.
Contributing
Bug reports and PRs welcome. See CONTRIBUTING.md for
building from source, and docs/ARCHITECTURE.md for how it
all fits together. Found a security issue? See the
security policy - please report it privately, not as a
public issue.
License
MIT + Commons Clause (c) 2026 BrainInBlack - free to use (including
inside a company), modify, and share; you just may not sell it. See the
Commons Clause for what "sell" means.