From 497be5fac92f779e5c108ee98a335c229297cde7 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com> Date: Mon, 20 Apr 2026 22:25:13 +0200 Subject: [PATCH 01/13] redesign ArchitectureSlide with island map aesthetic + turbopack dev - Replace grid/SVG-line layout with archipelago map: organic island blobs, quadratic bezier sea routes, circular map-marker nodes - Fix SVG distortion: all strokes use vectorEffect=non-scaling-stroke - No more preserveAspectRatio=none diagonal-line warping - LiteLLM hub gets spinning ring + ripple pulse on active - Ocean background with per-tier radial glows, dot grid, zone labels - Switch dev server to --turbopack for faster HMR Co-Authored-By: Claude Sonnet 4.6 --- pitch-deck/app/api/data/route.ts | 10 + .../components/slides/ArchitectureSlide.tsx | 653 +++++++++++++++--- pitch-deck/lib/i18n.ts | 16 +- pitch-deck/middleware.ts | 5 + pitch-deck/package.json | 2 +- 5 files changed, 582 insertions(+), 104 deletions(-) diff --git a/pitch-deck/app/api/data/route.ts b/pitch-deck/app/api/data/route.ts index 413a179..58899d9 100644 --- a/pitch-deck/app/api/data/route.ts +++ b/pitch-deck/app/api/data/route.ts @@ -78,6 +78,16 @@ export async function GET() { } } catch (error) { console.error('Database query error:', error) + // Return minimal stub in dev so the pitch renders without a DB connection + if (process.env.NODE_ENV === 'development') { + return NextResponse.json({ + company: { name: 'BreakPilot', tagline: '[dev mode — no DB]' }, + team: [], financials: [], market: [], competitors: [], + features: [], milestones: [], metrics: [], + funding: { instrument: 'Wandeldarlehen', amount: 500000, valuation_cap: 3000000, currency: 'EUR' }, + products: [], + }) + } return NextResponse.json({ error: 'Failed to load pitch data' }, { status: 500 }) } } diff --git a/pitch-deck/components/slides/ArchitectureSlide.tsx b/pitch-deck/components/slides/ArchitectureSlide.tsx index bc293e4..f4beb86 100644 --- a/pitch-deck/components/slides/ArchitectureSlide.tsx +++ b/pitch-deck/components/slides/ArchitectureSlide.tsx @@ -1,129 +1,588 @@ 'use client' +import { useState } 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 GlassCard from '../ui/GlassCard' -import { Server, Cpu, Shield, Database, Globe, Lock, Layers, Workflow } from 'lucide-react' +import { + Shield, Brain, ScanLine, Zap, Cpu, Globe, Cloud, + X, Users, Lock, Server, Network, ChevronRight, Layers, +} from 'lucide-react' interface ArchitectureSlideProps { lang: Language } -export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) { - const i = t(lang) - const de = lang === 'de' +type NodeId = 'breakpilot' | 'certifai' | 'compliance-scanner' | 'litellm' | 'ollama' | 'claude' | 'openai' - const layers = [ - { - icon: Server, - color: 'text-indigo-400', - bg: 'bg-indigo-500/10 border-indigo-500/20', - title: de ? 'Hardware-Schicht' : 'Hardware Layer', - items: [ - { label: 'ComplAI Mini', desc: de ? 'Mac Mini M4 (geplant, optional)' : 'Mac Mini M4 (planned, optional)' }, - { label: 'ComplAI Studio', desc: de ? 'Mac Studio M4 Max (geplant, optional)' : 'Mac Studio M4 Max (planned, optional)' }, - { label: 'ComplAI Cloud', desc: de ? 'BSI-zertifizierte Cloud in Deutschland' : 'BSI-certified cloud in Germany' }, - ], - }, - { - icon: Cpu, - color: 'text-purple-400', - bg: 'bg-purple-500/10 border-purple-500/20', - title: de ? 'KI-Engine' : 'AI Engine', - items: [ - { label: 'Ollama Runtime', desc: de ? 'Lokale LLM-Inferenz, GPU-optimiert' : 'Local LLM inference, GPU-optimized' }, - { label: 'RAG Pipeline', desc: de ? 'Vektorsuche mit Compliance-Wissensbasis' : 'Vector search with compliance knowledge base' }, - { label: 'Agent Framework', desc: de ? 'Autonome Compliance-Agenten (Audit, Monitoring, Reporting)' : 'Autonomous compliance agents (Audit, Monitoring, Reporting)' }, - ], - }, +interface NodeDef { + id: NodeId + icon: React.ElementType + title: string + subtitle: string + color: string + twColor: string + twBorder: string + twBg: string + twDot: string + cx: number + cy: number + tier: 'product' | 'proxy' | 'inference' + tech: string[] + services: { name: string; desc: string }[] + badge?: string +} + +const CONNECTIONS: [NodeId, NodeId][] = [ + ['breakpilot', 'litellm'], + ['certifai', 'litellm'], + ['compliance-scanner', 'litellm'], + ['litellm', 'ollama'], + ['litellm', 'claude'], + ['litellm', 'openai'], +] + +// Quadratic bezier paths in 0-100 viewBox space +const ROUTES: Record = { + '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', +} + +function getNodes(de: boolean): NodeDef[] { + return [ { + id: 'breakpilot', icon: Shield, - color: 'text-emerald-400', - bg: 'bg-emerald-500/10 border-emerald-500/20', - title: de ? 'Compliance-Module' : 'Compliance Modules', - items: [ - { label: 'DSGVO Engine', desc: de ? 'VVT, DSFA, Betroffenenrechte, Löschkonzept' : 'RoPA, DPIA, Data Subject Rights, Deletion Concept' }, - { label: 'AI Act Module', desc: de ? 'Risikoklassifizierung, Konformitätsbewertung, Dokumentation' : 'Risk Classification, Conformity Assessment, Documentation' }, - { label: 'NIS2 Module', desc: de ? 'Cybersecurity-Policies, Incident Response, Meldewege' : 'Cybersecurity Policies, Incident Response, Reporting Chains' }, + title: 'BreakPilot', + subtitle: de ? 'Compliance & Bildung' : 'Compliance & Education', + color: '#818cf8', twColor: 'text-indigo-400', + twBorder: 'border-indigo-500/50', twBg: 'bg-indigo-500/10', twDot: 'bg-indigo-400', + cx: 15, cy: 22, tier: 'product', + badge: de ? 'Kernprodukt' : 'Core 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: de ? 'Lehrer Plattform' : 'Lehrer Platform', desc: de ? 'KI-Unterrichtsassistent' : 'AI teaching assistant' }, + { name: 'Control Pipeline', desc: de ? 'Gesetzestextanalyse via Claude' : 'Legal text analysis via Claude' }, ], }, { - icon: Layers, - color: 'text-blue-400', - bg: 'bg-blue-500/10 border-blue-500/20', - title: de ? 'Plattform-Services' : 'Platform Services', - items: [ - { label: de ? 'Admin-Dashboard' : 'Admin Dashboard', desc: 'Next.js · ' + (de ? 'Mandantenfähig · Rollenbasiert' : 'Multi-Tenant · Role-Based') }, - { label: 'SDK API', desc: 'Go/Gin · REST · ' + (de ? 'Tenant-isoliert' : 'Tenant-Isolated') }, - { label: 'DevSecOps Suite', desc: 'Semgrep · Trivy · Gitleaks · CycloneDX SBOM' }, + id: 'certifai', + 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, + title: 'Compliance Scanner', + subtitle: de ? 'Autonome Sicherheit' : 'Autonomous Security', + color: '#34d399', twColor: 'text-emerald-400', + twBorder: 'border-emerald-500/50', twBg: 'bg-emerald-500/10', twDot: 'bg-emerald-400', + cx: 85, cy: 22, tier: 'product', + badge: 'Rust + AI', + 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 AI-Tools' : 'Live findings for AI tools' }, + ], + }, + { + id: 'litellm', + icon: Zap, + title: 'LiteLLM Proxy', + subtitle: de ? 'Zentrale KI-Infrastruktur' : 'Central AI Proxy', + color: '#fbbf24', twColor: 'text-amber-400', + twBorder: 'border-amber-500/60', twBg: 'bg-amber-500/10', twDot: 'bg-amber-400', + cx: 50, cy: 50, tier: 'proxy', + badge: 'Hub', + tech: ['OpenAI-compatible API', 'Bearer Auth', 'Rate Limiting', 'Spend Tracking'], + services: [ + { name: de ? 'Multi-Provider' : 'Multi-Provider', desc: 'Ollama · Claude · OpenAI · HuggingFace' }, + { 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 routing' }, + ], + }, + { + id: 'ollama', + icon: Cpu, + title: 'Ollama', + subtitle: de ? 'Lokale Inferenz' : 'Local Inference', + color: '#60a5fa', twColor: 'text-blue-400', + twBorder: 'border-blue-500/50', twBg: 'bg-blue-500/10', twDot: 'bg-blue-400', + cx: 15, cy: 80, tier: 'inference', + badge: de ? 'On-Premise' : 'On-Premise', + tech: ['Qwen3-Coder-30B', 'Qwen3-32b', 'bge-m3', 'GPU-optimized'], + 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' }, + ], + }, + { + id: 'claude', + icon: Globe, + title: 'Anthropic Claude', + subtitle: de ? 'Externe Inferenz' : 'External Inference', + color: '#fb7185', twColor: 'text-rose-400', + twBorder: 'border-rose-500/50', twBg: 'bg-rose-500/10', twDot: 'bg-rose-400', + cx: 50, cy: 80, tier: 'inference', + badge: de ? 'Optional' : 'Optional', + tech: ['claude-sonnet-4-6', 'claude-haiku-4-5', 'Direct API'], + services: [ + { name: 'Control Pipeline', desc: de ? 'Gesetzestextanalyse & Control-Gen.' : 'Legal text analysis & control gen.' }, + { name: 'Pitch Deck Chatbot', desc: de ? 'Investor-FAQ' : 'Investor FAQ' }, + ], + }, + { + id: 'openai', + icon: Cloud, + title: de ? 'OpenAI / Weitere' : 'OpenAI / Others', + subtitle: de ? 'Cloud-Modelle' : 'Cloud Models', + color: '#2dd4bf', twColor: 'text-teal-400', + twBorder: 'border-teal-500/50', twBg: 'bg-teal-500/10', twDot: 'bg-teal-400', + cx: 85, cy: 80, tier: 'inference', + badge: de ? 'Optional' : 'Optional', + tech: ['gpt-oss-120b', 'HuggingFace', 'text-embedding-3-small'], + services: [ + { name: de ? 'Erweiterbar' : 'Extensible', desc: de ? 'Jeder OpenAI-kompatibler Anbieter' : 'Any OpenAI-compatible provider' }, ], }, ] +} - const securityFeatures = [ - { icon: Lock, label: de ? 'Zero-Trust Architektur' : 'Zero-Trust Architecture' }, - { icon: Database, label: de ? 'Daten verlassen nie BSI-zertifizierte Server in DE' : 'Data Never Leaves BSI-Certified Servers in DE' }, - { icon: Globe, label: de ? '100% EU-Cloud · Keine US-Anbieter' : '100% EU Cloud · No US Providers' }, - { icon: Workflow, label: de ? 'Air-Gap fähig' : 'Air-Gap Capable' }, - ] +function SeaRoute({ d, color, active }: { d: string; color: string; active: boolean }) { + return ( + + + {active && ( + + )} + + ) +} + +function MapMarker({ node, active, onClick }: { node: NodeDef; active: boolean; onClick: () => void }) { + const Icon = node.icon + const isHub = node.id === 'litellm' + const iconSize = isHub ? 'w-[52px] h-[52px]' : 'w-[40px] h-[40px]' return ( -
- -

- {de ? 'Anhang' : 'Appendix'} -

-

- {i.annex.architecture.title} -

-

{i.annex.architecture.subtitle}

-
+ +
+ {/* Ripple pulse when active */} + {active && ( + + )} - {/* Architecture Layers */} -
- {layers.map((layer, idx) => { - const Icon = layer.icon - return ( - -
-
- -

{layer.title}

-
-
- {layer.items.map((item, iidx) => ( -
-
-
- {item.label} - {item.desc} -
-
- ))} -
-
- - ) - })} + {/* Icon circle */} +
+ + {/* Spinning dashed ring on active hub */} + {isHub && active && ( + + )} +
- {/* Security Bar */} - - -
- {securityFeatures.map((feat, idx) => { - const Icon = feat.icon + {/* Label */} +
+

+ {node.title} +

+ {(active || isHub) && ( +

+ {node.subtitle} +

+ )} + {node.badge && ( + + {node.badge} + + )} +
+ + ) +} + +export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) { + const i = t(lang) + const de = lang === 'de' + const nodes = getNodes(de) + + const [activeId, setActiveId] = useState(null) + const active = nodes.find(n => n.id === activeId) ?? null + + 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…'] + + return ( +
+ {/* Header */} + +

+ {de ? 'Anhang' : 'Appendix'} +

+

+ {i.annex.architecture.title} +

+

+ {de ? 'Klicke auf eine Komponente für Details' : 'Click any component to explore'} +

+
+ + + {/* Customer namespace strip */} +
+ + + {de ? 'Kundenmandanten' : 'Customer Namespaces'} + + {tenants.map(tn => ( + + {tn} + + ))} +
+
+ + {/* ── THE MAP ──────────────────────────────────────────────────── */} +
+ + {/* Ocean background (clipped to rounded rect) */} +
+ {/* Fine dot grid */} +
+ {/* Zone separator lines */} + {(['35%', '65%'] as const).map(y => ( +
+ ))} +
+ + {/* Zone labels */} + {[ + { y: '22%', label: de ? 'Produkte' : 'Products', clr: '#818cf8' }, + { y: '50%', label: de ? 'KI-Proxy' : 'AI Proxy', clr: '#fbbf24' }, + { y: '80%', label: de ? 'Inferenz' : 'Inference', clr: '#60a5fa' }, + ].map(({ y, label, clr }) => ( +
+
+ + {label} + +
+ ))} + + {/* SVG: island territories + sea routes */} + + {/* Wide zone territory fills */} + + + + + {/* Per-node island blobs (outer contour + fill) */} + {nodes.map(node => { + const isHub = node.id === 'litellm' + const isActive = activeId === node.id + const rx = isHub ? 10 : 7 + const ry = isHub ? 6 : 3.8 + const rxOuter = isHub ? 14.5 : 10 + const ryOuter = isHub ? 8.5 : 5.5 return ( -
- - {feat.label} -
+ + {/* Outer contour ring */} + + {/* Inner island fill */} + + ) })} -
- + + {/* Sea routes */} + {CONNECTIONS.map(([fromId, toId]) => { + const key = `${fromId}-${toId}` + const from = nodes.find(n => n.id === fromId)! + const to = nodes.find(n => n.id === toId)! + const isActive = activeId === fromId || activeId === toId + const color = isActive + ? (activeId === fromId ? from.color : to.color) + : '#ffffff' + return ( + + ) + })} + + + {/* Node markers */} + {nodes.map(node => ( + toggle(node.id)} + /> + ))} + + {!activeId && ( +
+ + {de ? 'Komponente anklicken' : 'Click any node'} +
+ )} +
+ + {/* ── DETAIL PANEL ─────────────────────────────────────────────── */} + + {active && ( + + + +
+
+ +
+
+
+

{active.title}

+ {active.badge && ( + + {active.badge} + + )} +
+

{active.subtitle}

+
+
+ +
+
+

+ {de ? 'Stack' : 'Tech Stack'} +

+
+ {active.tech.map(tk => ( + + {tk} + + ))} +
+
+
+

+ {de ? 'Funktionen' : 'Capabilities'} +

+
+ {active.services.map(s => ( +
+
+
+ {s.name} + {s.desc} +
+
+ ))} +
+
+
+ +
+ + + {de ? 'Verbunden mit:' : 'Connects to:'} + + {CONNECTIONS + .filter(([a, b]) => a === active.id || b === active.id) + .map(([a, b]) => { + const peerId = a === active.id ? b : a + const peer = nodes.find(n => n.id === peerId)! + return ( + + ) + })} +
+ + )} + + + {/* Bottom legend */} + {!active && ( + + {[ + { icon: Lock, text: de ? 'DSGVO-konform · BSI-zertifizierbar' : 'GDPR-compliant · BSI-certifiable' }, + { icon: Server, text: de ? 'On-Premise oder EU-Cloud' : 'On-Premise or EU Cloud' }, + { icon: Layers, text: de ? 'Air-Gap fähig' : 'Air-Gap Capable' }, + ].map(({ icon: Icon, text }) => ( + + + {text} + + ))} + + )}
) diff --git a/pitch-deck/lib/i18n.ts b/pitch-deck/lib/i18n.ts index a3e68b3..7a7d0b0 100644 --- a/pitch-deck/lib/i18n.ts +++ b/pitch-deck/lib/i18n.ts @@ -26,7 +26,9 @@ const translations = { 'Investition & Cap Table', 'Kundenersparnis', 'KI Q&A', - 'Anhang: Architektur', + 'Anhang: Annahmen', + 'Anhang: Systemarchitektur', + 'Anhang: Go-to-Market', 'Anhang: Regulatorik', 'Anhang: Engineering', 'Anhang: KI-Pipeline', @@ -276,8 +278,8 @@ const translations = { subtitle: 'Drei Szenarien für robuste Planung', }, architecture: { - title: 'Technische Architektur', - subtitle: 'Self-Hosted KI-Stack für maximale Datensouveränität', + title: 'Systemarchitektur', + subtitle: 'BreakPilot · CERTifAI · Compliance Scanner — verbunden über LiteLLM', }, gtm: { title: 'Go-to-Market Strategie', @@ -322,7 +324,9 @@ const translations = { 'Investment & Cap Table', 'Customer Savings', 'AI Q&A', - 'Appendix: Architecture', + 'Appendix: Assumptions', + 'Appendix: System Architecture', + 'Appendix: Go-to-Market', 'Appendix: Regulatory', 'Appendix: Engineering', 'Appendix: AI Pipeline', @@ -572,8 +576,8 @@ const translations = { subtitle: 'Three scenarios for robust planning', }, architecture: { - title: 'Technical Architecture', - subtitle: 'Self-hosted AI stack for maximum data sovereignty', + title: 'System Architecture', + subtitle: 'BreakPilot · CERTifAI · Compliance Scanner — connected via LiteLLM', }, gtm: { title: 'Go-to-Market Strategy', diff --git a/pitch-deck/middleware.ts b/pitch-deck/middleware.ts index 5a0f33d..2cfd3ba 100644 --- a/pitch-deck/middleware.ts +++ b/pitch-deck/middleware.ts @@ -34,6 +34,11 @@ export async function middleware(request: NextRequest) { const { pathname } = request.nextUrl const secret = process.env.PITCH_JWT_SECRET + // Skip all auth in local dev when no secret is configured + if (!secret && process.env.NODE_ENV === 'development') { + return NextResponse.next() + } + // Allow public paths if (isPublicPath(pathname)) { return NextResponse.next() diff --git a/pitch-deck/package.json b/pitch-deck/package.json index 2c0a658..e78f486 100644 --- a/pitch-deck/package.json +++ b/pitch-deck/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "private": true, "scripts": { - "dev": "next dev -p 3012", + "dev": "next dev --turbopack -p 3012", "build": "next build", "start": "next start -p 3012", "admin:create": "tsx scripts/create-admin.ts", From 39fcf58d1bfd0349bf1be5f1f1ecba3cf7c29120 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com> Date: Mon, 20 Apr 2026 22:31:47 +0200 Subject: [PATCH 02/13] fix bad merge: remove duplicate slide names + move CSS imports before tailwind Merge conflict resolution incorrectly added Anhang: Annahmen and Anhang: Go-to-Market twice. Remove the duplicates, keep only the renamed Anhang: Systemarchitektur entry in the correct position. Also move @import rules above @tailwind directives (Turbopack CSS spec). Co-Authored-By: Claude Sonnet 4.6 --- pitch-deck/app/globals.css | 6 +++--- pitch-deck/lib/i18n.ts | 4 ---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/pitch-deck/app/globals.css b/pitch-deck/app/globals.css index cdeaab4..155b862 100644 --- a/pitch-deck/app/globals.css +++ b/pitch-deck/app/globals.css @@ -1,10 +1,10 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&display=swap'); + @tailwind base; @tailwind components; @tailwind utilities; -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap'); -@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&display=swap'); - /* === Dark Mode (default) === */ :root { --bg-primary: #0a0a1a; diff --git a/pitch-deck/lib/i18n.ts b/pitch-deck/lib/i18n.ts index 7a7d0b0..b6abff2 100644 --- a/pitch-deck/lib/i18n.ts +++ b/pitch-deck/lib/i18n.ts @@ -26,9 +26,7 @@ const translations = { 'Investition & Cap Table', 'Kundenersparnis', 'KI Q&A', - 'Anhang: Annahmen', 'Anhang: Systemarchitektur', - 'Anhang: Go-to-Market', 'Anhang: Regulatorik', 'Anhang: Engineering', 'Anhang: KI-Pipeline', @@ -324,9 +322,7 @@ const translations = { 'Investment & Cap Table', 'Customer Savings', 'AI Q&A', - 'Appendix: Assumptions', 'Appendix: System Architecture', - 'Appendix: Go-to-Market', 'Appendix: Regulatory', 'Appendix: Engineering', 'Appendix: AI Pipeline', From ecc1423a4fc7d9067d43d753b135678d9d255240 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com> Date: Mon, 20 Apr 2026 22:46:40 +0200 Subject: [PATCH 03/13] redesign ArchitectureSlide: COMPLAI/CERTifAI/Scanner + EU-only infra MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../components/slides/ArchitectureSlide.tsx | 636 ++++++++++-------- 1 file changed, 354 insertions(+), 282 deletions(-) diff --git a/pitch-deck/components/slides/ArchitectureSlide.tsx b/pitch-deck/components/slides/ArchitectureSlide.tsx index f4beb86..a9c182d 100644 --- a/pitch-deck/components/slides/ArchitectureSlide.tsx +++ b/pitch-deck/components/slides/ArchitectureSlide.tsx @@ -1,21 +1,23 @@ 'use client' -import { useState } from 'react' +import { useState, useEffect } 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 { - Shield, Brain, ScanLine, Zap, Cpu, Globe, Cloud, - X, Users, Lock, Server, Network, ChevronRight, Layers, + Brain, Shield, ScanLine, Zap, Cpu, + Layers, Wrench, X, Users, Lock, + Server, Network, ChevronRight, BadgeCheck, } from 'lucide-react' interface ArchitectureSlideProps { 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 { id: NodeId @@ -35,265 +37,324 @@ interface NodeDef { badge?: string } -const CONNECTIONS: [NodeId, NodeId][] = [ - ['breakpilot', 'litellm'], - ['certifai', 'litellm'], - ['compliance-scanner', 'litellm'], - ['litellm', 'ollama'], - ['litellm', 'claude'], - ['litellm', 'openai'], -] - -// Quadratic bezier paths in 0-100 viewBox space -const ROUTES: Record = { - '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', +interface ConnDef { + from: NodeId + to: NodeId + type: ConnType + d: string + revD?: string // reverse path for bidirectional MCP } +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[] { 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, - title: 'BreakPilot', - subtitle: de ? 'Compliance & Bildung' : 'Compliance & Education', + 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: 15, cy: 22, tier: 'product', - badge: de ? 'Kernprodukt' : 'Core Product', + cx: 50, cy: 22, tier: 'product', + badge: 'Next.js · FastAPI', 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: de ? 'Lehrer Plattform' : 'Lehrer Platform', desc: de ? 'KI-Unterrichtsassistent' : 'AI teaching assistant' }, - { name: 'Control Pipeline', desc: de ? 'Gesetzestextanalyse via Claude' : 'Legal text analysis via Claude' }, + { 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: 'certifai', - 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', + id: 'scanner', icon: ScanLine, title: 'Compliance Scanner', - subtitle: de ? 'Autonome Sicherheit' : 'Autonomous Security', + 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: 85, cy: 22, tier: 'product', - badge: 'Rust + AI', + cx: 82, cy: 22, tier: 'product', + badge: 'Rust · Axum', 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 AI-Tools' : 'Live findings for AI tools' }, + { name: 'MCP Server', desc: de ? 'Live-Findings für COMPLAI' : 'Live findings for COMPLAI' }, ], }, { id: 'litellm', icon: Zap, 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', 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', - tech: ['OpenAI-compatible API', 'Bearer Auth', 'Rate Limiting', 'Spend Tracking'], + tech: ['OpenAI-kompatible API', 'Bearer Auth', 'Rate Limiting', 'PII-Filter', 'Spend Tracking'], 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 ? '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 routing' }, + { name: de ? 'Failover-Routing' : 'Failover Routing', desc: de ? 'Automatisches Fallback' : 'Automatic fallback between models' }, ], }, { - id: 'ollama', + id: 'llm', icon: Cpu, - title: 'Ollama', - subtitle: de ? 'Lokale Inferenz' : 'Local Inference', + 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: 15, cy: 80, tier: 'inference', - badge: de ? 'On-Premise' : 'On-Premise', - tech: ['Qwen3-Coder-30B', 'Qwen3-32b', 'bge-m3', 'GPU-optimized'], + cx: 18, cy: 82, tier: 'inference', + badge: de ? 'On-Premise · BSI' : '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' }, { 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', - icon: Globe, - title: 'Anthropic Claude', - subtitle: de ? 'Externe Inferenz' : 'External Inference', - color: '#fb7185', twColor: 'text-rose-400', - twBorder: 'border-rose-500/50', twBg: 'bg-rose-500/10', twDot: 'bg-rose-400', - cx: 50, cy: 80, tier: 'inference', - badge: de ? 'Optional' : 'Optional', - tech: ['claude-sonnet-4-6', 'claude-haiku-4-5', 'Direct API'], + 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', + badge: de ? 'EU-Souverän' : 'EU Sovereign', + tech: ['bge-m3', 'Qdrant Vector DB', 'Sentence-Transformers'], services: [ - { name: 'Control Pipeline', desc: de ? 'Gesetzestextanalyse & Control-Gen.' : 'Legal text analysis & control gen.' }, - { name: 'Pitch Deck Chatbot', desc: de ? 'Investor-FAQ' : 'Investor FAQ' }, + { 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: 'openai', - icon: Cloud, - title: de ? 'OpenAI / Weitere' : 'OpenAI / Others', - subtitle: de ? 'Cloud-Modelle' : 'Cloud Models', + 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: 85, cy: 80, tier: 'inference', - badge: de ? 'Optional' : 'Optional', - tech: ['gpt-oss-120b', 'HuggingFace', 'text-embedding-3-small'], + cx: 82, cy: 82, tier: 'inference', + badge: de ? 'EU-Souverän' : 'EU Sovereign', + tech: ['SearXNG', 'MCP Protocol', 'Semgrep API', 'Gitleaks API'], 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 ( +
+ + + {n.toLocaleString('de-DE')} tok/s + +
+ ) +} + +// ── 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 ( + {/* Base line */} - {active && ( - ( + - )} + ))} + + {/* Reverse packets for MCP (bidirectional) */} + {isMcp && revD && [0, -42].map((off, i) => ( + + ))} ) } -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 isHub = node.id === 'litellm' - const iconSize = isHub ? 'w-[52px] h-[52px]' : 'w-[40px] h-[40px]' return ( - -
- {/* Ripple pulse when active */} - {active && ( - - )} - - {/* Icon circle */} +
- - {/* Spinning dashed ring on active hub */} + + {/* Icon + title */} +
+
+ +
+ + {node.title} + + {active && ( +
+ )} +
+ + {/* Subtitle */} +

+ {node.subtitle} +

+ + {/* Badge */} + {node.badge && ( +
+ + {node.badge} + +
+ )} + + {/* Token ticker on inference nodes */} + {active && node.tier === 'inference' && ( +
+ +
+ )} + + {/* Hub: spinning indicator ring */} {isHub && active && ( )}
-
- - {/* Label */} -
-

- {node.title} -

- {(active || isHub) && ( -

- {node.subtitle} -

- )} - {node.badge && ( - - {node.badge} - - )} -
-
+ +
) } +// ── Main slide ───────────────────────────────────────────────────────────────── export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) { - const i = t(lang) - const de = lang === 'de' - const nodes = getNodes(de) + const i = t(lang) + const de = lang === 'de' + const nodes = getNodes(de) const [activeId, setActiveId] = useState(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…'] : ['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 (
{/* Header */} @@ -329,151 +396,149 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) { {de ? 'Kundenmandanten' : 'Customer Namespaces'} {tenants.map(tn => ( - + {tn} ))}
- {/* ── THE MAP ──────────────────────────────────────────────────── */} -
+ {/* ── MAP ──────────────────────────────────────────────────────────── */} +
- {/* Ocean background (clipped to rounded rect) */} -
- {/* Fine dot grid */} -
- {/* Zone separator lines */} - {(['35%', '65%'] as const).map(y => ( -
- ))}
- {/* Zone labels */} - {[ - { y: '22%', label: de ? 'Produkte' : 'Products', clr: '#818cf8' }, - { y: '50%', label: de ? 'KI-Proxy' : 'AI Proxy', clr: '#fbbf24' }, - { y: '80%', label: de ? 'Inferenz' : 'Inference', clr: '#60a5fa' }, - ].map(({ y, label, clr }) => ( -
-
- - {label} - + {/* Tier separator lines */} + {['37%', '66%'].map(y => ( +
+ ))} + + {/* Tier labels (left) + BSI badges (right) */} + {tiers.map(({ y, label, clr, badge }) => ( +
+
+
+ + {label} + +
+ {badge && ( +
+ + + {badge} + +
+ )}
))} - {/* SVG: island territories + sea routes */} - - {/* Wide zone territory fills */} - - - + MCP ↔ +
- {/* Per-node island blobs (outer contour + fill) */} - {nodes.map(node => { - const isHub = node.id === 'litellm' - const isActive = activeId === node.id - const rx = isHub ? 10 : 7 - const ry = isHub ? 6 : 3.8 - const rxOuter = isHub ? 14.5 : 10 - const ryOuter = isHub ? 8.5 : 5.5 + {/* Infra tech chips (secondary, very subtle, in the infra tier band) */} +
+ {['PostgreSQL', 'MongoDB', 'Qdrant', 'Valkey', 'Nginx', 'Vault', 'Gitea', 'Orca', 'Woodpecker'].map(chip => ( + + {chip} + + ))} +
+ + {/* SVG: island territories + data-flow paths */} + + + {/* Tier zone fills */} + + + + + {/* Island blobs per node */} + {nodes.map(n => { + const isHub = n.id === 'litellm' + const isActive = activeId === n.id return ( - - {/* Outer contour ring */} - + - {/* Inner island fill */} - ) })} - {/* Sea routes */} - {CONNECTIONS.map(([fromId, toId]) => { - const key = `${fromId}-${toId}` - const from = nodes.find(n => n.id === fromId)! - const to = nodes.find(n => n.id === toId)! - const isActive = activeId === fromId || activeId === toId - const color = isActive - ? (activeId === fromId ? from.color : to.color) + {/* 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 + ? (activeId === c.from ? fromNode.color : toNode.color) : '#ffffff' return ( - + ) })} - {/* Node markers */} + {/* Node cards */} {nodes.map(node => ( - toggle(node.id)} + de={de} /> ))} {!activeId && ( -
+
{de ? 'Komponente anklicken' : 'Click any node'}
)}
- {/* ── DETAIL PANEL ─────────────────────────────────────────────── */} + {/* ── DETAIL PANEL ──────────────────────────────────────────────────── */} {active && ( + {/* Header */}
-
+

{active.title}

{active.badge && ( @@ -503,11 +569,17 @@ 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')} +
-

{active.subtitle}

+

{active.subtitle}

+ {/* Body */}

@@ -539,23 +611,26 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {

+ {/* Connects-to */}
{de ? 'Verbunden mit:' : 'Connects to:'} - {CONNECTIONS - .filter(([a, b]) => a === active.id || b === active.id) - .map(([a, b]) => { - const peerId = a === active.id ? b : a + {CONNS + .filter(c => c.from === active.id || c.to === active.id) + .map(c => { + const peerId = c.from === active.id ? c.to : c.from const peer = nodes.find(n => n.id === peerId)! + const label = c.type === 'mcp' ? 'MCP' : c.type === 'embed' ? 'Embed' : c.type === 'tool' ? 'Tool' : 'API' return ( ) })} @@ -566,15 +641,12 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) { {/* Bottom legend */} {!active && ( - + {[ - { icon: Lock, text: de ? 'DSGVO-konform · BSI-zertifizierbar' : 'GDPR-compliant · BSI-certifiable' }, - { icon: Server, text: de ? 'On-Premise oder EU-Cloud' : 'On-Premise or EU Cloud' }, - { icon: Layers, text: de ? 'Air-Gap fähig' : 'Air-Gap Capable' }, + { 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 }) => ( From e37aecab185037d65dda6732da7e0af46daa52dd Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com> Date: Mon, 20 Apr 2026 23:39:46 +0200 Subject: [PATCH 04/13] redesign ArchitectureSlide with metro/subway map theme - Replace rectangular cards with circular station nodes on metro tracks - Right-angle SVG paths only (no distortion with preserveAspectRatio=none) - Animated data packet flows along tracks, bidirectional MCP arc - LiteLLM hub enlarged (82px), centered mandants strip - Vertical tier labels, BSI badges, junction corner dots - Preserve all node data, detail panel, i18n, TokenTicker Co-Authored-By: Claude Sonnet 4.6 --- .../components/slides/ArchitectureSlide.tsx | 570 +++++++++--------- 1 file changed, 295 insertions(+), 275 deletions(-) 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 */} +
+
+
+
+ + BSI DC +
+
+
+
+ + + {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 && ( - - {/* 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 ( - @@ -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} ))} From ac8ef371ff198bd5fe5fa106d4c40b04072f652e Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com> Date: Mon, 20 Apr 2026 23:48:33 +0200 Subject: [PATCH 05/13] fix(pitch-deck): center mandants strip and fix LiteLLM overlap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove flex-1 trailing divider that was pushing pills left - Increase map height 420→480px to give clearance between app labels and gateway circle - Update SVG viewBox to 0 0 1100 480 (consistent with CONNS coordinates) - Update cy% for all nodes to match new 480px coordinate space (app 22.1%, gateway 50%, inference 80%) - Update SEP1/SEP2 to 36%/65% for new height - Update all SVG element y-coords: track lines, tick marks, junction dots, gateway stub Co-Authored-By: Claude Sonnet 4.6 --- .../components/slides/ArchitectureSlide.tsx | 72 +++++++++---------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/pitch-deck/components/slides/ArchitectureSlide.tsx b/pitch-deck/components/slides/ArchitectureSlide.tsx index 6c972ec..a14175a 100644 --- a/pitch-deck/components/slides/ArchitectureSlide.tsx +++ b/pitch-deck/components/slides/ArchitectureSlide.tsx @@ -39,30 +39,31 @@ interface ConnDef { from: NodeId to: NodeId type: ConnType - d: string // SVG path in viewBox "0 0 1100 420" — right-angle only + d: string // SVG path in viewBox "0 0 1100 480" — right-angle only revD?: string } // All right-angle paths — safe with preserveAspectRatio="none" + vectorEffect="non-scaling-stroke" +// viewBox: 0 0 1100 480 | app y=106 gateway y=240 inference y=384 const CONNS: ConnDef[] = [ { from: 'certifai', to: 'litellm', type: 'api', - d: 'M 195 105 L 195 158 L 522 158 L 522 210' }, + d: 'M 195 106 L 195 178 L 522 178 L 522 240' }, { from: 'complai', to: 'litellm', type: 'api', - d: 'M 550 105 L 550 210' }, + d: 'M 550 106 L 550 240' }, { from: 'scanner', to: 'litellm', type: 'api', - d: 'M 905 105 L 905 158 L 578 158 L 578 210' }, + d: 'M 905 106 L 905 178 L 578 178 L 578 240' }, { from: 'complai', to: 'scanner', type: 'mcp', - d: 'M 550 105 Q 727 52 905 105', - revD: 'M 905 105 Q 727 52 550 105' }, + d: 'M 550 106 Q 727 55 905 106', + revD: 'M 905 106 Q 727 55 550 106' }, { from: 'litellm', to: 'llm', type: 'api', - d: 'M 550 210 L 550 268 L 218 268 L 218 340' }, + d: 'M 550 240 L 550 308 L 218 308 L 218 384' }, { from: 'litellm', to: 'embeddings', type: 'embed', - d: 'M 550 210 L 550 340' }, + d: 'M 550 240 L 550 384' }, { from: 'litellm', to: 'tools', type: 'tool', - d: 'M 550 210 L 550 268 L 882 268 L 882 340' }, + d: 'M 550 240 L 550 308 L 882 308 L 882 384' }, ] -// cx = svgX/1100*100, cy = svgY/420*100 (exact match between SVG coords and CSS %) +// cx = svgX/1100*100, cy = svgY/480*100 (exact match between SVG coords and CSS %) function getNodes(de: boolean): NodeDef[] { return [ { @@ -71,7 +72,7 @@ function getNodes(de: boolean): NodeDef[] { 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: 17.7, cy: 25, tier: 'product', + cx: 17.7, cy: 22.1, tier: 'product', badge: 'Rust · Dioxus', tech: ['Rust', 'Dioxus', 'MongoDB', 'Keycloak', 'SearXNG', 'LangGraph'], services: [ @@ -87,7 +88,7 @@ function getNodes(de: boolean): NodeDef[] { 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: 25, tier: 'product', + cx: 50, cy: 22.1, tier: 'product', badge: 'Next.js · FastAPI', tech: ['Next.js 15', 'FastAPI', 'Go/Gin', 'PostgreSQL', 'Qdrant', 'Valkey'], services: [ @@ -103,7 +104,7 @@ function getNodes(de: boolean): NodeDef[] { 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.3, cy: 25, tier: 'product', + cx: 82.3, cy: 22.1, tier: 'product', badge: 'Rust · Axum', tech: ['Rust', 'Axum', 'MongoDB', 'Semgrep', 'Gitleaks', 'Syft'], services: [ @@ -136,7 +137,7 @@ function getNodes(de: boolean): NodeDef[] { 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: 17.7, cy: 81, tier: 'inference', + cx: 17.7, cy: 80, tier: 'inference', badge: 'On-Premise · BSI', tech: ['Qwen3-32B', 'Qwen3-Coder-30B', 'DeepSeek-R1-8B', 'Ollama'], services: [ @@ -151,7 +152,7 @@ function getNodes(de: boolean): NodeDef[] { 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: 81, tier: 'inference', + cx: 50, cy: 80, tier: 'inference', badge: de ? 'EU-Souverän' : 'EU Sovereign', tech: ['bge-m3', 'Qdrant Vector DB', 'Sentence-Transformers'], services: [ @@ -166,7 +167,7 @@ function getNodes(de: boolean): NodeDef[] { 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.3, cy: 81, tier: 'inference', + cx: 82.3, cy: 80, tier: 'inference', badge: de ? 'EU-Souverän' : 'EU Sovereign', tech: ['SearXNG', 'MCP Protocol', 'Semgrep API', 'Gitleaks API'], services: [ @@ -377,10 +378,10 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) { ? ['Mandant A', 'Mandant B', 'Mandant C', 'Mandant N…'] : ['Namespace A', 'Namespace B', 'Namespace C', 'Namespace N…'] - // 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%' + // Tier separator positions (% of 480px container height) + // App: 0–36% (y=0–173), Gateway: 36–65% (y=173–312), Inference: 65–100% + const SEP1 = '36%' + const SEP2 = '65%' return (
@@ -410,11 +411,10 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) { {tn} ))} -
{/* ── METRO MAP ──────────────────────────────────────────────── */} -
+
{/* Background */}
+ style={{ top: SEP1, height: '29%', background: 'rgba(251,191,36,0.032)' }} />
@@ -451,7 +451,7 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) { {de ? 'ANWENDUNG' : 'APP LAYER'}
-
+
GATEWAY @@ -468,7 +468,7 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) { {/* BSI badges — right strip */}
-
+
BSI DC @@ -494,40 +494,40 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) { {/* ── SVG: metro tracks + animated data flows ── */} + viewBox="0 0 1100 480" preserveAspectRatio="none"> - {/* Horizontal metro line — Application layer (y=105) */} - {/* Station tick marks on app line */} {[195, 550, 905].map(x => ( - ))} - {/* 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 }, + { cx: 195, cy: 178 }, { cx: 522, cy: 178 }, + { cx: 905, cy: 178 }, { cx: 578, cy: 178 }, + { cx: 218, cy: 308 }, { cx: 550, cy: 308 }, { cx: 882, cy: 308 }, ].map(({ cx, cy }) => ( From 34e2614e366ae6e3066a21a4c0fe22eb1a846009 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com> Date: Tue, 21 Apr 2026 09:06:45 +0200 Subject: [PATCH 06/13] feat(pitch-deck): redesign architecture slide with V4 layered stack MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port Claude Design's V4 design exactly: three 3D perspective slabs (Application / Gateway / Infrastructure) with animated data-flow connectors between them, per-node live activity tickers, and a slide-up detail panel. Replaces metro map with V4's floating slab aesthetic — dark purple gradient background, CSS perspective rotateX transforms, JetBrains Mono terminal tickers, amber LiteLLM hub with pulse indicator. All node data (titles, tech stacks, services) preserved from previous design. Co-Authored-By: Claude Sonnet 4.6 --- .../components/slides/ArchitectureSlide.tsx | 1022 +++++++++-------- 1 file changed, 528 insertions(+), 494 deletions(-) diff --git a/pitch-deck/components/slides/ArchitectureSlide.tsx b/pitch-deck/components/slides/ArchitectureSlide.tsx index a14175a..f75b827 100644 --- a/pitch-deck/components/slides/ArchitectureSlide.tsx +++ b/pitch-deck/components/slides/ArchitectureSlide.tsx @@ -1,6 +1,6 @@ 'use client' -import { useState, useEffect } from 'react' +import { useState, useEffect, useRef, Fragment } from 'react' import { motion, AnimatePresence } from 'framer-motion' import { Language } from '@/lib/types' import { t } from '@/lib/i18n' @@ -9,13 +9,11 @@ import FadeInView from '../ui/FadeInView' import { Brain, Shield, ScanLine, Zap, Cpu, Layers, Wrench, X, Users, Lock, - Server, Network, ChevronRight, BadgeCheck, + Server, BadgeCheck, } from 'lucide-react' interface ArchitectureSlideProps { lang: Language } - type NodeId = 'certifai' | 'complai' | 'scanner' | 'litellm' | 'llm' | 'embeddings' | 'tools' -type ConnType = 'api' | 'mcp' | 'embed' | 'tool' interface NodeDef { id: NodeId @@ -23,57 +21,19 @@ interface NodeDef { title: string subtitle: string color: string - twColor: string - twBorder: string - twBg: string - twDot: string - 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 }[] - badge?: string + primary?: boolean + tier: 'product' | 'proxy' | 'inference' } -interface ConnDef { - from: NodeId - to: NodeId - type: ConnType - d: string // SVG path in viewBox "0 0 1100 480" — right-angle only - revD?: string -} - -// All right-angle paths — safe with preserveAspectRatio="none" + vectorEffect="non-scaling-stroke" -// viewBox: 0 0 1100 480 | app y=106 gateway y=240 inference y=384 -const CONNS: ConnDef[] = [ - { from: 'certifai', to: 'litellm', type: 'api', - d: 'M 195 106 L 195 178 L 522 178 L 522 240' }, - { from: 'complai', to: 'litellm', type: 'api', - d: 'M 550 106 L 550 240' }, - { from: 'scanner', to: 'litellm', type: 'api', - d: 'M 905 106 L 905 178 L 578 178 L 578 240' }, - { from: 'complai', to: 'scanner', type: 'mcp', - d: 'M 550 106 Q 727 55 905 106', - revD: 'M 905 106 Q 727 55 550 106' }, - { from: 'litellm', to: 'llm', type: 'api', - d: 'M 550 240 L 550 308 L 218 308 L 218 384' }, - { from: 'litellm', to: 'embeddings', type: 'embed', - d: 'M 550 240 L 550 384' }, - { from: 'litellm', to: 'tools', type: 'tool', - d: 'M 550 240 L 550 308 L 882 308 L 882 384' }, -] - -// cx = svgX/1100*100, cy = svgY/480*100 (exact match between SVG coords and CSS %) function getNodes(de: boolean): NodeDef[] { return [ { 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: 17.7, cy: 22.1, tier: 'product', - badge: 'Rust · Dioxus', + color: '#c084fc', tier: 'product', tech: ['Rust', 'Dioxus', 'MongoDB', 'Keycloak', 'SearXNG', 'LangGraph'], services: [ { name: 'LiteLLM Dashboard', desc: de ? 'Modellverwaltung & Kostentracking' : 'Model mgmt & cost tracking' }, @@ -86,10 +46,7 @@ function getNodes(de: boolean): NodeDef[] { 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.1, tier: 'product', - badge: 'Next.js · FastAPI', + 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' }, @@ -102,10 +59,7 @@ function getNodes(de: boolean): NodeDef[] { 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.3, cy: 22.1, tier: 'product', - badge: 'Rust · Axum', + color: '#34d399', tier: 'product', tech: ['Rust', 'Axum', 'MongoDB', 'Semgrep', 'Gitleaks', 'Syft'], services: [ { name: 'SAST / SBOM / CVE', desc: de ? 'Vollautomatische Pipeline' : 'Fully automated pipeline' }, @@ -118,10 +72,7 @@ function getNodes(de: boolean): NodeDef[] { 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: 50, tier: 'proxy', - badge: 'Hub', + 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' }, @@ -135,10 +86,7 @@ function getNodes(de: boolean): NodeDef[] { 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: 17.7, cy: 80, tier: 'inference', - badge: 'On-Premise · BSI', + 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' }, @@ -150,10 +98,7 @@ function getNodes(de: boolean): NodeDef[] { 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: 80, tier: 'inference', - badge: de ? 'EU-Souverän' : 'EU Sovereign', + 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' }, @@ -165,10 +110,7 @@ function getNodes(de: boolean): NodeDef[] { 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.3, cy: 80, tier: 'inference', - badge: de ? 'EU-Souverän' : 'EU Sovereign', + color: '#2dd4bf', tier: 'inference', tech: ['SearXNG', 'MCP Protocol', 'Semgrep API', 'Gitleaks API'], services: [ { name: 'SearXNG', desc: de ? 'Anonymisierte EU-Websuche' : 'Anonymized EU web search' }, @@ -179,212 +121,389 @@ function getNodes(de: boolean): NodeDef[] { ] } -// ── Token counter ───────────────────────────────────────────────────────────── -function TokenTicker({ color }: { color: string }) { - const [n, setN] = useState(() => 12480 + Math.floor(Math.random() * 8000)) +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; } + } +` + +// ── Ticker primitives ───────────────────────────────────────────────────────── +function useTicker(fn: () => void, min = 140, max = 360, skipChance = 0.1) { + const ref = useRef(fn) + ref.current = fn useEffect(() => { - const id = setInterval(() => setN(v => v + Math.floor(Math.random() * 180 + 40)), 220) - return () => clearInterval(id) - }, []) + let tid: ReturnType + const loop = () => { + if (Math.random() > skipChance) ref.current() + tid = setTimeout(loop, min + Math.random() * (max - min)) + } + loop() + return () => clearTimeout(tid) + }, [min, max, skipChance]) +} + +const MONO: React.CSSProperties = { + fontFamily: '"JetBrains Mono","SF Mono",ui-monospace,monospace', + fontVariantNumeric: 'tabular-nums', +} + +function TickerShell({ color, children }: { color: string; children: React.ReactNode }) { return ( -
- - - {n.toLocaleString('de-DE')} tok/s - +
{children}
+ ) +} + +function Caret({ color }: { color: string }) { + return ( + + ) +} + +// ── Per-node live tickers ───────────────────────────────────────────────────── +function TickCertifAI({ color }: { color: string }) { + 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 ( + + + sig + {n.toLocaleString()} + {hash} + + ) +} + +function TickComplAI({ color }: { color: string }) { + 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 ( + + + eval + {evals.toLocaleString()} + pass + {rate.toFixed(1)}% + + ) +} + +function TickScanner({ color }: { color: string }) { + const lines = [ + { k: 'PASS', c: '#4ade80', t: 'CWE-79 xss check' }, + { k: 'WARN', c: '#fbbf24', t: 'drift: model v2.1→2.2' }, + { k: 'PASS', c: '#4ade80', t: 'bias: demographic parity' }, + { k: 'FAIL', c: '#f87171', t: 'license: GPL-3 detected' }, + { k: 'PASS', c: '#4ade80', t: 'prompt-inject: 214 vectors' }, + { k: 'SCAN', c: '#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 ( + + {l.k} + {l.t} + + ) +} + +function TickLiteLLM({ color }: { color: string }) { + 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 ( + + + req/s + {Math.round(rps)} + · + p50 + {Math.round(p50)}ms + + + ) +} + +function TickLLM({ color }: { color: string }) { + 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 ( + + + tok + {tokens.toLocaleString()} + + {stream} + + + ) +} + +function TickEmbeddings({ color }: { color: string }) { + const [vecs, setVecs] = useState(284112) + useTicker(() => setVecs(v => v + 1 + Math.floor(Math.random() * 8)), 180, 420, 0.1) + return ( + + + idx + {vecs.toLocaleString()} + · 1024d + + + ) +} + +function TickTools({ color }: { color: string }) { + 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 ( + + + call + {ops[i]} + + ) +} + +const NODE_TICKER: Record> = { + certifai: TickCertifAI, + complai: TickComplAI, + scanner: TickScanner, + litellm: TickLiteLLM, + llm: TickLLM, + embeddings: TickEmbeddings, + tools: TickTools, +} + +// ── Animated connector between layers ──────────────────────────────────────── +function LayerConnector({ tint }: { tint: string }) { + const tracks = [ + { x: '32%', primary: false }, + { x: '50%', primary: true }, + { x: '68%', primary: false }, + ] + return ( +
+ {tracks.map(({ x, primary }, ti) => { + const color = primary ? '#fbbf24' : tint + const dots = primary ? 4 : 3 + const dur = primary ? 1.6 : 2.4 + return ( +
+ {/* Rail */} +
+ {/* Staggered dots */} + {Array.from({ length: dots }, (_, j) => ( +
+ ))} +
+ ) + })}
) } -// ── Data-flow packets along metro tracks ────────────────────────────────────── -function DataFlow({ d, revD, color, active, type }: { - d: string; revD?: string; color: string; active: boolean; type: ConnType +// ── Single node card ────────────────────────────────────────────────────────── +function NodeCard({ + node, selected, onClick, +}: { + node: NodeDef; selected: boolean; onClick: () => void }) { - 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 + const [hover, setHover] = useState(false) + const active = hover || selected + const c = node.color + const Ticker = NODE_TICKER[node.id] + const Icon = node.icon return ( - - {/* Track rail */} - - {/* Primary packet */} - - {/* Staggered trailing packet when active */} - {active && ( - + ) } -// ── 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 +// ── 3D perspective slab ─────────────────────────────────────────────────────── +function LayerSlab({ + label, sublabel, nodes, tint, depth, selectedId, onSelect, +}: { + label: string; sublabel: string; nodes: NodeDef[] + tint: string; depth: number + selectedId: NodeId | null; onSelect: (id: NodeId) => void }) { - const Icon = node.icon - const isHub = node.id === 'litellm' - const sz = isHub ? 82 : 52 - + const isProxy = nodes.length === 1 && !!nodes[0].primary return ( -
- - {/* Station circle */} -
- {/* Outer pulse ring on active */} - {active && ( - - )} - {/* Hub ambient glow */} - {isHub && ( -
- )} - {/* Main circle */} -
- + {/* Top edge highlight */} +
+ {/* Layer label row */} +
+
{label}
+
{sublabel}
+
+ {/* Cards */} +
+ {isProxy ? ( +
+ onSelect(nodes[0].id)} />
- {/* Hub spinning dashed ring */} - {isHub && active && ( - - )} - {/* Active indicator dot */} - {active && ( -
- )} -
- - {/* Station label */} -
-
- {node.title} -
-
- {node.subtitle} -
- {node.badge && ( -
- {node.badge} -
- )} - {active && node.tier === 'inference' && ( -
- )} -
- + ) : ( + nodes.map(n => ( + onSelect(n.id)} /> + )) + )} +
) } // ── Main slide ──────────────────────────────────────────────────────────────── export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) { - const i = t(lang) - const de = lang === 'de' - const nodes = getNodes(de) + const i = t(lang) + const de = lang === 'de' + const allNodes = getNodes(de) + const nodeMap = Object.fromEntries(allNodes.map(n => [n.id, n])) as Record const [activeId, setActiveId] = useState(null) - const active = nodes.find(n => n.id === activeId) ?? 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…'] - // Tier separator positions (% of 480px container height) - // App: 0–36% (y=0–173), Gateway: 36–65% (y=173–312), Inference: 65–100% - const SEP1 = '36%' - const SEP2 = '65%' + 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 (
+ + {/* Header */}

@@ -394,13 +513,13 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) { {i.annex.architecture.title}

- {de ? 'Klicke auf eine Station für Details' : 'Click any station to explore'} + {de ? 'Klicke auf eine Station für Details' : 'Click any node to explore'}

- + {/* Customer namespace strip */} -
+
{de ? 'Kundenmandanten' : 'Customer Namespaces'} @@ -413,268 +532,183 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) { ))}
- {/* ── METRO MAP ──────────────────────────────────────────────── */} -
+ {/* ── MAIN CANVAS ─────────────────────────────────────────────── */} +
- {/* Background */} -
-
-
+ {/* Ambient glows */} +
+
- {/* Tier band fills */} -
-
-
- - {/* Tier separator lines */} - {[SEP1, SEP2].map(y => ( -
- ))} - - {/* Vertical tier labels — left strip */} -
-
- - {de ? 'ANWENDUNG' : 'APP LAYER'} - -
-
- - GATEWAY - -
-
- - {de ? 'INFERENZ' : 'INFERENCE'} - -
-
- - {/* BSI badges — right strip */} -
-
-
-
- - BSI DC -
-
-
-
- - - {de ? 'EU-Souverän' : 'EU Sovereign'} - -
-
-
- - {/* MCP label */} -
- - ⇌ MCP - -
- - {/* ── SVG: metro tracks + animated data flows ── */} - - - {/* Horizontal metro line — Application layer (y=106) */} - - {/* Station tick marks on app line */} - {[195, 550, 905].map(x => ( - - ))} - - {/* Horizontal metro line — Inference layer (y=384) */} - - {/* 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: 178 }, { cx: 522, cy: 178 }, - { cx: 905, cy: 178 }, { cx: 578, cy: 178 }, - { cx: 218, cy: 308 }, { cx: 550, cy: 308 }, { cx: 882, cy: 308 }, - ].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 - ? (activeId === c.from ? fromNode.color : toNode.color) - : '#ffffff' + {/* Slabs + connectors */} +
+ {LAYERS.map((layer, li) => { + const nodes = layer.nodeIds.map(id => nodeMap[id]) return ( - + + + {li < LAYERS.length - 1 && } + ) })} - +
- {/* Metro station nodes */} - {nodes.map(node => ( - toggle(node.id)} - de={de} - /> - ))} - - {!activeId && ( -
- - {de ? 'Station anklicken' : 'Click any station'} -
- )} -
- - {/* ── DETAIL PANEL ─────────────────────────────────────────────── */} - - {active && ( - - - -
-
- -
-
-
-

{active.title}

- {active.badge && ( - - {active.badge} - - )} - - {active.tier === 'product' ? (de ? 'Anwendungsschicht' : 'Application Layer') : - active.tier === 'proxy' ? (de ? 'GenAI-Infrastruktur' : 'GenAI Infra') : - (de ? 'Inferenzschicht' : 'Inference Layer')} - -
-

{active.subtitle}

-
+ {/* Footer badges */} +
+ {([ + { 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 }) => ( +
+ + {label}
+ ))} +
-
-
-

- {de ? 'Stack' : 'Tech Stack'} -

-
- {active.tech.map(tk => ( - - {tk} - - ))} -
-
-
-

- {de ? 'Funktionen' : 'Capabilities'} -

-
- {active.services.map(s => ( -
-
-
- {s.name} - {s.desc} + {/* ── Detail panel: slides up from bottom ── */} + + {active && ( + +
+ {/* Panel header */} +
+
+
+ +
+
+
+ + {active.title} + + + {active.tier === 'product' ? (de ? 'Anwendung' : 'Application') : + active.tier === 'proxy' ? 'Gateway' : + (de ? 'Inferenz' : 'Inference')} + +
+
+ {active.subtitle}
- ))} +
+ +
+ {/* Tech + capabilities grid */} +
+
+
+ {de ? 'Stack' : 'Tech Stack'} +
+
+ {active.tech.map(tk => ( + {tk} + ))} +
+
+
+
+ {de ? 'Funktionen' : 'Capabilities'} +
+
+ {active.services.map(s => ( +
+
+ {s.name} + {s.desc} +
+ ))} +
+
-
- -
- - - {de ? 'Verbunden mit:' : 'Connects to:'} - - {CONNS - .filter(c => c.from === active.id || c.to === active.id) - .map(c => { - const peerId = c.from === active.id ? c.to : c.from - const peer = nodes.find(n => n.id === peerId)! - const label = c.type === 'mcp' ? 'MCP' : c.type === 'embed' ? 'Embed' : c.type === 'tool' ? 'Tool' : 'API' - return ( - - ) - })} -
-
- )} -
- - {/* 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: BadgeCheck, text: de ? 'EU-souveräne Inferenz' : 'EU-sovereign inference' }, - ].map(({ icon: Icon, text }) => ( - - {text} - - ))} - - )} + + )} + +
) From 8a4e1968640d876d8aa286d43cc73f70d08b3231 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com> Date: Tue, 21 Apr 2026 10:03:18 +0200 Subject: [PATCH 07/13] feat(pitch-deck): redesign USP slide with symmetric bridge composition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port Claude Design's USP v2 exactly: Compliance ↔ Code bridge with a central glowing orb, two pillar rows per column, animated SVG flow connectors, and four Under-the-Hood feature cards with live tickers. Detail modal (Framer Motion AnimatePresence) slides in on click for all 9 interactive elements. Star field background. All text is our actual content (RFQ Verification, Process Compliance, Bidirectional Sync, Continuous, End-to-End Traceability, Compliance Optimizer, EU Trust Stack, killer quote). Full DE/EN i18n. Co-Authored-By: Claude Sonnet 4.6 --- pitch-deck/components/slides/USPSlide.tsx | 931 +++++++++++++++++----- 1 file changed, 749 insertions(+), 182 deletions(-) diff --git a/pitch-deck/components/slides/USPSlide.tsx b/pitch-deck/components/slides/USPSlide.tsx index 7c0904b..84ece33 100644 --- a/pitch-deck/components/slides/USPSlide.tsx +++ b/pitch-deck/components/slides/USPSlide.tsx @@ -1,222 +1,789 @@ 'use client' +import { useState, useEffect, useRef, useMemo } from 'react' +import { motion, AnimatePresence } from 'framer-motion' import { Language } from '@/lib/types' import GradientText from '../ui/GradientText' import FadeInView from '../ui/FadeInView' -import GlassCard from '../ui/GlassCard' -import { - FileCheck, - Code, - Zap, - Shield, - GitPullRequest, - ArrowLeftRight, -} from 'lucide-react' +import { X } from 'lucide-react' -interface USPSlideProps { - lang: Language +interface USPSlideProps { lang: Language } + +const MONO: React.CSSProperties = { + fontFamily: '"JetBrains Mono","SF Mono",ui-monospace,monospace', + fontVariantNumeric: 'tabular-nums', } -export default function USPSlide({ lang }: USPSlideProps) { - const de = lang === 'de' +const CSS_KF = ` + @keyframes uspFlowR { 0%{stroke-dashoffset:0} 100%{stroke-dashoffset:-14px} } + @keyframes uspSpin { from{transform:rotate(0deg)} to{transform:rotate(360deg)} } + @keyframes uspPulse { + 0%,100% { box-shadow: 0 0 38px rgba(167,139,250,.55), 0 0 80px rgba(167,139,250,.2), inset 0 3px 0 rgba(255,255,255,.35), inset 0 -6px 12px rgba(0,0,0,.35); } + 50% { box-shadow: 0 0 58px rgba(167,139,250,.85), 0 0 110px rgba(167,139,250,.35), inset 0 3px 0 rgba(255,255,255,.4), inset 0 -6px 12px rgba(0,0,0,.35); } + } + @keyframes uspHeading { + 0%,100% { text-shadow: 0 0 22px rgba(167,139,250,.3); } + 50% { text-shadow: 0 0 36px rgba(167,139,250,.55); } + } +` - const subtitle = de - ? 'Die erste Plattform, die Compliance-Dokumente und tatsächliche Code-Umsetzung verbindet' - : 'The first platform that connects compliance documents with actual code implementation' +// ── Ticker ──────────────────────────────────────────────────────────────────── +function useTicker(fn: () => void, min = 180, max = 420, skip = 0.1) { + const ref = useRef(fn) + ref.current = fn + useEffect(() => { + let t: ReturnType + const loop = () => { + if (Math.random() > skip) ref.current() + t = setTimeout(loop, min + Math.random() * (max - min)) + } + loop() + return () => clearTimeout(t) + }, [min, max, skip]) +} - const complianceItems = de - ? ['DSGVO-Dokumente', 'Audit-Management', 'RFQ-Anforderungen', 'CE-Bewertungen'] - : ['GDPR documents', 'Audit management', 'RFQ requirements', 'CE assessments'] +function TickerShell({ tint, children }: { tint: string; children: React.ReactNode }) { + return ( +
{children}
+ ) +} - const codeItems = de - ? ['SAST / DAST / SBOM', 'Pentesting', 'Issue-Tracker', 'Auto-Fixes'] - : ['SAST / DAST / SBOM', 'Pentesting', 'Issue tracker', 'Auto-fixes'] +function TickTrace({ tint }: { tint: string }) { + const [n, setN] = useState(12748) + useTicker(() => setN(v => v + 1 + Math.floor(Math.random() * 3)), 250, 500) + return ( + + + trace + {n.toLocaleString()} + evidence-chain + + ) +} - const capabilities = [ - { - icon: GitPullRequest, - color: 'text-indigo-400', - label: de ? 'RFQ-Prüfung' : 'RFQ Verification', - desc: de - ? 'Kunden-Anforderungsdokumente werden automatisiert gegen die aktuelle Source-Code-Umsetzung geprüft. Abweichungen werden erkannt, Änderungen vorgeschlagen und auf Wunsch direkt im Code umgesetzt — ohne manuelles Nacharbeiten.' +function TickEngine({ tint }: { tint: string }) { + const [v, setV] = useState(428) + const [rate, setRate] = useState(99.4) + useTicker(() => { + setV(x => x + 1 + Math.floor(Math.random() * 4)) + setRate(r => Math.max(97, Math.min(99.9, r + (Math.random() - 0.5) * 0.3))) + }, 220, 420) + return ( + + + validate + {v.toLocaleString()} + {rate.toFixed(1)}% + + ) +} + +function TickOptimizer({ tint }: { tint: string }) { + const ops = ['ROI: 2.418 € / dev', 'gap → policy §4.2', 'dedup 128 tickets', 'sweet-spot: 22 KLOC', 'tradeoff: speed↔risk'] + const [i, setI] = useState(0) + useTicker(() => setI(x => (x + 1) % ops.length), 900, 1600, 0.05) + return ( + + + optimize + {ops[i]} + + ) +} + +function TickStack({ tint }: { tint: string }) { + const regs = ['DSGVO', 'NIS-2', 'DORA', 'EU AI Act', 'ISO 27001', 'BSI C5'] + const [i, setI] = useState(0) + const [c, setC] = useState(1208) + useTicker(() => { setI(x => (x + 1) % regs.length); setC(v => v + Math.floor(Math.random() * 3)) }, 800, 1400, 0.05) + return ( + + + check + {regs[i]} + · + {c.toLocaleString()} + + ) +} + +// ── Data ────────────────────────────────────────────────────────────────────── +interface DetailItem { + tint: string + icon: string + kicker: string + title: string + body: string + bullets?: string[] + stat?: { k: string; v: string } +} + +function getDetails(de: boolean): Record { + return { + rfq: { + tint: '#a78bfa', icon: '⇄', + kicker: de ? 'Säule · Compliance' : 'Pillar · Compliance', + title: de ? 'RFQ-Prüfung' : 'RFQ Verification', + body: de + ? 'Kunden-Anforderungsdokumente werden automatisch gegen den aktuellen Source-Code geprüft. Abweichungen werden erkannt, Änderungen vorgeschlagen und auf Wunsch direkt im Code umgesetzt — ohne manuelles Nacharbeiten.' : 'Customer requirement documents are automatically verified against current source code. Deviations are detected, changes proposed and implemented directly in code on request — no manual rework needed.', + bullets: de + ? ['Klauseln automatisch gegen SBOM, SAST-Findings und Policy-Docs abgeglichen', 'Lücken mit konkreten Implementierungsvorschlägen markiert', 'RFQ-Antworten in Stunden statt Wochen'] + : ['Auto-match clauses against SBOM, SAST findings and policy docs', 'Flag gaps with concrete implementation proposals', 'Win-ready RFQ replies in hours, not weeks'], + stat: { k: de ? 'Ø Antwortzeit' : 'avg response time', v: de ? '4,2h (war 12 Tage)' : '4.2h (was 12 days)' }, }, - { - icon: ArrowLeftRight, - color: 'text-purple-400', - label: de ? 'Bidirektional' : 'Bidirectional', - desc: de - ? 'Compliance-Anforderungen fliessen direkt in den Code. Umgekehrt aktualisieren Code-Änderungen automatisch die Compliance-Dokumentation. Beide Seiten sind immer synchron — kein Informationsverlust zwischen Audit und Entwicklung.' - : 'Compliance requirements flow directly into code. Conversely, code changes automatically update compliance documentation. Both sides always stay in sync — no information loss between audit and development.', - }, - { - icon: Zap, - color: 'text-amber-400', - label: de ? 'Prozess-Compliance' : 'Process Compliance', - desc: de + process: { + tint: '#c084fc', icon: '⟲', + kicker: de ? 'Säule · Compliance' : 'Pillar · Compliance', + title: de ? 'Prozess-Compliance' : 'Process Compliance', + body: de ? 'Vom Audit-Finding über das Ticket bis zur Code-Änderung läuft der gesamte Prozess automatisiert durch. Rollen, Fristen und Eskalation werden End-to-End verwaltet. Nachweise werden automatisch generiert und archiviert.' : 'From audit finding to ticket to code change, the entire process runs automatically. Roles, deadlines and escalation are managed end-to-end. Evidence is automatically generated and archived.', + bullets: de + ? ['Finding → Ticket → PR → Nachweis in einem Thread', 'SLA-Tracking pro Control mit Auto-Eskalation', 'Unveränderliches Audit-Log, pro Änderung signiert'] + : ['Finding → ticket → PR → evidence in one thread', 'SLA tracking per control with auto-escalation', 'Immutable audit log signed per change'], + stat: { k: de ? 'automatisierte Prozessschritte' : 'process steps automated', v: '87%' }, }, - { - icon: Shield, - color: 'text-emerald-400', - label: de ? 'Kontinuierlich' : 'Continuous', - desc: de + bidir: { + tint: '#fbbf24', icon: '⟷', + kicker: de ? 'Säule · Code' : 'Pillar · Code', + title: de ? 'Bidirektional' : 'Bidirectional Sync', + body: de + ? 'Compliance-Anforderungen fliessen direkt in den Code. Umgekehrt aktualisieren Code-Änderungen automatisch die Compliance-Dokumentation. Beide Seiten sind immer synchron — kein Informationsverlust zwischen Audit und Entwicklung.' + : 'Compliance requirements flow directly into code. Conversely, code changes automatically update compliance documentation. Both sides always stay in sync — no information loss between audit and development.', + bullets: de + ? ['Policy ↔ Code-Mapping via semantischem Diff', 'Git-nativ: jede Änderung als PR', 'Zero Drift zwischen Audit-Artefakten und Realität'] + : ['Policy ↔ code mapping via semantic diff', 'Git-native: every change shipped as a PR', 'Zero drift between audit artefacts and reality'], + stat: { k: de ? 'Drift-Vorfälle' : 'drift incidents', v: de ? '0 seit März 2024' : '0 since Mar-2024' }, + }, + cont: { + tint: '#f59e0b', icon: '◎', + kicker: de ? 'Säule · Code' : 'Pillar · Code', + title: de ? 'Kontinuierlich' : 'Continuous, Not Yearly', + body: de ? 'Klassische Compliance prüft einmal im Jahr und hofft auf das Beste. Unsere Plattform prüft bei jeder Code-Änderung. Findings werden sofort zu Tickets mit konkreten Implementierungsvorschlägen im Issue-Tracker der Wahl.' : 'Traditional compliance checks once a year and hopes for the best. Our platform checks on every code change. Findings immediately become tickets with concrete implementation proposals in the issue tracker of choice.', + bullets: de + ? ['CI-integrierte Validierung bei jedem Push', 'Fix-Vorschläge generiert, nicht nur gemeldet', 'Compliance-Frische: Minuten statt Monate'] + : ['CI-integrated validation on each push', 'Fix suggestions generated, not just reported', 'Compliance freshness: minutes, not months'], + stat: { k: de ? 'Validierungen / Tag' : 'validations / day', v: '~2.400 / repo' }, }, - ] + trace: { + tint: '#a78bfa', icon: '⇄', + kicker: de ? 'Under the Hood' : 'Under the Hood', + title: de ? 'End-to-End Rückverfolgbarkeit' : 'End-to-End Traceability', + body: de + ? 'Regulatorische Anforderungen (Gesetz → Obligation → Control) deterministisch mit realem Systemzustand und Code verknüpft — inklusive revisionssicherem Evidence-Layer.' + : 'Regulatory requirements (law → obligation → control) deterministically linked to real system state and code — including audit-proof evidence layer.', + bullets: de + ? ['Versionierter Evidence-Chain, unveränderlich gespeichert', 'Ein Klick von Klausel bis Codezeile', 'Signierte Attestierungen pro Build'] + : ['Versioned evidence chain stored immutably', 'One-click drill from clause to line of code', 'Signed attestations per build'], + }, + engine: { + tint: '#c084fc', icon: '◉', + kicker: de ? 'Under the Hood' : 'Under the Hood', + title: de ? 'Continuous Compliance Engine' : 'Continuous Compliance Engine', + body: de + ? 'Statt punktueller Audits: Validierung bei jeder Änderung (Code, Infrastruktur, Prozesse) mit auditierbaren Nachweisen in Echtzeit.' + : 'Instead of point-in-time audits: validation on every change (code, infrastructure, processes) with auditable evidence in real time.', + bullets: de + ? ['Rule-Packs pro Framework (NIS-2, DORA, …)', 'Verarbeitet Code, IaC und Prozess-Events', 'Findings automatisch ans richtige Team geroutet'] + : ['Rule packs per framework (NIS-2, DORA, …)', 'Handles code, infra-as-code, and process events', 'Findings routed to the right team automatically'], + }, + opt: { + tint: '#fbbf24', icon: '✦', + kicker: de ? 'Under the Hood' : 'Under the Hood', + title: de ? 'Compliance Optimizer' : 'Compliance Optimizer', + body: de + ? 'Nicht nur „erlaubt/verboten", sondern die maximal zulässige Ausgestaltung jedes KI-Use-Cases. Deterministische Constraint-Optimierung zeigt den Sweet Spot zwischen Regulierung und Innovation — ersetzt 20–200k EUR Anwaltskosten.' + : 'Not just "allowed/forbidden" but the maximum permissible configuration of every AI use case. Deterministic constraint optimization shows the sweet spot between regulation and innovation — replaces EUR 20–200k in legal fees.', + bullets: de + ? ['ROI-Ranking jedes offenen Findings', 'Abwägung zwischen Liefergeschwindigkeit und Restrisiko', 'Low-Hanging-Wins zuerst'] + : ['ROI-ranks every open finding', 'Balances speed of delivery with residual risk', 'Highlights low-hanging wins first'], + }, + stack: { + tint: '#f59e0b', icon: '◎', + kicker: de ? 'Under the Hood' : 'Under the Hood', + title: de ? 'EU-Trust & Governance Stack' : 'EU Trust & Governance Stack', + body: de + ? 'Souveräne, DSGVO-/AI-Act-konforme Architektur (EU-Hosting, Isolation, Betriebsrat-Fähigkeit) — Marktzugang, den US-Lösungen strukturell nicht erreichen.' + : 'Sovereign, GDPR/AI Act compliant architecture (EU hosting, isolation, works council capability) — market access that US solutions structurally cannot achieve.', + bullets: de + ? ['DSGVO · NIS-2 · DORA · EU AI Act · ISO 27001 · BSI C5', 'EU-souveränes Hosting und Key-Management', 'Eine Plattform, ein Audit, eine Rechnung'] + : ['DSGVO · NIS-2 · DORA · EU AI Act · ISO 27001 · BSI C5', 'EU-sovereign hosting and key-management', 'One platform, one audit, one bill'], + }, + hub: { + tint: '#a78bfa', icon: '∞', + kicker: de ? 'Die Schleife' : 'The Loop', + title: de ? 'Compliance ↔ Code · Immer in Sync' : 'Compliance ↔ Code · Always in sync', + body: de + ? 'Die Plattform ist eine einzige geschlossene Schleife. Jede Policy-Änderung fliesst in den Code; jede Code-Änderung fliesst in die Policy zurück.' + : 'The platform is a single closed loop. Every policy change ripples into code; every code change ripples back into policy. That\'s the USP in one diagram.', + bullets: de + ? ['Single Source of Truth, zwei Oberflächen', 'Echtzeit-Sync, kein Batch-Abgleich', 'Auditoren, Entwickler und Sales fragen denselben Graphen ab'] + : ['Single source of truth, two surfaces', 'Real-time sync, not batch reconciliation', 'Auditors, engineers and sales all query the same graph'], + }, + } +} + +// ── Pillar row ──────────────────────────────────────────────────────────────── +function PillarRow({ side, title, body, tint, onClick, active }: { + side: 'left' | 'right' + title: string; body: string; tint: string + onClick: () => void; active: boolean +}) { + const [hover, setHover] = useState(false) + const lit = hover || active + const isLeft = side === 'left' + return ( +
setHover(true)} + onMouseLeave={() => setHover(false)} + style={{ + display: 'flex', alignItems: 'flex-start', gap: 12, + flexDirection: isLeft ? 'row-reverse' : 'row', + textAlign: isLeft ? 'right' : 'left', + padding: '10px 14px', borderRadius: 10, cursor: 'pointer', + transition: 'transform .25s, background .25s, box-shadow .25s', + background: lit + ? `linear-gradient(${isLeft ? '270deg' : '90deg'}, ${tint}24 0%, ${tint}0a 70%, transparent 100%)` + : 'transparent', + boxShadow: lit + ? `0 10px 30px ${tint}26, inset 0 0 0 1px ${tint}44` + : 'inset 0 0 0 1px transparent', + transform: lit ? (isLeft ? 'translateX(-3px)' : 'translateX(3px)') : 'translateX(0)', + }} + > +
+
+
+ {title} + {isLeft ? '‹' : '›'} +
+
{body}
+
+
+ ) +} + +// ── Column header ───────────────────────────────────────────────────────────── +function ColHeader({ side, label, color, icon, sub }: { + side: 'left' | 'right'; label: string; color: string; icon: string; sub: string +}) { + const isLeft = side === 'left' + return ( +
+
{icon}
+
+
{label}
+
{sub}
+
+
+ ) +} + +// ── Central hub ─────────────────────────────────────────────────────────────── +function CentralHub({ caption }: { caption: string }) { + return ( +
+
+
+
+ + + +
+
{caption}
+
+ ) +} + +// ── Bridge SVG connectors ───────────────────────────────────────────────────── +function BridgeConnectors() { + const rfpY = 130 + const sub2Y = 250 + const hubCx = 500 + const hubR = 72 + return ( + + + + + + + + + + + + + + + + + + + + {([rfpY, sub2Y] as number[]).map(y => ( + + + + + + + ))} + + + + + + + + + + + ) +} + +// ── Under-the-hood feature card ─────────────────────────────────────────────── +function FeatureCard({ icon, title, body, tint, Ticker, onClick, active }: { + icon: string; title: string; body: string; tint: string + Ticker: React.ComponentType<{ tint: string }> + onClick: () => void; active: boolean +}) { + const [hover, setHover] = useState(false) + const lit = hover || active + return ( +
setHover(true)} + onMouseLeave={() => setHover(false)} + style={{ + position: 'relative', padding: '13px 15px', + background: `linear-gradient(180deg, ${tint}${lit ? '2a' : '1a'} 0%, ${tint}07 55%, rgba(14,8,28,.85) 100%)`, + border: `1px solid ${lit ? tint : tint + '4a'}`, + borderRadius: 12, + boxShadow: lit + ? `0 18px 40px ${tint}33, 0 0 0 1px ${tint}66, inset 0 1px 0 ${tint}60` + : `0 10px 24px rgba(0,0,0,.4), inset 0 1px 0 ${tint}35`, + minWidth: 0, cursor: 'pointer', + transform: lit ? 'translateY(-3px)' : 'translateY(0)', + transition: 'transform .25s, box-shadow .25s, background .25s, border-color .25s', + }} + > +
+ {icon} + {title} + +
+
{body}
+ +
+ ) +} + +// ── Detail modal ────────────────────────────────────────────────────────────── +function DetailModal({ item, onClose }: { item: DetailItem | null; onClose: () => void }) { + useEffect(() => { + if (!item) return + const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose() } + window.addEventListener('keydown', onKey) + return () => window.removeEventListener('keydown', onKey) + }, [item, onClose]) + + return ( + + {item && ( + + e.stopPropagation()} + style={{ + width: 560, maxWidth: '88%', + background: `linear-gradient(180deg, ${item.tint}18 0%, rgba(20,10,40,.96) 50%, rgba(14,8,28,.98) 100%)`, + border: `1px solid ${item.tint}66`, + borderRadius: 16, + boxShadow: `0 30px 80px rgba(0,0,0,.6), 0 0 60px ${item.tint}33, inset 0 1px 0 ${item.tint}55`, + padding: '22px 26px', color: '#ece9f7', + }} + > +
+
{item.icon}
+
+
+ {item.kicker} +
+
{item.title}
+
+ +
+
+ {item.body} +
+ {item.bullets && ( +
+ {item.bullets.map((b, i) => ( +
+ + {b} +
+ ))} +
+ )} + {item.stat && ( +
+ + {item.stat.k} + {item.stat.v} +
+ )} +
+
+ )} +
+ ) +} + +// ── Star field ──────────────────────────────────────────────────────────────── +function StarField() { + const stars = useMemo(() => { + let s = 41 + const r = () => { s = (s * 9301 + 49297) % 233280; return s / 233280 } + return Array.from({ length: 90 }, () => ({ x: r() * 100, y: r() * 100, size: r() * 1.4 + 0.3, op: r() * 0.5 + 0.15 })) + }, []) + return ( +
+ {stars.map((st, i) => ( +
+ ))} +
+ ) +} + +// ── Main slide ──────────────────────────────────────────────────────────────── +export default function USPSlide({ lang }: USPSlideProps) { + const de = lang === 'de' + const details = getDetails(de) + const [detail, setDetail] = useState(null) + const open = (k: string) => setDetail(details[k]) + const close = () => setDetail(null) return (
- -

+ + + +

USP

-

{subtitle}

- -
+ + {/* ── MAIN CANVAS ───────────────────────────────────────────────── */} +
+ {/* Ambient glow */} +
+ - {/* CENTER: Large circle */} -
-
-
- -
-
- -
-
- -
-
- - Compliance -
-
    - {complianceItems.map((item, idx) => ( -
  • - - {item} -
  • - ))} -
-
- -
-
- - Code -
-
    - {codeItems.map((item, idx) => ( -
  • - - {item} -
  • - ))} -
-
- -
-
-
-
+ {/* Interaction hint */} +
+ + {de ? 'Element anklicken' : 'Click any element'}
- {/* 4 CORNER CARDS */} - {capabilities.map((cap, idx) => { - const Icon = cap.icon - const posClass = idx === 0 ? 'top-0 left-0' - : idx === 1 ? 'top-0 right-0' - : idx === 2 ? 'bottom-0 left-0' - : 'bottom-0 right-0' - return ( -
- -
- -

{cap.label}

+ {/* Heading */} +
+
+ {de ? 'Alleinstellungsmerkmal' : 'Unique Selling Proposition'} +
+

+ {de ? 'Die erste Plattform, die ' : 'The first platform bridging '} + + {de ? 'Compliance' : 'compliance'} + + {' ↔ '} + + {de ? 'Code' : 'code'} + + {de ? ' verbindet' : ''} +

+
+ + {/* Bridge */} +
+ +
+ {/* LEFT — Compliance */} +
+
+ +
+
+
+ open('rfq')} + active={detail?.title === details.rfq.title} + />
-

{cap.desc}

- +
+
+
+ open('process')} + active={detail?.title === details.process.title} + /> +
+
- ) - })} - {/* SVG connection lines */} - - - - - - -
- + {/* CENTER hub */} +
+
open('hub')} + style={{ cursor: 'pointer', transition: 'transform .25s, filter .25s' }} + onMouseEnter={e => { (e.currentTarget as HTMLDivElement).style.transform = 'scale(1.05)'; (e.currentTarget as HTMLDivElement).style.filter = 'brightness(1.15)' }} + onMouseLeave={e => { (e.currentTarget as HTMLDivElement).style.transform = 'scale(1)'; (e.currentTarget as HTMLDivElement).style.filter = 'brightness(1)' }} + > + +
+
- {/* MOAT — 3 Sätze */} - -

- {de ? 'Unser MOAT' : 'Our MOAT'} -

-
- -
- -

End-to-End Traceability

+ {/* RIGHT — Code */} +
+
+ +
+
+
+ open('bidir')} + active={detail?.title === details.bidir.title} + /> +
+
+
+
+ open('cont')} + active={detail?.title === details.cont.title} + /> +
+
+
-

- {de - ? 'Regulatorische Anforderungen (Gesetz → Obligation → Control) deterministisch mit realem Systemzustand und Code verknüpft — inklusive revisionssicherem Evidence-Layer.' - : 'Regulatory requirements (law → obligation → control) deterministically linked to real system state and code — including audit-proof evidence layer.'} -

-
- -
- -

Continuous Compliance Engine

-
-

- {de - ? 'Statt punktueller Audits: Validierung bei jeder Änderung (Code, Infrastruktur, Prozesse) mit auditierbaren Nachweisen in Echtzeit.' - : 'Instead of point-in-time audits: validation on every change (code, infrastructure, processes) with auditable evidence in real time.'} -

-
- -
- -

Compliance Optimizer

-
-

- {de - ? 'Nicht nur „erlaubt/verboten", sondern die maximal zulässige Ausgestaltung jedes KI-Use-Cases. Deterministische Constraint-Optimierung zeigt den Sweet Spot zwischen Regulierung und Innovation — ersetzt 20-200k EUR Anwaltskosten.' - : 'Not just "allowed/forbidden" but the maximum permissible configuration of every AI use case. Deterministic constraint optimization shows the sweet spot between regulation and innovation — replaces EUR 20-200k in legal fees.'} -

-
- -
- -

EU-Trust & Governance Stack

-
-

- {de - ? 'Souveräne, DSGVO-/AI-Act-konforme Architektur (EU-Hosting, Isolation, Betriebsrat-Fähigkeit) — Marktzugang, den US-Lösungen strukturell nicht erreichen.' - : 'Sovereign, GDPR/AI Act compliant architecture (EU hosting, isolation, works council capability) — market access that US solutions structurally cannot achieve.'} -

-
-
-
+
- {/* Killer Quote */} - -
-

+ {/* Under the Hood */} +

+
+ + {de ? 'Unter der Haube' : 'Under the Hood'} + +
+
+ open('trace')} + active={detail?.title === details.trace.title} + Ticker={TickTrace} + /> + open('engine')} + active={detail?.title === details.engine.title} + Ticker={TickEngine} + /> + open('opt')} + active={detail?.title === details.opt.title} + Ticker={TickOptimizer} + /> + open('stack')} + active={detail?.title === details.stack.title} + Ticker={TickStack} + /> +
+
+ + {/* Killer quote */} +
+ " {de - ? '„Jeder kann sagen, was verboten ist. Kaum jemand kann sagen, wie weit du maximal gehen kannst. Das ist unser Produkt."' - : '"Everyone can say what is forbidden. Almost no one can say how far you can go. That is our product."'} -

+ ? 'Jeder kann sagen, was verboten ist. Kaum jemand kann sagen, wie weit du maximal gehen kannst. Das ist unser Produkt.' + : 'Anyone can say what\'s forbidden. Almost no one can tell you how far you can actually go. That\'s our product.'} + " +
+ +
From c0c44adaaa0489cab232c522eb662929f44a1723 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com> Date: Tue, 21 Apr 2026 10:34:36 +0200 Subject: [PATCH 08/13] pitch-deck: light mode support + MilestonesSlide redesign MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- pitch-deck/components/PitchDeck.tsx | 3 + .../components/slides/ArchitectureSlide.tsx | 289 ++++--- .../components/slides/MilestonesSlide.tsx | 814 ++++++++++++++++++ pitch-deck/components/slides/USPSlide.tsx | 271 +++--- 4 files changed, 1138 insertions(+), 239 deletions(-) create mode 100644 pitch-deck/components/slides/MilestonesSlide.tsx diff --git a/pitch-deck/components/PitchDeck.tsx b/pitch-deck/components/PitchDeck.tsx index bfea8d7..7b03796 100644 --- a/pitch-deck/components/PitchDeck.tsx +++ b/pitch-deck/components/PitchDeck.tsx @@ -52,6 +52,7 @@ import StrategySlide from './slides/StrategySlide' import FinanzplanSlide from './slides/FinanzplanSlide' import GlossarySlide from './slides/GlossarySlide' import RiskSlide from './slides/RiskSlide' +import MilestonesSlide from './slides/MilestonesSlide' interface PitchDeckProps { lang: Language @@ -182,6 +183,8 @@ export default function PitchDeck({ lang, onToggleLanguage, investor, onLogout, return case 'traction': return + case 'milestones': + return case 'competition': return case 'team': diff --git a/pitch-deck/components/slides/ArchitectureSlide.tsx b/pitch-deck/components/slides/ArchitectureSlide.tsx index f75b827..ef84b05 100644 --- a/pitch-deck/components/slides/ArchitectureSlide.tsx +++ b/pitch-deck/components/slides/ArchitectureSlide.tsx @@ -139,6 +139,24 @@ const CSS_KF = ` } ` +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) @@ -154,19 +172,14 @@ function useTicker(fn: () => void, min = 140, max = 360, skipChance = 0.1) { }, [min, max, skipChance]) } -const MONO: React.CSSProperties = { - fontFamily: '"JetBrains Mono","SF Mono",ui-monospace,monospace', - fontVariantNumeric: 'tabular-nums', -} - -function TickerShell({ color, children }: { color: string; children: React.ReactNode }) { +function TickerShell({ color, children, isLight }: { color: string; children: React.ReactNode; isLight: boolean }) { return (
{children}
@@ -182,24 +195,24 @@ function Caret({ color }: { color: string }) { ) } -// ── Per-node live tickers ───────────────────────────────────────────────────── -function TickCertifAI({ color }: { color: string }) { +// ── 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 ( - - + + sig - {n.toLocaleString()} - {hash} + {n.toLocaleString()} + {hash} ) } -function TickComplAI({ color }: { color: string }) { +function TickComplAI({ color, isLight }: { color: string; isLight: boolean }) { const [evals, setEvals] = useState(1284) const [rate, setRate] = useState(99.2) useTicker(() => { @@ -207,37 +220,37 @@ function TickComplAI({ color }: { color: string }) { setRate(r => Math.max(97, Math.min(99.9, r + (Math.random() - 0.5) * 0.4))) }, 200, 500, 0.1) return ( - - + + eval - {evals.toLocaleString()} - pass - {rate.toFixed(1)}% + {evals.toLocaleString()} + pass + {rate.toFixed(1)}% ) } -function TickScanner({ color }: { color: string }) { +function TickScanner({ color, isLight }: { color: string; isLight: boolean }) { const lines = [ - { k: 'PASS', c: '#4ade80', t: 'CWE-79 xss check' }, - { k: 'WARN', c: '#fbbf24', t: 'drift: model v2.1→2.2' }, - { k: 'PASS', c: '#4ade80', t: 'bias: demographic parity' }, - { k: 'FAIL', c: '#f87171', t: 'license: GPL-3 detected' }, - { k: 'PASS', c: '#4ade80', t: 'prompt-inject: 214 vectors' }, - { k: 'SCAN', c: '#a78bfa', t: 'artifact model-card.json' }, + { 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 ( - - {l.k} - {l.t} + + {l.k} + {l.t} ) } -function TickLiteLLM({ color }: { color: string }) { +function TickLiteLLM({ color, isLight }: { color: string; isLight: boolean }) { const [rps, setRps] = useState(428) const [p50, setP50] = useState(84) useTicker(() => { @@ -245,19 +258,19 @@ function TickLiteLLM({ color }: { color: string }) { setP50(v => Math.max(40, Math.min(160, v + (Math.random() - 0.5) * 20))) }, 250, 500, 0.05) return ( - - + + req/s - {Math.round(rps)} - · + {Math.round(rps)} + · p50 - {Math.round(p50)}ms + {Math.round(p50)}ms ) } -function TickLLM({ color }: { color: string }) { +function TickLLM({ color, isLight }: { color: string; isLight: boolean }) { const [tokens, setTokens] = useState(14832) const [stream, setStream] = useState('t_a91f') const pool = 'abcdef0123456789' @@ -266,32 +279,32 @@ function TickLLM({ color }: { color: string }) { setStream('t_' + Array.from({ length: 4 }, () => pool[Math.floor(Math.random() * pool.length)]).join('')) }, 120, 340, 0.15) return ( - - + + tok - {tokens.toLocaleString()} - + {tokens.toLocaleString()} + {stream} ) } -function TickEmbeddings({ color }: { color: string }) { +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 ( - - + + idx - {vecs.toLocaleString()} - · 1024d + {vecs.toLocaleString()} + · 1024d ) } -function TickTools({ color }: { color: string }) { +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', @@ -300,15 +313,15 @@ function TickTools({ color }: { color: string }) { const [i, setI] = useState(0) useTicker(() => setI(x => (x + 1) % ops.length), 900, 1600, 0.05) return ( - - + + call - {ops[i]} + {ops[i]} ) } -const NODE_TICKER: Record> = { +const NODE_TICKER: Record> = { certifai: TickCertifAI, complai: TickComplAI, scanner: TickScanner, @@ -318,7 +331,7 @@ const NODE_TICKER: Record> = { tools: TickTools, } -// ── Animated connector between layers ──────────────────────────────────────── +// ── Animated connector ──────────────────────────────────────────────────────── function LayerConnector({ tint }: { tint: string }) { const tracks = [ { x: '32%', primary: false }, @@ -333,18 +346,14 @@ function LayerConnector({ tint }: { tint: string }) { const dur = primary ? 1.6 : 2.4 return (
- {/* Rail */}
- {/* Staggered dots */} {Array.from({ length: dots }, (_, j) => (
))} @@ -355,11 +364,9 @@ function LayerConnector({ tint }: { tint: string }) { ) } -// ── Single node card ────────────────────────────────────────────────────────── -function NodeCard({ - node, selected, onClick, -}: { - node: NodeDef; selected: boolean; onClick: () => void +// ── 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 @@ -375,19 +382,20 @@ function NodeCard({ style={{ flex: 1, background: active - ? `linear-gradient(180deg, ${c}33, ${c}12)` - : 'linear-gradient(180deg, rgba(255,255,255,.055), rgba(255,255,255,.015))', - border: `1px solid ${active ? c : 'rgba(255,255,255,.14)'}`, - borderRadius: 12, - padding: '12px 14px', + ? `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: '#ece9f7', fontFamily: 'inherit', + 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` - : '0 1px 0 rgba(255,255,255,.04)', + : isLight ? '0 1px 4px rgba(0,0,0,.06)' : '0 1px 0 rgba(255,255,255,.04)', minWidth: 0, position: 'relative', }} > @@ -404,16 +412,18 @@ function NodeCard({
{node.title}
{node.subtitle}
- + {node.primary && (
void + isLight: boolean }) { const isProxy = nodes.length === 1 && !!nodes[0].primary return (
- {/* Top edge highlight */}
- {/* Layer label row */}
{label}
-
{sublabel}
+
{sublabel}
- {/* Cards */}
{isProxy ? (
- onSelect(nodes[0].id)} /> + onSelect(nodes[0].id)} isLight={isLight} />
) : ( nodes.map(n => ( - onSelect(n.id)} /> + onSelect(n.id)} isLight={isLight} /> )) )}
@@ -482,6 +496,7 @@ function LayerSlab({ 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 @@ -504,7 +519,6 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
- {/* Header */}

{de ? 'Anhang' : 'Appendix'} @@ -518,24 +532,24 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) { - {/* Customer namespace strip */}

{de ? 'Kundenmandanten' : 'Customer Namespaces'} {tenants.map(tn => ( - + {tn} ))}
- {/* ── MAIN CANVAS ─────────────────────────────────────────────── */} + {/* ── Main canvas ── */}
{/* Ambient glows */} -
-
+ {!isLight && ( + <> +
+
+ + )} {/* Slabs + connectors */}
{li < LAYERS.length - 1 && } @@ -595,10 +614,12 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
{label} @@ -606,7 +627,7 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) { ))}
- {/* ── Detail panel: slides up from bottom ── */} + {/* Detail panel */} {active && (
- {/* Panel header */}
- + {active.title}
-
+
{active.subtitle}
@@ -661,8 +681,8 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) { onClick={() => setActiveId(null)} style={{ background: 'transparent', - border: '1px solid rgba(167,139,250,.25)', - color: 'rgba(236,233,247,.5)', + 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, @@ -671,10 +691,9 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
- {/* Tech + capabilities grid */}
-
+
{de ? 'Stack' : 'Tech Stack'}
@@ -682,23 +701,23 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) { {tk} ))}
-
+
{de ? 'Funktionen' : 'Capabilities'}
{active.services.map(s => (
- {s.name} - {s.desc} + {s.name} + {s.desc}
))}
diff --git a/pitch-deck/components/slides/MilestonesSlide.tsx b/pitch-deck/components/slides/MilestonesSlide.tsx new file mode 100644 index 0000000..7318938 --- /dev/null +++ b/pitch-deck/components/slides/MilestonesSlide.tsx @@ -0,0 +1,814 @@ +'use client' + +import { useState, useEffect, useRef, useMemo, useCallback, Fragment } from 'react' +import { Language } from '@/lib/types' +import GradientText from '../ui/GradientText' +import FadeInView from '../ui/FadeInView' + +interface MilestonesSlideProps { lang: Language } + +const MONO: React.CSSProperties = { + fontFamily: '"JetBrains Mono","SF Mono",ui-monospace,monospace', + fontVariantNumeric: 'tabular-nums', +} + +const CSS_KF = ` + @keyframes msFlow { 0%{stroke-dashoffset:0} 100%{stroke-dashoffset:-18} } + @keyframes msFadeIn { from{opacity:0} to{opacity:1} } + @keyframes msScaleIn { from{opacity:0;transform:scale(.94)} to{opacity:1;transform:scale(1)} } + @keyframes msHeadingDark { + 0%,100%{text-shadow:0 0 22px rgba(167,139,250,.3)} + 50% {text-shadow:0 0 40px rgba(167,139,250,.6)} + } + @keyframes msHeadingLight { + 0%,100%{text-shadow:0 0 22px rgba(124,58,237,.15)} + 50% {text-shadow:0 0 36px rgba(124,58,237,.30)} + } + @keyframes msPulse { + 0%,100%{r:9;opacity:.4} + 50% {r:14;opacity:.05} + } +` + +// ── Light mode hook ─────────────────────────────────────────────────────────── +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 +} + +// ── Themes ──────────────────────────────────────────────────────────────────── +const THEMES = { + dark: { + key: 'dark' as const, + bg: 'radial-gradient(ellipse at 50% 25%, #1a0f34 0%, #0e0720 55%, #050210 100%)', + ambient: 'radial-gradient(ellipse, rgba(167,139,250,.18), transparent 65%)', + stars: true, + fg: '#f7f5fc', + fgSoft: 'rgba(236,233,247,.82)', + fgMid: 'rgba(236,233,247,.72)', + fgMuted: 'rgba(236,233,247,.62)', + fgFaint: 'rgba(236,233,247,.55)', + fgGhost: 'rgba(236,233,247,.45)', + fgWhisper: 'rgba(236,233,247,.4)', + accent: '#a78bfa', + accent80: 'rgba(167,139,250,.8)', + accent70: 'rgba(167,139,250,.7)', + accent50: 'rgba(167,139,250,.5)', + accent40: 'rgba(167,139,250,.4)', + accent20: 'rgba(167,139,250,.2)', + headingGrad: 'linear-gradient(90deg, #e9e2ff, #a78bfa 50%, #e9e2ff)', + headingAnim: 'msHeadingDark 4s ease-in-out infinite', + heuteText: '#e4d4ff', + heutePillBg: 'rgba(14,8,28,.95)', + heuteCore: '#f0e9ff', + done: '#4ade80', + doneBright: '#86efac', + doneDeep: '#166534', + doneSolid: '#22c55e', + cardBase: 'rgba(14,8,28,', + cardBaseA: '.9', + cardBaseAH: '.95', + cardTintTop: '18', cardTintTopH: '2e', + cardTintMid: '08', cardTintMidH: '14', + cardShadowSoft: '0 10px 24px rgba(0,0,0,.45)', + cardShadowLift: (t: string) => `0 20px 44px ${t}33, 0 0 0 1px ${t}66, inset 0 1px 0 ${t}66`, + statTintTop: '18', statTintTopH: '2a', + statTintMid: '06', + statShadowSoft: '0 10px 24px rgba(0,0,0,.45)', + statShadowLift: (t: string) => `0 18px 40px ${t}33, 0 0 0 1px ${t}55, inset 0 1px 0 ${t}55`, + modalScrim: 'rgba(5,2,16,.75)', + modalBgMid: 'rgba(20,10,40,.97)', + modalBgLow: 'rgba(14,8,28,.98)', + modalShadow: (t: string) => `0 30px 80px rgba(0,0,0,.65), 0 0 60px ${t}33, inset 0 1px 0 ${t}55`, + bulletBg: 'rgba(0,0,0,.3)', + progressTrackBg: 'rgba(255,255,255,.08)', + progressTrackBorder: 'rgba(167,139,250,.2)', + dotTodoDeep: '#1a0f34', + dotLitHi: 'rgba(255,255,255,.5)', + dotSoftHi: 'rgba(255,255,255,.3)', + sparkOp: 0.45, + }, + light: { + key: 'light' as const, + bg: 'radial-gradient(ellipse at 50% 12%, #ffffff 0%, #f5efff 55%, #ebdfff 100%)', + ambient: 'radial-gradient(ellipse, rgba(124,58,237,.14), transparent 65%)', + stars: false, + fg: '#1a0f34', + fgSoft: 'rgba(26,15,52,.85)', + fgMid: 'rgba(26,15,52,.72)', + fgMuted: 'rgba(26,15,52,.62)', + fgFaint: 'rgba(26,15,52,.50)', + fgGhost: 'rgba(26,15,52,.40)', + fgWhisper: 'rgba(26,15,52,.32)', + accent: '#7c3aed', + accent80: 'rgba(124,58,237,.8)', + accent70: 'rgba(124,58,237,.75)', + accent50: 'rgba(124,58,237,.55)', + accent40: 'rgba(124,58,237,.4)', + accent20: 'rgba(124,58,237,.18)', + headingGrad: 'linear-gradient(90deg, #3b0e7a, #7c3aed 50%, #3b0e7a)', + headingAnim: 'msHeadingLight 4s ease-in-out infinite', + heuteText: '#4c1d95', + heutePillBg: 'rgba(255,255,255,.98)', + heuteCore: '#7c3aed', + done: '#16a34a', + doneBright: '#4ade80', + doneDeep: '#14532d', + doneSolid: '#22c55e', + cardBase: 'rgba(255,255,255,', + cardBaseA: '.92', + cardBaseAH: '.98', + cardTintTop: '22', cardTintTopH: '3a', + cardTintMid: '10', cardTintMidH: '1c', + cardShadowSoft: '0 10px 24px rgba(59,26,122,.10), 0 2px 6px rgba(59,26,122,.06)', + cardShadowLift: (t: string) => `0 20px 44px ${t}38, 0 0 0 1px ${t}77, inset 0 1px 0 rgba(255,255,255,.9)`, + statTintTop: '1e', statTintTopH: '34', + statTintMid: '08', + statShadowSoft: '0 10px 24px rgba(59,26,122,.10), 0 2px 6px rgba(59,26,122,.06)', + statShadowLift: (t: string) => `0 18px 40px ${t}38, 0 0 0 1px ${t}77, inset 0 1px 0 rgba(255,255,255,.9)`, + modalScrim: 'rgba(40,20,80,.28)', + modalBgMid: 'rgba(255,255,255,.98)', + modalBgLow: 'rgba(250,247,255,.98)', + modalShadow: (t: string) => `0 30px 80px rgba(59,26,122,.25), 0 0 60px ${t}33, inset 0 1px 0 rgba(255,255,255,.9)`, + bulletBg: 'rgba(124,58,237,.06)', + progressTrackBg: 'rgba(124,58,237,.12)', + progressTrackBorder: 'rgba(124,58,237,.25)', + dotTodoDeep: '#faf5ff', + dotLitHi: 'rgba(255,255,255,.85)', + dotSoftHi: 'rgba(255,255,255,.55)', + sparkOp: 0.55, + }, +} + +type Theme = typeof THEMES.dark + +// ── Data ────────────────────────────────────────────────────────────────────── +const TODAY_POSITION = 0.56 + +interface Milestone { + id: string + when: string + tick: string + title: { de: string; en: string } + short: { de: string; en: string } + body: { de: string; en: string } + bullets: { de: string[]; en: string[] } + tint: string + done: boolean + next?: boolean +} + +const MILESTONES: Milestone[] = [ + { + id: 'start', + when: 'Mär. 2025', tick: '03 · 25', + title: { de: 'Idee & Team-Start', en: 'Idea & Team Start' }, + short: { de: 'Gründerteam formiert sich, erste Konzeption.', en: 'Founding team forms, first product concept.' }, + body: { + de: 'Zwei Gründer, ein klares Problem: Compliance-Doks und Code leben in getrennten Welten. Start der Konzeption für eine Plattform, die beide Welten verbindet.', + en: 'Two founders, one clear problem: compliance docs and code live in separate worlds. Started designing a platform that bridges both.', + }, + bullets: { + de: ['Team von 2 Gründern', 'Markt-Research DACH + EU', 'Erste Architektur-Skizze'], + en: ['Team of 2 founders', 'Market research DACH + EU', 'First architecture sketch'], + }, + tint: '#a78bfa', done: true, + }, + { + id: 'ihk', + when: 'Okt. 2025', tick: '10 · 25', + title: { de: 'IHK & Agentur für Arbeit', en: 'IHK & Employment Agency' }, + short: { de: 'Gründerzuschuss beantragt & gesichert.', en: 'Founder grant applied for & secured.' }, + body: { + de: 'Information und Austausch mit Agentur für Arbeit und IHK Konstanz für den Gründerzuschuss — seit Oktober 2025 in Bearbeitung und Aufbau.', + en: 'Collaboration with Employment Agency and IHK Konstanz for the founder grant — in processing since October 2025.', + }, + bullets: { + de: ['Gründerzuschuss genehmigt', 'Mentorship-Programm IHK', 'Erste öffentliche Sichtbarkeit'], + en: ['Founder grant approved', 'IHK mentorship program', 'First public visibility'], + }, + tint: '#a78bfa', done: true, + }, + { + id: 'proto', + when: 'Dez. 2025', tick: '12 · 25', + title: { de: 'Prototyp Compliance SDK', en: 'Compliance SDK Prototype' }, + short: { de: 'Compliance SDK & Security Cloud laufen.', en: 'Compliance SDK & Security Cloud running.' }, + body: { + de: 'Entwicklung eines funktionsfähigen Prototypen der Compliance SDK und der Security-Cloud-Lösung — erste End-to-End-Demo läuft seit Dezember 2025.', + en: 'Built a working prototype of the Compliance SDK and Security Cloud solution — first end-to-end demo running since December 2025.', + }, + bullets: { + de: ['SDK: policy → code mapping', 'Security Cloud MVP', 'Interne Demo-Audits erfolgreich'], + en: ['SDK: policy → code mapping', 'Security Cloud MVP', 'Internal demo audits successful'], + }, + tint: '#c084fc', done: true, + }, + { + id: 'pilot', + when: 'Dez. 2025', tick: '12 · 25', + title: { de: '2 Pilotkunden im Gespräch', en: '2 Pilot Customers in Talks' }, + short: { de: 'Schwarzwald + Mobilitäts-Sektor.', en: 'Black Forest + Mobility Sector.' }, + body: { + de: 'Kommunikation seit Dezember 2025 mit Kunden aus dem Schwarzwald (CE-Software-Risikobeurteilung) und einem globalen Maschinen- und Anlagenbauer aus dem Mobilitätssektor (KI-Roadmap).', + en: 'Since December 2025 in talks with a Black Forest CE-software customer (risk assessment) and a global mobility-sector machine builder (AI roadmap).', + }, + bullets: { + de: ['CE-Software-Risikobeurteilung', 'KI-Roadmap für Mobilitätssektor', 'LOIs in Vorbereitung'], + en: ['CE software risk assessment', 'AI roadmap for mobility sector', 'LOIs in preparation'], + }, + tint: '#c084fc', done: true, + }, + { + id: 'reg', + when: '27. Mär. 2026', tick: '03 · 26', + title: { de: 'Eintragung GmbH', en: 'GmbH Registration' }, + short: { de: 'Offizielle Gründung im Handelsregister.', en: 'Official incorporation in commercial register.' }, + body: { + de: 'Notartermin und Eintragung ins Handelsregister am 27.03.2026. Ab diesem Datum voll operative GmbH mit klaren Governance-Strukturen.', + en: 'Notary appointment and commercial register entry on 27.03.2026. Fully operative GmbH with clear governance structures from this date.', + }, + bullets: { + de: ['Gesellschaftsvertrag unterzeichnet', 'HRB-Eintrag Konstanz', 'Erste Rechnung ausgestellt'], + en: ['Articles of association signed', 'HRB entry Constance', 'First invoice issued'], + }, + tint: '#fbbf24', done: false, next: true, + }, + { + id: 'seed', + when: 'Q2 2026', tick: 'Q2 · 26', + title: { de: 'Seed-Runde', en: 'Seed Round' }, + short: { de: '1,5 Mio € für 18 Monate Runway.', en: '€1.5M for 18 months runway.' }, + body: { + de: 'Pre-Seed / Seed-Runde zur Finanzierung des ersten Kundensegments, Ausbau des Teams und Zertifizierung (ISO 27001, BSI C5).', + en: 'Pre-Seed / Seed round to fund first customer segment, team growth and certification (ISO 27001, BSI C5).', + }, + bullets: { + de: ['Ziel: 1,5 Mio € Seed', 'Ausbau auf 8 FTE', 'Zertifizierungs-Track startet'], + en: ['Target: €1.5M seed', 'Scale to 8 FTE', 'Certification track starts'], + }, + tint: '#fbbf24', done: false, + }, + { + id: 'beta', + when: 'Q3 2026', tick: 'Q3 · 26', + title: { de: 'Öffentliches Beta', en: 'Public Beta' }, + short: { de: 'Beta-Launch mit ersten zahlenden Kunden.', en: 'Beta launch with first paying customers.' }, + body: { + de: 'Öffentliches Beta-Release der Plattform. Erste zahlende Kunden aus dem Pilotprogramm gehen live. Integration in Gitlab + GitHub Cloud.', + en: 'Public beta release of the platform. First paying customers from the pilot program go live. GitLab + GitHub Cloud integration.', + }, + bullets: { + de: ['3–5 zahlende Pilot-Kunden', 'Public Beta verfügbar', 'Git-Integration live'], + en: ['3–5 paying pilot customers', 'Public beta available', 'Git integration live'], + }, + tint: '#f59e0b', done: false, + }, + { + id: 'v1', + when: 'Q4 2026', tick: 'Q4 · 26', + title: { de: 'EU Trust Stack v1.0', en: 'EU Trust Stack v1.0' }, + short: { de: 'DSGVO · NIS-2 · DORA · EU AI Act.', en: 'GDPR · NIS-2 · DORA · EU AI Act.' }, + body: { + de: 'Alle vier zentralen EU-Frameworks voll abgedeckt. EU-souveränes Hosting, vollständige Audit-Trail-Unterstützung, Zertifizierung ISO 27001 abgeschlossen.', + en: 'All four central EU frameworks fully covered. EU-sovereign hosting, complete audit trail support, ISO 27001 certification completed.', + }, + bullets: { + de: ['4 EU-Frameworks live', 'EU-souveränes Hosting', 'ISO 27001 zertifiziert'], + en: ['4 EU frameworks live', 'EU-sovereign hosting', 'ISO 27001 certified'], + }, + tint: '#f59e0b', done: false, + }, +] + +interface StatItem { k: { de: string; en: string }; v: string; tint: string } + +const STATS: StatItem[] = [ + { k: { de: 'Gesetze & Dokumente im RAG', en: 'Laws & Docs in RAG' }, v: '385', tint: '#a78bfa' }, + { k: { de: 'Atomare Controls', en: 'Atomic Controls' }, v: '25.000+', tint: '#c084fc' }, + { k: { de: 'Compliance-Module', en: 'Compliance Modules' }, v: '12', tint: '#fbbf24' }, + { k: { de: 'Pilotkunden', en: 'Pilot Customers' }, v: '2', tint: '#f59e0b' }, + { k: { de: 'Lines of Code', en: 'Lines of Code' }, v: '500.000+', tint: '#8b5cf6' }, +] + +// ── Star Field ──────────────────────────────────────────────────────────────── +function StarField() { + const stars = useMemo(() => { + let s = 77 + const r = () => { s = (s * 9301 + 49297) % 233280; return s / 233280 } + return Array.from({ length: 95 }, () => ({ x: r() * 100, y: r() * 100, size: r() * 1.4 + 0.3, op: r() * 0.5 + 0.15 })) + }, []) + return ( +
+ {stars.map((st, i) => ( +
+ ))} +
+ ) +} + +function SoftGrid({ t }: { t: Theme }) { + return ( +
+ ) +} + +// ── Timeline ────────────────────────────────────────────────────────────────── +interface MilestoneWithPos extends Milestone { x: number; row: 'top' | 'bottom' } + +function Timeline({ onSelect, selectedId, t, de }: { + onSelect: (m: Milestone) => void + selectedId: string | null + t: Theme + de: boolean +}) { + const trackW = 1160 + const innerPad = 120 + const usableW = trackW - innerPad * 2 + const positions = MILESTONES.map((_, i) => innerPad + (usableW * i) / (MILESTONES.length - 1)) + const todayX = innerPad + usableW * TODAY_POSITION + + const layout: MilestoneWithPos[] = MILESTONES.map((m, i) => ({ + ...m, x: positions[i], + row: i % 2 === 0 ? 'top' : 'bottom', + })) + + const railColor = t.key === 'dark' ? '#a78bfa' : '#7c3aed' + + return ( +
+ + + + + + + + + + + + + + {/* rail background */} + + {/* past progress */} + + {/* future dashed */} + + + {/* connector stubs */} + {layout.map((m) => ( + + ))} + + {/* HEUTE marker */} + + + + + + + + + + HEUTE + + + + + {layout.map((m) => ( + onSelect(m)} + active={selectedId === m.id} /> + ))} +
+ ) +} + +function MilestoneNode({ m, onClick, active, t, de }: { + m: MilestoneWithPos; onClick: () => void; active: boolean; t: Theme; de: boolean +}) { + const [hover, setHover] = useState(false) + const lit = hover || active + const isTop = m.row === 'top' + const cardY = isTop ? 4 : 200 + const nodeColor = m.done ? t.done : m.tint + + const bgTopA = lit ? m.tint + t.cardTintTopH : m.tint + t.cardTintTop + const bgMidA = lit ? m.tint + t.cardTintMidH : m.tint + t.cardTintMid + const cardBg = `linear-gradient(180deg, ${bgTopA} 0%, ${bgMidA} 55%, ${t.cardBase}${lit ? t.cardBaseAH : t.cardBaseA})` + const badge = m.done ? (de ? 'erledigt' : 'done') : (m.next ? (de ? 'als nächstes' : 'next') : (de ? 'geplant' : 'plan')) + + return ( + <> + {/* dot */} +
setHover(true)} + onMouseLeave={() => setHover(false)} + style={{ + position: 'absolute', left: m.x - 14, top: 180 - 14, + width: 28, height: 28, borderRadius: '50%', + background: m.done + ? `radial-gradient(circle at 35% 30%, ${t.doneBright}, ${t.doneSolid} 60%, ${t.doneDeep})` + : `radial-gradient(circle at 35% 30%, ${m.tint}dd, ${m.tint}66 60%, ${t.dotTodoDeep})`, + border: `2px solid ${lit ? '#fff' : nodeColor}`, + boxShadow: lit + ? `0 0 22px ${nodeColor}, 0 0 44px ${nodeColor}66, inset 0 1px 0 ${t.dotLitHi}` + : `0 0 10px ${nodeColor}88, inset 0 1px 0 ${t.dotSoftHi}`, + display: 'flex', alignItems: 'center', justifyContent: 'center', + color: '#fff', fontSize: 11, fontWeight: 700, + cursor: 'pointer', zIndex: 5, + transition: 'all .25s', + transform: lit ? 'scale(1.15)' : 'scale(1)', + }}> + {m.done ? '✓' : (m.next ? '◉' : '○')} +
+ + {/* card */} +
setHover(true)} + onMouseLeave={() => setHover(false)} + style={{ + position: 'absolute', left: m.x - 112, top: cardY, + width: 224, height: 150, padding: '12px 14px', + borderRadius: 12, + background: cardBg, + border: `1px solid ${lit ? m.tint : m.tint + '55'}`, + boxShadow: lit ? t.cardShadowLift(m.tint) : t.cardShadowSoft, + cursor: 'pointer', zIndex: 4, + transition: 'all .25s', + transform: lit ? `translateY(${isTop ? -2 : 2}px)` : 'translateY(0)', + display: 'flex', flexDirection: 'column', gap: 6, + backdropFilter: t.key === 'light' ? 'blur(6px)' : 'none', + }}> +
+ {m.tick} + + {badge} +
+
+ {de ? m.title.de : m.title.en} +
+
+ {de ? m.short.de : m.short.en} +
+
+ {m.when} + {de ? 'Details →' : 'Details →'} +
+
+ + ) +} + +// ── Stat Card ───────────────────────────────────────────────────────────────── +function StatCard({ item, t, de }: { item: StatItem; t: Theme; de: boolean }) { + const [hover, setHover] = useState(false) + const bgTop = hover ? item.tint + t.statTintTopH : item.tint + t.statTintTop + const bgMid = item.tint + t.statTintMid + return ( +
setHover(true)} + onMouseLeave={() => setHover(false)} + style={{ + position: 'relative', padding: '14px 18px', borderRadius: 12, + background: `linear-gradient(180deg, ${bgTop} 0%, ${bgMid} 60%, ${t.cardBase}${t.cardBaseA})`, + border: `1px solid ${hover ? item.tint : item.tint + '55'}`, + boxShadow: hover ? t.statShadowLift(item.tint) : t.statShadowSoft, + transform: hover ? 'translateY(-3px)' : 'translateY(0)', + transition: 'all .25s', + overflow: 'hidden', + backdropFilter: t.key === 'light' ? 'blur(6px)' : 'none', + }}> +
+
+ {de ? item.k.de : item.k.en} +
+
+ {item.v} +
+ + + + + + + + + + +
+ ) +} + +// ── Detail modal ────────────────────────────────────────────────────────────── +function DetailModal({ item, onClose, t, de }: { + item: Milestone | null; onClose: () => void; t: Theme; de: boolean +}) { + useEffect(() => { + if (!item) return + const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose() } + window.addEventListener('keydown', onKey) + return () => window.removeEventListener('keydown', onKey) + }, [item, onClose]) + + if (!item) return null + const tint = item.tint + const badge = item.done + ? (de ? 'ABGESCHLOSSEN' : 'COMPLETED') + : (item.next ? (de ? 'ALS NÄCHSTES' : 'NEXT UP') : (de ? 'GEPLANT' : 'PLANNED')) + const badgeColor = item.done ? t.done : tint + + return ( +
+
e.stopPropagation()} style={{ + width: 580, maxWidth: '88%', + background: `linear-gradient(180deg, ${tint}22 0%, ${t.modalBgMid} 50%, ${t.modalBgLow} 100%)`, + border: `1px solid ${tint}77`, + borderRadius: 16, + boxShadow: t.modalShadow(tint), + padding: '24px 28px', color: t.fg, + animation: 'msScaleIn .22s ease-out', + }}> +
+
{item.done ? '✓' : (item.next ? '◉' : '○')}
+
+
+ {badge} + {item.when} +
+
+ {de ? item.title.de : item.title.en} +
+
+ +
+
+ {de ? item.body.de : item.body.en} +
+
+ {(de ? item.bullets.de : item.bullets.en).map((b, i) => ( +
+ + {item.done ? '✓' : '▸'} + + {b} +
+ ))} +
+
+
+ ) +} + +// ── Inner slide (fixed 1280×680) ────────────────────────────────────────────── +function MilestonesInner({ t, de, sel, setSel }: { + t: Theme; de: boolean + sel: Milestone | null + setSel: (m: Milestone | null) => void +}) { + const doneCnt = useMemo(() => MILESTONES.filter(m => m.done).length, []) + const total = MILESTONES.length + + return ( +
+ {/* Ambient glow */} +
+ + {t.stars ? : } + + {/* Progress indicator */} +
+
+ {de ? 'Fortschritt' : 'Progress'} +
+
+
+
+
+ {doneCnt} + / {total} +
+
+ + {/* Tip */} +
+ {de ? 'Tipp:' : 'Tip:'} + {de ? 'Klick auf einen Meilenstein' : 'Click any milestone'} +
+ + {/* Heading */} +
+
+ + {de ? 'Roadmap' : 'Roadmap'} + +
+

