Turn any TV into a retro split-flap display. The classic flip-board look, without the $3,500 hardware. And it's free.
FlipOff is a free, open-source web app that emulates a classic mechanical split-flap (flip-board) airport terminal display — the kind you'd see at train stations and airports. It runs full-screen in any browser, turning a TV or large monitor into a beautiful retro display.
No accounts. No subscriptions. No $199 fee. Just open index.html and go.
- Realistic split-flap animation with mechanical character-stepping (top/bottom flap halves)
- Dynamic messages: live date/time (🕐 with yellow airport-board styling) and weather (🌡️ with temperature-based color gradient blue→cyan→green→yellow→orange→red)
- Weather shows city, country flag + full name, temperature, and condition — auto-detected from IP, refreshed every 10 minutes
- Multiple sound modes: Authentic (default), Soft, Joke (rubber duck + fart), Mute — with stereo panning per tile
- Auto-rotating messages with shuffle — date/time always first, weather second, then random rotation
- Display modes: Color, Matrix, Grayscale accent palettes
- Countdown progress bar showing time until next message (hidden in fullscreen)
- Config source indicator (green dot = config.json loaded, orange = using built-in defaults)
- Control panel for custom messages, clock, countdown, and message queue (same-browser, no server needed)
- Optional Python backend with admin dashboard, plugin system, and real-time WebSocket sync
- Built-in plugins: Weather forecast, GitHub stats, Quote of the Day, Crypto prices
- Multi-board support — run multiple independent displays from one server
- Fullscreen TV mode with automatic tile resizing
- Keyboard controls for manual navigation
- Emoji support in messages (animate through random charset characters before landing)
- Responsive from mobile to 4K displays
- Pure vanilla HTML/CSS/JS frontend — no frameworks, no build tools, no npm
FlipOff can run in three ways. Choose the one that fits your needs:
| GitHub Pages | Static Server | Docker / Backend | |
|---|---|---|---|
| Setup | Fork + enable Pages | python3 -m http.server |
docker compose up --build |
| Display + animations | Yes | Yes | Yes |
| Sound modes | Yes | Yes | Yes |
| Dynamic date/time + weather | Yes | Yes | Yes |
| Keyboard shortcuts | Yes | Yes | Yes |
| Control panel (same-browser) | Yes | Yes | Yes |
| Emoji + colored rows | Yes | Yes | Yes |
| config.json customization | Yes (edit before deploy) | Yes | Yes (live reload on restart) |
| Admin dashboard | No | No | Yes |
| Server-side plugins | No | No | Yes |
| REST API / WebSocket | No | No | Yes |
| Multi-board support | No | No | Yes |
| Remote control (other devices) | No | No | Yes |
Deploy to GitHub Pages for a zero-maintenance public display:
- Fork this repository on GitHub
- Go to your fork's Settings → Pages
- Under Source, select GitHub Actions
- Push any change to
main(or go to Actions → Deploy to GitHub Pages → Run workflow) - Your display will be live at
https://<your-username>.github.io/flipoff/
To customize messages, edit config.json in your fork and push — GitHub Actions will redeploy automatically.
Everything runs in the browser. No server needed. Dynamic date/time and weather work because they use client-side APIs (the browser's clock and free public APIs for geolocation + weather).
Run on a local machine or Raspberry Pi to display on a TV:
git clone https://github.com/<your-username>/flipoff.git
cd flipoff
python3 -m http.server 8080
# Open http://localhost:8080Same features as GitHub Pages, but you can edit config.json and refresh the browser to see changes immediately. Good for a home display or office TV.
Any static file server works — nginx, Apache, npx serve, etc.
Run the full backend for admin dashboard, plugins, multi-board, and remote control:
git clone https://github.com/<your-username>/flipoff.git
cd flipoff
# With Docker (recommended):
docker compose up --build
# Or without Docker:
pip install -r requirements.txt
python server.py| URL | What |
|---|---|
http://localhost:8080 |
Display |
http://localhost:8080/control.html |
Control panel (same-browser tab-to-tab) |
http://localhost:8080/admin |
Admin dashboard (network-wide, password-protected) |
http://localhost:8080/display.html |
Standalone fullscreen display (no header/hero) |
The admin password is auto-generated on first run and printed to the console. Set it explicitly with the ADMIN_PASSWORD environment variable.
Board config and screens persist in a Docker volume (flipoff-data). Message changes in config.json are picked up automatically on restart — no need to reset the volume.
Add dynamic messages to config.json alongside regular quotes:
{
"messages": [
{"dynamic": "datetime"},
{"dynamic": "weather"},
["", "🏛️ GOD IS IN", "THE DETAILS .", "(LUDWIG MIES)", ""]
]
}| Type | What it shows | Color |
|---|---|---|
datetime |
🕐 Time, day of week, 📅 date, timezone (e.g. GMT+1) |
Airport-board yellow |
weather |
City, 🇵🇹 country flag + name, temperature, condition | Temperature gradient (blue at -10C → red at 40C) |
- Date/time always shows first, weather second, then all messages rotate randomly
- Weather is auto-detected from IP geolocation (no API key needed) using Open-Meteo
- Weather data refreshes every 10 minutes in the background
- Both dynamic messages work on all deployment options (GitHub Pages, static server, Docker)
| Control Panel | Admin Dashboard | |
|---|---|---|
| How it works | BroadcastChannel (tab-to-tab) | REST API + WebSocket |
| Requires backend | No | Yes (python server.py) |
| Reach | Same browser only | Any device on the network |
| Auth | None | Password |
| Features | Custom message, clock, countdown, message queue | Board config, screen management, plugins, message override |
Both are accessible from the display page header. The Admin button only appears when the backend is detected.
| Key | Action |
|---|---|
Enter / Space |
Next message |
Arrow Left |
Previous message |
Arrow Right |
Next message |
F |
Toggle fullscreen |
M |
Cycle sound mode (Authentic / Soft / Joke / Mute) |
R |
Toggle random message order |
C |
Cycle display mode (Color / Matrix / Grayscale) |
Escape |
Exit fullscreen |
Each tile on the board is an independent split-flap element with top/bottom halves that animate through an ordered character-stepping sequence — just like a real mechanical board. Only tiles whose content changes between messages animate. Emojis are supported: tiles flip through random charset characters before snapping to the final emoji.
Sound is generated per-tile using extracted tick slices from a recorded split-flap audio clip, with stereo panning based on tile position. A master audio chain applies lowpass filtering, EQ, and compression across four sound profiles. Authentic mode is the default.
Dynamic messages (date/time, weather) support per-row coloring — airport-board yellow for date/time, and a temperature-based gradient (blue→cyan→green→yellow→orange→red) for weather. Colors are applied after the flip animation completes and cleared when a regular message displays.
flipoff/
index.html — Main display page
display.html — Standalone fullscreen display (no chrome)
control.html — Browser-based control panel
admin.html — Admin dashboard (server-side)
config.json — Configuration (grid, timing, messages, colors)
server.py — Python backend (aiohttp) with API, WebSocket, plugins
requirements.txt — Python dependencies
Dockerfile — Container image (runs server.py)
docker-compose.yml — Docker Compose with persistent volume
PLUGINS.md — Plugin development guide
css/
reset.css — CSS reset
layout.css — Page layout, nav buttons, countdown bar, fullscreen
board.css — Board container, accent bars, shortcuts overlay
tile.css — Split-flap tile halves and flip animations
responsive.css — Media queries (mobile through 4K)
control.css — Control panel dark theme
admin.css — Admin dashboard styles
js/
main.js — Entry point, audio init, fullscreen, remote sync
Board.js — Tile grid, display modes, row colors, transitions
Tile.js — Split-flap flip with character stepping and emoji support
SoundEngine.js — Sound profiles, tick extraction, stereo panning
MessageRotator.js — Shuffle, pinned start order, random mode, remote override
KeyboardController.js — Keyboard shortcuts (F, M, R, C, arrows, etc.)
DynamicMessages.js — Date/time and weather providers with colored output
ControlChannel.js — BroadcastChannel wrapper for control panel
boardFormatter.js — Text wrapping and centering for the board grid
RemoteMessageSync.js — REST + WebSocket sync with the backend
control.js — Control panel logic (custom, clock, countdown, queue)
admin.js — Admin dashboard UI
constants.js — Config loader (reads config.json, exports defaults)
flapAudio.js — Embedded base64 audio clip
ass-ets/audio/
farrrt.wav — Joke mode: fart finisher
mixkit-rubber-duck-squeak-1014.wav — Joke mode: rubber duck squeak
plugins/
base.py — ScreenPlugin base class and types
__init__.py — Plugin discovery and loader
weather/ — Open-Meteo 3-day forecast
github/ — Repo stats, open issues/PRs
api_ninjas/ — Quote of the Day, random quotes, crypto prices
tests/ — Backend, plugin, and config.json test suite
.github/workflows/
deploy.yml — GitHub Pages deployment
Edit config.json to change:
- Messages: Static quotes (5-line arrays) and dynamic markers (
{"dynamic": "datetime"},{"dynamic": "weather"}) - Grid size:
grid.colsandgrid.rows - Timing:
timing.flipStepDuration,timing.staggerDelay,timing.messageInterval, etc. - Colors:
accentColorsarray - Character set:
charsetstring (includes A-Z, 0-9, punctuation, parentheses)
Message changes in config.json take effect on restart — no need to reset Docker volumes or clear state.
When running with the backend, board config can also be changed at runtime through the admin dashboard.
| Variable | Default | Description |
|---|---|---|
PORT |
8080 |
Server listen port |
ADMIN_PASSWORD |
Auto-generated | Admin dashboard password |
API_NINJAS_API_KEY |
— | API key for quote and crypto price plugins |
MIT — do whatever you want with it.