diff --git a/website/content/docs/docker.mdx b/website/content/docs/docker.mdx
index 0d31fd5..193a0f8 100644
--- a/website/content/docs/docker.mdx
+++ b/website/content/docs/docker.mdx
@@ -4,6 +4,8 @@ title: "Execution (light-run)"
light-process no longer talks to Docker directly. Every node runs inside a container spawned by the [light-run](https://github.com/enixCode/light-run) HTTP service.
+
+
## Configuration
```bash
diff --git a/website/content/docs/index.mdx b/website/content/docs/index.mdx
index 13f22ef..5dc3b87 100644
--- a/website/content/docs/index.mdx
+++ b/website/content/docs/index.mdx
@@ -7,6 +7,8 @@ description: A DAG workflow engine that orchestrates code in Docker containers v
Execution itself is delegated: each node runs on a [light-run](https://github.com/enixCode/light-run) instance over HTTP, which in turn drives [light-runner](https://github.com/enixCode/light-runner) and Docker. light-process never touches Docker directly; it owns orchestration, identity, and structured results.
+
+
## Where to start
- **[Getting started](/docs/getting-started)** - install, point at a light-run instance, run the example.
diff --git a/website/content/docs/workflows.mdx b/website/content/docs/workflows.mdx
index f02975c..07856d2 100644
--- a/website/content/docs/workflows.mdx
+++ b/website/content/docs/workflows.mdx
@@ -4,6 +4,8 @@ title: "Workflows"
A workflow is a directed acyclic graph (DAG) of nodes connected by links. Each node runs code in a Docker container.
+
+
## Folder structure
```
@@ -168,6 +170,8 @@ Isolation is topological: put each group of nodes on its own `networkDef` and th
A `service` is a long-running container started **before** the DAG and stopped **after** it, living outside the DAG flow (it is never a node and never an entry node). The canonical use is a network proxy that holds a provider API key so a workload node can reach another provider by hostname **without the secret ever entering the workload container**.
+
+
```json
{
"id": "claude-wf",
diff --git a/website/src/components/diagrams/Architecture.tsx b/website/src/components/diagrams/Architecture.tsx
new file mode 100644
index 0000000..b91ddf3
--- /dev/null
+++ b/website/src/components/diagrams/Architecture.tsx
@@ -0,0 +1,109 @@
+import styles from './diagrams.module.css';
+
+/*
+ * The strict top-down delegation chain:
+ * light-process (graph) -> light-run (HTTP) -> light-runner (Docker SDK) -> Docker
+ * A request token travels down the left rail, a result token back up the right.
+ * Pure CSS animation, no client JS - safe in static export.
+ */
+
+const LAYERS = [
+ { name: 'light-process', sub: 'DAG orchestrator - evaluates the graph', current: true },
+ { name: 'light-run', sub: 'HTTP wrapper - POST /run, serves artifacts' },
+ { name: 'light-runner', sub: 'Docker SDK - dockerode, extract, state' },
+ { name: 'Docker', sub: 'isolation boundary - the container runs here' },
+];
+
+const ROW_H = 52;
+const GAP = 18;
+const TOP = 16;
+const BOX_X = 150;
+const BOX_W = 372;
+
+export function Architecture() {
+ const height = TOP * 2 + LAYERS.length * ROW_H + (LAYERS.length - 1) * GAP;
+ const railTop = TOP + ROW_H / 2;
+ const railBottom = TOP + (LAYERS.length - 1) * (ROW_H + GAP) + ROW_H / 2;
+ const travel = railBottom - railTop;
+
+ return (
+
+
+
+ Fig. - one request, three layers, one isolation boundary.
+ no upward coupling
+
+
+ );
+}
diff --git a/website/src/components/diagrams/Dag.tsx b/website/src/components/diagrams/Dag.tsx
new file mode 100644
index 0000000..36efef1
--- /dev/null
+++ b/website/src/components/diagrams/Dag.tsx
@@ -0,0 +1,106 @@
+import styles from './diagrams.module.css';
+
+/*
+ * The DAG model: entry node fans out into a parallel batch; links carry output
+ * to the next node, optionally gated by a `when` condition. Live edges animate
+ * a flowing dash; the conditional edge is labelled with its predicate. Pure CSS,
+ * no client JS - safe in static export. Reused (scaled) as the landing hero.
+ */
+
+type NodeDef = {
+ id: string;
+ x: number;
+ y: number;
+ label: string;
+ sub: string;
+ cls?: string;
+ pulse?: string;
+};
+
+const W = 124;
+const H = 46;
+
+const NODES: NodeDef[] = [
+ { id: 'fetch', x: 40, y: 110, label: 'fetch', sub: 'node:24', pulse: 'pulse' },
+ { id: 'parse', x: 286, y: 40, label: 'parse', sub: 'python', pulse: 'pulseB' },
+ { id: 'store', x: 286, y: 180, label: 'store', sub: 'node:24', pulse: 'pulseC' },
+ { id: 'report', x: 516, y: 40, label: 'report', sub: 'python', pulse: 'pulseB' },
+];
+
+function center(n: NodeDef) {
+ return { cx: n.x + W / 2, cy: n.y + H / 2 };
+}
+
+export function Dag() {
+ const byId = Object.fromEntries(NODES.map((n) => [n.id, n]));
+ const edge = (from: string, to: string) => {
+ const a = center(byId[from]);
+ const b = center(byId[to]);
+ const x1 = byId[from].x + W;
+ const y1 = a.cy;
+ const x2 = byId[to].x;
+ const y2 = b.cy;
+ const mx = (x1 + x2) / 2;
+ return `M ${x1} ${y1} C ${mx} ${y1}, ${mx} ${y2}, ${x2} ${y2}`;
+ };
+
+ return (
+
+
+
+ Fig. - nodes fan out; a link fires only when its condition holds.
+ data + conditions
+
+
+ );
+}
diff --git a/website/src/components/diagrams/NetworkServices.tsx b/website/src/components/diagrams/NetworkServices.tsx
new file mode 100644
index 0000000..8903a0a
--- /dev/null
+++ b/website/src/components/diagrams/NetworkServices.tsx
@@ -0,0 +1,104 @@
+import styles from './diagrams.module.css';
+
+/*
+ * The networks/services proxy pattern. A run-scoped Docker network
+ * (`lp--svc-net`) holds the workload and a sidecar proxy service. The
+ * secret (API key) lives only in the proxy: the workload reaches it by
+ * hostname, no credential in the workload's env. The proxy is the single hop
+ * that attaches the key on the way out to the external API. Pure CSS animation,
+ * no client JS - safe in static export.
+ */
+
+const BOX = { x: 20, y: 52, w: 432, h: 140 };
+
+const WORKLOAD = { x: 44, y: 100, w: 150, h: 54 };
+const PROXY = { x: 258, y: 100, w: 150, h: 54 };
+const EXTERNAL = { x: 512, y: 100, w: 148, h: 54 };
+
+function rightMid(n: { x: number; y: number; w: number; h: number }) {
+ return { x: n.x + n.w, y: n.y + n.h / 2 };
+}
+function leftMid(n: { x: number; y: number; w: number; h: number }) {
+ return { x: n.x, y: n.y + n.h / 2 };
+}
+
+export function NetworkServices() {
+ const a = rightMid(WORKLOAD);
+ const b = leftMid(PROXY);
+ const c = rightMid(PROXY);
+ const d = leftMid(EXTERNAL);
+
+ return (
+
+
+
+ Fig. - the workload reaches the API by hostname; the key never leaves the proxy.
+ secret isolation
+
+
+ );
+}
diff --git a/website/src/components/diagrams/diagrams.module.css b/website/src/components/diagrams/diagrams.module.css
new file mode 100644
index 0000000..0101819
--- /dev/null
+++ b/website/src/components/diagrams/diagrams.module.css
@@ -0,0 +1,266 @@
+/*
+ * Self-contained design tokens + animations for the light-process docs
+ * diagrams. The docs site (Fumadocs) runs a neutral/blue theme; these figures
+ * deliberately carry the light-* landing aesthetic (near-black canvas, ice-blue
+ * accent, Fraunces/JetBrains Mono voice) as a framed "diagram panel", so they
+ * read the same in light and dark docs. Tokens are hardcoded here on purpose:
+ * the landing CSS variables are not in scope inside the docs.
+ */
+
+.figure {
+ --d-ink: #050608;
+ --d-ink-2: #0a0c10;
+ --d-line: #1a1d24;
+ --d-line-hot: #242933;
+ --d-paper: #edeef0;
+ --d-paper-dim: #8a8f9a;
+ --d-paper-dimmer: #4d525c;
+ --d-halo: #c7e8ff;
+ --d-halo-soft: #7fddff;
+ --d-amber: #ffb855;
+ --d-ember: #ff6b35;
+
+ position: relative;
+ margin: 1.75rem 0;
+ padding: 1.4rem 1.4rem 1rem;
+ background: radial-gradient(120% 140% at 50% 0%, var(--d-ink-2), var(--d-ink) 70%);
+ border: 1px solid var(--d-line);
+ border-radius: 3px;
+ overflow: hidden;
+ color: var(--d-paper);
+ font-family:
+ 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
+}
+
+/* Corner brackets, the landing's signature motif. */
+.figure::before,
+.figure::after {
+ content: '';
+ position: absolute;
+ width: 14px;
+ height: 14px;
+ border: 1px solid var(--d-halo-soft);
+ opacity: 0.5;
+ pointer-events: none;
+}
+.figure::before {
+ top: 8px;
+ left: 8px;
+ border-right: 0;
+ border-bottom: 0;
+}
+.figure::after {
+ bottom: 8px;
+ right: 8px;
+ border-left: 0;
+ border-top: 0;
+}
+
+.svg {
+ display: block;
+ width: 100%;
+ height: auto;
+}
+
+.caption {
+ margin-top: 0.9rem;
+ display: flex;
+ justify-content: space-between;
+ gap: 1rem;
+ font-size: 10.5px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--d-paper-dimmer);
+}
+.captionRight {
+ color: var(--d-halo-soft);
+ opacity: 0.7;
+}
+
+/* --- SVG primitives (applied via className on SVG elements) --- */
+
+.panel {
+ fill: var(--d-ink-2);
+ stroke: var(--d-line-hot);
+ stroke-width: 1;
+}
+.panelDashed {
+ fill: rgba(199, 232, 255, 0.03);
+ stroke: var(--d-halo-soft);
+ stroke-width: 1;
+ stroke-dasharray: 5 4;
+ opacity: 0.8;
+}
+.node {
+ fill: var(--d-ink-2);
+ stroke: var(--d-halo);
+ stroke-width: 1.25;
+}
+.nodeMuted {
+ fill: var(--d-ink-2);
+ stroke: var(--d-line-hot);
+ stroke-width: 1.25;
+}
+.nodeWarm {
+ fill: var(--d-ink-2);
+ stroke: var(--d-amber);
+ stroke-width: 1.25;
+}
+
+.label {
+ fill: var(--d-paper);
+ font-family: 'Fraunces', Georgia, serif;
+ font-style: italic;
+ font-size: 15px;
+}
+.labelMono {
+ fill: var(--d-paper);
+ font-family: 'JetBrains Mono', ui-monospace, monospace;
+ font-size: 12px;
+}
+.sub {
+ fill: var(--d-paper-dim);
+ font-family: 'JetBrains Mono', ui-monospace, monospace;
+ font-size: 10px;
+ letter-spacing: 0.04em;
+}
+.tag {
+ fill: var(--d-halo-soft);
+ font-family: 'JetBrains Mono', ui-monospace, monospace;
+ font-size: 9.5px;
+ letter-spacing: 0.06em;
+}
+.tagWarm {
+ fill: var(--d-amber);
+ font-family: 'JetBrains Mono', ui-monospace, monospace;
+ font-size: 9.5px;
+ letter-spacing: 0.06em;
+}
+
+.arrowLive {
+ fill: var(--d-halo);
+}
+.arrowMuted {
+ fill: var(--d-paper-dimmer);
+}
+.arrowWarm {
+ fill: var(--d-amber);
+}
+
+.edge {
+ fill: none;
+ stroke: var(--d-line-hot);
+ stroke-width: 1.5;
+}
+.edgeLive {
+ fill: none;
+ stroke: var(--d-halo);
+ stroke-width: 1.5;
+ stroke-dasharray: 4 5;
+ animation: flow 1.1s linear infinite;
+}
+.edgeWarm {
+ fill: none;
+ stroke: var(--d-amber);
+ stroke-width: 1.5;
+ stroke-dasharray: 4 5;
+ animation: flow 1.4s linear infinite;
+}
+
+/* A glowing pulse on a node outline. */
+.pulse {
+ animation: pulse 2.4s ease-in-out infinite;
+ transform-box: fill-box;
+ transform-origin: center;
+}
+.pulseB {
+ animation: pulse 2.4s ease-in-out infinite 0.8s;
+ transform-box: fill-box;
+ transform-origin: center;
+}
+.pulseC {
+ animation: pulse 2.4s ease-in-out infinite 1.6s;
+ transform-box: fill-box;
+ transform-origin: center;
+}
+
+/* A token that travels along a path (architecture rails / proxy hop). */
+.travelDown {
+ fill: var(--d-halo);
+ animation: travelDown 2.6s ease-in-out infinite;
+}
+.travelUp {
+ fill: var(--d-halo-soft);
+ animation: travelUp 2.6s ease-in-out infinite 1.3s;
+}
+.glowDot {
+ fill: var(--d-halo);
+ filter: drop-shadow(0 0 4px var(--d-halo-soft));
+}
+
+@keyframes flow {
+ to {
+ stroke-dashoffset: -18;
+ }
+}
+@keyframes pulse {
+ 0%,
+ 100% {
+ opacity: 0.55;
+ }
+ 50% {
+ opacity: 1;
+ }
+}
+@keyframes travelDown {
+ 0% {
+ opacity: 0;
+ transform: translateY(0);
+ }
+ 10% {
+ opacity: 1;
+ }
+ 90% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ transform: translateY(var(--travel, 210px));
+ }
+}
+@keyframes travelUp {
+ 0% {
+ opacity: 0;
+ transform: translateY(0);
+ }
+ 10% {
+ opacity: 1;
+ }
+ 90% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ transform: translateY(calc(-1 * var(--travel, 210px)));
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .edgeLive,
+ .edgeWarm,
+ .pulse,
+ .pulseB,
+ .pulseC,
+ .travelDown,
+ .travelUp {
+ animation: none;
+ }
+ .edgeLive,
+ .edgeWarm {
+ stroke-dasharray: 4 5;
+ }
+ .travelDown,
+ .travelUp {
+ opacity: 0;
+ }
+}
diff --git a/website/src/components/diagrams/index.ts b/website/src/components/diagrams/index.ts
new file mode 100644
index 0000000..a6ad7d3
--- /dev/null
+++ b/website/src/components/diagrams/index.ts
@@ -0,0 +1,3 @@
+export { Architecture } from './Architecture';
+export { Dag } from './Dag';
+export { NetworkServices } from './NetworkServices';
diff --git a/website/src/components/mdx.tsx b/website/src/components/mdx.tsx
index a640575..dc68724 100644
--- a/website/src/components/mdx.tsx
+++ b/website/src/components/mdx.tsx
@@ -1,9 +1,13 @@
import defaultMdxComponents from 'fumadocs-ui/mdx';
import type { MDXComponents } from 'mdx/types';
+import { Architecture, Dag, NetworkServices } from './diagrams';
export function getMDXComponents(components?: MDXComponents) {
return {
...defaultMdxComponents,
+ Architecture,
+ Dag,
+ NetworkServices,
...components,
} satisfies MDXComponents;
}