+ {de ? 'Meilensteine' : 'Milestones'} +

+
+ {de + ? 'Was wir bereits erreicht haben — und was als Nächstes kommt' + : 'What we\'ve already achieved — and what\'s coming next'} +
+
+ + {/* Timeline */} +
+ +
+ + {/* Stats */} +
+ {STATS.map(s => )} +
+ + {/* Footer */} +
+ {de ? 'Stand heute · live-Metriken aus der Plattform' : 'As of today · live metrics from the platform'} +
+ + setSel(null)} t={t} de={de} /> +
+ ) +} + +// ── Main slide ──────────────────────────────────────────────────────────────── +const INNER_W = 1280 +const INNER_H = 680 + +export default function MilestonesSlide({ lang }: MilestonesSlideProps) { + const de = lang === 'de' + const isLight = useIsLight() + const t = isLight ? THEMES.light : THEMES.dark + const [sel, setSel] = useState(null) + const [scale, setScale] = useState(1) + const containerRef = useRef(null) + + const calcScale = useCallback(() => { + if (containerRef.current) { + const w = containerRef.current.offsetWidth + setScale(Math.min(w / INNER_W, 1)) + } + }, []) + + useEffect(() => { + calcScale() + const obs = new ResizeObserver(calcScale) + if (containerRef.current) obs.observe(containerRef.current) + return () => obs.disconnect() + }, [calcScale]) + + return ( +
+ + + +

