"use client"; import { Handle, Position, type Node, type NodeProps } from "@xyflow/react"; import { KtxMascot } from "./mascot"; /** Fixed palette mirrored from the approved SVG diagrams so the exported PNG * is theme-independent (one image that reads on light and dark GitHub). */ export const C = { ink: "#1b1b18", inkSoft: "#57534e", inkMuted: "#8c857f", cardBorder: "#e2dfd9", engineBg: "#15323a", engineBorder: "#23474f", cyan: "#55dced", stepNum: "#06262c", stepTitle: "#f3f1ec", stepDesc: "#9fb6bc", hubRow: "#eef4f5", chipBg: "#faf9f6", chipBorder: "#e7e5e4", teal: "#0e7490", emerald: "#059669", orange: "#f97316", amber: "#d97706", slate: "#334155", neutral: "#94a3b8", } as const; const DISPLAY = "var(--font-display), system-ui, sans-serif"; const BODY = "var(--font-inter), system-ui, sans-serif"; const MONO = "var(--font-mono), ui-monospace, monospace"; const CARD_SHADOW = "0 3px 12px rgba(27, 49, 57, 0.10)"; const ENGINE_SHADOW = "0 6px 22px rgba(2, 12, 15, 0.30)"; /** ktx logo mascot size, shared by the engine and hub headers. */ const LOGO_SIZE = 56; type HandleSpec = { side: "left" | "right"; type: "source" | "target"; id: string; top?: string; }; function Handles({ specs }: { specs?: HandleSpec[] }) { if (!specs) return null; return ( <> {specs.map((h) => ( ))} ); } /* ------------------------------- Card node ------------------------------- */ type CardRow = | { kind: "title"; text: string } | { kind: "mono"; text: string; color: string } | { kind: "desc"; text: string } | { kind: "muted"; text: string } | { kind: "chips"; items: string[] } | { kind: "badge"; text: string; bg: string; border: string; color: string }; type CardData = { width: number; height: number; accent: string; align?: "center"; rows: CardRow[]; handles?: HandleSpec[]; }; function gapFor(kind: CardRow["kind"], prev?: CardRow["kind"]): number { if (!prev) return 0; if (kind === "desc" && prev === "desc") return 3; if (kind === "mono" && prev === "mono") return 2; if (kind === "title") return 6; return 10; } function CardRowView({ row }: { row: CardRow }) { switch (row.kind) { case "title": return ( {row.text} ); case "mono": return ( {row.text} ); case "desc": return ( {row.text} ); case "muted": return ( {row.text} ); case "chips": return (
{row.items.map((c) => ( {c} ))}
); case "badge": return ( {row.text} ); } } function CardNode({ data }: NodeProps>) { const center = data.align === "center"; return (
{data.rows.map((row, i) => (
))}
); } /* ------------------------------ Engine node ------------------------------ */ type EngineStep = { n: number; title: string; desc: string }; type EngineData = { width: number; height: number; steps: EngineStep[]; handles?: HandleSpec[]; }; function EngineNode({ data }: NodeProps>) { return (
ktx
{data.steps.map((s) => (
{s.n}
{s.title} {s.desc}
))}
); } /* -------------------------------- Hub node ------------------------------- */ type HubData = { width: number; height: number; rows: string[]; handles?: HandleSpec[]; }; function HubNode({ data }: NodeProps>) { return (
ktx
{data.rows.map((r) => (
{r}
))}
); } /* ------------------------------- Title node ------------------------------ */ type TitleData = { width: number; eyebrow: string; title: string }; function TitleNode({ data }: NodeProps>) { return (
{data.eyebrow} {data.title}
); } export const nodeTypes = { card: CardNode, engine: EngineNode, hub: HubNode, title: TitleNode, };