redesign ArchitectureSlide: COMPLAI/CERTifAI/Scanner + EU-only infra
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 34s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 33s

- New architecture: CERTifAI (GenAI portal), COMPLAI (compliance), Scanner
  (code security) — MCP bidirectional connection between COMPLAI and Scanner
- LiteLLM hub: token budget, PII guardrails, anon web search, no US providers
- Inference layer: Qwen3/DeepSeek (local LLM), bge-m3 embeddings, AI Tools
- Fix hover snapping: position wrapper (div) separate from scale (motion.button)
- Always-on data traffic packets on all connections
- Bidirectional MCP packets + MCP badge between COMPLAI and Scanner
- Live token ticker counter on active inference nodes
- BSI/EU sovereign badges on tier labels
- Spinning dashed ring on active LiteLLM hub
- Secondary infra chips in GenAI tier (PostgreSQL, Gitea, Orca, etc.)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-04-20 22:46:40 +02:00
parent 39fcf58d1b
commit ecc1423a4f

View File

@@ -1,21 +1,23 @@
'use client' 'use client'
import { useState } from 'react' import { useState, useEffect } from 'react'
import { motion, AnimatePresence } from 'framer-motion' import { motion, AnimatePresence } from 'framer-motion'
import { Language } from '@/lib/types' import { Language } from '@/lib/types'
import { t } from '@/lib/i18n' import { t } from '@/lib/i18n'
import GradientText from '../ui/GradientText' import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView' import FadeInView from '../ui/FadeInView'
import { import {
Shield, Brain, ScanLine, Zap, Cpu, Globe, Cloud, Brain, Shield, ScanLine, Zap, Cpu,
X, Users, Lock, Server, Network, ChevronRight, Layers, Layers, Wrench, X, Users, Lock,
Server, Network, ChevronRight, BadgeCheck,
} from 'lucide-react' } from 'lucide-react'
interface ArchitectureSlideProps { interface ArchitectureSlideProps {
lang: Language lang: Language
} }
type NodeId = 'breakpilot' | 'certifai' | 'compliance-scanner' | 'litellm' | 'ollama' | 'claude' | 'openai' type NodeId = 'certifai' | 'complai' | 'scanner' | 'litellm' | 'llm' | 'embeddings' | 'tools'
type ConnType = 'api' | 'mcp' | 'embed' | 'tool'
interface NodeDef { interface NodeDef {
id: NodeId id: NodeId
@@ -35,265 +37,324 @@ interface NodeDef {
badge?: string badge?: string
} }
const CONNECTIONS: [NodeId, NodeId][] = [ interface ConnDef {
['breakpilot', 'litellm'], from: NodeId
['certifai', 'litellm'], to: NodeId
['compliance-scanner', 'litellm'], type: ConnType
['litellm', 'ollama'], d: string
['litellm', 'claude'], revD?: string // reverse path for bidirectional MCP
['litellm', 'openai'],
]
// Quadratic bezier paths in 0-100 viewBox space
const ROUTES: Record<string, string> = {
'breakpilot-litellm': 'M 15 22 Q 26 43 50 50',
'certifai-litellm': 'M 50 18 Q 50 34 50 50',
'compliance-scanner-litellm': 'M 85 22 Q 74 43 50 50',
'litellm-ollama': 'M 50 50 Q 39 64 15 80',
'litellm-claude': 'M 50 50 Q 50 65 50 80',
'litellm-openai': 'M 50 50 Q 61 64 85 80',
} }
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' },
]
function getNodes(de: boolean): NodeDef[] { function getNodes(de: boolean): NodeDef[] {
return [ return [
{ {
id: 'breakpilot', 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',
badge: 'Rust · Dioxus',
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, icon: Shield,
title: 'BreakPilot', title: 'COMPLAI',
subtitle: de ? 'Compliance & Bildung' : 'Compliance & Education', subtitle: de ? 'Compliance & Audit' : 'Compliance & Audit',
color: '#818cf8', twColor: 'text-indigo-400', color: '#818cf8', twColor: 'text-indigo-400',
twBorder: 'border-indigo-500/50', twBg: 'bg-indigo-500/10', twDot: 'bg-indigo-400', twBorder: 'border-indigo-500/50', twBg: 'bg-indigo-500/10', twDot: 'bg-indigo-400',
cx: 15, cy: 22, tier: 'product', cx: 50, cy: 22, tier: 'product',
badge: de ? 'Kernprodukt' : 'Core Product', badge: 'Next.js · FastAPI',
tech: ['Next.js 15', 'FastAPI', 'Go/Gin', 'PostgreSQL', 'Qdrant', 'Valkey'], tech: ['Next.js 15', 'FastAPI', 'Go/Gin', 'PostgreSQL', 'Qdrant', 'Valkey'],
services: [ services: [
{ name: de ? 'DSGVO / AI Act / NIS2' : 'GDPR / AI Act / NIS2', desc: de ? '70k+ auditierbare Controls' : '70k+ auditable controls' }, { 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: 'RAG Pipeline', desc: de ? '75+ Rechtsquellen, semantische Suche' : '75+ legal sources, semantic search' },
{ name: de ? 'Lehrer Plattform' : 'Lehrer Platform', desc: de ? 'KI-Unterrichtsassistent' : 'AI teaching assistant' }, { name: 'Control Pipeline', desc: de ? 'Gesetzestextanalyse via LLM' : 'Legal text analysis via LLM' },
{ name: 'Control Pipeline', desc: de ? 'Gesetzestextanalyse via Claude' : 'Legal text analysis via Claude' }, { name: 'MCP Client', desc: de ? 'Echtzeit-Findings vom Scanner' : 'Real-time findings from Scanner' },
], ],
}, },
{ {
id: 'certifai', id: 'scanner',
icon: Brain,
title: 'CERTifAI',
subtitle: de ? 'GenAI Infrastruktur' : 'GenAI Infrastructure',
color: '#c084fc', twColor: 'text-purple-400',
twBorder: 'border-purple-500/50', twBg: 'bg-purple-500/10', twDot: 'bg-purple-400',
cx: 50, cy: 18, tier: 'product',
badge: de ? 'DSGVO-konform' : 'GDPR-Compliant',
tech: ['Rust', 'Dioxus', 'MongoDB', 'Keycloak', 'SearXNG'],
services: [
{ name: 'LiteLLM Dashboard', desc: de ? 'Modellverwaltung & Kosten' : 'Model mgmt & spend tracking' },
{ name: 'LibreChat + SSO', desc: de ? 'Mandanten-Chat' : 'Tenant chat with Keycloak' },
{ name: 'LangGraph Agents', desc: de ? 'Agent-Orchestrierung' : 'Agent orchestration' },
{ name: 'MCP Hub', desc: de ? 'Tool-Integration für KI' : 'Tool integration for AI clients' },
],
},
{
id: 'compliance-scanner',
icon: ScanLine, icon: ScanLine,
title: 'Compliance Scanner', title: 'Compliance Scanner',
subtitle: de ? 'Autonome Sicherheit' : 'Autonomous Security', subtitle: de ? 'Code-Sicherheit' : 'Code Security',
color: '#34d399', twColor: 'text-emerald-400', color: '#34d399', twColor: 'text-emerald-400',
twBorder: 'border-emerald-500/50', twBg: 'bg-emerald-500/10', twDot: 'bg-emerald-400', twBorder: 'border-emerald-500/50', twBg: 'bg-emerald-500/10', twDot: 'bg-emerald-400',
cx: 85, cy: 22, tier: 'product', cx: 82, cy: 22, tier: 'product',
badge: 'Rust + AI', badge: 'Rust · Axum',
tech: ['Rust', 'Axum', 'MongoDB', 'Semgrep', 'Gitleaks', 'Syft'], tech: ['Rust', 'Axum', 'MongoDB', 'Semgrep', 'Gitleaks', 'Syft'],
services: [ services: [
{ name: 'SAST / SBOM / CVE', desc: de ? 'Vollautomatische Pipeline' : 'Fully automated pipeline' }, { 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-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: de ? 'KI-Pentest' : 'AI Pentest', desc: de ? 'Autonome Angriffsketten' : 'Autonomous attack chains' },
{ name: 'MCP Server', desc: de ? 'Live-Findings für AI-Tools' : 'Live findings for AI tools' }, { name: 'MCP Server', desc: de ? 'Live-Findings für COMPLAI' : 'Live findings for COMPLAI' },
], ],
}, },
{ {
id: 'litellm', id: 'litellm',
icon: Zap, icon: Zap,
title: 'LiteLLM Proxy', title: 'LiteLLM Proxy',
subtitle: de ? 'Zentrale KI-Infrastruktur' : 'Central AI Proxy', subtitle: de ? 'KI-Gateway & Guardrails' : 'AI Gateway & Guardrails',
color: '#fbbf24', twColor: 'text-amber-400', color: '#fbbf24', twColor: 'text-amber-400',
twBorder: 'border-amber-500/60', twBg: 'bg-amber-500/10', twDot: 'bg-amber-400', twBorder: 'border-amber-500/60', twBg: 'bg-amber-500/10', twDot: 'bg-amber-400',
cx: 50, cy: 50, tier: 'proxy', cx: 50, cy: 52, tier: 'proxy',
badge: 'Hub', badge: 'Hub',
tech: ['OpenAI-compatible API', 'Bearer Auth', 'Rate Limiting', 'Spend Tracking'], tech: ['OpenAI-kompatible API', 'Bearer Auth', 'Rate Limiting', 'PII-Filter', 'Spend Tracking'],
services: [ services: [
{ name: de ? 'Multi-Provider' : 'Multi-Provider', desc: 'Ollama · Claude · OpenAI · HuggingFace' }, { 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 ? 'Namespace-Isolierung' : 'Namespace Isolation', desc: de ? 'Mandantentrennung per API-Key' : 'Tenant isolation per API key' },
{ name: de ? 'Kosten-Tracking' : 'Cost Tracking', desc: de ? 'Token-Verbrauch & USD-Kosten' : 'Token usage & USD costs' }, { name: de ? 'Failover-Routing' : 'Failover Routing', desc: de ? 'Automatisches Fallback' : 'Automatic fallback between models' },
{ name: de ? 'Failover-Routing' : 'Failover Routing', desc: de ? 'Automatisches Fallback' : 'Automatic fallback routing' },
], ],
}, },
{ {
id: 'ollama', id: 'llm',
icon: Cpu, icon: Cpu,
title: 'Ollama', title: de ? 'LLM Inferenz' : 'LLM Inference',
subtitle: de ? 'Lokale Inferenz' : 'Local Inference', subtitle: de ? 'Lokale Sprachmodelle' : 'Local Language Models',
color: '#60a5fa', twColor: 'text-blue-400', color: '#60a5fa', twColor: 'text-blue-400',
twBorder: 'border-blue-500/50', twBg: 'bg-blue-500/10', twDot: 'bg-blue-400', twBorder: 'border-blue-500/50', twBg: 'bg-blue-500/10', twDot: 'bg-blue-400',
cx: 15, cy: 80, tier: 'inference', cx: 18, cy: 82, tier: 'inference',
badge: de ? 'On-Premise' : 'On-Premise', badge: de ? 'On-Premise · BSI' : 'On-Premise · BSI',
tech: ['Qwen3-Coder-30B', 'Qwen3-32b', 'bge-m3', 'GPU-optimized'], tech: ['Qwen3-32B', 'Qwen3-Coder-30B', 'DeepSeek-R1-8B', 'Ollama'],
services: [ services: [
{ name: de ? 'Vollständig lokal' : 'Fully local', desc: de ? 'Daten verlassen nie den Server' : 'Data never leaves the server' }, { 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 ? '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: 'claude', id: 'embeddings',
icon: Globe, icon: Layers,
title: 'Anthropic Claude', title: 'Embeddings',
subtitle: de ? 'Externe Inferenz' : 'External Inference', subtitle: de ? 'Semantische Suche' : 'Semantic Search',
color: '#fb7185', twColor: 'text-rose-400', color: '#a78bfa', twColor: 'text-violet-400',
twBorder: 'border-rose-500/50', twBg: 'bg-rose-500/10', twDot: 'bg-rose-400', twBorder: 'border-violet-500/50', twBg: 'bg-violet-500/10', twDot: 'bg-violet-400',
cx: 50, cy: 80, tier: 'inference', cx: 50, cy: 82, tier: 'inference',
badge: de ? 'Optional' : 'Optional', badge: de ? 'EU-Souverän' : 'EU Sovereign',
tech: ['claude-sonnet-4-6', 'claude-haiku-4-5', 'Direct API'], tech: ['bge-m3', 'Qdrant Vector DB', 'Sentence-Transformers'],
services: [ services: [
{ name: 'Control Pipeline', desc: de ? 'Gesetzestextanalyse & Control-Gen.' : 'Legal text analysis & control gen.' }, { name: 'RAG Pipeline', desc: de ? '75+ Rechtsquellen indexiert' : '75+ legal sources indexed' },
{ name: 'Pitch Deck Chatbot', desc: de ? 'Investor-FAQ' : 'Investor FAQ' }, { 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: 'openai', id: 'tools',
icon: Cloud, icon: Wrench,
title: de ? 'OpenAI / Weitere' : 'OpenAI / Others', title: de ? 'KI-Tools' : 'AI Tools',
subtitle: de ? 'Cloud-Modelle' : 'Cloud Models', subtitle: de ? 'Web-Suche & MCP' : 'Web Search & MCP',
color: '#2dd4bf', twColor: 'text-teal-400', color: '#2dd4bf', twColor: 'text-teal-400',
twBorder: 'border-teal-500/50', twBg: 'bg-teal-500/10', twDot: 'bg-teal-400', twBorder: 'border-teal-500/50', twBg: 'bg-teal-500/10', twDot: 'bg-teal-400',
cx: 85, cy: 80, tier: 'inference', cx: 82, cy: 82, tier: 'inference',
badge: de ? 'Optional' : 'Optional', badge: de ? 'EU-Souverän' : 'EU Sovereign',
tech: ['gpt-oss-120b', 'HuggingFace', 'text-embedding-3-small'], tech: ['SearXNG', 'MCP Protocol', 'Semgrep API', 'Gitleaks API'],
services: [ services: [
{ name: de ? 'Erweiterbar' : 'Extensible', desc: de ? 'Jeder OpenAI-kompatibler Anbieter' : 'Any OpenAI-compatible provider' }, { 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' },
], ],
}, },
] ]
} }
function SeaRoute({ d, color, active }: { d: string; color: string; active: boolean }) { // ── Token counter (inference tier ambient animation) ─────────────────────────
function TokenTicker({ color }: { color: string }) {
const [n, setN] = useState(() => 12480 + Math.floor(Math.random() * 8000))
useEffect(() => {
const id = setInterval(() => setN(v => v + Math.floor(Math.random() * 180 + 40)), 220)
return () => clearInterval(id)
}, [])
return (
<div className="flex items-center gap-1">
<motion.div
className="w-1 h-1 rounded-full"
style={{ background: color }}
animate={{ opacity: [1, 0.3, 1] }}
transition={{ duration: 0.6, repeat: Infinity }}
/>
<span className="text-[7px] font-mono tabular-nums" style={{ color, opacity: 0.65 }}>
{n.toLocaleString('de-DE')} tok/s
</span>
</div>
)
}
// ── Data-flow path (always-on packets, fast when active) ─────────────────────
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
return ( return (
<g> <g>
{/* Base line */}
<path <path
d={d} d={d} fill="none"
fill="none" stroke={color}
stroke={active ? color : '#ffffff'} strokeWidth={active ? 1 : 0.5}
strokeWidth={active ? 1.5 : 0.6} strokeOpacity={active ? 0.4 : 0.07}
strokeOpacity={active ? 0.5 : 0.09} strokeDasharray={isMcp ? '3 6' : undefined}
vectorEffect="non-scaling-stroke" vectorEffect="non-scaling-stroke"
/> />
{active && (
<motion.path {/* Forward packets */}
d={d} {[0, -28, -56].map((off, i) => (
fill="none" <motion.path key={`f${i}`}
stroke={color} d={d} fill="none"
strokeWidth={2.5} stroke={color} strokeWidth={w} strokeLinecap="round"
strokeLinecap="round" strokeDasharray={`${dash} 80`}
strokeDasharray="3 22" strokeOpacity={op - i * 0.07}
strokeOpacity={0.9}
vectorEffect="non-scaling-stroke" vectorEffect="non-scaling-stroke"
initial={{ strokeDashoffset: 0 }} initial={{ strokeDashoffset: off }}
animate={{ strokeDashoffset: -25 }} animate={{ strokeDashoffset: off - 83 }}
transition={{ duration: 1.4, repeat: Infinity, ease: 'linear' }} transition={{ duration: speed, repeat: Infinity, ease: 'linear' }}
/> />
)} ))}
{/* Reverse packets for MCP (bidirectional) */}
{isMcp && revD && [0, -42].map((off, i) => (
<motion.path key={`r${i}`}
d={revD} fill="none"
stroke={color} strokeWidth={w * 0.75} strokeLinecap="round"
strokeDasharray={`${dash} 80`}
strokeOpacity={(op - i * 0.1) * 0.75}
vectorEffect="non-scaling-stroke"
initial={{ strokeDashoffset: off }}
animate={{ strokeDashoffset: off - 83 }}
transition={{ duration: speed * 1.4, repeat: Infinity, ease: 'linear' }}
/>
))}
</g> </g>
) )
} }
function MapMarker({ node, active, onClick }: { node: NodeDef; active: boolean; onClick: () => void }) { // ── 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,
}: {
node: NodeDef; active: boolean; onClick: () => void; de: boolean
}) {
const Icon = node.icon const Icon = node.icon
const isHub = node.id === 'litellm' const isHub = node.id === 'litellm'
const iconSize = isHub ? 'w-[52px] h-[52px]' : 'w-[40px] h-[40px]'
return ( return (
<motion.button <div
onClick={onClick} className="absolute"
whileHover={{ scale: 1.1, transition: { duration: 0.14 } }}
whileTap={{ scale: 0.93 }}
className="absolute flex flex-col items-center text-center focus:outline-none"
style={{ left: `${node.cx}%`, top: `${node.cy}%`, transform: 'translate(-50%, -50%)' }} style={{ left: `${node.cx}%`, top: `${node.cy}%`, transform: 'translate(-50%, -50%)' }}
> >
<div className="relative flex items-center justify-center"> <motion.button
{/* Ripple pulse when active */} onClick={onClick}
{active && ( whileHover={{ scale: 1.06 }}
<motion.div whileTap={{ scale: 0.95 }}
className="absolute rounded-full pointer-events-none" transition={{ type: 'spring', stiffness: 400, damping: 25 }}
style={{ className="focus:outline-none block"
width: isHub ? 88 : 66, >
height: isHub ? 88 : 66,
border: `1px solid ${node.color}`,
}}
initial={{ scale: 0.75, opacity: 0.7 }}
animate={{ scale: 1.55, opacity: 0 }}
transition={{ duration: 2, repeat: Infinity, ease: 'easeOut' }}
/>
)}
{/* Icon circle */}
<div <div
className={`relative flex items-center justify-center rounded-full transition-all duration-300 ${iconSize}`} className={`relative rounded-xl border text-left transition-colors duration-200 overflow-hidden ${
style={{ isHub ? 'w-[148px] px-3 py-3' : 'w-[128px] px-2.5 py-2.5'
background: active } ${
? `radial-gradient(circle at 38% 32%, ${node.color}38, ${node.color}14)` active
: `${node.color}0a`, ? `${node.twBorder} ${node.twBg}`
border: `${active ? 2 : 1.5}px solid ${active ? node.color : node.color + '30'}`, : 'border-white/[0.09] bg-white/[0.04] hover:border-white/20'
boxShadow: active }`}
? `0 0 22px ${node.color}55, 0 0 6px ${node.color}28, inset 0 1px 0 ${node.color}30` style={{ boxShadow: active ? `0 0 22px -6px ${node.color}55` : 'none' }}
: 'none',
}}
> >
<Icon {/* Left accent bar */}
className={`transition-colors duration-300 ${isHub ? 'w-[20px] h-[20px]' : 'w-[14px] h-[14px]'} ${ <div
active ? node.twColor : 'text-white/30' className="absolute left-0 top-0 bottom-0 w-[2px] rounded-l-xl transition-all duration-300"
}`} style={{ background: active ? node.color : `${node.color}28` }}
/> />
{/* Spinning dashed ring on active hub */}
{/* Icon + title */}
<div className="flex items-center gap-2 pl-1 mb-1">
<div
className={`w-[22px] h-[22px] rounded-md flex items-center justify-center flex-shrink-0 border transition-colors duration-200 ${
active ? `${node.twBorder} ${node.twBg}` : 'border-white/10 bg-white/[0.05]'
}`}
>
<Icon className={`w-3 h-3 ${active ? node.twColor : 'text-white/35'}`} />
</div>
<span className={`text-[10px] font-semibold leading-tight truncate ${active ? 'text-white' : 'text-white/55'}`}>
{node.title}
</span>
{active && (
<div className={`w-1.5 h-1.5 rounded-full flex-shrink-0 animate-pulse ${node.twDot}`} />
)}
</div>
{/* Subtitle */}
<p className={`text-[8px] leading-snug pl-1 mb-1 ${active ? node.twColor : 'text-white/28'}`}
style={{ opacity: active ? 0.8 : 1 }}>
{node.subtitle}
</p>
{/* Badge */}
{node.badge && (
<div className="pl-1">
<span className={`text-[7px] px-1.5 py-[1px] rounded border transition-colors duration-200 ${
active ? `${node.twBorder} ${node.twColor}` : 'border-white/[0.08] text-white/22'
}`}>
{node.badge}
</span>
</div>
)}
{/* Token ticker on inference nodes */}
{active && node.tier === 'inference' && (
<div className="pl-1 mt-1.5">
<TokenTicker color={node.color} />
</div>
)}
{/* Hub: spinning indicator ring */}
{isHub && active && ( {isHub && active && (
<motion.div <motion.div
className="absolute rounded-full pointer-events-none" className="absolute rounded-full pointer-events-none"
style={{ style={{ inset: -5, border: `1px dashed ${node.color}45`, borderRadius: '12px' }}
inset: -6,
border: `1px dashed ${node.color}55`,
borderRadius: '50%',
}}
animate={{ rotate: 360 }} animate={{ rotate: 360 }}
transition={{ duration: 10, repeat: Infinity, ease: 'linear' }} transition={{ duration: 12, repeat: Infinity, ease: 'linear' }}
/> />
)} )}
</div> </div>
</div> </motion.button>
</div>
{/* Label */}
<div className="mt-1.5" style={{ maxWidth: isHub ? 96 : 80 }}>
<p className={`text-[9.5px] font-semibold leading-tight transition-colors duration-200 ${
active ? 'text-white' : 'text-white/48'
}`}>
{node.title}
</p>
{(active || isHub) && (
<p className={`text-[7.5px] mt-[2px] leading-snug transition-colors duration-200 ${
active ? node.twColor : 'text-white/28'
}`} style={{ opacity: active ? 0.85 : 1 }}>
{node.subtitle}
</p>
)}
{node.badge && (
<span className={`inline-block mt-[3px] text-[7px] px-[6px] py-[1px] rounded-full border transition-all duration-200 ${
active ? `${node.twBorder} ${node.twColor}` : 'border-white/10 text-white/22'
}`}>
{node.badge}
</span>
)}
</div>
</motion.button>
) )
} }
// ── Main slide ─────────────────────────────────────────────────────────────────
export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) { export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
const i = t(lang) const i = t(lang)
const de = lang === 'de' const de = lang === 'de'
const nodes = getNodes(de) const nodes = getNodes(de)
const [activeId, setActiveId] = useState<NodeId | null>(null) const [activeId, setActiveId] = useState<NodeId | null>(null)
const active = nodes.find(n => n.id === activeId) ?? null const active = nodes.find(n => n.id === activeId) ?? null
@@ -306,6 +367,12 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
? ['Mandant A', 'Mandant B', 'Mandant C', 'Mandant N…'] ? ['Mandant A', 'Mandant B', 'Mandant C', 'Mandant N…']
: ['Namespace A', 'Namespace B', 'Namespace C', 'Namespace 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' },
]
return ( return (
<div className="space-y-3"> <div className="space-y-3">
{/* Header */} {/* Header */}
@@ -329,151 +396,149 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
{de ? 'Kundenmandanten' : 'Customer Namespaces'} {de ? 'Kundenmandanten' : 'Customer Namespaces'}
</span> </span>
{tenants.map(tn => ( {tenants.map(tn => (
<span <span key={tn}
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">
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} {tn}
</span> </span>
))} ))}
<div className="flex-1 h-px bg-gradient-to-r from-white/10 to-transparent ml-1" /> <div className="flex-1 h-px bg-gradient-to-r from-white/10 to-transparent ml-1" />
</div> </div>
{/* ── THE MAP ──────────────────────────────────────────────────── */} {/* ── MAP ──────────────────────────────────────────────────────────── */}
<div className="relative w-full" style={{ height: '390px' }}> <div className="relative w-full" style={{ height: '410px' }}>
{/* Ocean background (clipped to rounded rect) */} {/* Background */}
<div <div className="absolute inset-0 rounded-xl overflow-hidden"
className="absolute inset-0 rounded-xl overflow-hidden"
style={{ style={{
background: ` background: `
radial-gradient(ellipse 55% 35% at 50% 50%, rgba(251,191,36,0.055) 0%, transparent 68%), radial-gradient(ellipse 50% 30% at 50% 52%, rgba(251,191,36,0.06) 0%, transparent 65%),
radial-gradient(ellipse 65% 25% at 50% 20%, rgba(129,140,248,0.07) 0%, transparent 58%), radial-gradient(ellipse 60% 22% at 50% 22%, rgba(129,140,248,0.07) 0%, transparent 55%),
radial-gradient(ellipse 65% 22% at 50% 82%, rgba(96,165,250,0.04) 0%, transparent 58%), radial-gradient(ellipse 60% 20% at 50% 82%, rgba(96,165,250,0.045) 0%, transparent 55%),
linear-gradient(170deg, #030d1e 0%, #040f20 50%, #030c1c 100%) linear-gradient(170deg, #030d1e 0%, #040f21 50%, #030c1b 100%)
`, `,
}} }}
> >
{/* Fine dot grid */} <div className="absolute inset-0 opacity-[0.04]"
<div
className="absolute inset-0 opacity-[0.042]"
style={{ style={{
backgroundImage: 'radial-gradient(circle, #6b7280 1px, transparent 1px)', backgroundImage: 'radial-gradient(circle, #6b7280 1px, transparent 1px)',
backgroundSize: '22px 22px', backgroundSize: '22px 22px',
}} }}
/> />
{/* Zone separator lines */}
{(['35%', '65%'] as const).map(y => (
<div
key={y}
className="absolute left-0 right-0 h-px"
style={{ top: y, background: 'linear-gradient(to right, transparent 0%, rgba(255,255,255,0.045) 20%, rgba(255,255,255,0.045) 80%, transparent 100%)' }}
/>
))}
</div> </div>
{/* Zone labels */} {/* Tier separator lines */}
{[ {['37%', '66%'].map(y => (
{ y: '22%', label: de ? 'Produkte' : 'Products', clr: '#818cf8' }, <div key={y} className="absolute left-0 right-0 h-px pointer-events-none"
{ y: '50%', label: de ? 'KI-Proxy' : 'AI Proxy', clr: '#fbbf24' }, style={{ top: y, background: 'linear-gradient(to right, transparent 0%, rgba(255,255,255,0.055) 15%, rgba(255,255,255,0.055) 85%, transparent 100%)' }}
{ y: '80%', label: de ? 'Inferenz' : 'Inference', clr: '#60a5fa' }, />
].map(({ y, label, clr }) => ( ))}
<div
key={label} {/* Tier labels (left) + BSI badges (right) */}
className="absolute left-3 flex items-center gap-1.5 select-none pointer-events-none" {tiers.map(({ y, label, clr, badge }) => (
style={{ top: y, transform: 'translateY(-50%)' }} <div key={label} className="absolute inset-x-0 flex items-center justify-between px-3 pointer-events-none select-none"
> style={{ top: y, transform: 'translateY(-50%)' }}>
<div className="w-2.5 h-px" style={{ background: clr, opacity: 0.28 }} /> <div className="flex items-center gap-1.5">
<span <div className="w-2.5 h-px" style={{ background: clr, opacity: 0.3 }} />
className="text-[7px] font-mono tracking-[0.16em] uppercase" <span className="text-[7.5px] font-mono tracking-[0.14em] uppercase" style={{ color: clr, opacity: 0.42 }}>
style={{ color: clr, opacity: 0.38 }} {label}
> </span>
{label} </div>
</span> {badge && (
<div className="flex items-center gap-1" style={{ opacity: 0.45 }}>
<BadgeCheck className="w-2.5 h-2.5" style={{ color: clr }} />
<span className="text-[7px] font-mono tracking-wider" style={{ color: clr }}>
{badge}
</span>
</div>
)}
</div> </div>
))} ))}
{/* SVG: island territories + sea routes */} {/* MCP badge between COMPLAI and Scanner */}
<svg <div
className="absolute inset-0 w-full h-full pointer-events-none" className="absolute text-[7px] px-2 py-[2px] rounded-full border border-emerald-500/35 text-emerald-400/65 font-mono tracking-wider select-none pointer-events-none"
viewBox="0 0 100 100" style={{ left: '66%', top: '9%', transform: 'translate(-50%, -50%)' }}
preserveAspectRatio="none"
> >
{/* Wide zone territory fills */} MCP
<ellipse cx="50" cy="18" rx="43" ry="11" fill="#818cf8" fillOpacity="0.025" /> </div>
<ellipse cx="50" cy="50" rx="13" ry="8" fill="#fbbf24" fillOpacity="0.04" />
<ellipse cx="50" cy="80" rx="43" ry="10" fill="#60a5fa" fillOpacity="0.02" />
{/* Per-node island blobs (outer contour + fill) */} {/* Infra tech chips (secondary, very subtle, in the infra tier band) */}
{nodes.map(node => { <div className="absolute left-[5%] right-[5%] flex flex-wrap gap-1.5 pointer-events-none select-none"
const isHub = node.id === 'litellm' style={{ top: '51.5%', transform: 'translateY(-50%)', paddingRight: '160px' }}>
const isActive = activeId === node.id {['PostgreSQL', 'MongoDB', 'Qdrant', 'Valkey', 'Nginx', 'Vault', 'Gitea', 'Orca', 'Woodpecker'].map(chip => (
const rx = isHub ? 10 : 7 <span key={chip}
const ry = isHub ? 6 : 3.8 className="text-[6.5px] px-1.5 py-[1px] rounded border border-white/[0.055] text-white/14 font-mono">
const rxOuter = isHub ? 14.5 : 10 {chip}
const ryOuter = isHub ? 8.5 : 5.5 </span>
))}
</div>
{/* SVG: island territories + data-flow paths */}
<svg className="absolute inset-0 w-full h-full pointer-events-none"
viewBox="0 0 100 100"
preserveAspectRatio="none">
{/* Tier zone fills */}
<rect x="0" y="0" width="100" height="37" fill="#818cf8" fillOpacity="0.018" />
<rect x="0" y="37" width="100" height="29" fill="#fbbf24" fillOpacity="0.022" />
<rect x="0" y="66" width="100" height="34" fill="#60a5fa" fillOpacity="0.015" />
{/* Island blobs per node */}
{nodes.map(n => {
const isHub = n.id === 'litellm'
const isActive = activeId === n.id
return ( return (
<g key={`island-${node.id}`}> <g key={`blob-${n.id}`}>
{/* Outer contour ring */} <ellipse cx={n.cx} cy={n.cy}
<ellipse rx={isHub ? 14 : 8} ry={isHub ? 8.5 : 5}
cx={node.cx} cy={node.cy} fill={n.color} fillOpacity={isActive ? 0.13 : 0.038}
rx={rxOuter} ry={ryOuter} stroke={n.color} strokeWidth={0.5}
fill="none" strokeOpacity={isActive ? 0.35 : 0.09}
stroke={node.color} strokeDasharray={isHub ? undefined : '1.5 3.5'}
strokeWidth={0.4}
vectorEffect="non-scaling-stroke" vectorEffect="non-scaling-stroke"
strokeDasharray="1.5 4"
strokeOpacity={isActive ? 0.2 : 0.06}
/>
{/* Inner island fill */}
<ellipse
cx={node.cx} cy={node.cy}
rx={rx} ry={ry}
fill={node.color}
fillOpacity={isActive ? 0.14 : 0.042}
stroke={node.color}
strokeWidth={0.5}
vectorEffect="non-scaling-stroke"
strokeOpacity={isActive ? 0.38 : 0.1}
/> />
</g> </g>
) )
})} })}
{/* Sea routes */} {/* Connection flows */}
{CONNECTIONS.map(([fromId, toId]) => { {CONNS.map(c => {
const key = `${fromId}-${toId}` const fromNode = nodes.find(n => n.id === c.from)!
const from = nodes.find(n => n.id === fromId)! const toNode = nodes.find(n => n.id === c.to)!
const to = nodes.find(n => n.id === toId)! const isActive = activeId === c.from || activeId === c.to
const isActive = activeId === fromId || activeId === toId const color = isActive
const color = isActive ? (activeId === c.from ? fromNode.color : toNode.color)
? (activeId === fromId ? from.color : to.color)
: '#ffffff' : '#ffffff'
return ( return (
<SeaRoute key={key} d={ROUTES[key]} color={color} active={isActive} /> <DataFlow
key={`${c.from}-${c.to}`}
d={c.d} revD={c.revD}
color={color} active={isActive} type={c.type}
/>
) )
})} })}
</svg> </svg>
{/* Node markers */} {/* Node cards */}
{nodes.map(node => ( {nodes.map(node => (
<MapMarker <NodeCard
key={node.id} key={node.id}
node={node} node={node}
active={activeId === node.id} active={activeId === node.id}
onClick={() => toggle(node.id)} onClick={() => toggle(node.id)}
de={de}
/> />
))} ))}
{!activeId && ( {!activeId && (
<div className="absolute bottom-3 right-3 flex items-center gap-1 text-[8.5px] text-white/18 pointer-events-none"> <div className="absolute bottom-3 right-3 flex items-center gap-1 text-[8px] text-white/18 pointer-events-none">
<ChevronRight className="w-3 h-3" /> <ChevronRight className="w-3 h-3" />
{de ? 'Komponente anklicken' : 'Click any node'} {de ? 'Komponente anklicken' : 'Click any node'}
</div> </div>
)} )}
</div> </div>
{/* ── DETAIL PANEL ─────────────────────────────────────────────── */} {/* ── DETAIL PANEL ──────────────────────────────────────────────────── */}
<AnimatePresence> <AnimatePresence>
{active && ( {active && (
<motion.div <motion.div
@@ -482,7 +547,7 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
exit={{ opacity: 0, y: 8 }} exit={{ opacity: 0, y: 8 }}
transition={{ duration: 0.22, ease: [0.22, 1, 0.36, 1] }} transition={{ duration: 0.22, ease: [0.22, 1, 0.36, 1] }}
className={`relative rounded-2xl border ${active.twBorder} ${active.twBg} p-4`} className={`relative rounded-2xl border ${active.twBorder} ${active.twBg} p-4`}
style={{ boxShadow: `0 0 30px -10px ${active.color}40` }} style={{ boxShadow: `0 0 32px -10px ${active.color}45` }}
> >
<button <button
onClick={() => setActiveId(null)} onClick={() => setActiveId(null)}
@@ -491,11 +556,12 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
<X className="w-3 h-3 text-white/50" /> <X className="w-3 h-3 text-white/50" />
</button> </button>
{/* Header */}
<div className="flex items-start gap-3 mb-4"> <div className="flex items-start gap-3 mb-4">
<div className={`w-9 h-9 rounded-xl flex items-center justify-center flex-shrink-0 border ${active.twBorder} ${active.twBg}`}> <div className={`w-9 h-9 rounded-xl flex items-center justify-center flex-shrink-0 border ${active.twBorder} ${active.twBg}`}>
<active.icon className={`w-5 h-5 ${active.twColor}`} /> <active.icon className={`w-5 h-5 ${active.twColor}`} />
</div> </div>
<div className="min-w-0"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-2 flex-wrap"> <div className="flex items-center gap-2 flex-wrap">
<h3 className="text-sm font-bold text-white">{active.title}</h3> <h3 className="text-sm font-bold text-white">{active.title}</h3>
{active.badge && ( {active.badge && (
@@ -503,11 +569,17 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
{active.badge} {active.badge}
</span> </span>
)} )}
<span className={`text-[8px] px-1.5 py-0.5 rounded border border-white/10 text-white/35`}>
{active.tier === 'product' ? (de ? 'Anwendungsschicht' : 'Application Layer') :
active.tier === 'proxy' ? (de ? 'GenAI-Infrastruktur' : 'GenAI Infra') :
(de ? 'Inferenzschicht' : 'Inference Layer')}
</span>
</div> </div>
<p className="text-xs text-white/45">{active.subtitle}</p> <p className="text-xs text-white/45 mt-0.5">{active.subtitle}</p>
</div> </div>
</div> </div>
{/* Body */}
<div className="grid grid-cols-2 gap-5"> <div className="grid grid-cols-2 gap-5">
<div> <div>
<p className="text-[9px] uppercase tracking-widest text-white/30 mb-2 font-semibold"> <p className="text-[9px] uppercase tracking-widest text-white/30 mb-2 font-semibold">
@@ -539,23 +611,26 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
</div> </div>
</div> </div>
{/* Connects-to */}
<div className="mt-3 pt-3 border-t border-white/[0.06] flex items-center gap-2 flex-wrap"> <div className="mt-3 pt-3 border-t border-white/[0.06] flex items-center gap-2 flex-wrap">
<Network className="w-3 h-3 text-white/20 flex-shrink-0" /> <Network className="w-3 h-3 text-white/20 flex-shrink-0" />
<span className="text-[9px] text-white/30"> <span className="text-[9px] text-white/30">
{de ? 'Verbunden mit:' : 'Connects to:'} {de ? 'Verbunden mit:' : 'Connects to:'}
</span> </span>
{CONNECTIONS {CONNS
.filter(([a, b]) => a === active.id || b === active.id) .filter(c => c.from === active.id || c.to === active.id)
.map(([a, b]) => { .map(c => {
const peerId = a === active.id ? b : a const peerId = c.from === active.id ? c.to : c.from
const peer = nodes.find(n => n.id === peerId)! const peer = nodes.find(n => n.id === peerId)!
const label = c.type === 'mcp' ? 'MCP' : c.type === 'embed' ? 'Embed' : c.type === 'tool' ? 'Tool' : 'API'
return ( return (
<button <button
key={peerId} key={peerId}
onClick={() => setActiveId(peerId)} onClick={() => setActiveId(peerId)}
className={`text-[9px] px-2 py-0.5 rounded-full border ${peer.twBorder} ${peer.twColor} transition-opacity hover:opacity-80`} 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} {peer.title}
<span className="opacity-50 text-[7px]">{label}</span>
</button> </button>
) )
})} })}
@@ -566,15 +641,12 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
{/* Bottom legend */} {/* Bottom legend */}
{!active && ( {!active && (
<motion.div <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }}
initial={{ opacity: 0 }} className="flex items-center justify-center gap-6 flex-wrap">
animate={{ opacity: 1 }}
className="flex items-center justify-center gap-6 flex-wrap"
>
{[ {[
{ icon: Lock, text: de ? 'DSGVO-konform · BSI-zertifizierbar' : 'GDPR-compliant · BSI-certifiable' }, { icon: Lock, text: de ? 'Kein US-Anbieter · 100% DSGVO' : 'No US providers · 100% GDPR' },
{ icon: Server, text: de ? 'On-Premise oder EU-Cloud' : 'On-Premise or EU Cloud' }, { icon: Server, text: de ? 'BSI-zertifiziertes Rechenzentrum' : 'BSI-certified data center' },
{ icon: Layers, text: de ? 'Air-Gap fähig' : 'Air-Gap Capable' }, { icon: BadgeCheck, text: de ? 'EU-souveräne Inferenz' : 'EU-sovereign inference' },
].map(({ icon: Icon, text }) => ( ].map(({ icon: Icon, text }) => (
<span key={text} className="flex items-center gap-1.5 text-[10px] text-white/22"> <span key={text} className="flex items-center gap-1.5 text-[10px] text-white/22">
<Icon className="w-3 h-3" /> <Icon className="w-3 h-3" />