"use client"; import { useEffect, useRef, useState } from "react"; type Mode = "cloud" | "self"; type Palette = { ink: string; ink3: string; rule: string; panel: string; accent: string; up: string; pend: string; mono: string; sans: string; }; type Node = { label: string; sub: string; x: number; y: number; w: number; h: number; state: "up" | "pend"; flash: number; }; type Wire = { node: number; ax: number; ay: number; bx: number; by: number; c1x: number; c1y: number; c2x: number; c2y: number; }; type Packet = { wire: number; t: number; speed: number; /** true when travelling control plane to agent, which only happens down a wire the agent opened. */ inbound: boolean; }; const AGENTS: { label: string; sub: string; state: "up" | "pend" }[] = [ { label: "proxmox-node-1", sub: "4 keys", state: "up" }, { label: "db-primary", sub: "3 keys", state: "up" }, { label: "edge-gw-02", sub: "2 keys", state: "up" }, { label: "win-build-01", sub: "windows", state: "pend" }, ]; const MODES: { id: Mode; label: string }[] = [ { id: "cloud", label: "Hosted by us" }, { id: "self", label: "Hosted by you" }, ]; const NOTES: Record = { cloud: "We run Vantage. Your servers reach out to it, so nothing needs to be open to the internet on your side.", self: "You run Vantage too, on your own hardware. Nothing leaves your network at all, and there is nothing to phone home to.", }; function readPalette(el: HTMLElement): Palette { const s = getComputedStyle(el); const v = (name: string, fallback: string) => s.getPropertyValue(name).trim() || fallback; return { ink: v("--ink", "#0a1b33"), ink3: v("--ink-3", "#6c7f96"), rule: v("--rule", "#cdd6e2"), panel: v("--panel", "#ffffff"), accent: v("--accent", "#0b2a58"), up: v("--up", "#2f8a60"), pend: v("--pend", "#b0801f"), mono: `11px ${v("--mono", "monospace")}`, sans: `600 15px ${v("--sans", "sans-serif")}`, }; } function pointAt(w: Wire, t: number) { const u = 1 - t; const a = u * u * u; const b = 3 * u * u * t; const c = 3 * u * t * t; const d = t * t * t; return { x: a * w.ax + b * w.c1x + c * w.c2x + d * w.bx, y: a * w.ay + b * w.c1y + c * w.c2y + d * w.by, }; } function roundRect(ctx: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, r: number) { ctx.beginPath(); ctx.moveTo(x + r, y); ctx.arcTo(x + w, y, x + w, y + h, r); ctx.arcTo(x + w, y + h, x, y + h, r); ctx.arcTo(x, y + h, x, y, r); ctx.arcTo(x, y, x + w, y, r); ctx.closePath(); } export function TopologyDiagram() { const [mode, setMode] = useState("cloud"); const canvasRef = useRef(null); const modeRef = useRef(mode); modeRef.current = mode; useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext("2d"); if (!ctx) return; let palette = readPalette(document.documentElement); let width = 0; let height = 0; let nodes: Node[] = []; let wires: Wire[] = []; let plane = { x: 0, y: 0, w: 0, h: 0 }; let boundary: { x: number; y: number; w: number; h: number; label: string; split: number | null } | null = null; let stacked = false; const packets: Packet[] = []; let planePulse = 0; let sinceSpawn = 0; let sinceCommand = 2.2; let raf = 0; let last = 0; const reduced = window.matchMedia("(prefers-reduced-motion: reduce)"); const layout = () => { const rect = canvas.getBoundingClientRect(); width = rect.width; height = rect.height; stacked = width < 660; const dpr = Math.min(window.devicePixelRatio || 1, 2); canvas.width = Math.round(width * dpr); canvas.height = Math.round(height * dpr); ctx.setTransform(dpr, 0, 0, dpr, 0, 0); const pad = stacked ? 14 : 22; const nodeW = stacked ? Math.min(150, (width - pad * 2 - 16) / 2) : 168; const nodeH = 46; if (stacked) { plane = { x: width / 2 - 100, y: pad + 30, w: 200, h: 78 }; const cols = 2; const gapX = 14; const gapY = 14; const gridW = cols * nodeW + gapX; const startX = width / 2 - gridW / 2; const startY = plane.y + plane.h + (mode === "cloud" ? 84 : 66); nodes = AGENTS.map((a, i) => ({ label: a.label, sub: a.sub, state: a.state, flash: 0, w: nodeW, h: nodeH, x: startX + (i % cols) * (nodeW + gapX), y: startY + Math.floor(i / cols) * (nodeH + gapY), })); } else { const colX = pad + 8; plane = { x: width - pad - 214, y: height / 2 - 46, w: 206, h: 92 }; const total = AGENTS.length * nodeH + (AGENTS.length - 1) * 16; const startY = height / 2 - total / 2; nodes = AGENTS.map((a, i) => ({ label: a.label, sub: a.sub, state: a.state, flash: 0, w: nodeW, h: nodeH, x: colX, y: startY + i * (nodeH + 16), })); } wires = nodes.map((n, i) => { if (stacked) { const ax = n.x + n.w / 2; const ay = n.y; const bx = plane.x + plane.w * (0.2 + 0.2 * i); const by = plane.y + plane.h; const mid = (ay + by) / 2; return { node: i, ax, ay, bx, by, c1x: ax, c1y: mid, c2x: bx, c2y: mid }; } const ax = n.x + n.w; const ay = n.y + n.h / 2; const bx = plane.x; const by = plane.y + plane.h * (0.24 + 0.17 * i); const mid = ax + (bx - ax) * 0.5; return { node: i, ax, ay, bx, by, c1x: mid, c1y: ay, c2x: mid, c2y: by }; }); if (modeRef.current === "self") { boundary = { x: pad - 8, y: pad - 8, w: width - (pad - 8) * 2, h: height - (pad - 8) * 2, label: "YOUR NETWORK", split: null }; } else if (stacked) { const y = plane.y + plane.h + 34; boundary = { x: pad - 8, y, w: width - (pad - 8) * 2, h: height - y - (pad - 8), label: "YOUR NETWORK", split: y }; } else { const split = plane.x - 46; boundary = { x: pad - 8, y: pad - 8, w: split - (pad - 8), h: height - (pad - 8) * 2, label: "YOUR NETWORK", split }; } }; const spawn = () => { if (packets.length > 60) return; const wire = Math.floor(Math.random() * wires.length); packets.push({ wire, t: 0, speed: 0.34 + Math.random() * 0.16, inbound: false }); }; const drawWire = (w: Wire) => { ctx.beginPath(); ctx.moveTo(w.ax, w.ay); ctx.bezierCurveTo(w.c1x, w.c1y, w.c2x, w.c2y, w.bx, w.by); ctx.stroke(); }; const draw = (dt: number) => { ctx.clearRect(0, 0, width, height); const mono = palette.mono; // Boundary of what you own. if (boundary) { ctx.save(); ctx.setLineDash([5, 5]); ctx.lineWidth = 1; ctx.strokeStyle = palette.rule; roundRect(ctx, boundary.x, boundary.y, boundary.w, boundary.h, 10); ctx.stroke(); ctx.restore(); ctx.font = mono; ctx.fillStyle = palette.ink3; ctx.textBaseline = "middle"; ctx.textAlign = "left"; ctx.fillText(boundary.label, boundary.x + 12, boundary.y + 13); if (modeRef.current === "cloud") { ctx.textAlign = stacked ? "left" : "right"; const x = stacked ? boundary.x + 12 : width - 22; const y = stacked ? 18 : boundary.y + 13; ctx.fillText("HOSTED BY US", x, y); } } // Wires. Drawn under everything, because they are infrastructure. ctx.lineWidth = 1.25; ctx.strokeStyle = palette.rule; wires.forEach(drawWire); // Packets travelling the wires. for (let i = packets.length - 1; i >= 0; i--) { const p = packets[i]; p.t += p.speed * dt; if (p.t >= 1) { if (p.inbound) nodes[wires[p.wire].node].flash = 1; else planePulse = 1; packets.splice(i, 1); continue; } const w = wires[p.wire]; const pos = pointAt(w, p.inbound ? 1 - p.t : p.t); const fade = Math.min(1, Math.sin(p.t * Math.PI) * 2.2); ctx.globalAlpha = fade; ctx.fillStyle = p.inbound ? palette.accent : palette.up; ctx.beginPath(); ctx.arc(pos.x, pos.y, p.inbound ? 3.4 : 2.4, 0, Math.PI * 2); ctx.fill(); if (p.inbound) { ctx.globalAlpha = fade * 0.22; ctx.beginPath(); ctx.arc(pos.x, pos.y, 8, 0, Math.PI * 2); ctx.fill(); } ctx.globalAlpha = 1; } // Agents. for (const n of nodes) { if (n.flash > 0) { ctx.globalAlpha = n.flash * 0.5; ctx.strokeStyle = palette.accent; ctx.lineWidth = 2; roundRect(ctx, n.x - 2, n.y - 2, n.w + 4, n.h + 4, 8); ctx.stroke(); ctx.globalAlpha = 1; n.flash = Math.max(0, n.flash - dt * 1.6); } ctx.fillStyle = palette.panel; ctx.strokeStyle = palette.rule; ctx.lineWidth = 1; roundRect(ctx, n.x, n.y, n.w, n.h, 6); ctx.fill(); ctx.stroke(); ctx.fillStyle = n.state === "up" ? palette.up : palette.pend; ctx.beginPath(); ctx.arc(n.x + 14, n.y + n.h / 2, 3.5, 0, Math.PI * 2); ctx.fill(); ctx.textAlign = "left"; ctx.textBaseline = "middle"; ctx.font = mono; ctx.fillStyle = palette.ink; ctx.fillText(n.label, n.x + 26, n.y + n.h / 2 - 6); ctx.fillStyle = palette.ink3; ctx.fillText(n.sub, n.x + 26, n.y + n.h / 2 + 8); } // Control plane. if (planePulse > 0) { ctx.globalAlpha = planePulse * 0.35; ctx.strokeStyle = palette.accent; ctx.lineWidth = 2; roundRect(ctx, plane.x - 3 - planePulse * 4, plane.y - 3 - planePulse * 4, plane.w + 6 + planePulse * 8, plane.h + 6 + planePulse * 8, 10); ctx.stroke(); ctx.globalAlpha = 1; planePulse = Math.max(0, planePulse - dt * 1.5); } ctx.fillStyle = palette.accent; roundRect(ctx, plane.x, plane.y, plane.w, plane.h, 8); ctx.fill(); ctx.textAlign = "center"; ctx.textBaseline = "middle"; const cx = plane.x + plane.w / 2; ctx.fillStyle = palette.panel; ctx.font = palette.sans; ctx.fillText("Vantage", cx, plane.y + plane.h / 2 - 11); ctx.font = mono; ctx.globalAlpha = 0.78; ctx.fillText(modeRef.current === "cloud" ? "vantage.hostxtra.co.uk" : "your own hardware", cx, plane.y + plane.h / 2 + 8); ctx.globalAlpha = 1; // Direction of travel, stated once rather than on every wire. if (!stacked) { ctx.font = mono; ctx.fillStyle = palette.ink3; ctx.textAlign = "center"; const midX = (nodes[0].x + nodes[0].w + plane.x) / 2; ctx.fillText("your servers dial out", midX, height - 26); } }; const frame = (now: number) => { const dt = Math.min((now - last) / 1000, 0.05); last = now; sinceSpawn += dt; if (sinceSpawn > 0.42) { sinceSpawn = 0; spawn(); } sinceCommand += dt; if (sinceCommand > 4.4) { sinceCommand = 0; const wire = Math.floor(Math.random() * wires.length); packets.push({ wire, t: 0, speed: 0.62, inbound: true }); } draw(dt); raf = requestAnimationFrame(frame); }; const start = () => { cancelAnimationFrame(raf); layout(); if (reduced.matches) { packets.length = 0; wires.forEach((_, i) => packets.push({ wire: i, t: 0.35 + i * 0.12, speed: 0, inbound: false })); draw(0); return; } last = performance.now(); raf = requestAnimationFrame(frame); }; const onResize = () => start(); const observer = new ResizeObserver(onResize); observer.observe(canvas); const repaint = () => { palette = readPalette(document.documentElement); if (reduced.matches) draw(0); }; const themeObserver = new MutationObserver(repaint); themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme"] }); const scheme = window.matchMedia("(prefers-color-scheme: dark)"); const onScheme = repaint; scheme.addEventListener("change", onScheme); reduced.addEventListener("change", start); start(); return () => { cancelAnimationFrame(raf); observer.disconnect(); themeObserver.disconnect(); scheme.removeEventListener("change", onScheme); reduced.removeEventListener("change", start); }; }, [mode]); return (
{MODES.map((m) => ( ))}

your server checking in something you asked for

{NOTES[mode]}
); }