Flipoff Split Flap Display

by reason-machines2384a003145aNo licenseListed Oct 8, 2026Updated Oct 8, 2026

Expert skill for building, customizing, and embedding the FlipOff split-flap display emulator — a free, offline-capable web app that turns any browser/TV into a retro airport departure board.

AI-generated overview

Guides building, customizing and embedding the FlipOff split-flap display emulator, a vanilla HTML/CSS/JS retro flip-board web app.

What it does
Explains how to install and configure the FlipOff split-flap display emulator, covering grid size, colors, messages, keyboard shortcuts, sound and animation settings. It documents the project's file structure and JavaScript classes such as Board, MessageRotator, SoundEngine and KeyboardController. It also shows how to embed the board via iframe or inline modules, including React/Vue wrappers and TV or kiosk deployment.
When to use it
Use it when adding a retro split-flap board to a website, customizing FlipOff messages, colors or grid dimensions, or embedding the emulator in another page or framework. It also fits TV and kiosk setups that need a full-screen flip-board display.
Requirements
Requires the FlipOff project files (cloned from its repository) and a browser; a local static server such as python3 -m http.server or npx serve is recommended for audio and module loading. No build step, npm packages or frameworks are needed. The skill is instructions only and ships no scripts.

FlipOff Split-Flap Display Emulator

Skill by ara.so — Daily 2026 Skills collection.

FlipOff is a pure vanilla HTML/CSS/JS web app that emulates classic mechanical split-flap (flip-board) airport displays. No frameworks, no npm, no build step — open index.html and you have a full-screen retro display with authentic scramble animations and clacking sounds.


Installation

bash
git clone https://github.com/magnum6actual/flipoff.gitcd flipoff
# Option 1: Open directlyopen index.html
# Option 2: Serve locally (recommended for audio)python3 -m http.server 8080# Visit http://localhost:8080

Audio note: Browsers block autoplay. The user must click once to enable the Web Audio API context. After that, sound plays automatically on each message transition.


File Structure

flipoff/  index.html               — Single-page app entry point  css/    reset.css              — CSS reset    layout.css             — Header, hero, page layout    board.css              — Board container and accent bars    tile.css               — Tile styling and 3D flip animation    responsive.css         — Media queries (mobile → 4K)  js/    main.js                — Entry point, wires everything together    Board.js               — Grid manager, transition orchestration    Tile.js                — Per-tile animation logic    SoundEngine.js         — Web Audio API playback    MessageRotator.js      — Auto-rotate timer    KeyboardController.js  — Keyboard shortcut handling    constants.js           — All configuration lives here    flapAudio.js           — Base64-encoded audio data

Key Configuration — js/constants.js

Everything you'd want to change lives in one file:

