Video — Pipeline Tracker
A cinematic dark kanban board for production teams that moves projects through brief, shoot, edit, grade and delivery stages. Drag cards between columns, watch live per-stage counts and progress bars update, and read status and due-date badges at a glance. Includes overdue flags, client and format tags, assignee avatars, filtering by status, and a slim toast confirmation on every move.
MCP
Code
:root {
--bg: #0a0a0b;
--surface: #141416;
--surface-2: #1c1c1f;
--amber: #ffb020;
--amber-d: #e6971a;
--red: #ff4d4d;
--ink: #f4f4f6;
--muted: #8a8a92;
--line: rgba(255, 255, 255, 0.10);
--line-2: rgba(255, 255, 255, 0.18);
--white: #fff;
--ok: #35d07f;
--warn: #ffb020;
--bad: #ff4d4d;
--r-sm: 6px;
--r-md: 10px;
--r-lg: 16px;
}
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
margin: 0;
background: var(--bg);
color: var(--ink);
font-family: "Inter", system-ui, sans-serif;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
display: flex;
flex-direction: column;
min-height: 100vh;
}
.letterbox {
height: 22px;
background: #000;
flex: none;
}
.letterbox--top { box-shadow: inset 0 -1px 0 rgba(255, 176, 32, 0.14); }
.letterbox--bottom { box-shadow: inset 0 1px 0 rgba(255, 176, 32, 0.14); }
/* ---------- Topbar ---------- */
.topbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
flex-wrap: wrap;
padding: 20px 28px;
border-bottom: 1px solid var(--line);
background: linear-gradient(180deg, #0d0d0f, var(--bg));
}
.brand { display: flex; align-items: center; gap: 14px; }
.brand__mark {
display: grid;
place-items: center;
width: 40px;
height: 40px;
border-radius: var(--r-md);
color: var(--bg);
background: linear-gradient(135deg, var(--amber), var(--amber-d));
box-shadow: 0 6px 20px rgba(255, 176, 32, 0.28);
}
.brand__text h1 {
margin: 0;
font-size: 17px;
font-weight: 800;
letter-spacing: -0.01em;
}
.brand__text p {
margin: 2px 0 0;
font-size: 12.5px;
color: var(--muted);
}
.filters {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.filters__label {
font-size: 11px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--muted);
margin-right: 2px;
}
.chip {
display: inline-flex;
align-items: center;
gap: 7px;
padding: 7px 13px;
font: inherit;
font-size: 13px;
font-weight: 600;
color: var(--ink);
background: var(--surface-2);
border: 1px solid var(--line);
border-radius: 999px;
cursor: pointer;
transition: transform .12s ease, border-color .16s ease, background .16s ease, color .16s ease;
}
.chip:hover { border-color: var(--line-2); transform: translateY(-1px); }
.chip:active { transform: translateY(0); }
.chip:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; }
.chip.is-active {
background: linear-gradient(135deg, var(--amber), var(--amber-d));
color: #0a0a0b;
border-color: transparent;
}
.dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
.dot--ok { background: var(--ok); }
.dot--warn { background: var(--warn); }
.dot--bad { background: var(--bad); }
/* ---------- Board ---------- */
.board {
flex: 1;
display: flex;
gap: 16px;
padding: 22px 28px 30px;
overflow-x: auto;
scrollbar-width: thin;
scrollbar-color: var(--line-2) transparent;
}
.board::-webkit-scrollbar { height: 10px; }
.board::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 999px; }
.column {
flex: 0 0 300px;
min-width: 280px;
display: flex;
flex-direction: column;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
overflow: hidden;
transition: border-color .16s ease, box-shadow .16s ease;
}
.column.drag-over {
border-color: var(--amber);
box-shadow: 0 0 0 1px var(--amber), 0 18px 40px rgba(0, 0, 0, .5);
}
.column__head {
padding: 14px 15px 12px;
border-bottom: 1px solid var(--line);
background: var(--surface-2);
position: relative;
}
.column__head::before {
content: "";
position: absolute;
left: 0; top: 0; bottom: 0;
width: 3px;
background: var(--stage, var(--amber));
}
.column__title-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.column__title {
display: flex;
align-items: center;
gap: 9px;
font-size: 13px;
font-weight: 700;
letter-spacing: 0.02em;
text-transform: uppercase;
}
.column__title .stage-dot {
width: 9px; height: 9px; border-radius: 3px;
background: var(--stage, var(--amber));
}
.column__count {
font-family: "JetBrains Mono", monospace;
font-size: 12px;
font-weight: 600;
color: var(--muted);
background: var(--bg);
border: 1px solid var(--line);
border-radius: 999px;
padding: 2px 9px;
}
.column__bar {
margin-top: 11px;
height: 4px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.07);
overflow: hidden;
}
.column__bar > span {
display: block;
height: 100%;
width: 0;
border-radius: 999px;
background: var(--stage, var(--amber));
transition: width .5s cubic-bezier(.2, .7, .2, 1);
}
.column__list {
flex: 1;
padding: 12px;
display: flex;
flex-direction: column;
gap: 12px;
overflow-y: auto;
min-height: 90px;
}
.column__list.is-empty::after {
content: "Drop a card here";
display: grid;
place-items: center;
height: 70px;
color: var(--muted);
font-size: 12.5px;
border: 1px dashed var(--line-2);
border-radius: var(--r-md);
opacity: .7;
}
/* ---------- Card ---------- */
.card {
background: var(--surface-2);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 13px 13px 12px;
cursor: grab;
position: relative;
transition: transform .14s ease, border-color .16s ease, box-shadow .16s ease, opacity .2s ease;
}
.card:hover {
transform: translateY(-2px);
border-color: var(--line-2);
box-shadow: 0 12px 28px rgba(0, 0, 0, .45);
}
.card:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; }
.card.dragging { opacity: .45; cursor: grabbing; }
.card.is-hidden { display: none; }
.card.is-dim { opacity: .28; filter: saturate(.4); }
.card__top {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 8px;
}
.card__client {
font-size: 10.5px;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--amber);
font-weight: 700;
}
.card__handle {
color: var(--muted);
line-height: 0;
cursor: grab;
}
.card__title {
margin: 6px 0 10px;
font-size: 14.5px;
font-weight: 700;
letter-spacing: -0.01em;
line-height: 1.3;
}
.card__tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-bottom: 11px;
}
.tag {
font-size: 11px;
font-weight: 600;
color: var(--muted);
background: var(--bg);
border: 1px solid var(--line);
border-radius: var(--r-sm);
padding: 2px 7px;
}
.tag--format { color: var(--ink); }
.card__meta {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-top: 2px;
}
.badge {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 11px;
font-weight: 700;
padding: 3px 9px;
border-radius: 999px;
border: 1px solid transparent;
}
.badge i { width: 7px; height: 7px; border-radius: 50%; }
.badge--ok { color: var(--ok); background: rgba(53, 208, 127, 0.12); border-color: rgba(53, 208, 127, 0.3); }
.badge--ok i { background: var(--ok); }
.badge--warn { color: var(--warn); background: rgba(255, 176, 32, 0.12); border-color: rgba(255, 176, 32, 0.32); }
.badge--warn i { background: var(--warn); }
.badge--bad { color: var(--bad); background: rgba(255, 77, 77, 0.13); border-color: rgba(255, 77, 77, 0.34); }
.badge--bad i { background: var(--bad); }
.card__due {
display: inline-flex;
align-items: center;
gap: 6px;
font-family: "JetBrains Mono", monospace;
font-size: 11.5px;
font-weight: 500;
color: var(--muted);
}
.card__due.is-overdue { color: var(--red); }
.card__due svg { opacity: .8; }
.card__foot {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-top: 11px;
padding-top: 11px;
border-top: 1px solid var(--line);
}
.avatar {
display: inline-flex;
align-items: center;
gap: 8px;
}
.avatar__img {
width: 24px; height: 24px;
border-radius: 50%;
display: grid;
place-items: center;
font-size: 10.5px;
font-weight: 700;
color: #0a0a0b;
}
.avatar__name { font-size: 12px; color: var(--muted); font-weight: 500; }
.move-wrap { position: relative; }
.move-btn {
display: grid;
place-items: center;
width: 26px; height: 26px;
border-radius: var(--r-sm);
background: var(--bg);
border: 1px solid var(--line);
color: var(--muted);
cursor: pointer;
transition: border-color .16s ease, color .16s ease;
}
.move-btn:hover { color: var(--ink); border-color: var(--line-2); }
.move-btn:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; }
.move-menu {
position: absolute;
right: 0;
bottom: calc(100% + 6px);
width: 150px;
background: var(--surface);
border: 1px solid var(--line-2);
border-radius: var(--r-md);
padding: 5px;
z-index: 20;
box-shadow: 0 18px 40px rgba(0, 0, 0, .6);
display: none;
}
.move-menu.is-open { display: block; }
.move-menu button {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
text-align: left;
font: inherit;
font-size: 12.5px;
color: var(--ink);
background: none;
border: 0;
padding: 7px 8px;
border-radius: var(--r-sm);
cursor: pointer;
}
.move-menu button:hover:not(:disabled) { background: var(--surface-2); }
.move-menu button:disabled { color: var(--muted); opacity: .5; cursor: default; }
.move-menu button:focus-visible { outline: 2px solid var(--amber); outline-offset: -2px; }
.move-menu .mm-dot { width: 8px; height: 8px; border-radius: 3px; }
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 34px;
transform: translate(-50%, 20px);
background: var(--surface-2);
border: 1px solid var(--line-2);
color: var(--ink);
font-size: 13px;
font-weight: 600;
padding: 11px 18px;
border-radius: var(--r-md);
box-shadow: 0 18px 44px rgba(0, 0, 0, .6);
opacity: 0;
pointer-events: none;
transition: opacity .22s ease, transform .22s ease;
z-index: 60;
display: flex;
align-items: center;
gap: 9px;
}
.toast::before {
content: "";
width: 8px; height: 8px; border-radius: 50%;
background: var(--amber);
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }
/* ---------- Responsive ---------- */
@media (max-width: 520px) {
.topbar { padding: 16px; }
.board {
flex-direction: column;
padding: 16px;
overflow-x: visible;
}
.column {
flex: 1 1 auto;
min-width: 0;
width: 100%;
}
.column__list { max-height: none; }
.filters { width: 100%; }
}(function () {
"use strict";
var STAGES = [
{ id: "brief", label: "Brief", color: "#8a8a92" },
{ id: "shoot", label: "Shoot", color: "#ff4d4d" },
{ id: "edit", label: "Edit", color: "#ffb020" },
{ id: "grade", label: "Grade", color: "#7c5cff" },
{ id: "delivery", label: "Delivery", color: "#35d07f" }
];
// "Today" reference so due-date logic is deterministic in the demo.
var TODAY = new Date("2026-07-02T00:00:00");
var PROJECTS = [
{ id: "p1", client: "Northwind EV", title: "Launch film — 60s hero cut", format: "4K ProRes", stage: "brief", due: "2026-07-18", assignee: "Mara Ito", initials: "MI", color: "#ffb020" },
{ id: "p2", client: "Solaria Coffee", title: "Spring social reel pack", format: "9:16 H.264", stage: "brief", due: "2026-07-05", assignee: "Dev Okafor", initials: "DO", color: "#35d07f" },
{ id: "p3", client: "Atlas Fitness", title: "Founder interview series", format: "1080p", stage: "shoot", due: "2026-07-01", assignee: "Lena Cruz", initials: "LC", color: "#ff4d4d" },
{ id: "p4", client: "Harbor Bank", title: "Brand anthem — location shoot", format: "6K RAW", stage: "shoot", due: "2026-07-12", assignee: "Sam Ahmadi", initials: "SA", color: "#7c5cff" },
{ id: "p5", client: "Verde Living", title: "Product spot — assembly rough", format: "4K ProRes", stage: "edit", due: "2026-07-03", assignee: "Mara Ito", initials: "MI", color: "#ffb020" },
{ id: "p6", client: "Pulse Audio", title: "Unboxing explainer v2", format: "1080p", stage: "edit", due: "2026-06-30", assignee: "Rui Tanaka", initials: "RT", color: "#ff8a3d" },
{ id: "p7", client: "Meridian Air", title: "In-flight sizzle — color pass", format: "4K DCI", stage: "grade", due: "2026-07-08", assignee: "Lena Cruz", initials: "LC", color: "#35d07f" },
{ id: "p8", client: "Kestrel Games", title: "Trailer — HDR grade", format: "4K HDR10", stage: "grade", due: "2026-07-04", assignee: "Dev Okafor", initials: "DO", color: "#7c5cff" },
{ id: "p9", client: "Bloomwell", title: "Testimonial cutdowns x3", format: "9:16 H.264", stage: "delivery", due: "2026-07-15", assignee: "Sam Ahmadi", initials: "SA", color: "#ffb020" },
{ id: "p10", client: "Orbit Retail", title: "Holiday teaser — master deliver", format: "4K ProRes", stage: "delivery", due: "2026-07-20", assignee: "Rui Tanaka", initials: "RT", color: "#35d07f" }
];
var CAPACITY = 4; // used for the per-column progress bar
var board = document.getElementById("board");
var toastEl = document.getElementById("toast");
var activeFilter = "all";
var toastTimer = null;
function stageById(id) {
for (var i = 0; i < STAGES.length; i++) if (STAGES[i].id === id) return STAGES[i];
return STAGES[0];
}
function dayDiff(dueStr) {
var d = new Date(dueStr + "T00:00:00");
return Math.round((d - TODAY) / 86400000);
}
// Delivery = always shipped/on-track; otherwise derive from due date.
function statusOf(p) {
if (p.stage === "delivery") return "on-track";
var diff = dayDiff(p.due);
if (diff < 0) return "overdue";
if (diff <= 3) return "at-risk";
return "on-track";
}
function fmtDue(dueStr) {
var d = new Date(dueStr + "T00:00:00");
var m = ["JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV","DEC"];
return m[d.getMonth()] + " " + String(d.getDate()).padStart(2, "0");
}
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () { toastEl.classList.remove("show"); }, 2400);
}
var BADGE = {
"on-track": { cls: "badge--ok", text: "On track" },
"at-risk": { cls: "badge--warn", text: "At risk" },
"overdue": { cls: "badge--bad", text: "Overdue" }
};
function buildCard(p) {
var status = statusOf(p);
var badge = BADGE[status];
var diff = dayDiff(p.due);
var overdue = status === "overdue";
var el = document.createElement("article");
el.className = "card";
el.setAttribute("draggable", "true");
el.setAttribute("tabindex", "0");
el.setAttribute("role", "listitem");
el.dataset.id = p.id;
el.dataset.status = status;
el.setAttribute("aria-label", p.client + " — " + p.title + ", " + badge.text + ", stage " + stageById(p.stage).label);
var dueLabel = overdue
? Math.abs(diff) + "d late"
: (p.stage === "delivery" ? "delivered" : "due " + fmtDue(p.due));
var opts = STAGES.map(function (s) {
return '<button data-stage="' + s.id + '"' + (s.id === p.stage ? " disabled" : "") +
'><span class="mm-dot" style="background:' + s.color + '"></span>' + s.label + "</button>";
}).join("");
el.innerHTML =
'<div class="card__top">' +
'<span class="card__client">' + p.client + "</span>" +
'<span class="card__handle" aria-hidden="true"><svg viewBox="0 0 20 20" width="16" height="16" fill="currentColor"><circle cx="7" cy="5" r="1.4"/><circle cx="13" cy="5" r="1.4"/><circle cx="7" cy="10" r="1.4"/><circle cx="13" cy="10" r="1.4"/><circle cx="7" cy="15" r="1.4"/><circle cx="13" cy="15" r="1.4"/></svg></span>' +
"</div>" +
'<h3 class="card__title">' + p.title + "</h3>" +
'<div class="card__tags">' +
'<span class="tag tag--format">' + p.format + "</span>" +
"</div>" +
'<div class="card__meta">' +
'<span class="badge ' + badge.cls + '"><i></i>' + badge.text + "</span>" +
'<span class="card__due' + (overdue ? " is-overdue" : "") + '"><svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>' + dueLabel + "</span>" +
"</div>" +
'<div class="card__foot">' +
'<span class="avatar"><span class="avatar__img" style="background:' + p.color + '">' + p.initials + '</span><span class="avatar__name">' + p.assignee + "</span></span>" +
'<span class="move-wrap">' +
'<button class="move-btn" aria-haspopup="true" aria-expanded="false" aria-label="Move ' + p.title + ' to another stage"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg></button>' +
'<div class="move-menu" role="menu">' + opts + "</div>" +
"</span>" +
"</div>";
wireCard(el, p);
return el;
}
function wireCard(el, p) {
el.addEventListener("dragstart", function (e) {
el.classList.add("dragging");
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("text/plain", p.id);
});
el.addEventListener("dragend", function () { el.classList.remove("dragging"); });
var btn = el.querySelector(".move-btn");
var menu = el.querySelector(".move-menu");
btn.addEventListener("click", function (e) {
e.stopPropagation();
var open = menu.classList.contains("is-open");
closeAllMenus();
if (!open) { menu.classList.add("is-open"); btn.setAttribute("aria-expanded", "true"); }
});
menu.querySelectorAll("button").forEach(function (b) {
b.addEventListener("click", function (e) {
e.stopPropagation();
if (b.disabled) return;
moveProject(p.id, b.dataset.stage);
closeAllMenus();
});
});
// keyboard: Enter/Space opens move menu on the card
el.addEventListener("keydown", function (e) {
if ((e.key === "Enter" || e.key === " ") && e.target === el) {
e.preventDefault();
btn.click();
var first = menu.querySelector("button:not(:disabled)");
if (first) first.focus();
}
});
}
function closeAllMenus() {
document.querySelectorAll(".move-menu.is-open").forEach(function (m) {
m.classList.remove("is-open");
var b = m.parentNode.querySelector(".move-btn");
if (b) b.setAttribute("aria-expanded", "false");
});
}
document.addEventListener("click", closeAllMenus);
document.addEventListener("keydown", function (e) { if (e.key === "Escape") closeAllMenus(); });
function render() {
board.innerHTML = "";
STAGES.forEach(function (s) {
var col = document.createElement("section");
col.className = "column";
col.dataset.stage = s.id;
col.style.setProperty("--stage", s.color);
var head = document.createElement("div");
head.className = "column__head";
head.innerHTML =
'<div class="column__title-row">' +
'<span class="column__title"><span class="stage-dot"></span>' + s.label + "</span>" +
'<span class="column__count">0</span>' +
"</div>" +
'<div class="column__bar"><span></span></div>';
var list = document.createElement("div");
list.className = "column__list";
list.setAttribute("role", "list");
list.setAttribute("aria-label", s.label + " projects");
PROJECTS.filter(function (p) { return p.stage === s.id; })
.forEach(function (p) { list.appendChild(buildCard(p)); });
wireColumn(col, list, s.id);
col.appendChild(head);
col.appendChild(list);
board.appendChild(col);
});
updateAll();
applyFilter();
}
function wireColumn(col, list, stageId) {
col.addEventListener("dragover", function (e) {
e.preventDefault();
e.dataTransfer.dropEffect = "move";
col.classList.add("drag-over");
});
col.addEventListener("dragleave", function (e) {
if (!col.contains(e.relatedTarget)) col.classList.remove("drag-over");
});
col.addEventListener("drop", function (e) {
e.preventDefault();
col.classList.remove("drag-over");
var id = e.dataTransfer.getData("text/plain");
moveProject(id, stageId);
});
}
function moveProject(id, toStage) {
var p = null;
for (var i = 0; i < PROJECTS.length; i++) if (PROJECTS[i].id === id) p = PROJECTS[i];
if (!p || p.stage === toStage) return;
var from = stageById(p.stage).label;
p.stage = toStage;
var card = board.querySelector('.card[data-id="' + id + '"]');
var targetList = board.querySelector('.column[data-stage="' + toStage + '"] .column__list');
if (card && targetList) {
var fresh = buildCard(p); // rebuild so badge/due recompute for new stage
card.replaceWith(fresh);
targetList.appendChild(fresh);
}
updateAll();
applyFilter();
toast(p.client + " moved · " + from + " → " + stageById(toStage).label);
}
function updateAll() {
STAGES.forEach(function (s) {
var col = board.querySelector('.column[data-stage="' + s.id + '"]');
var list = col.querySelector(".column__list");
var n = list.querySelectorAll(".card").length;
col.querySelector(".column__count").textContent = n;
var pct = Math.min(100, Math.round((n / CAPACITY) * 100));
col.querySelector(".column__bar > span").style.width = pct + "%";
list.classList.toggle("is-empty", n === 0);
});
}
function applyFilter() {
var cards = board.querySelectorAll(".card");
cards.forEach(function (c) {
var match = activeFilter === "all" || c.dataset.status === activeFilter;
c.classList.toggle("is-dim", !match);
});
}
// Filter chips
document.querySelector(".filters").addEventListener("click", function (e) {
var chip = e.target.closest(".chip");
if (!chip) return;
document.querySelectorAll(".chip").forEach(function (c) {
c.classList.remove("is-active");
c.setAttribute("aria-pressed", "false");
});
chip.classList.add("is-active");
chip.setAttribute("aria-pressed", "true");
activeFilter = chip.dataset.filter;
applyFilter();
});
render();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Video — Pipeline Tracker</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="letterbox letterbox--top" aria-hidden="true"></div>
<header class="topbar">
<div class="brand">
<span class="brand__mark" aria-hidden="true">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="14" height="16" rx="2"/><path d="M16 9l6-3v12l-6-3"/></svg>
</span>
<div class="brand__text">
<h1>Aperture Studio</h1>
<p>Production pipeline · Q3 slate</p>
</div>
</div>
<div class="filters" role="toolbar" aria-label="Filter by status">
<span class="filters__label">Filter</span>
<button class="chip is-active" data-filter="all" aria-pressed="true">All</button>
<button class="chip" data-filter="on-track" aria-pressed="false"><i class="dot dot--ok"></i>On track</button>
<button class="chip" data-filter="at-risk" aria-pressed="false"><i class="dot dot--warn"></i>At risk</button>
<button class="chip" data-filter="overdue" aria-pressed="false"><i class="dot dot--bad"></i>Overdue</button>
</div>
</header>
<main class="board" id="board" aria-label="Video production pipeline board"></main>
<div class="letterbox letterbox--bottom" aria-hidden="true"></div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>A production-pipeline board built for video teams, styled with letterbox bars and amber film accents on a near-black canvas. Five stages — Brief, Shoot, Edit, Grade and Delivery — run left to right as scrollable columns, each with a live count, a color-coded rail and a thin progress bar that reflects how full the stage is. Project cards carry a client tag, a delivery format chip, a monospaced timecode-style due date, a status badge and a stacked assignee avatar.
Cards are fully draggable between columns using native HTML5 drag and drop, with a keyboard-accessible move menu as a fallback. Dropping a card recalculates every column count, re-evaluates overdue state, and fires a small toast confirming the move. A status filter bar at the top narrows the board to on-track, at-risk or overdue work, dimming cards that don’t match.
The layout collapses to a single scannable column stack under 520px while keeping the drag handles, badges and hover, focus and active states intact for touch and keyboard users.