diff --git a/app/changelog/page.tsx b/app/changelog/page.tsx index 1cfd351..ce2380f 100644 --- a/app/changelog/page.tsx +++ b/app/changelog/page.tsx @@ -2,9 +2,9 @@ import type { Metadata } from "next"; import Link from "next/link"; import { Fragment } from "react"; import { Terminal, ArrowLeft, Github, ExternalLink } from "lucide-react"; -import { ArcadeSplash } from "@/components/changelog/ArcadeSplash"; -import { ArcadeBackground } from "@/components/changelog/ArcadeBackground"; -import { InstallBlock } from "@/components/changelog/InstallBlock"; +import { ArcadeSplash } from "@/components/marketing/arcade/ArcadeSplash"; +import { ArcadeBackground } from "@/components/marketing/arcade/ArcadeBackground"; +import { InstallBlock } from "@/components/marketing/changelog/InstallBlock"; export const dynamic = "force-static"; diff --git a/app/roadmap/page.tsx b/app/roadmap/page.tsx new file mode 100644 index 0000000..4509faa --- /dev/null +++ b/app/roadmap/page.tsx @@ -0,0 +1,105 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { Terminal, ArrowLeft, Github } from "lucide-react"; +import { ArcadeBackground } from "@/components/marketing/arcade/ArcadeBackground"; +import { RoadmapBoard } from "@/components/marketing/roadmap/RoadmapBoard"; + +export const dynamic = "force-static"; + +export const metadata: Metadata = { + title: "Roadmap · AI DevKit Skills", + description: "What's in progress on AI DevKit Skills, and what's already shipped.", +}; + +const ACCENT = "#82AAFF"; +const REPO = "https://github.com/CommandOSSLabs/ai-devkit"; + +// Same corner-mark decoration as the changelog page's "Back to site" link — +// reused directly, not re-derived, so the two pages read as one site. +function CornerMark({ className }: { className?: string }) { + return ( + + + + + ); +} + +export default function RoadmapPage() { + return ( +
+ {/* Same ambient background as /changelog, no splash — the reveal + moment belongs to the changelog; landing here should feel calm. */} + + +
+
+
+ +
+ +
+ ai-devkit + + + + + + + + Back to site + +
+
+ +
+
+ + Roadmap + +

+ What's In Progress +

+

+ What's being worked on right now, and what's already shipped. Next and Later are open — + there's no fixed plan for them yet. +

+

+ Want something prioritized?{" "} + + Open an issue on GitHub + + . +

