Skip to content
← Everything I've made

2026 · Designer & developer

Rabbit Ears

90s cable TV, for my family

Discipline
CodingUI / UX
For
Fun
Stack
Node.jsJavaScriptCSSWeb AudioTMDB APIOpenSubtitles APIWhisperffmpeg
Problem

Streaming apps are built to keep you scrolling. I wanted my family to watch the way I did growing up: switch on, something good is already playing, and at bedtime the channel says goodnight.

What I built

A browser TV with a weekly scheduler, eleven themed channels drawn from a 150-show catalogue, parental controls, closed captions and a 3D set made entirely in CSS. Episodes stream from my own home media server through a small three-endpoint API.

Result

Runs at home on our TV, laptops and phones.

Rabbit Ears is a private, always-on TV for my home: eleven channels of gentle, educational and classic shows, a TV guide, vintage public-service ads between programmes, and a wood-and-chrome set you drive with real knobs.

The problem

Streaming apps are built to keep you scrolling: endless rows, autoplay, one more episode. I wanted my kids to watch the way I did in the 90s. You switch on, something good is already playing, short public-service ads remind you to brush your teeth, and at bedtime the channel signs off.

What I built

  • A 3D television and cable box built entirely from CSS gradients: a VHF channel dial with detents, a volume knob, a power button that collapses the picture to a dot, rocker switches and a red seven-segment readout. The sounds are synthesised in Web Audio, not recorded.
  • Eleven channels (Gentle TV, Faith & Wisdom, Curious Minds, Creation & Nature and more) drawn from a 150-show catalogue I matched to TMDB and IMDb, each with its own ident I designed.
  • A scrolling TV guide channel, a channel list, closed captions, a full-screen mode and a phone layout where the cable box becomes the remote.
  • Wisdom breaks: vintage PSAs and classic commercials, levelled to the same loudness as the shows, plus my own poster-style PSAs for things I couldn't find.
  • Parental controls: a profile per child with age-based defaults, channels, ratings, hidden shows, wake time, bedtime and a daily limit, all behind a parent PIN.

Engineering decisions

  • A schedule with no database. Each channel's weekly grid is built deterministically from its show list, so every screen computes the same thing at the same second. Weekdays share a strip, weekends get Saturday-morning blocks, every show airs weekly, and each airing advances one episode. Tests check three weeks of every channel for gaps and episode order.
  • Static is the loading state. Tuning shows static and hiss while the player loads and seeks to the live position, then re-checks the offset against the server's clock right before playing, so screens never drift apart.
  • Slots fill like a broadcast block: short cartoons play back to back, and if an episode can't be served the next playable one takes its place instead of a stand-by screen.
  • My home media server stays private. The app talks to it through a three-endpoint contract (episodes, stream, subtitles) via a server-side proxy with range requests, so its address and token never reach a browser.
  • Captions cached forever per episode. Missing subtitles are fetched once from OpenSubtitles under a daily quota with back-off, and the vintage ads are captioned locally with Whisper.
  • One design system for everything. The review page and the app share the same tokens, components and controller, so changing a knob in the design system changes it on the TV.

Results

Runs at home on our TV, laptops and phones. The kids can tell you what's on at four o'clock.

How I thought about it

The knobs were the fun part; time was the hard part. Every TV in the house has to agree on what's on to the second, so the schedule is worked out from the clock instead of stored anywhere, and the TV static is the loading screen.

Process & pictures

NextAshesi Student Council →