diff --git a/src/components/blocks/FleetDiagram.astro b/src/components/blocks/FleetDiagram.astro
new file mode 100644
index 0000000..b8c3577
--- /dev/null
+++ b/src/components/blocks/FleetDiagram.astro
@@ -0,0 +1,163 @@
+---
+// FleetDiagram — orchestrator card, an arrow, and a 1-up of worker cards.
+//
+// Static shell: the captain renders literally, the workers render as toggle
+// buttons with the `data-worker` hook asserted by `scripts/verify.mjs`. The
+// `initial` worker is marked active and pressed.
+//
+// The source uses `gap:1px` over a coloured parent background to fake
+// hairlines between worker cards; the parent background is an off-token seam
+// colour marked inline with `token-gap`.
+
+interface Worker {
+ /** Used as the `data-worker` hook and the key in the source. */
+ id: string;
+ /** Short label rendered uppercase, e.g. "UI". */
+ label: string;
+ /** Body copy describing the worker's remit. */
+ strong: string;
+ /** Path label, e.g. "agent/ui". */
+ code: string;
+}
+
+interface Props {
+ orchestrator: {
+ eyebrow: string;
+ /** May contain inline ` `; rendered with `set:html`. */
+ title: string;
+ code: string;
+ };
+ workers: Worker[];
+ initial?: string;
+}
+
+const { orchestrator, workers, initial = workers[0]?.id } = Astro.props;
+---
+
+
+
+ {orchestrator.eyebrow}
+
+ {orchestrator.code}
+
+
→
+
+ {
+ workers.map((worker) => (
+
+ {worker.label}
+ {worker.strong}
+ {worker.code}
+
+ ))
+ }
+
+
+
+
diff --git a/src/components/blocks/HandoffTable.astro b/src/components/blocks/HandoffTable.astro
new file mode 100644
index 0000000..7dca7d1
--- /dev/null
+++ b/src/components/blocks/HandoffTable.astro
@@ -0,0 +1,93 @@
+---
+// HandoffTable — the four-row "what crosses contexts" table.
+//
+// Static shell: the row data is passed in via `rows` so this component has
+// no opinion on what each handoff package contains. The table structure is
+// the load-bearing part of the design (dark header, blue row labels,
+// muted body) and lives here so the next page that needs it gets the same
+// beat for free.
+
+interface Row {
+ /** The package name, rendered as a `` (column 1). */
+ package: string;
+ /** What the package contains (column 2). */
+ contains: string;
+ /** Why this matters (column 3). */
+ why: string;
+}
+
+interface Props {
+ /** Column headers in render order. */
+ columns: [string, string, string];
+ rows: Row[];
+}
+
+const { columns, rows } = Astro.props;
+---
+
+
+
+
+ {columns[0]}
+ {columns[1]}
+ {columns[2]}
+
+
+
+ {
+ rows.map((row) => (
+
+ {row.package}
+ {row.contains}
+ {row.why}
+
+ ))
+ }
+
+
+
+
diff --git a/src/components/blocks/PhasePanel.astro b/src/components/blocks/PhasePanel.astro
new file mode 100644
index 0000000..12fbe50
--- /dev/null
+++ b/src/components/blocks/PhasePanel.astro
@@ -0,0 +1,131 @@
+---
+// PhasePanel — the three-step "Click a phase / see the handoff" block.
+//
+// Static shell: the tab buttons and the panel markup are server-rendered.
+// The `initial` phase's content is shown by default; the other tabs still
+// carry the `data-phase` hook so the interactive island (task 15) can swap
+// the panel on click.
+//
+// Every `data-phase` value is asserted by `scripts/verify.mjs`.
+
+export type PhaseId = 'plan' | 'build' | 'review';
+
+interface Phase {
+ id: PhaseId;
+ /** Two-letter label rendered in the tab, e.g. "PLAN". */
+ label: string;
+ /** Numeric prefix, e.g. "01". */
+ number: string;
+ title: string;
+ body: string;
+ /** Headline + small caption shown above the panel title. */
+ meta: { deliverable: string; gate: string };
+ /** Code-line evidence shown at the bottom of the panel. */
+ evidence: string;
+}
+
+interface Props {
+ phases: Phase[];
+ /** Initial active phase. Defaults to the first entry. */
+ initial?: PhaseId;
+}
+
+const { phases, initial = phases[0]?.id ?? 'plan' } = Astro.props;
+const active = phases.find((phase) => phase.id === initial) ?? phases[0];
+---
+
+
+ {
+ phases.map((phase) => (
+
+ {phase.number} {phase.label}
+
+ ))
+ }
+
+
+
+
+ {active.meta.deliverable}
+ {active.meta.gate}
+
+ {active.title}
+ {active.body}
+ {active.evidence}
+
+
+
diff --git a/src/components/blocks/RouteTable.astro b/src/components/blocks/RouteTable.astro
new file mode 100644
index 0000000..c66345b
--- /dev/null
+++ b/src/components/blocks/RouteTable.astro
@@ -0,0 +1,120 @@
+---
+// RouteTable — the model-routing matrix. Four buttons (one per job profile),
+// each carrying the `data-route` hook asserted by `scripts/verify.mjs`.
+//
+// Static shell: the initial route is marked active and pressed. The shell
+// renders the column header + the four rows; task 15 will hydrate the
+// "route detail" panel to the right.
+
+interface Route {
+ id: 'plan' | 'build' | 'explore' | 'review' | string;
+ /** Strong label, e.g. "Plan". */
+ strong: string;
+ /** Profile descriptor, e.g. "strong / broad". */
+ profile: string;
+ /** Prompt shape copy. */
+ prompt: string;
+}
+
+interface Props {
+ routes: Route[];
+ initial?: string;
+}
+
+const { routes, initial = routes[0]?.id ?? 'plan' } = Astro.props;
+---
+
+
+
+ Work
+ Profile
+ Prompt shape
+
+ {
+ routes.map((route) => (
+
+ {route.strong}
+ {route.profile}
+ {route.prompt}
+
+ ))
+ }
+
+
+
diff --git a/src/components/blocks/SkillPackage.astro b/src/components/blocks/SkillPackage.astro
new file mode 100644
index 0000000..e045b3d
--- /dev/null
+++ b/src/components/blocks/SkillPackage.astro
@@ -0,0 +1,94 @@
+---
+// SkillPackage — the four-file skill-package picker (SKILL.md, references/,
+// scripts/, assets/). Buttons carry the `data-skill-file` hook asserted by
+// `scripts/verify.mjs`.
+//
+// Static shell: the initial file is marked active and selected. The block is
+// paired with a `` detail panel by the parent page; this component
+// renders only the picker.
+
+interface PackageFile {
+ id: 'skill' | 'references' | 'scripts' | 'assets' | string;
+ /** Path rendered inside ``, e.g. "SKILL.md". */
+ path: string;
+ /** Helper copy under the path. */
+ small: string;
+}
+
+interface Props {
+ files: PackageFile[];
+ initial?: string;
+}
+
+const { files, initial = files[0]?.id ?? 'skill' } = Astro.props;
+---
+
+
+ SKILL PACKAGE
+ {
+ files.map((file) => (
+
+ {file.path}
+ {file.small}
+
+ ))
+ }
+
+
+
diff --git a/src/components/blocks/WorktreeMap.astro b/src/components/blocks/WorktreeMap.astro
new file mode 100644
index 0000000..4e44213
--- /dev/null
+++ b/src/components/blocks/WorktreeMap.astro
@@ -0,0 +1,165 @@
+---
+// WorktreeMap — the repository-topology diagram with the SVG trunk-and-branches
+// behind four `tree-node` buttons.
+//
+// Static shell: the SVG paths render server-side; the nodes are buttons with
+// the `data-tree` hook asserted by `scripts/verify.mjs`. The `root` node and
+// the `initial` branch are marked selected.
+
+interface Branch {
+ /** The `data-tree` hook, e.g. "ui", "tests", "docs". */
+ id: string;
+ /** Uppercase label, e.g. "UI AGENT". */
+ label: string;
+ /** Strong line, e.g. "agent/ui". */
+ strong: string;
+ /** Status small, e.g. "3 files · working". */
+ small: string;
+ /** CSS modifier so each branch picks up its tone. */
+ tone: 'ui' | 'tests' | 'docs' | string;
+}
+
+interface Props {
+ branches: Branch[];
+ initial?: string;
+}
+
+const { branches, initial = 'main' } = Astro.props;
+---
+
+
+
+
+
+
+
+
+
ROOT main ● clean
+ {
+ branches.map((branch) => (
+
+ <>
+ {branch.label}
+ {branch.strong}
+ {branch.small}
+ >
+
+ ))
+ }
+
+
+