+ + + View the repo on GitHub + + +
+ + +
+
+
+ ); +} diff --git a/components/changelog/ArcadeBackground.tsx b/components/marketing/arcade/ArcadeBackground.tsx similarity index 96% rename from components/changelog/ArcadeBackground.tsx rename to components/marketing/arcade/ArcadeBackground.tsx index 2f18bac..51ca172 100644 --- a/components/changelog/ArcadeBackground.tsx +++ b/components/marketing/arcade/ArcadeBackground.tsx @@ -8,8 +8,8 @@ import { useEffect, useRef } from "react"; import { cn } from "@/lib/utils"; -import { Arcade } from "./arcade/engine"; -import { COLORWAYS } from "./arcade/params"; +import { Arcade } from "./engine"; +import { COLORWAYS } from "./params"; const DARK_COLORWAY = COLORWAYS.findIndex((c) => c.name === "Cyanide Dark"); diff --git a/components/changelog/ArcadeSplash.tsx b/components/marketing/arcade/ArcadeSplash.tsx similarity index 97% rename from components/changelog/ArcadeSplash.tsx rename to components/marketing/arcade/ArcadeSplash.tsx index dd87cd7..e8063ac 100644 --- a/components/changelog/ArcadeSplash.tsx +++ b/components/marketing/arcade/ArcadeSplash.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useRef, useState } from "react"; -import { Arcade } from "./arcade/engine"; +import { Arcade } from "./engine"; const TOTAL_MS = 5000; const FADE_MS = 600; diff --git a/components/changelog/arcade/engine.ts b/components/marketing/arcade/engine.ts similarity index 100% rename from components/changelog/arcade/engine.ts rename to components/marketing/arcade/engine.ts diff --git a/components/changelog/arcade/params.ts b/components/marketing/arcade/params.ts similarity index 100% rename from components/changelog/arcade/params.ts rename to components/marketing/arcade/params.ts diff --git a/components/changelog/arcade/shaders.ts b/components/marketing/arcade/shaders.ts similarity index 100% rename from components/changelog/arcade/shaders.ts rename to components/marketing/arcade/shaders.ts diff --git a/components/changelog/arcade/text-mask.ts b/components/marketing/arcade/text-mask.ts similarity index 100% rename from components/changelog/arcade/text-mask.ts rename to components/marketing/arcade/text-mask.ts diff --git a/components/changelog/arcade/textures.ts b/components/marketing/arcade/textures.ts similarity index 100% rename from components/changelog/arcade/textures.ts rename to components/marketing/arcade/textures.ts diff --git a/components/changelog/InstallBlock.tsx b/components/marketing/changelog/InstallBlock.tsx similarity index 100% rename from components/changelog/InstallBlock.tsx rename to components/marketing/changelog/InstallBlock.tsx diff --git a/components/marketing/roadmap/RoadmapBoard.tsx b/components/marketing/roadmap/RoadmapBoard.tsx new file mode 100644 index 0000000..f12d165 --- /dev/null +++ b/components/marketing/roadmap/RoadmapBoard.tsx @@ -0,0 +1,165 @@ +"use client"; + +// Three-column board (Now / Next / Later) plus a Shipped section that +// mirrors the real /changelog. Same bordered-card, mono-label visual +// language as the changelog page — border-subtle cards, "$ ref"-style +// commit/PR chips, accent color reused from there. + +import { useState } from "react"; +import Link from "next/link"; +import { CATEGORIES, ROADMAP_ITEMS, STATUS_DESCRIPTIONS, STATUS_LABELS, type RoadmapCategory, type RoadmapItem, type RoadmapStatus } from "./data"; + +const COLUMNS: RoadmapStatus[] = ["now", "next", "later"]; + +const STATUS_DOT: Record = { + now: "bg-emerald-400 animate-pulse", + next: "bg-amber-400", + later: "bg-[var(--text-disabled)]", + shipped: "bg-emerald-400", +}; + +function StatusDot({ status }: { status: RoadmapStatus }) { + return ; +} + +function RefChip({ label, url }: { label: string; url: string }) { + return ( + + $ + {label} + + ); +} + +function ActiveCard({ item }: { item: Extract }) { + return ( +
+

{item.title}

+

{item.description}