js
// js/constants.js (representative structure)
export const GRID_COLS = 26;        // Characters per rowexport const GRID_ROWS = 8;         // Number of rows
export const SCRAMBLE_DURATION = 600;  // ms each tile scrambles before settlingexport const STAGGER_DELAY = 18;       // ms between each tile starting its scrambleexport const AUTO_ROTATE_INTERVAL = 8000; // ms between auto-advancing messages
export const SCRAMBLE_COLORS = [  '#FF6B35', '#F7C59F', '#EFEFD0',  '#004E89', '#1A936F', '#C6E0F5'];
export const ACCENT_COLORS = ['#FF6B35', '#004E89', '#1A936F'];
export const MESSAGES = [  "HAVE A NICE DAY",  "ALL FLIGHTS ON TIME",  "WELCOME TO THE FUTURE",  // Add your own here];

Adding Custom Messages

Edit MESSAGES in js/constants.js. Each message is a plain string. The board wraps text across the grid automatically.

js
export const MESSAGES = [  "DEPARTING GATE 7",  "YOUR COFFEE IS READY",  "BUILD THINGS THAT MATTER",  "FLIGHT AA 404 NOT FOUND",    // max GRID_COLS * GRID_ROWS chars];

Padding rules: Messages shorter than the grid are padded with spaces. Messages longer than the grid are truncated. Keep messages at or under GRID_COLS × GRID_ROWS characters.


Changing Grid Size

js
// Compact 16×4 ticker-style boardexport const GRID_COLS = 16;export const GRID_ROWS = 4;
// Wide cinema boardexport const GRID_COLS = 40;export const GRID_ROWS = 6;
// Tall info kioskexport const GRID_COLS = 20;export const GRID_ROWS = 12;

After changing grid dimensions, tiles re-render automatically on next page load.


Keyboard Shortcuts

KeyAction
Enter / SpaceNext message
Arrow LeftPrevious message
Arrow RightNext message
FToggle fullscreen
MToggle mute
EscapeExit fullscreen

Programmatic API

Board

js
// Board.js exposes a class you can instantiate directlyimport Board from './js/Board.js';
const board = new Board(document.getElementById('board-container'));
// Display a specific stringboard.setMessage('GATE CHANGE B12');
// Advance to next message in the rotationboard.next();
// Go backboard.previous();

MessageRotator

js
import MessageRotator from './js/MessageRotator.js';
const rotator = new MessageRotator(board, messages, AUTO_ROTATE_INTERVAL);
rotator.start();   // begin auto-advancingrotator.stop();    // pause rotationrotator.next();    // manual advancerotator.previous(); // manual back

SoundEngine

js
import SoundEngine from './js/SoundEngine.js';
const sound = new SoundEngine();
// Must call after a user gesture (click/keypress)await sound.init();
sound.play();    // play the flap transition soundsound.mute();    // silencesound.unmute();sound.toggle();  // flip mute state

KeyboardController

js
import KeyboardController from './js/KeyboardController.js';
const kb = new KeyboardController({  onNext:       () => rotator.next(),  onPrevious:   () => rotator.previous(),  onFullscreen: () => toggleFullscreen(),  onMute:       () => sound.toggle(),});
kb.attach();   // start listeningkb.detach();   // stop listening

Embedding FlipOff in Another Page

As an iframe

html
<iframe  src="/flipoff/index.html"  width="1280"  height="400"  style="border:none; background:#000;"  allowfullscreen></iframe>

Inline embed (pull in just the board)

html
<!-- In your page --><div id="flip-board"></div>
<script type="module">  import Board from '/flipoff/js/Board.js';  import SoundEngine from '/flipoff/js/SoundEngine.js';  import { MESSAGES, AUTO_ROTATE_INTERVAL } from '/flipoff/js/constants.js';
  const board = new Board(document.getElementById('flip-board'));  const sound = new SoundEngine();
  let idx = 0;  board.setMessage(MESSAGES[idx]);
  document.addEventListener('click', async () => {    await sound.init();  }, { once: true });
  setInterval(() => {    idx = (idx + 1) % MESSAGES.length;    board.setMessage(MESSAGES[idx]);    sound.play();  }, AUTO_ROTATE_INTERVAL);</script>

Custom Color Themes

js
// js/constants.js — dark blue terminal themeexport const SCRAMBLE_COLORS = [  '#0D1B2A', '#1B2838', '#00FF41',  '#003459', '#007EA7', '#00A8E8'];
export const ACCENT_COLORS = ['#00FF41', '#007EA7', '#00A8E8'];
css
/* css/board.css — override tile background */.tile {  background-color: #0D1B2A;  color: #00FF41;  border-color: #003459;}

Common Patterns

Show real-time data (e.g., a flight API)

js
import Board from './js/Board.js';import SoundEngine from './js/SoundEngine.js';
const board = new Board(document.getElementById('board'));const sound = new SoundEngine();
async function fetchAndDisplay() {  const res = await fetch('/api/departures');  const data = await res.json();  const message = `${data.flight}  ${data.destination}  ${data.gate}`;  board.setMessage(message.toUpperCase());  sound.play();}
document.addEventListener('click', () => sound.init(), { once: true });setInterval(fetchAndDisplay, 30_000);fetchAndDisplay();

Cycle through a custom message list

js
const promos = [  "SALE ENDS SUNDAY",  "FREE SHIPPING OVER $50",  "NEW ARRIVALS THIS WEEK",];
let i = 0;setInterval(() => {  board.setMessage(promos[i % promos.length]);  sound.play();  i++;}, 5000);

React/Vue wrapper (import as a module)

jsx
// FlipBoard.jsximport { useEffect, useRef } from 'react';import Board from '../flipoff/js/Board.js';import { MESSAGES } from '../flipoff/js/constants.js';
export default function FlipBoard({ messages = MESSAGES, interval = 8000 }) {  const containerRef = useRef(null);  const boardRef = useRef(null);
  useEffect(() => {    boardRef.current = new Board(containerRef.current);    let idx = 0;    boardRef.current.setMessage(messages[idx]);
    const timer = setInterval(() => {      idx = (idx + 1) % messages.length;      boardRef.current.setMessage(messages[idx]);    }, interval);
    return () => clearInterval(timer);  }, []);
  return <div ref={containerRef} className="flip-board-container" />;}

Troubleshooting

ProblemFix
No soundUser must click/interact first; Web Audio requires a user gesture
Sound works locally but not deployedEnsure flapAudio.js (base64) is served; check MIME types
Tiles don't animateVerify CSS tile.css is loaded; check for JS console errors
Grid overflows on small screensReduce GRID_COLS/GRID_ROWS in constants.js or add CSS overflow: hidden
Fullscreen not workingF key calls requestFullscreen() — some browsers require the page to be focused
Messages cut offString length exceeds GRID_COLS × GRID_ROWS; shorten or increase grid size
Audio blocked by CSPAdd media-src 'self' blob: data: to your Content-Security-Policy
CORS error loading modulesServe with a local server (python3 -m http.server), not file://

Tips for TV / Kiosk Deployment

bash
# Serve with a simple static servernpx serve .              # Nodepython3 -m http.server   # Python
# Auto-launch fullscreen in Chromium kiosk modechromium-browser --kiosk --app=http://localhost:8080
# Hide cursor after idle (add to index.html)document.addEventListener('mousemove', () => {  document.body.style.cursor = 'default';  clearTimeout(window._cursorTimer);  window._cursorTimer = setTimeout(() => {    document.body.style.cursor = 'none';  }, 3000);});

Source and attribution

Source:reason-machines/trending-skillsinskills/flipoff-split-flap-displayat commit2384a00

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal