diff --git a/src/components/roadmap/band-view.tsx b/src/components/roadmap/band-view.tsx
new file mode 100644
index 0000000..4fe8626
--- /dev/null
+++ b/src/components/roadmap/band-view.tsx
@@ -0,0 +1,66 @@
+import { RoadmapCard } from "@/components/roadmap/roadmap-card";
+import type { Band } from "@/lib/roadmap/band";
+import type { RoadmapCard as Card } from "@/lib/roadmap/load";
+
+const BANDS: Array<{ key: Band; title: string; blurb: string }> = [
+ { key: "now", title: "Now", blurb: "In flight, critical, or due within a week" },
+ { key: "next", title: "Next", blurb: "Queued up" },
+ { key: "later", title: "Later", blurb: "Not started" },
+];
+
+export function BandView({
+ cards,
+ inboxCount,
+}: {
+ cards: Card[];
+ inboxCount: number;
+}) {
+ return (
+
+ {inboxCount > 0 && (
+ // Counted, not placed: an unaccepted request is not committed work,
+ // but it should not be invisible either.
+
+ {inboxCount}{" "}
+ {inboxCount === 1 ? "request is" : "requests are"} waiting in an Inbox.
+
+ )}
+
+
+ {BANDS.map((band) => {
+ const inBand = cards.filter((card) => card.band === band.key);
+
+ return (
+
+
+
+
+ {band.title}
+
+
+ {inBand.length}
+
+
+
{band.blurb}
+
+
+ {inBand.length === 0 ? (
+
+ Nothing here
+
+ ) : (
+
+ {inBand.map((card) => (
+ -
+
+
+ ))}
+
+ )}
+
+ );
+ })}
+
+
+ );
+}
diff --git a/src/components/roadmap/roadmap-card.tsx b/src/components/roadmap/roadmap-card.tsx
new file mode 100644
index 0000000..3cbedc8
--- /dev/null
+++ b/src/components/roadmap/roadmap-card.tsx
@@ -0,0 +1,55 @@
+import Link from "next/link";
+
+import { Pill } from "@/components/ui/pill";
+import { classifyDueDate, formatDueDate } from "@/lib/board/dates";
+import type { RoadmapCard as Card } from "@/lib/roadmap/load";
+
+const PRIORITY_TONE = {
+ critical: "critical",
+ high: "high",
+ medium: "medium",
+ low: "low",
+} as const;
+
+export function RoadmapCard({ card }: { card: Card }) {
+ const due = classifyDueDate(card.targetDate);
+ const dueLabel = formatDueDate(card.targetDate);
+
+ return (
+
+
+ {/* The client's own colour, so a cross-client page is scannable. */}
+
+
+
+ {card.title}
+
+
+ {card.clientName} ยท {card.projectName}
+
+
+ {card.percent !== null && (
+
+ {card.percent}%
+
+ )}
+
+
+
+
{card.priority}
+ {dueLabel && (
+
+ {dueLabel}
+
+ )}
+
+
+ );
+}