+
+ {item.category} + {item.pr && } +
+
+ ); +} + +function ShippedCard({ item }: { item: Extract }) { + const content = ( + <> +
+

+ {item.title} +

+

{item.description}

+ {/* A ref chip is itself a link — only shown when the card isn't + already wrapped in one, since an can't nest inside an . */} + {!item.href && item.ref && } +
+ {item.shippedDate} + + ); + + const className = "group flex items-start gap-3 border border-[var(--border-subtle)] p-4 transition-colors hover:border-[var(--border-strong)]"; + + if (item.href) { + return ( + + {content} + + ); + } + return
{content}
; +} + +export function RoadmapBoard() { + const [filter, setFilter] = useState(null); + + const filtered = filter ? ROADMAP_ITEMS.filter((item) => item.category === filter) : ROADMAP_ITEMS; + const itemsFor = (status: RoadmapStatus) => + filtered.filter((item): item is Extract => item.status !== "shipped" && item.status === status); + const shipped = filtered.filter((item): item is Extract => item.status === "shipped"); + + return ( +
+
+ +
+ {CATEGORIES.map((cat) => ( + + ))} +
+ +
+ {COLUMNS.map((status) => { + const items = itemsFor(status); + return ( +
+
+

+ + {STATUS_LABELS[status]} + {items.length} +

+

{STATUS_DESCRIPTIONS[status]}

+
+
+ {items.map((item) => ( +
+ +
+ ))} + {items.length === 0 && ( +

Nothing here yet

+ )} +
+
+ ); + })} +
+ + {shipped.length > 0 && ( +
+
+

+ + {STATUS_LABELS.shipped} + {shipped.length} +

+ + Full changelog → + +
+
+ {shipped.map((item) => ( +
+ +
+ ))} +
+
+ )} +
+ ); +} diff --git a/components/marketing/roadmap/data.ts b/components/marketing/roadmap/data.ts new file mode 100644 index 0000000..d743fc5 --- /dev/null +++ b/components/marketing/roadmap/data.ts @@ -0,0 +1,95 @@ +// Real signal only — no invented plans. "Now" comes from actual open PRs, +// "Shipped" mirrors the real /changelog entries, "Next" comes from the +// draft design doc at docs/design/visualize.md. "Later" has nothing behind +// it yet, so it stays empty rather than promise something that isn't decided. + +export type RoadmapCategory = "Website" | "Skills"; + +export type RoadmapStatus = "now" | "next" | "later" | "shipped"; + +interface RoadmapItemBase { + id: string; + title: string; + description: string; + category: RoadmapCategory; +} + +interface ActiveRoadmapItem extends RoadmapItemBase { + status: "now" | "next" | "later"; + /** Open PR covering this work, if any. */ + pr?: string; +} + +interface ShippedRoadmapItem extends RoadmapItemBase { + status: "shipped"; + shippedDate: string; + /** Commit or PR reference shown as a "$ git show"-style chip. */ + ref?: { label: string; url: string }; + /** Link to the changelog entry covering the ship. */ + href?: string; +} + +export type RoadmapItem = ActiveRoadmapItem | ShippedRoadmapItem; + +export const CATEGORIES: RoadmapCategory[] = ["Website", "Skills"]; + +export const STATUS_LABELS: Record = { + now: "In Progress", + next: "Next", + later: "Later", + shipped: "Shipped", +}; + +export const STATUS_DESCRIPTIONS: Record = { + now: "Being worked on right now.", + next: "Queued up after what's in progress.", + later: "On the radar, not yet started.", + shipped: "Already live.", +}; + +const REPO = "https://github.com/CommandOSSLabs/ai-devkit"; + +export const ROADMAP_ITEMS: RoadmapItem[] = [ + { + id: "browse-skills-nav", + status: "now", + category: "Website", + title: "Browse Skills nav + loading transition", + description: "A dedicated nav button to browse skills, with a TextMorph transition while the list loads.", + pr: `${REPO}/pull/15`, + }, + { + id: "repo-meta-rate-limit", + status: "now", + category: "Website", + title: "Fix GitHub repo-meta rate limiting", + description: "Stops hitting GitHub's rate limit on repo metadata calls and removes a dead WebGL scroll loop left running behind it.", + pr: `${REPO}/pull/13`, + }, + { + id: "cmk-visualize", + status: "next", + category: "Skills", + title: "New skill: cmk:visualize", + description: "Turns content you already have — a design doc, a tracker query, a raw description — into a rendered diagram, slide deck, or animated teaser, instead of a wall of markdown.", + }, + { + id: "cmk-interpret", + status: "shipped", + category: "Skills", + shippedDate: "Aug 14", + title: "New skill: cmk:interpret", + description: "For user-invoked interpret sessions, plus acceptance-criteria notation for requirements.", + ref: { label: "0d7b298", url: `${REPO}/commit/0d7b298` }, + href: "/changelog", + }, + { + id: "skills-site-launch", + status: "shipped", + category: "Website", + shippedDate: "Aug 13", + title: "skills.commandoss.com launched", + description: "Homepage moved to the skills page, nav links go to real URLs, and the install box got a package-manager switcher.", + href: "/changelog", + }, +];