A Chrome extension that replaces your new tab page with a mechanical split-flap board simulation — the kind you'd see on an old airport departures board, or on a Vestaboard, which inspired this project. Individually-animated flap tiles, synthesized mechanical tick sounds, a startup sweep through the full character set, and a handful of small stat modules you can turn on if you want them.
- Physical-feeling tiles. Each tile is a real two-flap mechanism, not a font trick — flaps only spin forward through a fixed character wheel, exactly like the hardware, with per-tile randomized timing so the board never looks synchronized.
- A permanent clock and date, pinned to the top-left corner, independent of whatever else is on the board.
- Time-of-day (and holiday-aware) greetings. A hardcoded table (fixed-date globals, plus India/Tamil-Nadu-relevant ones) is checked first; if nothing matches, a live lookup against the public Nager.Date API checks for a public holiday in the country you select, before falling back to the ordinary "good morning."
- Local weather, with a small pixel-art condition icon, auto-located via your browser (no manual city search needed).
- Optional stat modules (all off by default): GitHub, Chess.com, WakaTime, Steam, MonkeyType, and crypto prices (via CoinGecko). Reorder them however you like — whatever order they're listed in Settings is the order they rotate on the board.
- A private, on-device "time on sites" tracker. Off by default and
requires a separate, explicit opt-in beyond just adding it to the
rotation, since it's the one feature that needs the
tabspermission. Shows your top 5 sites by time today; resets at midnight; nothing ever leaves your device. - Search mode. Press
Ton the board, type a query or a URL, hitEnter— works like the browser's own address bar. - Six frame finishes: Matte Black, Brushed Metal, Modern White, Walnut, Transit Yellow, and a fully custom accent color. Cycle through them with the button on the new tab page, or pick one in Settings. There's also a floating volume control next to it.
- A jammed-tile error state. If a module's fetch fails, a couple of tiles visibly jam mid-flip instead of cleanly spelling "UNAVAILABLE" — more mechanical, less like a broken webpage.
From source:
npm install
npm run build
Then in Chrome: chrome://extensions → enable Developer mode → Load
unpacked → select the dist/ folder. Open a new tab.
This isn't (yet) published on the Chrome Web Store — see
STORE_LISTING.md for the submission draft if
you're picking that up.
Open Settings (the extension's toolbar icon → "Modules, weather & more," or right-click the extension icon → Options). The Modules tab lists everything currently in rotation — click a module's name to expand its config inline (username, API key, whatever it needs), use the arrows to reorder, or add more from the chip row below. The Display tab covers the frame finish, sound, clock format, and cursor tilt.
| Module | Needs | Auth |
|---|---|---|
| Greeting | Nothing (name and holiday country are optional) | — |
| Weather | Browser location permission | — |
| Time on sites | Explicit opt-in checkbox | — |
| GitHub | Username | None |
| Chess.com | Username | None |
| MonkeyType | Username | None |
| Crypto prices | Coin list + currency | None |
| WakaTime | Your own API key | Personal key you generate |
| Steam | Your own API key + Steam ID | Personal key you generate |
Full policy: PRIVACY_POLICY.md (also bundled
in-extension at Settings → Privacy Policy, and as a standalone page in
hosting/index.html for external hosting). Short
version: nothing is collected by the developer. Weather sends coordinates
to Open-Meteo; the stat modules send only the username/key you provide,
directly to that service; the site tracker never leaves your device and
is off unless you turn it on.
Hand-rolled Vite build — four separate build passes rather than a single MV3 bundler plugin, since the background service worker needs a fixed output filename and the New Tab/popup/options/privacy pages are a standard multi-page app:
npm run build
→ tsc -b (typecheck)
→ vite build (newtab + popup + options + privacy pages)
→ vite build --config vite.background.config.ts (background.js)
src/
├── newtab/ New Tab page (the board itself)
├── popup/ Toolbar popup (quick toggles)
├── options/ Settings page
├── privacy/ Bundled privacy policy page
├── background/ MV3 service worker (site-tracker time accounting)
├── components/ Board, Row, SplitFlapTile, Frame, ThemeToggle, VolumeControl
├── engine/ Animation, sound, character wheel, layout, sweep/error state
├── modules/ One file per board module (weather, github, chess, ...)
├── hooks/ Settings read/write/sync, board grid state
└── styles/ Tile and board CSS
The tile animation is the core of the whole project: CharacterWheel.ts
defines the fixed, forward-only wheel every flap cycles through (with a
small cache since the same (from, to) pairs get requested constantly),
Animator.ts drives the actual Web Animations API sequences per tile
(capped in both duration and step count so a worst-case wheel traversal
can't run away), and SoundEngine.ts synthesizes every tick procedurally
via Web Audio — no sample files, no licensing questions, and a small
pre-generated buffer pool plus a concurrency cap so a burst of hundreds of
simultaneous ticks (e.g. the startup sweep) doesn't spin up unbounded
audio nodes.
BOARD_COLS/BOARD_ROWSinengine/boardConfig.tsare the single source of truth for grid dimensions — nothing else should hardcode 32 or 8.- Rows 0–1 are permanently reserved for the clock/date overlay;
useBoard.setLinesandengine/iconTextLayout.ts'scomposeIconTextBoardboth already account for this, so any new module built on top of either helper gets it for free. SplitFlapTile,Row, andBoardare all memoized, andBoardkeeps row-slice array references stable across renders — don't undo this without a reason, it's what stops an unrelated settings change from re-executing all 256 tile components.- New stat modules follow a consistent shape: a
use<X>Module({ active, ...config, onUpdate })hook, a cache keyed on the config that changed, and ajammedErrorBoard(...)call on fetch failure instead of clean error text.githubModule.tsis the shortest reference implementation. - Adding a new external API means updating three places at once, not
just the module code:
public/manifest.json'shost_permissions, and the third-party services list in bothPRIVACY_POLICY.mdandsrc/privacy/Privacy.tsx(and ideallyhosting/index.htmltoo, if you're keeping the externally-hosted copy in sync). Easy to forget one —api.monkeytype.comwas missing fromhost_permissionsfor a while after that module shipped.
- Lunar-calendar holidays (Diwali, Holi) in
modules/holidays.tsare still hardcoded per-year and need manual upkeep. The greeting module now also checks the public Nager.Date API as a live supplement, which covers many more fixed/algorithmically-computable holidays across 150+ countries — but Nager explicitly doesn't cover Islamic-calendar holidays, and very likely doesn't cover Hindu ones either, so it doesn't close this gap on its own. A proper fix would be a real panchang calculation or a holiday source that specifically covers lunar Hindu festivals.
Inspired by Vestaboard, an independent hardware product this extension has no affiliation with.