+ {de ? 'Meilensteine' : 'Milestones'} +

+
+ + +
+
+ +
+
+
+
+ ) +} diff --git a/pitch-deck/components/slides/USPSlide.tsx b/pitch-deck/components/slides/USPSlide.tsx index 84ece33..719c5b4 100644 --- a/pitch-deck/components/slides/USPSlide.tsx +++ b/pitch-deck/components/slides/USPSlide.tsx @@ -21,12 +21,29 @@ const CSS_KF = ` 0%,100% { box-shadow: 0 0 38px rgba(167,139,250,.55), 0 0 80px rgba(167,139,250,.2), inset 0 3px 0 rgba(255,255,255,.35), inset 0 -6px 12px rgba(0,0,0,.35); } 50% { box-shadow: 0 0 58px rgba(167,139,250,.85), 0 0 110px rgba(167,139,250,.35), inset 0 3px 0 rgba(255,255,255,.4), inset 0 -6px 12px rgba(0,0,0,.35); } } + @keyframes uspPulseLight { + 0%,100% { box-shadow: 0 0 28px rgba(167,139,250,.4), 0 0 56px rgba(167,139,250,.15), inset 0 3px 0 rgba(255,255,255,.5), inset 0 -6px 12px rgba(0,0,0,.2); } + 50% { box-shadow: 0 0 44px rgba(167,139,250,.65), 0 0 80px rgba(167,139,250,.25), inset 0 3px 0 rgba(255,255,255,.55), inset 0 -6px 12px rgba(0,0,0,.2); } + } @keyframes uspHeading { 0%,100% { text-shadow: 0 0 22px rgba(167,139,250,.3); } 50% { text-shadow: 0 0 36px rgba(167,139,250,.55); } } ` +// ── Light mode hook ─────────────────────────────────────────────────────────── +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 ──────────────────────────────────────────────────────────────────── function useTicker(fn: () => void, min = 180, max = 420, skip = 0.1) { const ref = useRef(fn) @@ -42,32 +59,34 @@ function useTicker(fn: () => void, min = 180, max = 420, skip = 0.1) { }, [min, max, skip]) } -function TickerShell({ tint, children }: { tint: string; children: React.ReactNode }) { +function TickerShell({ tint, isLight, children }: { tint: string; isLight: boolean; children: React.ReactNode }) { return (
{children}
) } -function TickTrace({ tint }: { tint: string }) { +function TickTrace({ tint, isLight }: { tint: string; isLight: boolean }) { const [n, setN] = useState(12748) useTicker(() => setN(v => v + 1 + Math.floor(Math.random() * 3)), 250, 500) return ( - - + + trace - {n.toLocaleString()} - evidence-chain + {n.toLocaleString()} + evidence-chain ) } -function TickEngine({ tint }: { tint: string }) { +function TickEngine({ tint, isLight }: { tint: string; isLight: boolean }) { const [v, setV] = useState(428) const [rate, setRate] = useState(99.4) useTicker(() => { @@ -75,40 +94,40 @@ function TickEngine({ tint }: { tint: string }) { setRate(r => Math.max(97, Math.min(99.9, r + (Math.random() - 0.5) * 0.3))) }, 220, 420) return ( - - + + validate - {v.toLocaleString()} - {rate.toFixed(1)}% + {v.toLocaleString()} + {rate.toFixed(1)}% ) } -function TickOptimizer({ tint }: { tint: string }) { +function TickOptimizer({ tint, isLight }: { tint: string; isLight: boolean }) { const ops = ['ROI: 2.418 € / dev', 'gap → policy §4.2', 'dedup 128 tickets', 'sweet-spot: 22 KLOC', 'tradeoff: speed↔risk'] const [i, setI] = useState(0) useTicker(() => setI(x => (x + 1) % ops.length), 900, 1600, 0.05) return ( - + optimize - {ops[i]} + {ops[i]} ) } -function TickStack({ tint }: { tint: string }) { +function TickStack({ tint, isLight }: { tint: string; isLight: boolean }) { const regs = ['DSGVO', 'NIS-2', 'DORA', 'EU AI Act', 'ISO 27001', 'BSI C5'] const [i, setI] = useState(0) const [c, setC] = useState(1208) useTicker(() => { setI(x => (x + 1) % regs.length); setC(v => v + Math.floor(Math.random() * 3)) }, 800, 1400, 0.05) return ( - - + + check - {regs[i]} - · - {c.toLocaleString()} + {regs[i]} + · + {c.toLocaleString()} ) } @@ -233,10 +252,10 @@ function getDetails(de: boolean): Record { } // ── Pillar row ──────────────────────────────────────────────────────────────── -function PillarRow({ side, title, body, tint, onClick, active }: { +function PillarRow({ side, title, body, tint, onClick, active, isLight }: { side: 'left' | 'right' title: string; body: string; tint: string - onClick: () => void; active: boolean + onClick: () => void; active: boolean; isLight: boolean }) { const [hover, setHover] = useState(false) const lit = hover || active @@ -266,13 +285,15 @@ function PillarRow({ side, title, body, tint, onClick, active }: { background: lit ? `${tint}3a` : `${tint}22`, border: `1px solid ${lit ? tint : tint + '66'}`, display: 'flex', alignItems: 'center', justifyContent: 'center', - color: lit ? '#fff' : tint, fontSize: 13, fontWeight: 700, marginTop: 2, + color: lit ? (isLight ? tint : '#fff') : tint, fontSize: 13, fontWeight: 700, marginTop: 2, boxShadow: lit ? `0 0 14px ${tint}88, inset 0 1px 0 ${tint}80` : `inset 0 1px 0 ${tint}50`, transition: 'all .25s', }}>◆
@@ -283,15 +304,21 @@ function PillarRow({ side, title, body, tint, onClick, active }: { transition: 'all .25s', }}>{isLeft ? '‹' : '›'}
-
{body}
+
{body}
) } // ── Column header ───────────────────────────────────────────────────────────── -function ColHeader({ side, label, color, icon, sub }: { - side: 'left' | 'right'; label: string; color: string; icon: string; sub: string +function ColHeader({ side, label, color, icon, sub, isLight }: { + side: 'left' | 'right'; label: string; color: string; icon: string; sub: string; isLight: boolean }) { const isLeft = side === 'left' return ( @@ -305,11 +332,11 @@ function ColHeader({ side, label, color, icon, sub }: { background: `linear-gradient(135deg, ${color}55, ${color}20)`, border: `1px solid ${color}88`, display: 'flex', alignItems: 'center', justifyContent: 'center', - color: '#fff', fontSize: 15, fontWeight: 700, + color: isLight ? color : '#fff', fontSize: 15, fontWeight: 700, boxShadow: `0 0 18px ${color}55, inset 0 1px 0 ${color}aa`, }}>{icon}
-
{label}
+
{label}
{sub}
@@ -317,7 +344,7 @@ function ColHeader({ side, label, color, icon, sub }: { } // ── Central hub ─────────────────────────────────────────────────────────────── -function CentralHub({ caption }: { caption: string }) { +function CentralHub({ caption, isLight }: { caption: string; isLight: boolean }) { return (
-
-
+
+
@@ -337,7 +367,8 @@ function CentralHub({ caption }: { caption: string }) {
{caption}
@@ -345,7 +376,7 @@ function CentralHub({ caption }: { caption: string }) { } // ── Bridge SVG connectors ───────────────────────────────────────────────────── -function BridgeConnectors() { +function BridgeConnectors({ isLight }: { isLight: boolean }) { const rfpY = 130 const sub2Y = 250 const hubCx = 500 @@ -356,12 +387,12 @@ function BridgeConnectors() { - - + + - - + + @@ -381,9 +412,9 @@ function BridgeConnectors() { {([rfpY, sub2Y] as number[]).map(y => ( - + - + ))} @@ -401,10 +432,10 @@ function BridgeConnectors() { } // ── Under-the-hood feature card ─────────────────────────────────────────────── -function FeatureCard({ icon, title, body, tint, Ticker, onClick, active }: { +function FeatureCard({ icon, title, body, tint, Ticker, onClick, active, isLight }: { icon: string; title: string; body: string; tint: string - Ticker: React.ComponentType<{ tint: string }> - onClick: () => void; active: boolean + Ticker: React.ComponentType<{ tint: string; isLight: boolean }> + onClick: () => void; active: boolean; isLight: boolean }) { const [hover, setHover] = useState(false) const lit = hover || active @@ -415,12 +446,18 @@ function FeatureCard({ icon, title, body, tint, Ticker, onClick, active }: { onMouseLeave={() => setHover(false)} style={{ position: 'relative', padding: '13px 15px', - background: `linear-gradient(180deg, ${tint}${lit ? '2a' : '1a'} 0%, ${tint}07 55%, rgba(14,8,28,.85) 100%)`, - border: `1px solid ${lit ? tint : tint + '4a'}`, + background: isLight + ? lit + ? `linear-gradient(180deg, ${tint}18 0%, ${tint}08 55%, rgba(248,250,252,.95) 100%)` + : 'linear-gradient(180deg, #ffffff, #f8fafc)' + : `linear-gradient(180deg, ${tint}${lit ? '2a' : '1a'} 0%, ${tint}07 55%, rgba(14,8,28,.85) 100%)`, + border: `1px solid ${lit ? tint : isLight ? 'rgba(0,0,0,.1)' : tint + '4a'}`, borderRadius: 12, boxShadow: lit ? `0 18px 40px ${tint}33, 0 0 0 1px ${tint}66, inset 0 1px 0 ${tint}60` - : `0 10px 24px rgba(0,0,0,.4), inset 0 1px 0 ${tint}35`, + : isLight + ? '0 2px 8px rgba(0,0,0,.08), inset 0 1px 0 rgba(255,255,255,.8)' + : `0 10px 24px rgba(0,0,0,.4), inset 0 1px 0 ${tint}35`, minWidth: 0, cursor: 'pointer', transform: lit ? 'translateY(-3px)' : 'translateY(0)', transition: 'transform .25s, box-shadow .25s, background .25s, border-color .25s', @@ -432,21 +469,27 @@ function FeatureCard({ icon, title, body, tint, Ticker, onClick, active }: { background: lit ? `${tint}44` : `${tint}22`, border: `1px solid ${lit ? tint : tint + '66'}`, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', - color: lit ? '#fff' : tint, fontSize: 12, + color: lit ? (isLight ? tint : '#fff') : tint, fontSize: 12, boxShadow: lit ? `0 0 12px ${tint}88` : 'none', transition: 'all .25s', }}>{icon} - {title} + {title}
-
{body}
- +
{body}
+
) } // ── Detail modal ────────────────────────────────────────────────────────────── -function DetailModal({ item, onClose }: { item: DetailItem | null; onClose: () => void }) { +function DetailModal({ item, onClose, isLight }: { item: DetailItem | null; onClose: () => void; isLight: boolean }) { useEffect(() => { if (!item) return const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose() } @@ -465,7 +508,8 @@ function DetailModal({ item, onClose }: { item: DetailItem | null; onClose: () = onClick={onClose} style={{ position: 'absolute', inset: 0, zIndex: 50, - background: 'rgba(5,2,16,.72)', backdropFilter: 'blur(6px)', + background: isLight ? 'rgba(240,244,255,.72)' : 'rgba(5,2,16,.72)', + backdropFilter: 'blur(6px)', display: 'flex', alignItems: 'center', justifyContent: 'center', }} > @@ -477,11 +521,16 @@ function DetailModal({ item, onClose }: { item: DetailItem | null; onClose: () = onClick={e => e.stopPropagation()} style={{ width: 560, maxWidth: '88%', - background: `linear-gradient(180deg, ${item.tint}18 0%, rgba(20,10,40,.96) 50%, rgba(14,8,28,.98) 100%)`, - border: `1px solid ${item.tint}66`, + background: isLight + ? `linear-gradient(180deg, ${item.tint}10 0%, rgba(255,255,255,.98) 50%, rgba(248,250,252,.99) 100%)` + : `linear-gradient(180deg, ${item.tint}18 0%, rgba(20,10,40,.96) 50%, rgba(14,8,28,.98) 100%)`, + border: `1px solid ${item.tint}${isLight ? '44' : '66'}`, borderRadius: 16, - boxShadow: `0 30px 80px rgba(0,0,0,.6), 0 0 60px ${item.tint}33, inset 0 1px 0 ${item.tint}55`, - padding: '22px 26px', color: '#ece9f7', + boxShadow: isLight + ? `0 20px 60px rgba(0,0,0,.12), 0 0 40px ${item.tint}18, inset 0 1px 0 rgba(255,255,255,.9)` + : `0 30px 80px rgba(0,0,0,.6), 0 0 60px ${item.tint}33, inset 0 1px 0 ${item.tint}55`, + padding: '22px 26px', + color: isLight ? '#1a1a2e' : '#ece9f7', }} >
@@ -490,25 +539,25 @@ function DetailModal({ item, onClose }: { item: DetailItem | null; onClose: () = background: `linear-gradient(135deg, ${item.tint}66, ${item.tint}22)`, border: `1px solid ${item.tint}`, display: 'flex', alignItems: 'center', justifyContent: 'center', - color: '#fff', fontSize: 16, fontWeight: 700, + color: isLight ? item.tint : '#fff', fontSize: 16, fontWeight: 700, boxShadow: `0 0 18px ${item.tint}66`, }}>{item.icon}
{item.kicker}
-
{item.title}
+
{item.title}
-
+
{item.body}
{item.bullets && ( @@ -517,10 +566,11 @@ function DetailModal({ item, onClose }: { item: DetailItem | null; onClose: () =
- {b} + {b}
))}
@@ -528,13 +578,14 @@ function DetailModal({ item, onClose }: { item: DetailItem | null; onClose: () = {item.stat && (
- + {item.stat.k} - {item.stat.v} + {item.stat.v}
)} @@ -545,12 +596,13 @@ function DetailModal({ item, onClose }: { item: DetailItem | null; onClose: () = } // ── Star field ──────────────────────────────────────────────────────────────── -function StarField() { +function StarField({ isLight }: { isLight: boolean }) { const stars = useMemo(() => { let s = 41 const r = () => { s = (s * 9301 + 49297) % 233280; return s / 233280 } return Array.from({ length: 90 }, () => ({ x: r() * 100, y: r() * 100, size: r() * 1.4 + 0.3, op: r() * 0.5 + 0.15 })) }, []) + if (isLight) return null return (
{stars.map((st, i) => ( @@ -568,6 +620,7 @@ function StarField() { // ── Main slide ──────────────────────────────────────────────────────────────── export default function USPSlide({ lang }: USPSlideProps) { const de = lang === 'de' + const isLight = useIsLight() const details = getDetails(de) const [detail, setDetail] = useState(null) const open = (k: string) => setDetail(details[k]) @@ -587,38 +640,45 @@ export default function USPSlide({ lang }: USPSlideProps) { {/* ── MAIN CANVAS ───────────────────────────────────────────────── */}
- {/* Ambient glow */} -
- + {/* Ambient glow — dark only */} + {!isLight && ( +
+ )} + {/* Interaction hint */}
- + {de ? 'Element anklicken' : 'Click any element'}
{/* Heading */}
-
+
{de ? 'Alleinstellungsmerkmal' : 'Unique Selling Proposition'}

{de ? 'Die erste Plattform, die ' : 'The first platform bridging '} @@ -634,7 +694,7 @@ export default function USPSlide({ lang }: USPSlideProps) { {/* Bridge */}
- +
- +
-
- { (e.currentTarget as HTMLDivElement).style.transform = 'scale(1.05)'; (e.currentTarget as HTMLDivElement).style.filter = 'brightness(1.15)' }} onMouseLeave={e => { (e.currentTarget as HTMLDivElement).style.transform = 'scale(1)'; (e.currentTarget as HTMLDivElement).style.filter = 'brightness(1)' }} > - +
{/* RIGHT — Code */}
- +
-
-
- + {de ? 'Unter der Haube' : 'Under the Hood'} - +
- - - - " {de @@ -783,7 +846,7 @@ export default function USPSlide({ lang }: USPSlideProps) { "
- +
From a3a1ec44300d8fbb82ce71abb790533f220488f9 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com> Date: Tue, 21 Apr 2026 10:55:03 +0200 Subject: [PATCH 09/13] pitch-deck: wire MilestonesSlide to traction slide slot Replace old TractionSlide with new MilestonesSlide on the 'traction' route. Co-Authored-By: Claude Sonnet 4.6 --- pitch-deck/components/PitchDeck.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/pitch-deck/components/PitchDeck.tsx b/pitch-deck/components/PitchDeck.tsx index 7b03796..c78e31a 100644 --- a/pitch-deck/components/PitchDeck.tsx +++ b/pitch-deck/components/PitchDeck.tsx @@ -182,8 +182,6 @@ export default function PitchDeck({ lang, onToggleLanguage, investor, onLogout, case 'business-model': return case 'traction': - return - case 'milestones': return case 'competition': return From ad2bbab7b66c58273ba7e9fba98f84c62852b6b9 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com> Date: Tue, 21 Apr 2026 10:59:34 +0200 Subject: [PATCH 10/13] pitch-deck: remove unused TractionSlide import Co-Authored-By: Claude Sonnet 4.6 --- pitch-deck/components/PitchDeck.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/pitch-deck/components/PitchDeck.tsx b/pitch-deck/components/PitchDeck.tsx index c78e31a..a3b3031 100644 --- a/pitch-deck/components/PitchDeck.tsx +++ b/pitch-deck/components/PitchDeck.tsx @@ -29,7 +29,6 @@ import ProductSlide from './slides/ProductSlide' import HowItWorksSlide from './slides/HowItWorksSlide' import MarketSlide from './slides/MarketSlide' import BusinessModelSlide from './slides/BusinessModelSlide' -import TractionSlide from './slides/TractionSlide' import CompetitionSlide from './slides/CompetitionSlide' import TeamSlide from './slides/TeamSlide' import FinancialsSlide from './slides/FinancialsSlide' From b3baf603eeec59411cf2400d7611068369a1a698 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com> Date: Tue, 21 Apr 2026 11:11:38 +0200 Subject: [PATCH 11/13] pitch-deck: remove duplicate in-canvas headings from USP and Milestones slides Keep only the top-level GradientText h2; drop the redundant h1 and kicker inside each slide canvas. Co-Authored-By: Claude Sonnet 4.6 --- .../components/slides/MilestonesSlide.tsx | 30 ++----------------- pitch-deck/components/slides/USPSlide.tsx | 22 -------------- 2 files changed, 2 insertions(+), 50 deletions(-) diff --git a/pitch-deck/components/slides/MilestonesSlide.tsx b/pitch-deck/components/slides/MilestonesSlide.tsx index 7318938..d68eb5a 100644 --- a/pitch-deck/components/slides/MilestonesSlide.tsx +++ b/pitch-deck/components/slides/MilestonesSlide.tsx @@ -653,7 +653,7 @@ function MilestonesInner({ t, de, sel, setSel }: { return (
@@ -699,32 +699,6 @@ function MilestonesInner({ t, de, sel, setSel }: { {de ? 'Klick auf einen Meilenstein' : 'Click any milestone'}
- {/* Heading */} -
-
- - {de ? 'Roadmap' : 'Roadmap'} - -
-

- {de ? 'Meilensteine' : 'Milestones'} -

-
- {de - ? 'Was wir bereits erreicht haben — und was als Nächstes kommt' - : 'What we\'ve already achieved — and what\'s coming next'} -
-
- {/* Timeline */}
@@ -754,7 +728,7 @@ function MilestonesInner({ t, de, sel, setSel }: { // ── Main slide ──────────────────────────────────────────────────────────────── const INNER_W = 1280 -const INNER_H = 680 +const INNER_H = 600 export default function MilestonesSlide({ lang }: MilestonesSlideProps) { const de = lang === 'de' diff --git a/pitch-deck/components/slides/USPSlide.tsx b/pitch-deck/components/slides/USPSlide.tsx index 719c5b4..9395992 100644 --- a/pitch-deck/components/slides/USPSlide.tsx +++ b/pitch-deck/components/slides/USPSlide.tsx @@ -670,28 +670,6 @@ export default function USPSlide({ lang }: USPSlideProps) { {de ? 'Element anklicken' : 'Click any element'}
- {/* Heading */} -
-
- {de ? 'Alleinstellungsmerkmal' : 'Unique Selling Proposition'} -
-

- {de ? 'Die erste Plattform, die ' : 'The first platform bridging '} - - {de ? 'Compliance' : 'compliance'} - - {' ↔ '} - - {de ? 'Code' : 'code'} - - {de ? ' verbindet' : ''} -

-
- {/* Bridge */}
From e0a3ff5ca98d8aad0a95a1634099e89a2c02a434 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com> Date: Tue, 21 Apr 2026 11:20:41 +0200 Subject: [PATCH 12/13] pitch-deck: fix timeline overlap with tip/progress badges in MilestonesSlide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Increase timeline marginTop from 14→68 so it clears the absolute-positioned Tip and Fortschritt elements at top:36. Co-Authored-By: Claude Sonnet 4.6 --- pitch-deck/components/slides/MilestonesSlide.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pitch-deck/components/slides/MilestonesSlide.tsx b/pitch-deck/components/slides/MilestonesSlide.tsx index d68eb5a..c3a430c 100644 --- a/pitch-deck/components/slides/MilestonesSlide.tsx +++ b/pitch-deck/components/slides/MilestonesSlide.tsx @@ -700,7 +700,7 @@ function MilestonesInner({ t, de, sel, setSel }: {
{/* Timeline */} -
+
From 6b08ce6b6a447ccccc0568c307790a70f9876a6c Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com> Date: Tue, 21 Apr 2026 11:31:23 +0200 Subject: [PATCH 13/13] pitch-deck: fix HEUTE pill clipped behind milestone cards Move pill from SVG (behind HTML) to an absolutely-positioned HTML div with zIndex:10 so it always renders above the milestone cards. Co-Authored-By: Claude Sonnet 4.6 --- .../components/slides/MilestonesSlide.tsx | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/pitch-deck/components/slides/MilestonesSlide.tsx b/pitch-deck/components/slides/MilestonesSlide.tsx index c3a430c..9dcf748 100644 --- a/pitch-deck/components/slides/MilestonesSlide.tsx +++ b/pitch-deck/components/slides/MilestonesSlide.tsx @@ -393,7 +393,7 @@ function Timeline({ onSelect, selectedId, t, de }: { strokeDasharray={m.done ? '0' : '3 3'} /> ))} - {/* HEUTE marker */} + {/* HEUTE marker — circles only; pill is HTML below */} @@ -401,15 +401,23 @@ function Timeline({ onSelect, selectedId, t, de }: { - - - HEUTE - + {/* HEUTE pill — HTML so it sits above milestone cards */} +
HEUTE
+ {layout.map((m) => ( onSelect(m)}