diff --git a/pitch-deck/components/slides/ArchitectureSlide.tsx b/pitch-deck/components/slides/ArchitectureSlide.tsx
index a9c182d..6c972ec 100644
--- a/pitch-deck/components/slides/ArchitectureSlide.tsx
+++ b/pitch-deck/components/slides/ArchitectureSlide.tsx
@@ -12,9 +12,7 @@ import {
Server, Network, ChevronRight, BadgeCheck,
} from 'lucide-react'
-interface ArchitectureSlideProps {
- lang: Language
-}
+interface ArchitectureSlideProps { lang: Language }
type NodeId = 'certifai' | 'complai' | 'scanner' | 'litellm' | 'llm' | 'embeddings' | 'tools'
type ConnType = 'api' | 'mcp' | 'embed' | 'tool'
@@ -29,8 +27,8 @@ interface NodeDef {
twBorder: string
twBg: string
twDot: string
- cx: number
- cy: number
+ cx: number // % of container width (aligns with SVG x/1100)
+ cy: number // % of container height (aligns with SVG y/420)
tier: 'product' | 'proxy' | 'inference'
tech: string[]
services: { name: string; desc: string }[]
@@ -41,30 +39,39 @@ interface ConnDef {
from: NodeId
to: NodeId
type: ConnType
- d: string
- revD?: string // reverse path for bidirectional MCP
+ d: string // SVG path in viewBox "0 0 1100 420" — right-angle only
+ revD?: string
}
+// All right-angle paths — safe with preserveAspectRatio="none" + vectorEffect="non-scaling-stroke"
const CONNS: ConnDef[] = [
- { from: 'certifai', to: 'litellm', type: 'api', d: 'M 18 22 Q 28 43 50 52' },
- { from: 'complai', to: 'litellm', type: 'api', d: 'M 50 22 L 50 52' },
- { from: 'scanner', to: 'litellm', type: 'api', d: 'M 82 22 Q 72 43 50 52' },
- { from: 'complai', to: 'scanner', type: 'mcp', d: 'M 50 22 Q 66 9 82 22', revD: 'M 82 22 Q 66 9 50 22' },
- { from: 'litellm', to: 'llm', type: 'api', d: 'M 50 52 Q 37 67 18 82' },
- { from: 'litellm', to: 'embeddings', type: 'embed', d: 'M 50 52 L 50 82' },
- { from: 'litellm', to: 'tools', type: 'tool', d: 'M 50 52 Q 63 67 82 82' },
+ { from: 'certifai', to: 'litellm', type: 'api',
+ d: 'M 195 105 L 195 158 L 522 158 L 522 210' },
+ { from: 'complai', to: 'litellm', type: 'api',
+ d: 'M 550 105 L 550 210' },
+ { from: 'scanner', to: 'litellm', type: 'api',
+ d: 'M 905 105 L 905 158 L 578 158 L 578 210' },
+ { from: 'complai', to: 'scanner', type: 'mcp',
+ d: 'M 550 105 Q 727 52 905 105',
+ revD: 'M 905 105 Q 727 52 550 105' },
+ { from: 'litellm', to: 'llm', type: 'api',
+ d: 'M 550 210 L 550 268 L 218 268 L 218 340' },
+ { from: 'litellm', to: 'embeddings', type: 'embed',
+ d: 'M 550 210 L 550 340' },
+ { from: 'litellm', to: 'tools', type: 'tool',
+ d: 'M 550 210 L 550 268 L 882 268 L 882 340' },
]
+// cx = svgX/1100*100, cy = svgY/420*100 (exact match between SVG coords and CSS %)
function getNodes(de: boolean): NodeDef[] {
return [
{
- id: 'certifai',
- icon: Brain,
+ id: 'certifai', icon: Brain,
title: 'CERTifAI',
subtitle: de ? 'GenAI Mandantenportal' : 'GenAI Tenant Portal',
color: '#c084fc', twColor: 'text-purple-400',
twBorder: 'border-purple-500/50', twBg: 'bg-purple-500/10', twDot: 'bg-purple-400',
- cx: 18, cy: 22, tier: 'product',
+ cx: 17.7, cy: 25, tier: 'product',
badge: 'Rust · Dioxus',
tech: ['Rust', 'Dioxus', 'MongoDB', 'Keycloak', 'SearXNG', 'LangGraph'],
services: [
@@ -75,13 +82,12 @@ function getNodes(de: boolean): NodeDef[] {
],
},
{
- id: 'complai',
- icon: Shield,
+ id: 'complai', icon: Shield,
title: 'COMPLAI',
subtitle: de ? 'Compliance & Audit' : 'Compliance & Audit',
color: '#818cf8', twColor: 'text-indigo-400',
twBorder: 'border-indigo-500/50', twBg: 'bg-indigo-500/10', twDot: 'bg-indigo-400',
- cx: 50, cy: 22, tier: 'product',
+ cx: 50, cy: 25, tier: 'product',
badge: 'Next.js · FastAPI',
tech: ['Next.js 15', 'FastAPI', 'Go/Gin', 'PostgreSQL', 'Qdrant', 'Valkey'],
services: [
@@ -92,13 +98,12 @@ function getNodes(de: boolean): NodeDef[] {
],
},
{
- id: 'scanner',
- icon: ScanLine,
+ id: 'scanner', icon: ScanLine,
title: 'Compliance Scanner',
subtitle: de ? 'Code-Sicherheit' : 'Code Security',
color: '#34d399', twColor: 'text-emerald-400',
twBorder: 'border-emerald-500/50', twBg: 'bg-emerald-500/10', twDot: 'bg-emerald-400',
- cx: 82, cy: 22, tier: 'product',
+ cx: 82.3, cy: 25, tier: 'product',
badge: 'Rust · Axum',
tech: ['Rust', 'Axum', 'MongoDB', 'Semgrep', 'Gitleaks', 'Syft'],
services: [
@@ -109,13 +114,12 @@ function getNodes(de: boolean): NodeDef[] {
],
},
{
- id: 'litellm',
- icon: Zap,
+ id: 'litellm', icon: Zap,
title: 'LiteLLM Proxy',
subtitle: de ? 'KI-Gateway & Guardrails' : 'AI Gateway & Guardrails',
color: '#fbbf24', twColor: 'text-amber-400',
twBorder: 'border-amber-500/60', twBg: 'bg-amber-500/10', twDot: 'bg-amber-400',
- cx: 50, cy: 52, tier: 'proxy',
+ cx: 50, cy: 50, tier: 'proxy',
badge: 'Hub',
tech: ['OpenAI-kompatible API', 'Bearer Auth', 'Rate Limiting', 'PII-Filter', 'Spend Tracking'],
services: [
@@ -127,14 +131,13 @@ function getNodes(de: boolean): NodeDef[] {
],
},
{
- id: 'llm',
- icon: Cpu,
+ id: 'llm', icon: Cpu,
title: de ? 'LLM Inferenz' : 'LLM Inference',
subtitle: de ? 'Lokale Sprachmodelle' : 'Local Language Models',
color: '#60a5fa', twColor: 'text-blue-400',
twBorder: 'border-blue-500/50', twBg: 'bg-blue-500/10', twDot: 'bg-blue-400',
- cx: 18, cy: 82, tier: 'inference',
- badge: de ? 'On-Premise · BSI' : 'On-Premise · BSI',
+ cx: 17.7, cy: 81, tier: 'inference',
+ badge: 'On-Premise · BSI',
tech: ['Qwen3-32B', 'Qwen3-Coder-30B', 'DeepSeek-R1-8B', 'Ollama'],
services: [
{ name: de ? 'Vollständig lokal' : 'Fully local', desc: de ? 'Daten verlassen nie den Server' : 'Data never leaves the server' },
@@ -143,13 +146,12 @@ function getNodes(de: boolean): NodeDef[] {
],
},
{
- id: 'embeddings',
- icon: Layers,
+ id: 'embeddings', icon: Layers,
title: 'Embeddings',
subtitle: de ? 'Semantische Suche' : 'Semantic Search',
color: '#a78bfa', twColor: 'text-violet-400',
twBorder: 'border-violet-500/50', twBg: 'bg-violet-500/10', twDot: 'bg-violet-400',
- cx: 50, cy: 82, tier: 'inference',
+ cx: 50, cy: 81, tier: 'inference',
badge: de ? 'EU-Souverän' : 'EU Sovereign',
tech: ['bge-m3', 'Qdrant Vector DB', 'Sentence-Transformers'],
services: [
@@ -159,13 +161,12 @@ function getNodes(de: boolean): NodeDef[] {
],
},
{
- id: 'tools',
- icon: Wrench,
+ id: 'tools', icon: Wrench,
title: de ? 'KI-Tools' : 'AI Tools',
subtitle: de ? 'Web-Suche & MCP' : 'Web Search & MCP',
color: '#2dd4bf', twColor: 'text-teal-400',
twBorder: 'border-teal-500/50', twBg: 'bg-teal-500/10', twDot: 'bg-teal-400',
- cx: 82, cy: 82, tier: 'inference',
+ cx: 82.3, cy: 81, tier: 'inference',
badge: de ? 'EU-Souverän' : 'EU Sovereign',
tech: ['SearXNG', 'MCP Protocol', 'Semgrep API', 'Gitleaks API'],
services: [
@@ -177,7 +178,7 @@ function getNodes(de: boolean): NodeDef[] {
]
}
-// ── Token counter (inference tier ambient animation) ─────────────────────────
+// ── Token counter ─────────────────────────────────────────────────────────────
function TokenTicker({ color }: { color: string }) {
const [n, setN] = useState(() => 12480 + Math.floor(Math.random() * 8000))
useEffect(() => {
@@ -186,12 +187,8 @@ function TokenTicker({ color }: { color: string }) {
}, [])
return (
-
+
{n.toLocaleString('de-DE')} tok/s
@@ -199,158 +196,173 @@ function TokenTicker({ color }: { color: string }) {
)
}
-// ── Data-flow path (always-on packets, fast when active) ─────────────────────
-function DataFlow({
- d, revD, color, active, type,
-}: {
+// ── Data-flow packets along metro tracks ──────────────────────────────────────
+function DataFlow({ d, revD, color, active, type }: {
d: string; revD?: string; color: string; active: boolean; type: ConnType
}) {
- const isMcp = type === 'mcp'
- const speed = active ? 1.2 : 5
- const op = active ? 0.85 : 0.2
- const w = active ? 2.5 : 1.2
- const dash = active ? 3 : 2
+ const isMcp = type === 'mcp'
+ const dashLen = active ? 18 : 8
+ const gapLen = active ? 110 : 280
+ const speed = active ? 1.9 : 5.5
+ const cycle = dashLen + gapLen
+ const op = active ? 0.9 : 0.16
+ const pw = active ? 3.5 : 1.5
return (
- {/* Base line */}
-
-
- {/* Forward packets */}
- {[0, -28, -56].map((off, i) => (
-
+ {/* Staggered trailing packet when active */}
+ {active && (
+
- ))}
-
- {/* Reverse packets for MCP (bidirectional) */}
- {isMcp && revD && [0, -42].map((off, i) => (
-
- ))}
+ )}
)
}
-// ── Node card ──────────────────────────────────────────────────────────────────
-// Position wrapper (div) is never scaled — only the inner motion.button scales.
-// This prevents the "snap" that happened when translate(-50%,-50%) and scale
-// were on the same element.
-function NodeCard({
- node, active, onClick, de,
-}: {
+// ── Metro station card ────────────────────────────────────────────────────────
+// Outer div handles absolute positioning — never scaled.
+// Inner motion.button handles hover scale only.
+function MetroStation({ node, active, onClick, de }: {
node: NodeDef; active: boolean; onClick: () => void; de: boolean
}) {
- const Icon = node.icon
- const isHub = node.id === 'litellm'
+ const Icon = node.icon
+ const isHub = node.id === 'litellm'
+ const sz = isHub ? 82 : 52
return (
-
+
-
- {/* Left accent bar */}
-
-
- {/* Icon + title */}
-
-
-
-
-
- {node.title}
-
- {active && (
-
- )}
+ {/* Station circle */}
+
+ {/* Outer pulse ring on active */}
+ {active && (
+
+ )}
+ {/* Hub ambient glow */}
+ {isHub && (
+
+ )}
+ {/* Main circle */}
+
+
-
- {/* Subtitle */}
-
- {node.subtitle}
-
-
- {/* Badge */}
- {node.badge && (
-
-
- {node.badge}
-
-
- )}
-
- {/* Token ticker on inference nodes */}
- {active && node.tier === 'inference' && (
-
-
-
- )}
-
- {/* Hub: spinning indicator ring */}
+ {/* Hub spinning dashed ring */}
{isHub && active && (
-
)}
+ {/* Active indicator dot */}
+ {active && (
+
+ )}
+
+
+ {/* Station label */}
+
+
+ {node.title}
+
+
+ {node.subtitle}
+
+ {node.badge && (
+
+ {node.badge}
+
+ )}
+ {active && node.tier === 'inference' && (
+
+ )}
)
}
-// ── Main slide ─────────────────────────────────────────────────────────────────
+// ── Main slide ────────────────────────────────────────────────────────────────
export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
const i = t(lang)
const de = lang === 'de'
@@ -359,19 +371,16 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
const [activeId, setActiveId] = useState
(null)
const active = nodes.find(n => n.id === activeId) ?? null
- function toggle(id: NodeId) {
- setActiveId(prev => (prev === id ? null : id))
- }
+ function toggle(id: NodeId) { setActiveId(prev => prev === id ? null : id) }
const tenants = de
? ['Mandant A', 'Mandant B', 'Mandant C', 'Mandant N…']
: ['Namespace A', 'Namespace B', 'Namespace C', 'Namespace N…']
- const tiers = [
- { y: '22%', label: de ? 'Anwendungsschicht' : 'Application Layer', clr: '#818cf8', badge: null },
- { y: '52%', label: de ? 'GenAI-Infrastruktur' : 'GenAI Infrastructure', clr: '#fbbf24', badge: de ? 'BSI-Rechenzentrum' : 'BSI Data Center' },
- { y: '82%', label: de ? 'Inferenzschicht' : 'Inference Layer', clr: '#60a5fa', badge: de ? 'BSI · EU-Souverän' : 'BSI · EU Sovereign' },
- ]
+ // Tier separator positions (% of 420px container height)
+ // App: 0–38.1% (y=0–160), Gateway: 38.1–64.3% (y=160–270), Inference: 64.3–100%
+ const SEP1 = '38.1%'
+ const SEP2 = '64.3%'
return (
@@ -384,13 +393,13 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
{i.annex.architecture.title}
- {de ? 'Klicke auf eine Komponente für Details' : 'Click any component to explore'}
+ {de ? 'Klicke auf eine Station für Details' : 'Click any station to explore'}
{/* Customer namespace strip */}
-
+
{de ? 'Kundenmandanten' : 'Customer Namespaces'}
@@ -404,114 +413,136 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
- {/* ── MAP ──────────────────────────────────────────────────────────── */}
-
+ {/* ── METRO MAP ──────────────────────────────────────────────── */}
+
{/* Background */}
-
-
+ {/* Tier band fills */}
+
+
+
+
{/* Tier separator lines */}
- {['37%', '66%'].map(y => (
-
(
+
))}
- {/* Tier labels (left) + BSI badges (right) */}
- {tiers.map(({ y, label, clr, badge }) => (
-
-
-
-
- {label}
+ {/* Vertical tier labels — left strip */}
+
+
+
+ {de ? 'ANWENDUNG' : 'APP LAYER'}
+
+
+
+
+ GATEWAY
+
+
+
+
+ {de ? 'INFERENZ' : 'INFERENCE'}
+
+
+
+
+ {/* BSI badges — right strip */}
+
+
+
+
+
+
+
+ {de ? 'EU-Souverän' : 'EU Sovereign'}
- {badge && (
-
-
-
- {badge}
-
-
- )}
- ))}
-
- {/* MCP badge between COMPLAI and Scanner */}
-
- MCP ↔
- {/* Infra tech chips (secondary, very subtle, in the infra tier band) */}
-
- {['PostgreSQL', 'MongoDB', 'Qdrant', 'Valkey', 'Nginx', 'Vault', 'Gitea', 'Orca', 'Woodpecker'].map(chip => (
-
- {chip}
-
- ))}
+ {/* MCP label */}
+
+
+ ⇌ MCP
+
- {/* SVG: island territories + data-flow paths */}
+ {/* ── SVG: metro tracks + animated data flows ── */}
+ viewBox="0 0 1100 420" preserveAspectRatio="none">
- {/* Tier zone fills */}
-
-
-
+ {/* Horizontal metro line — Application layer (y=105) */}
+
+ {/* Station tick marks on app line */}
+ {[195, 550, 905].map(x => (
+
+ ))}
- {/* Island blobs per node */}
- {nodes.map(n => {
- const isHub = n.id === 'litellm'
- const isActive = activeId === n.id
- return (
-
-
-
- )
- })}
+ {/* Horizontal metro line — Inference layer (y=340) */}
+
+ {/* Station tick marks on inference line */}
+ {[195, 550, 905].map(x => (
+
+ ))}
+
+ {/* Gateway stub lines (short horizontal stubs from hub) */}
+
+
+ {/* Junction corner dots */}
+ {[
+ { cx: 195, cy: 158 }, { cx: 522, cy: 158 },
+ { cx: 905, cy: 158 }, { cx: 578, cy: 158 },
+ { cx: 218, cy: 268 }, { cx: 550, cy: 268 }, { cx: 882, cy: 268 },
+ ].map(({ cx, cy }) => (
+
+ ))}
{/* Connection flows */}
{CONNS.map(c => {
- const fromNode = nodes.find(n => n.id === c.from)!
- const toNode = nodes.find(n => n.id === c.to)!
- const isActive = activeId === c.from || activeId === c.to
- const color = isActive
+ const fromNode = nodes.find(n => n.id === c.from)!
+ const toNode = nodes.find(n => n.id === c.to)!
+ const isActive = activeId === c.from || activeId === c.to
+ const color = isActive
? (activeId === c.from ? fromNode.color : toNode.color)
: '#ffffff'
return (
-
@@ -519,11 +550,9 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
})}
- {/* Node cards */}
+ {/* Metro station nodes */}
{nodes.map(node => (
-
toggle(node.id)}
de={de}
@@ -531,14 +560,14 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
))}
{!activeId && (
-
+
- {de ? 'Komponente anklicken' : 'Click any node'}
+ {de ? 'Station anklicken' : 'Click any station'}
)}
- {/* ── DETAIL PANEL ──────────────────────────────────────────────────── */}
+ {/* ── DETAIL PANEL ─────────────────────────────────────────────── */}
{active && (
- setActiveId(null)}
- className="absolute top-3 right-3 w-6 h-6 rounded-full bg-white/10 hover:bg-white/20 flex items-center justify-center transition-colors"
- >
+ setActiveId(null)}
+ className="absolute top-3 right-3 w-6 h-6 rounded-full bg-white/10 hover:bg-white/20 flex items-center justify-center transition-colors">
- {/* Header */}
@@ -569,7 +595,7 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
{active.badge}
)}
-
+
{active.tier === 'product' ? (de ? 'Anwendungsschicht' : 'Application Layer') :
active.tier === 'proxy' ? (de ? 'GenAI-Infrastruktur' : 'GenAI Infra') :
(de ? 'Inferenzschicht' : 'Inference Layer')}
@@ -579,7 +605,6 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
- {/* Body */}
@@ -611,7 +636,6 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
- {/* Connects-to */}
@@ -624,11 +648,8 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
const peer = nodes.find(n => n.id === peerId)!
const label = c.type === 'mcp' ? 'MCP' : c.type === 'embed' ? 'Embed' : c.type === 'tool' ? 'Tool' : 'API'
return (
- setActiveId(peerId)}
- className={`text-[9px] px-2 py-0.5 rounded-full border ${peer.twBorder} ${peer.twColor} transition-opacity hover:opacity-80 flex items-center gap-1`}
- >
+ setActiveId(peerId)}
+ className={`text-[9px] px-2 py-0.5 rounded-full border ${peer.twBorder} ${peer.twColor} transition-opacity hover:opacity-80 flex items-center gap-1`}>
{peer.title}
{label}
@@ -639,18 +660,17 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
)}
- {/* Bottom legend */}
+ {/* Legend */}
{!active && (
{[
- { icon: Lock, text: de ? 'Kein US-Anbieter · 100% DSGVO' : 'No US providers · 100% GDPR' },
- { icon: Server, text: de ? 'BSI-zertifiziertes Rechenzentrum' : 'BSI-certified data center' },
+ { icon: Lock, text: de ? 'Kein US-Anbieter · 100% DSGVO' : 'No US providers · 100% GDPR' },
+ { icon: Server, text: de ? 'BSI-zertifiziertes Rechenzentrum' : 'BSI-certified data center' },
{ icon: BadgeCheck, text: de ? 'EU-souveräne Inferenz' : 'EU-sovereign inference' },
].map(({ icon: Icon, text }) => (
-
- {text}
+ {text}
))}