A full airport departures board with times, flights, destinations, gates and statuses that flip as boarding opens, gates close and flights leave the list.
╔══════════════════════════════════════════════════════════════════════════════════════════════════╗ ║ █ █ █ ███ ███ ║ ║ D E P A R T U R E S ██ █ █ █ █ █ █ ║ ║ ↗ terminal 2 · all gates LOCAL TIME █ ███ █ █ ███ ║ ║ █ █ █ █ █ █ ║ ║ ███ █ ███ ███ ║ ╠══════════════════════════════════════════════════════════════════════════════════════════════════╣ ║ TIME FLIGHT DESTINATION GATE CHECK-IN STATUS REMARKS ║ ║──────────────────────────────────────────────────────────────────────────────────────────────────║ ║ 14:02 VR 264 REYKJAVIK C29 closed DEPARTE7 AT 14:02 ║ ║ 14:05 NL 159 CAPE TOWN D34 closed ► FINAL CALL GATE D34 ║ ║ 14:10 QX 155 CAPE TOWN B31 closed ► FINAL CKMQ GATE B31 ║ ║ 14:14 QX 185 OSLO A21 closed ► BOARDING ║ ║ 14:18 NL 314 SINGAPORE A17 closed ► BOARDING ║ ║ 14:21 NL 967 PORTO NG 44-46 DELAYED EXP 15:01 ║ ║ 14:24 OB 195 AUCKLAND D9 closed GO TO GATE ║ ║ 14:28 QX 739 MARRAKESH B38 closed GO TO GATE ║ ║ 14:34 OB 215 HANOI C19 closed GO TO GATE ║ ║ 14:36 QX 385 BOGOTA D12 closed GO TO GGFG ║ ║ 14:41 MH 380 HANOI B27 closed ON TIME ║ ║ 14:44 VR 272 REYKJAVIK A37 closed ON TIME ║ ║ 14:49 TA 700 LISBON D35 20-22 ON TIME ║ ║ 14:53 SK 959 QUITO D30 34-36 ON TIME ║ ║ 14:56 NL 879 AUCKLAND B3 34-36 ON TIME ║ ║ 15:01 OB 245 MARRAKESH 50 60-62 ON TIME ║ ║──────────────────────────────────────────────────────────────────────────────────────────────────║ ║ KEEP YOUR BELONGINGS WITH YOU AT ALL TIMES · BOARDING CLOSES 10 MINUTES BEFORE DEPARTURE · ║ ╚══════════════════════════════════════════════════════════════════════════════════════════════════╝
Paste two lines into any page, play it in a terminal, or put a GIF in a README. The code follows the look you pick above.
<script type="module" src="https://www.ascii-magic.com/ascii/ascii.js"></script>
<ascii-art piece="departures-hall-board"></ascii-art>// copy departures-hall-board.js and kit.js into your project, then:
import { useEffect, useRef } from "react";
import create from "./departures-hall-board.js";
export function Piece() {
const ref = useRef(null);
useEffect(() => {
const draw = create(), t0 = performance.now();
let raf;
const tick = () => {
const f = draw((performance.now() - t0) / 1000);
ref.current.textContent = f.text ?? f;
raf = requestAnimationFrame(tick);
};
tick();
return () => cancelAnimationFrame(raf);
}, []);
return <pre ref={ref} style={{ lineHeight: 1, fontFamily: "Menlo, Consolas, monospace" }} />;
}npx --yes https://www.ascii-magic.com/ascii/cli.tgz departures-hall-board<!-- Download GIF below, commit it, then: -->
<p align="center">
<img src="assets/departures-hall-board.gif" alt="Departures Hall Board, animated ASCII art" width="720">
</p>Pause on the frame you like, pick a look, then download it at an exact size.
MIT licensed. It imports one small helper file, kit.js.
/* departures-hall-board: a full airport departures board whose gates, statuses and rows update as the clock runs. */
import { Grid, palette, bigText } from "../lib/kit.js";
export const meta = {
slug: "departures-hall-board",
name: "Departures Hall Board",
category: "lettering",
note: "A full airport departures board with times, flights, destinations, gates and statuses that flip as boarding opens, gates close and flights leave the list.",
tags: ["airport", "departures", "board", "travel", "dashboard", "text"],
cols: 100, rows: 28, fps: 20,
// 0 amber text, 1 muted, 2 frame, 3 green, 4 blue, 5 orange, 6 red, 7 dim, 8 white, 9 churn
palette: palette(["#ffc145", "#8b8577", "#3a3528", "#7ee787", "#79c0ff", "#ffa657", "#ff6b5e", "#5a5446", "#f4ecd8", "#b88a2c"]),
};
const DEST = ["LISBON", "OSLO", "REYKJAVIK", "NAIROBI", "SINGAPORE", "MONTREAL", "ATHENS", "KRAKOW", "BOGOTA", "SEOUL",
"MARRAKESH", "AUCKLAND", "DUBLIN", "VALENCIA", "HELSINKI", "CAPE TOWN", "TALLINN", "SANTIAGO", "HANOI", "BERGEN", "PORTO", "QUITO"];
const AIR = ["QX", "NL", "VR", "TA", "MH", "SK", "OB", "PZ"];
const hash = (a, b) => { let h = (a * 374761393 + b * 668265263) | 0; h = Math.imul(h ^ (h >>> 13), 1274126177); return ((h ^ (h >>> 16)) >>> 0) / 4294967296; };
function flight(i) {
const sched = 14 * 60 + i * 4 + Math.floor(hash(i, 1) * 3);
const delay = hash(i, 2) < 0.16 ? 15 + Math.floor(hash(i, 3) * 6) * 5 : 0;
return {
sched, act: sched + delay, delay,
code: AIR[Math.floor(hash(i, 4) * AIR.length)] + " " + String(100 + Math.floor(hash(i, 5) * 899)),
dest: DEST[Math.floor(hash(i, 6) * DEST.length)],
gate: "ABCD"[Math.floor(hash(i, 7) * 4)] + String(1 + Math.floor(hash(i, 8) * 38)),
desk: String(10 + Math.floor(hash(i, 9) * 60)).padStart(2, "0") + "-" + String(12 + Math.floor(hash(i, 9) * 60)).padStart(2, "0"),
};
}
// status at board minute `now`, plus when it began
function status(f, now) {
const d = f.act - now;
if (d <= 0) return ["DEPARTED", "1", f.act];
if (d <= 2) return ["GATE CLOSED", "6", f.act - 2];
if (d <= 8) return ["FINAL CALL", "5", f.act - 8];
if (d <= 20) return ["BOARDING", "4", f.act - 20];
if (d <= 34) return ["GO TO GATE", "3", f.act - 34];
if (f.delay) return ["DELAYED", "6", -1e9];
return ["ON TIME", "3", -1e9];
}
const hhmm = (m) => `${String(Math.floor(m / 60) % 24).padStart(2, "0")}:${String(Math.floor(m) % 60).padStart(2, "0")}`;
const POOL = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
export default function create() {
const { cols, rows } = meta;
const g = new Grid(cols, rows);
const RATE = 0.9; // board minutes per second
const N = 16; // rows of flights
const C = { time: 3, flight: 10, dest: 20, gate: 40, desk: 48, status: 58, rem: 74 };
const field = (x, y, s, w, co, age, seed) => {
// a cell that recently changed churns before settling, left to right
for (let i = 0; i < w; i++) {
const c = s[i] || " ";
if (age < 0.6 && age * 1.6 * w < i + 1 + w * 0.2) {
g.set(x + i, y, c === " " ? " " : POOL[Math.floor(hash(seed * 31 + i, Math.floor(age * 30)) * POOL.length)], "9");
} else g.set(x + i, y, c, co);
}
};
return (t) => {
g.clear();
const now = 14 * 60 + 2 + t * RATE;
// header
g.box(0, 0, cols, rows, "double", "2");
g.text(3, 2, "D E P A R T U R E S", "0");
g.text(3, 3, "↗ terminal 2 · all gates", "1");
bigText(g, cols - 23, 1, hhmm(now), "8");
g.text(cols - 34, 3, "LOCAL TIME", "1");
g.hline(1, cols - 2, 6, "═", "2");
g.set(0, 6, "╠", "2"); g.set(cols - 1, 6, "╣", "2");
const hy = 7;
[["TIME", C.time], ["FLIGHT", C.flight], ["DESTINATION", C.dest], ["GATE", C.gate], ["CHECK-IN", C.desk], ["STATUS", C.status], ["REMARKS", C.rem]].forEach(([s, x]) => g.text(x, hy, s, "1"));
g.hline(1, cols - 2, hy + 1, "─", "2");
// first flight still on the board: departed less than 4 minutes ago
let i0 = 0;
while (flight(i0).act < now - 4 && i0 < 100000) i0++;
// delayed flights may linger; collect the next N that are still listed
const list = [];
for (let i = Math.max(0, i0 - 12); list.length < N; i++) { const f = flight(i); if (f.act >= now - 4) list.push([i, f]); }
list.forEach(([id, f], k) => {
const y = hy + 2 + k;
const [st, co, since] = status(f, now);
const age = (now - since) / RATE; // seconds since the status changed
const dim = st === "DEPARTED";
g.text(C.time, y, hhmm(f.sched), dim ? "7" : "0");
g.text(C.flight, y, f.code, dim ? "7" : "8");
g.text(C.dest, y, f.dest, dim ? "7" : "0");
const gateOpen = f.act - now <= 55;
field(C.gate, y, gateOpen ? f.gate : "--", 4, dim ? "7" : "8", (now - (f.act - 55)) / RATE, id * 7 + 1);
g.text(C.desk, y, f.act - now > 45 ? f.desk : "closed", dim || f.act - now <= 45 ? "7" : "1");
field(C.status, y, st, 12, co, age, id * 7 + 2);
if (st === "FINAL CALL" || st === "BOARDING") g.set(C.status - 2, y, "►", co);
const rem = f.delay && st !== "DEPARTED" ? "EXP " + hhmm(f.act) : st === "DEPARTED" ? "AT " + hhmm(f.act) : st === "FINAL CALL" ? "GATE " + f.gate : "";
g.text(C.rem, y, rem, f.delay && st !== "DEPARTED" ? "6" : "1");
});
// footer
const fy = rows - 2;
g.hline(1, cols - 2, fy - 1, "─", "2");
const msg = "PLEASE KEEP YOUR BELONGINGS WITH YOU AT ALL TIMES · BOARDING CLOSES 10 MINUTES BEFORE DEPARTURE · ";
const off = Math.floor(t * 8);
for (let x = 3; x < cols - 3; x++) g.set(x, fy, msg[(x + off) % msg.length], "1");
return g.frame();
};
}
/YOUR OWN
The pieces here are hand-built animations. To turn your own photo, logo or video into animated ASCII, use the ASCII Magic editor.