Files
breakpilot-core/pitch-deck/components/slides/ArchitectureSlide.tsx
Sharang Parnerkar c0c44adaaa
All checks were successful
Build pitch-deck / build-push-deploy (push) Successful in 1m12s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 28s
CI / test-bqas (push) Successful in 32s
pitch-deck: light mode support + MilestonesSlide redesign
- ArchitectureSlide: full light mode via useIsLight() hook, all inline styles adapt
- USPSlide: full light mode via useIsLight() hook, all inline styles adapt
- MilestonesSlide: new component — horizontal timeline with past/HEUTE/future,
  THEMES object (dark + light), clickable milestone nodes and stat cards with
  detail modal, bilingual (de/en), scaling via ResizeObserver
- PitchDeck: register new 'milestones' slide case

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 10:34:36 +02:00

735 lines
34 KiB
TypeScript

'use client'
import { useState, useEffect, useRef, Fragment } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { Language } from '@/lib/types'
import { t } from '@/lib/i18n'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import {
Brain, Shield, ScanLine, Zap, Cpu,
Layers, Wrench, X, Users, Lock,
Server, BadgeCheck,
} from 'lucide-react'
interface ArchitectureSlideProps { lang: Language }
type NodeId = 'certifai' | 'complai' | 'scanner' | 'litellm' | 'llm' | 'embeddings' | 'tools'
interface NodeDef {
id: NodeId
icon: React.ElementType
title: string
subtitle: string
color: string
tech: string[]
services: { name: string; desc: string }[]
primary?: boolean
tier: 'product' | 'proxy' | 'inference'
}
function getNodes(de: boolean): NodeDef[] {
return [
{
id: 'certifai', icon: Brain,
title: 'CERTifAI',
subtitle: de ? 'GenAI Mandantenportal' : 'GenAI Tenant Portal',
color: '#c084fc', tier: 'product',
tech: ['Rust', 'Dioxus', 'MongoDB', 'Keycloak', 'SearXNG', 'LangGraph'],
services: [
{ name: 'LiteLLM Dashboard', desc: de ? 'Modellverwaltung & Kostentracking' : 'Model mgmt & cost tracking' },
{ name: 'LibreChat + SSO', desc: de ? 'Mandanten-Chat mit Keycloak' : 'Tenant chat with Keycloak' },
{ name: 'LangGraph Agents', desc: de ? 'Agent-Orchestrierung' : 'Agent orchestration' },
{ name: 'MCP Hub', desc: de ? 'Tool-Integration für KI-Clients' : 'Tool integration for AI clients' },
],
},
{
id: 'complai', icon: Shield,
title: 'COMPLAI',
subtitle: de ? 'Compliance & Audit' : 'Compliance & Audit',
color: '#818cf8', tier: 'product',
tech: ['Next.js 15', 'FastAPI', 'Go/Gin', 'PostgreSQL', 'Qdrant', 'Valkey'],
services: [
{ name: de ? 'DSGVO / AI Act / NIS2' : 'GDPR / AI Act / NIS2', desc: de ? '70k+ auditierbare Controls' : '70k+ auditable controls' },
{ name: 'RAG Pipeline', desc: de ? '75+ Rechtsquellen, semantische Suche' : '75+ legal sources, semantic search' },
{ name: 'Control Pipeline', desc: de ? 'Gesetzestextanalyse via LLM' : 'Legal text analysis via LLM' },
{ name: 'MCP Client', desc: de ? 'Echtzeit-Findings vom Scanner' : 'Real-time findings from Scanner' },
],
},
{
id: 'scanner', icon: ScanLine,
title: 'Compliance Scanner',
subtitle: de ? 'Code-Sicherheit' : 'Code Security',
color: '#34d399', tier: 'product',
tech: ['Rust', 'Axum', 'MongoDB', 'Semgrep', 'Gitleaks', 'Syft'],
services: [
{ name: 'SAST / SBOM / CVE', desc: de ? 'Vollautomatische Pipeline' : 'Fully automated pipeline' },
{ name: de ? 'KI-Triage' : 'AI Triage', desc: de ? 'LLM filtert False Positives' : 'LLM filters false positives' },
{ name: de ? 'KI-Pentest' : 'AI Pentest', desc: de ? 'Autonome Angriffsketten' : 'Autonomous attack chains' },
{ name: 'MCP Server', desc: de ? 'Live-Findings für COMPLAI' : 'Live findings for COMPLAI' },
],
},
{
id: 'litellm', icon: Zap,
title: 'LiteLLM Proxy',
subtitle: de ? 'KI-Gateway & Guardrails' : 'AI Gateway & Guardrails',
color: '#fbbf24', tier: 'proxy', primary: true,
tech: ['OpenAI-kompatible API', 'Bearer Auth', 'Rate Limiting', 'PII-Filter', 'Spend Tracking'],
services: [
{ name: de ? 'Token-Budget' : 'Token Budget', desc: de ? 'Pro-Mandant Kontingente & Abrechnung' : 'Per-tenant quotas & billing' },
{ name: 'PII Guardrails', desc: de ? 'Datenschutz-Filter für alle Anfragen' : 'Privacy filter on all requests' },
{ name: de ? 'Web-Suche (anonym)' : 'Web Search (anon)', desc: de ? 'SearXNG-Proxy, kein US-Anbieter' : 'SearXNG proxy, no US providers' },
{ name: de ? 'Namespace-Isolierung' : 'Namespace Isolation', desc: de ? 'Mandantentrennung per API-Key' : 'Tenant isolation per API key' },
{ name: de ? 'Failover-Routing' : 'Failover Routing', desc: de ? 'Automatisches Fallback' : 'Automatic fallback between models' },
],
},
{
id: 'llm', icon: Cpu,
title: de ? 'LLM Inferenz' : 'LLM Inference',
subtitle: de ? 'Lokale Sprachmodelle' : 'Local Language Models',
color: '#60a5fa', tier: 'inference',
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' },
{ name: de ? 'Air-Gap fähig' : 'Air-Gap Capable', desc: de ? 'Kein Internet erforderlich' : 'No internet required' },
{ name: de ? 'GPU-optimiert' : 'GPU-optimized', desc: de ? 'Dedizierte Inferenz-Hardware' : 'Dedicated inference hardware' },
],
},
{
id: 'embeddings', icon: Layers,
title: 'Embeddings',
subtitle: de ? 'Semantische Suche' : 'Semantic Search',
color: '#a78bfa', tier: 'inference',
tech: ['bge-m3', 'Qdrant Vector DB', 'Sentence-Transformers'],
services: [
{ name: 'RAG Pipeline', desc: de ? '75+ Rechtsquellen indexiert' : '75+ legal sources indexed' },
{ name: de ? 'Semantische Suche' : 'Semantic Search', desc: de ? 'Multi-linguale Einbettungen' : 'Multi-lingual embeddings' },
{ name: de ? 'Lokal' : 'Fully local', desc: de ? 'Keine externen APIs' : 'No external APIs' },
],
},
{
id: 'tools', icon: Wrench,
title: de ? 'KI-Tools' : 'AI Tools',
subtitle: de ? 'Web-Suche & MCP' : 'Web Search & MCP',
color: '#2dd4bf', tier: 'inference',
tech: ['SearXNG', 'MCP Protocol', 'Semgrep API', 'Gitleaks API'],
services: [
{ name: 'SearXNG', desc: de ? 'Anonymisierte EU-Websuche' : 'Anonymized EU web search' },
{ name: 'MCP Tools', desc: de ? 'Auditdokumente & Code-Findings' : 'Audit docs & code findings' },
{ name: de ? 'Kein US-Anbieter' : 'No US providers', desc: de ? '100% DSGVO-konform' : '100% GDPR-compliant' },
],
},
]
}
const LAYERS: { id: string; nodeIds: NodeId[]; tint: string; depth: number }[] = [
{ id: 'product', nodeIds: ['certifai', 'complai', 'scanner'], tint: '#a78bfa', depth: 24 },
{ id: 'proxy', nodeIds: ['litellm'], tint: '#fbbf24', depth: 12 },
{ id: 'inference', nodeIds: ['llm', 'embeddings', 'tools'], tint: '#8b5cf6', depth: 0 },
]
const CSS_KF = `
@keyframes v4FlowDown { from { stroke-dashoffset: 0 } to { stroke-dashoffset: -18px } }
@keyframes v4Pulse { 0%,100% { opacity:1;transform:scale(1) } 50% { opacity:.4;transform:scale(1.4) } }
@keyframes v4Caret { 0%,50% { opacity:1 } 51%,100% { opacity:0 } }
@keyframes v4DotFall {
0% { transform: translateY(-5px); opacity: 0; }
12% { opacity: 1; }
88% { opacity: 1; }
100% { transform: translateY(38px); opacity: 0; }
}
`
const MONO: React.CSSProperties = {
fontFamily: '"JetBrains Mono","SF Mono",ui-monospace,monospace',
fontVariantNumeric: 'tabular-nums',
}
// ── Theme detection ───────────────────────────────────────────────────────────
function useIsLight() {
const [isLight, setIsLight] = useState(false)
useEffect(() => {
const check = () => setIsLight(document.documentElement.classList.contains('theme-light'))
check()
const obs = new MutationObserver(check)
obs.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] })
return () => obs.disconnect()
}, [])
return isLight
}
// ── Ticker primitives ─────────────────────────────────────────────────────────
function useTicker(fn: () => void, min = 140, max = 360, skipChance = 0.1) {
const ref = useRef(fn)
ref.current = fn
useEffect(() => {
let tid: ReturnType<typeof setTimeout>
const loop = () => {
if (Math.random() > skipChance) ref.current()
tid = setTimeout(loop, min + Math.random() * (max - min))
}
loop()
return () => clearTimeout(tid)
}, [min, max, skipChance])
}
function TickerShell({ color, children, isLight }: { color: string; children: React.ReactNode; isLight: boolean }) {
return (
<div style={{
...MONO,
marginTop: 7, padding: '5px 9px',
background: isLight ? '#f1f5f9' : 'rgba(0,0,0,.38)',
border: `1px solid ${color}${isLight ? '55' : '55'}`, borderRadius: 6,
fontSize: 10, color: isLight ? '#475569' : 'rgba(236,233,247,.88)',
display: 'flex', alignItems: 'center', gap: 6,
whiteSpace: 'nowrap', overflow: 'hidden', height: 22,
}}>{children}</div>
)
}
function Caret({ color }: { color: string }) {
return (
<span style={{
display: 'inline-block', width: 5, height: 9, marginLeft: -2,
background: color, animation: 'v4Caret 1s step-end infinite',
}} />
)
}
// ── Per-node tickers ──────────────────────────────────────────────────────────
function TickCertifAI({ color, isLight }: { color: string; isLight: boolean }) {
const [n, setN] = useState(8421)
const [hash, setHash] = useState('9f3a…e10b')
const pool = 'abcdef0123456789'
const r = (k: number) => Array.from({ length: k }, () => pool[Math.floor(Math.random() * pool.length)]).join('')
useTicker(() => { setN(v => v + 1); setHash(`${r(4)}${r(4)}`) }, 1000, 2000, 0.1)
return (
<TickerShell color={color} isLight={isLight}>
<span style={{ color: isLight ? '#16a34a' : '#4ade80' }}></span>
<span style={{ color, opacity: .85 }}>sig</span>
<span style={{ color: isLight ? '#1a1a2e' : '#f5f3fc', fontWeight: 600 }}>{n.toLocaleString()}</span>
<span style={{ color: isLight ? '#94a3b8' : 'rgba(236,233,247,.55)' }}>{hash}</span>
</TickerShell>
)
}
function TickComplAI({ color, isLight }: { color: string; isLight: boolean }) {
const [evals, setEvals] = useState(1284)
const [rate, setRate] = useState(99.2)
useTicker(() => {
setEvals(v => v + 1 + Math.floor(Math.random() * 3))
setRate(r => Math.max(97, Math.min(99.9, r + (Math.random() - 0.5) * 0.4)))
}, 200, 500, 0.1)
return (
<TickerShell color={color} isLight={isLight}>
<span style={{ color: isLight ? '#16a34a' : '#4ade80' }}></span>
<span style={{ color, opacity: .85 }}>eval</span>
<span style={{ color: isLight ? '#1a1a2e' : '#f5f3fc', fontWeight: 600 }}>{evals.toLocaleString()}</span>
<span style={{ color: isLight ? '#94a3b8' : 'rgba(236,233,247,.45)' }}>pass</span>
<span style={{ color: isLight ? '#16a34a' : '#4ade80' }}>{rate.toFixed(1)}%</span>
</TickerShell>
)
}
function TickScanner({ color, isLight }: { color: string; isLight: boolean }) {
const lines = [
{ k: 'PASS', c: '#16a34a', cd: '#4ade80', t: 'CWE-79 xss check' },
{ k: 'WARN', c: '#d97706', cd: '#fbbf24', t: 'drift: model v2.1→2.2' },
{ k: 'PASS', c: '#16a34a', cd: '#4ade80', t: 'bias: demographic parity' },
{ k: 'FAIL', c: '#dc2626', cd: '#f87171', t: 'license: GPL-3 detected' },
{ k: 'PASS', c: '#16a34a', cd: '#4ade80', t: 'prompt-inject: 214 vectors' },
{ k: 'SCAN', c: '#7c3aed', cd: '#a78bfa', t: 'artifact model-card.json' },
]
const [i, setI] = useState(0)
useTicker(() => setI(x => (x + 1) % lines.length), 700, 1200, 0.05)
const l = lines[i]
return (
<TickerShell color={color} isLight={isLight}>
<span style={{ color: isLight ? l.c : l.cd, fontWeight: 600, minWidth: 30 }}>{l.k}</span>
<span style={{ color: isLight ? '#334155' : 'rgba(236,233,247,.85)', overflow: 'hidden', textOverflow: 'ellipsis', flex: 1 }}>{l.t}</span>
</TickerShell>
)
}
function TickLiteLLM({ color, isLight }: { color: string; isLight: boolean }) {
const [rps, setRps] = useState(428)
const [p50, setP50] = useState(84)
useTicker(() => {
setRps(v => Math.max(200, Math.min(800, v + (Math.random() - 0.5) * 60)))
setP50(v => Math.max(40, Math.min(160, v + (Math.random() - 0.5) * 20)))
}, 250, 500, 0.05)
return (
<TickerShell color={color} isLight={isLight}>
<span style={{ color: '#d97706' }}></span>
<span style={{ color, opacity: .9 }}>req/s</span>
<span style={{ color: isLight ? '#1a1a2e' : '#f5f3fc', fontWeight: 600 }}>{Math.round(rps)}</span>
<span style={{ color: isLight ? '#94a3b8' : 'rgba(236,233,247,.4)' }}>·</span>
<span style={{ color, opacity: .9 }}>p50</span>
<span style={{ color: isLight ? '#1a1a2e' : '#f5f3fc', fontWeight: 600 }}>{Math.round(p50)}ms</span>
<Caret color={color} />
</TickerShell>
)
}
function TickLLM({ color, isLight }: { color: string; isLight: boolean }) {
const [tokens, setTokens] = useState(14832)
const [stream, setStream] = useState('t_a91f')
const pool = 'abcdef0123456789'
useTicker(() => {
setTokens(v => v + 1 + Math.floor(Math.random() * 5))
setStream('t_' + Array.from({ length: 4 }, () => pool[Math.floor(Math.random() * pool.length)]).join(''))
}, 120, 340, 0.15)
return (
<TickerShell color={color} isLight={isLight}>
<span style={{ color: isLight ? '#16a34a' : '#4ade80' }}></span>
<span style={{ color, opacity: .85 }}>tok</span>
<span style={{ color: isLight ? '#1a1a2e' : '#f5f3fc', fontWeight: 600 }}>{tokens.toLocaleString()}</span>
<span style={{ color: isLight ? '#94a3b8' : 'rgba(236,233,247,.35)' }}></span>
<span style={{ color }}>{stream}</span>
<Caret color={color} />
</TickerShell>
)
}
function TickEmbeddings({ color, isLight }: { color: string; isLight: boolean }) {
const [vecs, setVecs] = useState(284112)
useTicker(() => setVecs(v => v + 1 + Math.floor(Math.random() * 8)), 180, 420, 0.1)
return (
<TickerShell color={color} isLight={isLight}>
<span style={{ color: isLight ? '#16a34a' : '#4ade80' }}></span>
<span style={{ color, opacity: .85 }}>idx</span>
<span style={{ color: isLight ? '#1a1a2e' : '#f5f3fc', fontWeight: 600 }}>{vecs.toLocaleString()}</span>
<span style={{ color: isLight ? '#94a3b8' : 'rgba(236,233,247,.4)' }}>· 1024d</span>
<Caret color={color} />
</TickerShell>
)
}
function TickTools({ color, isLight }: { color: string; isLight: boolean }) {
const ops = [
'search("BSI C5 controls")', 'fetch eur-lex.europa.eu',
'grep -r "DSGVO"', 'read docs/policy.md',
'mcp.call(filesystem)', 'search("vLLM 0.6 release")',
]
const [i, setI] = useState(0)
useTicker(() => setI(x => (x + 1) % ops.length), 900, 1600, 0.05)
return (
<TickerShell color={color} isLight={isLight}>
<span style={{ color: isLight ? '#16a34a' : '#4ade80' }}></span>
<span style={{ color, opacity: .85 }}>call</span>
<span style={{ color: isLight ? '#334155' : '#f5f3fc', overflow: 'hidden', textOverflow: 'ellipsis', flex: 1 }}>{ops[i]}</span>
</TickerShell>
)
}
const NODE_TICKER: Record<NodeId, React.ComponentType<{ color: string; isLight: boolean }>> = {
certifai: TickCertifAI,
complai: TickComplAI,
scanner: TickScanner,
litellm: TickLiteLLM,
llm: TickLLM,
embeddings: TickEmbeddings,
tools: TickTools,
}
// ── Animated connector ────────────────────────────────────────────────────────
function LayerConnector({ tint }: { tint: string }) {
const tracks = [
{ x: '32%', primary: false },
{ x: '50%', primary: true },
{ x: '68%', primary: false },
]
return (
<div style={{ position: 'relative', height: 34, width: '100%', maxWidth: 960, margin: '0 auto' }}>
{tracks.map(({ x, primary }, ti) => {
const color = primary ? '#fbbf24' : tint
const dots = primary ? 4 : 3
const dur = primary ? 1.6 : 2.4
return (
<div key={ti} style={{ position: 'absolute', left: x, top: 0, bottom: 0, transform: 'translateX(-50%)' }}>
<div style={{
position: 'absolute', left: -0.75, top: 0, bottom: 0, width: 1.5,
background: `linear-gradient(180deg, ${color}00, ${color}55 40%, ${color}55 60%, ${color}00)`,
}} />
{Array.from({ length: dots }, (_, j) => (
<div key={j} style={{
position: 'absolute', top: 0, left: -3, width: 6, height: 6, borderRadius: '50%',
background: color, boxShadow: `0 0 7px ${color}`,
animation: `v4DotFall ${dur}s ${-(j / dots) * dur}s linear infinite`,
}} />
))}
</div>
)
})}
</div>
)
}
// ── Node card ─────────────────────────────────────────────────────────────────
function NodeCard({ node, selected, onClick, isLight }: {
node: NodeDef; selected: boolean; onClick: () => void; isLight: boolean
}) {
const [hover, setHover] = useState(false)
const active = hover || selected
const c = node.color
const Ticker = NODE_TICKER[node.id]
const Icon = node.icon
return (
<button
onClick={onClick}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
style={{
flex: 1,
background: active
? `linear-gradient(180deg, ${c}${isLight ? '20' : '33'}, ${c}${isLight ? '0a' : '12'})`
: isLight
? 'linear-gradient(180deg, #ffffff, #f8fafc)'
: 'linear-gradient(180deg, rgba(255,255,255,.055), rgba(255,255,255,.015))',
border: `1px solid ${active ? c : isLight ? 'rgba(0,0,0,.1)' : 'rgba(255,255,255,.14)'}`,
borderRadius: 12, padding: '12px 14px',
cursor: 'pointer', textAlign: 'left',
color: isLight ? '#1a1a2e' : '#ece9f7', fontFamily: 'inherit',
display: 'flex', flexDirection: 'column',
transition: 'all .2s ease',
transform: active ? 'translateY(-1px)' : 'none',
boxShadow: active
? `0 8px 26px ${c}44, 0 0 0 4px ${c}14`
: isLight ? '0 1px 4px rgba(0,0,0,.06)' : '0 1px 0 rgba(255,255,255,.04)',
minWidth: 0, position: 'relative',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{
width: 36, height: 36, borderRadius: 10, flexShrink: 0,
background: `linear-gradient(135deg, ${c}3a, ${c}10)`,
border: `1px solid ${c}66`,
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: c,
boxShadow: node.primary ? `inset 0 0 14px ${c}40` : 'none',
}}>
<Icon style={{ width: 18, height: 18 }} />
</div>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{
fontSize: 13, fontWeight: 600,
color: isLight ? '#1a1a2e' : '#f7f5fc',
letterSpacing: -0.1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
}}>{node.title}</div>
<div style={{
fontSize: 10.5,
color: isLight ? '#64748b' : 'rgba(236,233,247,.65)',
marginTop: 1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
}}>{node.subtitle}</div>
</div>
</div>
<Ticker color={c} isLight={isLight} />
{node.primary && (
<div style={{
position: 'absolute', top: -1, right: -1,
width: 6, height: 6, borderRadius: '50%',
background: '#fbbf24', boxShadow: '0 0 8px #fbbf24',
animation: 'v4Pulse 1.6s ease-in-out infinite',
}} />
)}
</button>
)
}
// ── 3D slab ───────────────────────────────────────────────────────────────────
function LayerSlab({ label, sublabel, nodes, tint, depth, selectedId, onSelect, isLight }: {
label: string; sublabel: string; nodes: NodeDef[]
tint: string; depth: number
selectedId: NodeId | null; onSelect: (id: NodeId) => void
isLight: boolean
}) {
const isProxy = nodes.length === 1 && !!nodes[0].primary
return (
<div style={{
position: 'relative', margin: '0 auto',
padding: '14px 20px 18px', width: '100%', maxWidth: 960,
background: isLight
? `linear-gradient(180deg, ${tint}18 0%, ${tint}08 60%, rgba(248,250,252,.98) 100%)`
: `linear-gradient(180deg, ${tint}26 0%, ${tint}12 60%, rgba(14,8,28,.85) 100%)`,
border: `1px solid ${tint}${isLight ? '44' : '66'}`,
borderRadius: 16,
boxShadow: isLight
? `0 -2px 16px ${tint}18, 0 8px 30px rgba(0,0,0,.05), inset 0 1px 0 ${tint}44`
: `0 -6px 30px ${tint}22, 0 24px 60px rgba(0,0,0,.6), inset 0 1px 0 ${tint}55, inset 0 -1px 0 rgba(0,0,0,.4)`,
transform: `perspective(2000px) rotateX(12deg) translateZ(${depth}px)`,
}}>
<div style={{
position: 'absolute', top: 0, left: 20, right: 20, height: 1,
background: `linear-gradient(90deg, transparent, ${tint}${isLight ? 'aa' : 'cc'}, transparent)`,
pointerEvents: 'none',
}} />
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 }}>
<div style={{
fontSize: 9.5, letterSpacing: 2, textTransform: 'uppercase' as const, fontWeight: 600,
color: tint,
background: isLight ? `${tint}18` : `${tint}20`,
padding: '3px 9px', borderRadius: 99,
border: `1px solid ${tint}${isLight ? '44' : '50'}`, whiteSpace: 'nowrap',
}}>{label}</div>
<div style={{
fontSize: 11,
color: isLight ? '#64748b' : 'rgba(236,233,247,.55)',
whiteSpace: 'nowrap',
}}>{sublabel}</div>
</div>
<div style={{ display: 'flex', gap: 10, justifyContent: isProxy ? 'center' : undefined }}>
{isProxy ? (
<div style={{ width: '42%', minWidth: 260, display: 'flex' }}>
<NodeCard node={nodes[0]} selected={selectedId === nodes[0].id} onClick={() => onSelect(nodes[0].id)} isLight={isLight} />
</div>
) : (
nodes.map(n => (
<NodeCard key={n.id} node={n} selected={selectedId === n.id} onClick={() => onSelect(n.id)} isLight={isLight} />
))
)}
</div>
</div>
)
}
// ── Main slide ────────────────────────────────────────────────────────────────
export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
const i = t(lang)
const de = lang === 'de'
const isLight = useIsLight()
const allNodes = getNodes(de)
const nodeMap = Object.fromEntries(allNodes.map(n => [n.id, n])) as Record<NodeId, NodeDef>
const [activeId, setActiveId] = useState<NodeId | null>(null)
function toggle(id: NodeId) { setActiveId(prev => prev === id ? null : id) }
const active = activeId ? nodeMap[activeId] : null
const tenants = de
? ['Mandant A', 'Mandant B', 'Mandant C', 'Mandant N…']
: ['Namespace A', 'Namespace B', 'Namespace C', 'Namespace N…']
const layerLabels = de
? ['01 · Anwendung', '02 · Gateway', '03 · Infrastruktur']
: ['01 · Application', '02 · Gateway', '03 · Infrastructure']
const layerSublabels = de
? ['Benutzeroberflächen', 'Routing & Guardrails', 'Compute & Daten']
: ['User-facing services', 'Routing & guardrails', 'Compute & data']
return (
<div className="space-y-3">
<style>{CSS_KF}</style>
<FadeInView className="text-center mb-3">
<p className="text-[10px] font-mono text-indigo-400/50 uppercase tracking-widest mb-1.5">
{de ? 'Anhang' : 'Appendix'}
</p>
<h2 className="text-3xl md:text-4xl font-bold mb-1.5">
<GradientText>{i.annex.architecture.title}</GradientText>
</h2>
<p className="text-xs text-white/35">
{de ? 'Klicke auf eine Station für Details' : 'Click any node to explore'}
</p>
</FadeInView>
<FadeInView delay={0.15}>
<div className="flex items-center justify-center gap-2 flex-wrap mb-3 px-[4%]">
<Users className="w-3 h-3 text-white/25 flex-shrink-0" />
<span className="text-[9px] font-mono text-white/25 uppercase tracking-widest mr-1">
{de ? 'Kundenmandanten' : 'Customer Namespaces'}
</span>
{tenants.map(tn => (
<span key={tn} className="text-[9px] px-2 py-0.5 rounded-full border border-white/[0.08] bg-white/[0.03] text-white/35 font-mono">
{tn}
</span>
))}
</div>
{/* ── Main canvas ── */}
<div style={{
position: 'relative',
background: isLight
? 'linear-gradient(180deg, #f0f4ff 0%, #eef2ff 50%, #f0f4ff 100%)'
: 'linear-gradient(180deg, #0a0618 0%, #140a28 50%, #1a0f34 100%)',
borderRadius: 16, overflow: 'hidden',
padding: '22px 16px 20px',
fontFamily: '"Inter", system-ui, -apple-system, sans-serif',
WebkitFontSmoothing: 'antialiased',
MozOsxFontSmoothing: 'grayscale',
} as React.CSSProperties}>
{/* Ambient glows */}
{!isLight && (
<>
<div style={{
position: 'absolute', top: -80, left: '25%',
width: 400, height: 400, borderRadius: '50%',
background: 'radial-gradient(circle, rgba(167,139,250,.2), transparent 65%)',
filter: 'blur(50px)', pointerEvents: 'none',
}} />
<div style={{
position: 'absolute', bottom: -100, right: '15%',
width: 500, height: 500, borderRadius: '50%',
background: 'radial-gradient(circle, rgba(139,92,246,.15), transparent 65%)',
filter: 'blur(50px)', pointerEvents: 'none',
}} />
</>
)}
{/* Slabs + connectors */}
<div style={{
display: 'flex', flexDirection: 'column', alignItems: 'center',
position: 'relative', zIndex: 1,
perspective: '2000px', perspectiveOrigin: '50% 0%',
}}>
{LAYERS.map((layer, li) => {
const nodes = layer.nodeIds.map(id => nodeMap[id])
return (
<Fragment key={layer.id}>
<LayerSlab
label={layerLabels[li]}
sublabel={layerSublabels[li]}
nodes={nodes}
tint={layer.tint}
depth={layer.depth}
selectedId={activeId}
onSelect={toggle}
isLight={isLight}
/>
{li < LAYERS.length - 1 && <LayerConnector tint={layer.tint} />}
</Fragment>
)
})}
</div>
{/* Footer badges */}
<div style={{
display: 'flex', justifyContent: 'center', gap: 8,
flexWrap: 'wrap', marginTop: 20, position: 'relative', zIndex: 1,
}}>
{([
{ Icon: Lock, label: de ? 'Kein US-Anbieter · 100% DSGVO' : 'No US providers · 100% GDPR' },
{ Icon: Server, label: de ? 'BSI-zertifiziertes Rechenzentrum' : 'BSI-certified data center' },
{ Icon: BadgeCheck, label: de ? 'EU-souveräne Inferenz' : 'EU-sovereign inference' },
] as { Icon: React.ElementType; label: string }[]).map(({ Icon, label }) => (
<div key={label} style={{
display: 'flex', alignItems: 'center', gap: 6,
padding: '5px 11px', borderRadius: 99,
background: isLight ? '#ffffff' : 'rgba(10,6,24,.82)',
border: `1px solid ${isLight ? 'rgba(0,0,0,.1)' : 'rgba(167,139,250,.28)'}`,
fontSize: 10.5,
color: isLight ? '#64748b' : 'rgba(236,233,247,.7)',
whiteSpace: 'nowrap',
boxShadow: isLight ? '0 1px 3px rgba(0,0,0,.06)' : 'none',
}}>
<Icon style={{ width: 12, height: 12, color: '#a78bfa' }} />
{label}
</div>
))}
</div>
{/* Detail panel */}
<AnimatePresence>
{active && (
<motion.div
initial={{ y: '100%' }}
animate={{ y: 0 }}
exit={{ y: '100%' }}
transition={{ duration: 0.3, ease: [0.2, 0.7, 0.2, 1] }}
style={{
position: 'absolute', left: 0, right: 0, bottom: 0,
background: isLight ? 'rgba(255,255,255,.98)' : 'rgba(15,10,31,.97)',
borderTop: `1px solid ${active.color}${isLight ? '30' : '40'}`,
zIndex: 50,
padding: '18px 24px 20px',
boxShadow: isLight ? '0 -8px 30px rgba(0,0,0,.08)' : '0 -20px 60px rgba(0,0,0,.55)',
}}
>
<div style={{ maxWidth: 900, margin: '0 auto' }}>
<div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16, marginBottom: 14 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<div style={{
width: 38, height: 38, borderRadius: 11, flexShrink: 0,
background: `linear-gradient(135deg, ${active.color}3a, ${active.color}10)`,
border: `1px solid ${active.color}66`,
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: active.color,
}}>
<active.icon style={{ width: 19, height: 19 }} />
</div>
<div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
<span style={{ fontSize: 15, fontWeight: 600, color: isLight ? '#1a1a2e' : '#f5f3fc', letterSpacing: -0.2 }}>
{active.title}
</span>
<span style={{
fontSize: 9, padding: '2px 7px', borderRadius: 4,
background: `${active.color}18`, color: active.color,
border: `1px solid ${active.color}40`,
letterSpacing: 0.8, textTransform: 'uppercase' as const, fontWeight: 600,
}}>
{active.tier === 'product' ? (de ? 'Anwendung' : 'Application') :
active.tier === 'proxy' ? 'Gateway' :
(de ? 'Inferenz' : 'Inference')}
</span>
</div>
<div style={{ fontSize: 11.5, color: isLight ? '#64748b' : 'rgba(236,233,247,.5)', marginTop: 2 }}>
{active.subtitle}
</div>
</div>
</div>
<button
onClick={() => setActiveId(null)}
style={{
background: 'transparent',
border: `1px solid ${isLight ? 'rgba(0,0,0,.15)' : 'rgba(167,139,250,.25)'}`,
color: isLight ? '#64748b' : 'rgba(236,233,247,.5)',
width: 28, height: 28, borderRadius: 14,
cursor: 'pointer', display: 'flex',
alignItems: 'center', justifyContent: 'center', flexShrink: 0,
}}
>
<X style={{ width: 13, height: 13 }} />
</button>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20 }}>
<div>
<div style={{ fontSize: 8.5, letterSpacing: 1.5, textTransform: 'uppercase' as const, color: isLight ? '#94a3b8' : 'rgba(236,233,247,.32)', marginBottom: 7, fontWeight: 600 }}>
{de ? 'Stack' : 'Tech Stack'}
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 5 }}>
{active.tech.map(tk => (
<span key={tk} style={{
...MONO,
fontSize: 10, padding: '3px 8px', borderRadius: 5,
background: isLight ? '#f1f5f9' : 'rgba(255,255,255,.05)',
border: `1px solid ${isLight ? 'rgba(0,0,0,.1)' : 'rgba(255,255,255,.1)'}`,
color: isLight ? '#334155' : 'rgba(236,233,247,.65)',
}}>{tk}</span>
))}
</div>
</div>
<div>
<div style={{ fontSize: 8.5, letterSpacing: 1.5, textTransform: 'uppercase' as const, color: isLight ? '#94a3b8' : 'rgba(236,233,247,.32)', marginBottom: 7, fontWeight: 600 }}>
{de ? 'Funktionen' : 'Capabilities'}
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
{active.services.map(s => (
<div key={s.name} style={{ display: 'flex', alignItems: 'baseline', gap: 6 }}>
<div style={{ width: 3, height: 3, borderRadius: '50%', background: active.color, opacity: 0.7, flexShrink: 0, marginTop: 6 }} />
<span style={{ fontSize: 11.5, fontWeight: 600, color: isLight ? '#1a1a2e' : 'rgba(245,243,252,.82)' }}>{s.name}</span>
<span style={{ fontSize: 10, color: isLight ? '#64748b' : 'rgba(236,233,247,.38)' }}>{s.desc}</span>
</div>
))}
</div>
</div>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
</FadeInView>
</div>
)
}