feat: Add DevSecOps tools, Woodpecker proxy, Vault persistent storage, pitch-deck annex slides
All checks were successful
CI / test-bqas (push) Successful in 32s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 46s
CI / test-python-voice (push) Successful in 38s
All checks were successful
CI / test-bqas (push) Successful in 32s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 46s
CI / test-python-voice (push) Successful in 38s
- Install Gitleaks, Trivy, Grype, Syft, Semgrep, Bandit in backend-core Dockerfile - Add Woodpecker SQLite proxy API (fallback without API token) - Mount woodpecker_data volume read-only to backend-core - Add backend proxy fallback in admin-core Woodpecker route - Add Vault file-based persistent storage (config.hcl, init-vault.sh) - Auto-init, unseal and root-token persistence for Vault - Add 6 pitch-deck annex slides (Assumptions, Architecture, GTM, Regulatory, Engineering, AI Pipeline) - Dynamic margin/amortization KPIs in BusinessModelSlide - Market sources modal with citations in MarketSlide - Redesign nginx landing page to 3-column layout (Lehrer/Compliance/Core) - Extend MkDocs nav with Services and SDK documentation sections - Add SDK Protection architecture doc Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
329
pitch-deck/components/slides/AIPipelineSlide.tsx
Normal file
329
pitch-deck/components/slides/AIPipelineSlide.tsx
Normal file
@@ -0,0 +1,329 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
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 {
|
||||
Brain,
|
||||
Search,
|
||||
Database,
|
||||
FileText,
|
||||
Bot,
|
||||
Zap,
|
||||
Layers,
|
||||
ArrowRight,
|
||||
Activity,
|
||||
Shield,
|
||||
Cpu,
|
||||
MessageSquare,
|
||||
Eye,
|
||||
Gauge,
|
||||
Network,
|
||||
Sparkles,
|
||||
} from 'lucide-react'
|
||||
|
||||
interface AIPipelineSlideProps {
|
||||
lang: Language
|
||||
}
|
||||
|
||||
type PipelineTab = 'rag' | 'agents' | 'quality'
|
||||
|
||||
export default function AIPipelineSlide({ lang }: AIPipelineSlideProps) {
|
||||
const i = t(lang)
|
||||
const de = lang === 'de'
|
||||
const [activeTab, setActiveTab] = useState<PipelineTab>('rag')
|
||||
|
||||
const heroStats = [
|
||||
{ value: '19', label: de ? 'Indexierte Verordnungen' : 'Indexed Regulations', sub: 'DSGVO · AI Act · NIS2 · CRA · ePrivacy · ...', color: 'text-indigo-400' },
|
||||
{ value: '7', label: de ? 'Autonome Agenten' : 'Autonomous Agents', sub: de ? 'SOUL-basiert · Orchestriert' : 'SOUL-based · Orchestrated', color: 'text-purple-400' },
|
||||
{ value: '5', label: de ? 'KI-Modelle lokal' : 'Local AI Models', sub: 'Llama 3.2 · Qwen 2.5 · BGE-M3 · TrOCR · CrossEncoder', color: 'text-emerald-400' },
|
||||
{ value: '97', label: de ? 'Golden-Suite Tests' : 'Golden Suite Tests', sub: de ? 'Automatische Qualitaetssicherung' : 'Automatic Quality Assurance', color: 'text-amber-400' },
|
||||
]
|
||||
|
||||
const tabs: { id: PipelineTab; label: string; icon: typeof Brain }[] = [
|
||||
{ id: 'rag', label: de ? 'RAG-Pipeline' : 'RAG Pipeline', icon: Search },
|
||||
{ id: 'agents', label: de ? 'Multi-Agent-System' : 'Multi-Agent System', icon: Bot },
|
||||
{ id: 'quality', label: de ? 'Qualitaetssicherung' : 'Quality Assurance', icon: Gauge },
|
||||
]
|
||||
|
||||
// RAG Pipeline content
|
||||
const ragPipelineSteps = [
|
||||
{
|
||||
icon: FileText,
|
||||
color: 'text-blue-400',
|
||||
bg: 'bg-blue-500/10 border-blue-500/20',
|
||||
title: de ? '1. Ingestion' : '1. Ingestion',
|
||||
items: de
|
||||
? ['PDF-Upload, URL-Crawling, API-Import', 'Automatische Spracherkennung (DE/EN)', 'Semantisches Chunking (rekursiv, 512 Tokens)', 'Metadaten-Extraktion (Verordnung, Artikel, Absatz)']
|
||||
: ['PDF upload, URL crawling, API import', 'Automatic language detection (DE/EN)', 'Semantic chunking (recursive, 512 tokens)', 'Metadata extraction (regulation, article, paragraph)'],
|
||||
},
|
||||
{
|
||||
icon: Cpu,
|
||||
color: 'text-purple-400',
|
||||
bg: 'bg-purple-500/10 border-purple-500/20',
|
||||
title: de ? '2. Embedding' : '2. Embedding',
|
||||
items: de
|
||||
? ['BGE-M3 Multilingual Embeddings (lokal)', 'CrossEncoder Re-Ranking (lokal)', 'HyDE: Hypothetical Document Embeddings', 'Lazy Model Loading (Speicher-optimiert)']
|
||||
: ['BGE-M3 multilingual embeddings (local)', 'CrossEncoder re-ranking (local)', 'HyDE: Hypothetical Document Embeddings', 'Lazy model loading (memory-optimized)'],
|
||||
},
|
||||
{
|
||||
icon: Database,
|
||||
color: 'text-emerald-400',
|
||||
bg: 'bg-emerald-500/10 border-emerald-500/20',
|
||||
title: de ? '3. Vektorspeicher' : '3. Vector Store',
|
||||
items: de
|
||||
? ['Qdrant Vector DB (Self-hosted)', '5 Collections: Legal Corpus, DSFA, Compliance, Dokumente, Agenten-Wissen', 'MinIO Object Storage fuer Quelldokumente', 'Automatische Re-Indexierung bei Updates']
|
||||
: ['Qdrant Vector DB (self-hosted)', '5 Collections: Legal Corpus, DSFA, Compliance, Documents, Agent Knowledge', 'MinIO object storage for source documents', 'Automatic re-indexing on updates'],
|
||||
},
|
||||
{
|
||||
icon: Search,
|
||||
color: 'text-indigo-400',
|
||||
bg: 'bg-indigo-500/10 border-indigo-500/20',
|
||||
title: de ? '4. Hybrid Search' : '4. Hybrid Search',
|
||||
items: de
|
||||
? ['Dense Retrieval (70%) + BM25 Keyword (30%)', 'Deutsche Komposita-Zerlegung', 'Cross-Encoder Re-Ranking der Top-K Ergebnisse', 'Quellen-Attribution mit Artikel-Referenz']
|
||||
: ['Dense retrieval (70%) + BM25 keyword (30%)', 'German compound word decomposition', 'Cross-encoder re-ranking of top-K results', 'Source attribution with article reference'],
|
||||
},
|
||||
]
|
||||
|
||||
// Multi-Agent System content
|
||||
const agents = [
|
||||
{ name: de ? 'Compliance-Berater' : 'Compliance Advisor', soul: 'compliance-advisor.soul.md', desc: de ? 'Beantwortet Compliance-Fragen mit RAG-Kontext' : 'Answers compliance questions with RAG context', color: 'text-indigo-400' },
|
||||
{ name: de ? 'Audit-Agent' : 'Audit Agent', soul: 'quality-judge.soul.md', desc: de ? 'Prueft Dokumente gegen regulatorische Anforderungen' : 'Checks documents against regulatory requirements', color: 'text-emerald-400' },
|
||||
{ name: de ? 'Dokument-Agent' : 'Drafting Agent', soul: 'drafting-agent.soul.md', desc: de ? 'Erstellt Compliance-Dokumente und Policies' : 'Creates compliance documents and policies', color: 'text-purple-400' },
|
||||
{ name: 'Orchestrator', soul: 'orchestrator.soul.md', desc: de ? 'Task-Routing und Koordination aller Agenten' : 'Task routing and coordination of all agents', color: 'text-amber-400' },
|
||||
{ name: de ? 'Alert-Agent' : 'Alert Agent', soul: 'alert-agent.soul.md', desc: de ? 'Monitoring, Fristen und Benachrichtigungen' : 'Monitoring, deadlines and notifications', color: 'text-red-400' },
|
||||
{ name: de ? 'Tutor-Agent' : 'Tutor Agent', soul: 'tutor-agent.soul.md', desc: de ? 'Interaktive Compliance-Schulungen' : 'Interactive compliance training', color: 'text-blue-400' },
|
||||
]
|
||||
|
||||
const agentInfra = [
|
||||
{ icon: MessageSquare, label: 'SOUL Files', desc: de ? 'Deklarative Agenten-Persoenlichkeit in Markdown' : 'Declarative agent personality in Markdown' },
|
||||
{ icon: Brain, label: 'Shared Brain', desc: de ? 'Gemeinsamer Wissensspeicher + Langzeitgedaechtnis' : 'Shared knowledge store + long-term memory' },
|
||||
{ icon: Network, label: 'Message Bus', desc: de ? 'Valkey/Redis · Pub/Sub · Task Queue' : 'Valkey/Redis · Pub/Sub · Task Queue' },
|
||||
{ icon: Activity, label: 'Session Manager', desc: de ? 'Heartbeat · Checkpoints · Recovery' : 'Heartbeat · Checkpoints · Recovery' },
|
||||
]
|
||||
|
||||
// Quality Assurance content
|
||||
const qaFeatures = [
|
||||
{
|
||||
icon: Shield,
|
||||
color: 'text-emerald-400',
|
||||
title: de ? 'BQAS — Quality Assurance System' : 'BQAS — Quality Assurance System',
|
||||
items: de
|
||||
? ['97 Golden-Suite-Referenztests fuer Regressionserkennung', 'Synthetische Testgenerierung per LLM', 'RAG-Retrieval-Accuracy und Correction-Tests', 'Precision, Recall, F1 Tracking ueber alle Releases']
|
||||
: ['97 golden suite reference tests for regression detection', 'Synthetic test generation via LLM', 'RAG retrieval accuracy and correction tests', 'Precision, recall, F1 tracking across all releases'],
|
||||
},
|
||||
{
|
||||
icon: Eye,
|
||||
color: 'text-indigo-400',
|
||||
title: de ? 'LLM Evaluation & Vergleich' : 'LLM Evaluation & Comparison',
|
||||
items: de
|
||||
? ['Side-by-Side-Vergleich: Ollama lokal vs. OpenAI vs. Claude', 'Latenz-, Token- und Qualitaets-Metriken pro Provider', 'Automatischer Fallback bei Provider-Ausfall', 'Self-RAG: Selbstreflektierende Antwortvalidierung']
|
||||
: ['Side-by-side comparison: Ollama local vs. OpenAI vs. Claude', 'Latency, token and quality metrics per provider', 'Automatic fallback on provider failure', 'Self-RAG: Self-reflective answer validation'],
|
||||
},
|
||||
{
|
||||
icon: Sparkles,
|
||||
color: 'text-purple-400',
|
||||
title: de ? 'Document Intelligence' : 'Document Intelligence',
|
||||
items: de
|
||||
? ['TrOCR Handschrifterkennung mit LoRA Fine-Tuning', 'Multi-OCR-Pipeline: 5 Methoden parallel (Vision LLM, Tesseract, OpenCV, ...)', 'Labeling-Interface fuer Trainingsdaten-Erstellung (DSGVO-konform, lokal)', 'OpenCV Document Reconstruction (Deskew, Dewarp, Binarisierung)']
|
||||
: ['TrOCR handwriting recognition with LoRA fine-tuning', 'Multi-OCR pipeline: 5 methods in parallel (Vision LLM, Tesseract, OpenCV, ...)', 'Labeling interface for training data creation (GDPR-compliant, local)', 'OpenCV document reconstruction (deskew, dewarp, binarization)'],
|
||||
},
|
||||
{
|
||||
icon: Zap,
|
||||
color: 'text-amber-400',
|
||||
title: de ? 'GPU & Training' : 'GPU & Training',
|
||||
items: de
|
||||
? ['Lokales Training auf Apple Silicon (M4 Max, 64 GB unified)', 'vast.ai Integration fuer Cloud-GPU bei Bedarf', 'LoRA/QLoRA Fine-Tuning mit konfigurierbaren Hyperparametern', 'SSE-Streaming fuer Echtzeit-Trainingsmetriken (Loss, Accuracy, F1)']
|
||||
: ['Local training on Apple Silicon (M4 Max, 64 GB unified)', 'vast.ai integration for cloud GPU on demand', 'LoRA/QLoRA fine-tuning with configurable hyperparameters', 'SSE streaming for real-time training metrics (loss, accuracy, F1)'],
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FadeInView className="text-center mb-5">
|
||||
<p className="text-xs font-mono text-indigo-400/60 uppercase tracking-widest mb-2">
|
||||
{de ? 'Anhang' : 'Appendix'}
|
||||
</p>
|
||||
<h2 className="text-4xl md:text-5xl font-bold mb-2">
|
||||
<GradientText>{i.annex.aipipeline.title}</GradientText>
|
||||
</h2>
|
||||
<p className="text-lg text-white/50 max-w-2xl mx-auto">{i.annex.aipipeline.subtitle}</p>
|
||||
</FadeInView>
|
||||
|
||||
{/* Hero Stats */}
|
||||
<FadeInView delay={0.1}>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-2 mb-4">
|
||||
{heroStats.map((stat, idx) => (
|
||||
<div key={idx} className="border border-white/[0.08] rounded-xl p-2.5 bg-white/[0.03] text-center">
|
||||
<p className={`text-2xl font-black tracking-tight ${stat.color}`}>{stat.value}</p>
|
||||
<p className="text-[11px] font-semibold text-white/70">{stat.label}</p>
|
||||
<p className="text-[9px] text-white/30 mt-0.5 leading-tight">{stat.sub}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</FadeInView>
|
||||
|
||||
{/* Tab Navigation */}
|
||||
<FadeInView delay={0.15}>
|
||||
<div className="flex items-center justify-center gap-2 mb-4">
|
||||
{tabs.map((tab) => {
|
||||
const Icon = tab.icon
|
||||
return (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-xl text-sm transition-all
|
||||
${activeTab === tab.id
|
||||
? 'bg-indigo-500/20 text-indigo-300 border border-indigo-500/30'
|
||||
: 'bg-white/[0.04] text-white/40 border border-transparent hover:text-white/60 hover:bg-white/[0.06]'
|
||||
}`}
|
||||
>
|
||||
<Icon className="w-4 h-4" />
|
||||
{tab.label}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</FadeInView>
|
||||
|
||||
{/* Tab Content */}
|
||||
<FadeInView delay={0.2} key={activeTab}>
|
||||
{activeTab === 'rag' && (
|
||||
<div>
|
||||
{/* Pipeline Flow Visualization */}
|
||||
<div className="flex items-center justify-center gap-1 mb-4 flex-wrap">
|
||||
{[
|
||||
{ icon: FileText, label: de ? 'Dokumente' : 'Documents' },
|
||||
{ icon: Layers, label: 'Chunking' },
|
||||
{ icon: Cpu, label: 'BGE-M3' },
|
||||
{ icon: Database, label: 'Qdrant' },
|
||||
{ icon: Search, label: 'Hybrid Search' },
|
||||
{ icon: Brain, label: 'LLM' },
|
||||
].map((step, idx, arr) => (
|
||||
<div key={idx} className="flex items-center gap-1">
|
||||
<div className="flex items-center gap-1 px-2 py-1 rounded-lg bg-white/[0.05] border border-white/[0.08]">
|
||||
<step.icon className="w-3 h-3 text-indigo-400" />
|
||||
<span className="text-[10px] text-white/50">{step.label}</span>
|
||||
</div>
|
||||
{idx < arr.length - 1 && <ArrowRight className="w-3 h-3 text-white/20" />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* Pipeline Steps */}
|
||||
<div className="grid md:grid-cols-2 gap-3">
|
||||
{ragPipelineSteps.map((step, idx) => {
|
||||
const Icon = step.icon
|
||||
return (
|
||||
<div key={idx} className={`border rounded-xl p-3 ${step.bg}`}>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Icon className={`w-4 h-4 ${step.color}`} />
|
||||
<h3 className="text-xs font-bold text-white">{step.title}</h3>
|
||||
</div>
|
||||
<ul className="space-y-1">
|
||||
{step.items.map((item, iidx) => (
|
||||
<li key={iidx} className="flex items-start gap-1.5 text-[11px] text-white/50">
|
||||
<span className={`w-1 h-1 rounded-full mt-1.5 ${step.color} bg-current shrink-0`} />
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'agents' && (
|
||||
<div className="grid md:grid-cols-12 gap-4">
|
||||
{/* Agent List */}
|
||||
<div className="md:col-span-7">
|
||||
<GlassCard hover={false} className="p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Bot className="w-4 h-4 text-white/40" />
|
||||
<p className="text-xs font-semibold text-white/40 uppercase tracking-wider">
|
||||
{de ? 'Agenten-Fleet' : 'Agent Fleet'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{agents.map((agent, idx) => (
|
||||
<div key={idx} className="p-2 rounded-lg bg-white/[0.03] border border-white/5">
|
||||
<div className="flex items-center gap-1.5 mb-1">
|
||||
<div className={`w-1.5 h-1.5 rounded-full ${agent.color} bg-current`} />
|
||||
<p className="text-xs font-bold text-white/80">{agent.name}</p>
|
||||
</div>
|
||||
<p className="text-[10px] text-white/40 leading-tight">{agent.desc}</p>
|
||||
<p className="text-[9px] font-mono text-white/20 mt-1">{agent.soul}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
{/* Agent Infrastructure */}
|
||||
<div className="md:col-span-5">
|
||||
<GlassCard hover={false} className="p-4 h-full">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Network className="w-4 h-4 text-white/40" />
|
||||
<p className="text-xs font-semibold text-white/40 uppercase tracking-wider">
|
||||
{de ? 'Infrastruktur' : 'Infrastructure'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2.5">
|
||||
{agentInfra.map((inf, idx) => {
|
||||
const Icon = inf.icon
|
||||
return (
|
||||
<div key={idx} className="flex items-start gap-2.5">
|
||||
<div className="w-7 h-7 rounded-lg bg-purple-500/10 border border-purple-500/20 flex items-center justify-center shrink-0">
|
||||
<Icon className="w-3.5 h-3.5 text-purple-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs font-semibold text-white/70">{inf.label}</p>
|
||||
<p className="text-[10px] text-white/40">{inf.desc}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div className="mt-3 pt-3 border-t border-white/5">
|
||||
<p className="text-[10px] text-white/20">
|
||||
{de
|
||||
? 'Alle Agenten laufen lokal · Kein API-Schluessel erforderlich · DSGVO-konform'
|
||||
: 'All agents run locally · No API key required · GDPR-compliant'}
|
||||
</p>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'quality' && (
|
||||
<div className="grid md:grid-cols-2 gap-3">
|
||||
{qaFeatures.map((feat, idx) => {
|
||||
const Icon = feat.icon
|
||||
return (
|
||||
<GlassCard key={idx} hover={false} className="p-3">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Icon className={`w-4 h-4 ${feat.color}`} />
|
||||
<h3 className="text-xs font-bold text-white">{feat.title}</h3>
|
||||
</div>
|
||||
<ul className="space-y-1">
|
||||
{feat.items.map((item, iidx) => (
|
||||
<li key={iidx} className="flex items-start gap-1.5 text-[11px] text-white/50">
|
||||
<span className={`w-1 h-1 rounded-full mt-1.5 ${feat.color} bg-current shrink-0`} />
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</GlassCard>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</FadeInView>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
130
pitch-deck/components/slides/ArchitectureSlide.tsx
Normal file
130
pitch-deck/components/slides/ArchitectureSlide.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
'use client'
|
||||
|
||||
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'
|
||||
|
||||
interface ArchitectureSlideProps {
|
||||
lang: Language
|
||||
}
|
||||
|
||||
export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
|
||||
const i = t(lang)
|
||||
const de = lang === 'de'
|
||||
|
||||
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: 'Mac Mini M4 · 16 GB · Llama 3.2 3B' },
|
||||
{ label: 'ComplAI Studio', desc: 'Mac Studio M4 Max · 64 GB · Qwen 2.5 32B' },
|
||||
{ label: 'ComplAI Cloud', desc: de ? 'Managed GPU-Cluster · Multi-Model' : 'Managed GPU Cluster · Multi-Model' },
|
||||
],
|
||||
},
|
||||
{
|
||||
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)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
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, Loeschkonzept' : 'RoPA, DPIA, Data Subject Rights, Deletion Concept' },
|
||||
{ label: 'AI Act Module', desc: de ? 'Risikoklassifizierung, Konformitaetsbewertung, Dokumentation' : 'Risk Classification, Conformity Assessment, Documentation' },
|
||||
{ label: 'NIS2 Module', desc: de ? 'Cybersecurity-Policies, Incident Response, Meldewege' : 'Cybersecurity Policies, Incident Response, Reporting Chains' },
|
||||
],
|
||||
},
|
||||
{
|
||||
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 ? 'Mandantenfaehig · 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' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const securityFeatures = [
|
||||
{ icon: Lock, label: de ? 'Zero-Trust Architektur' : 'Zero-Trust Architecture' },
|
||||
{ icon: Database, label: de ? 'Daten verlassen nie das Unternehmen' : 'Data Never Leaves the Company' },
|
||||
{ icon: Globe, label: de ? 'Kein Cloud-Abhaengigkeit' : 'No Cloud Dependency' },
|
||||
{ icon: Workflow, label: de ? 'Air-Gap faehig' : 'Air-Gap Capable' },
|
||||
]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FadeInView className="text-center mb-8">
|
||||
<p className="text-xs font-mono text-indigo-400/60 uppercase tracking-widest mb-2">
|
||||
{de ? 'Anhang' : 'Appendix'}
|
||||
</p>
|
||||
<h2 className="text-4xl md:text-5xl font-bold mb-3">
|
||||
<GradientText>{i.annex.architecture.title}</GradientText>
|
||||
</h2>
|
||||
<p className="text-lg text-white/50 max-w-2xl mx-auto">{i.annex.architecture.subtitle}</p>
|
||||
</FadeInView>
|
||||
|
||||
{/* Architecture Layers */}
|
||||
<div className="grid md:grid-cols-2 gap-4 mb-6">
|
||||
{layers.map((layer, idx) => {
|
||||
const Icon = layer.icon
|
||||
return (
|
||||
<FadeInView key={idx} delay={0.2 + idx * 0.1}>
|
||||
<div className={`border rounded-xl p-4 ${layer.bg}`}>
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Icon className={`w-5 h-5 ${layer.color}`} />
|
||||
<h3 className="text-sm font-bold text-white">{layer.title}</h3>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{layer.items.map((item, iidx) => (
|
||||
<div key={iidx} className="flex items-start gap-2">
|
||||
<div className={`w-1.5 h-1.5 rounded-full mt-1.5 ${layer.color} bg-current opacity-50`} />
|
||||
<div>
|
||||
<span className="text-xs font-semibold text-white/80">{item.label}</span>
|
||||
<span className="text-xs text-white/40 ml-2">{item.desc}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</FadeInView>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Security Bar */}
|
||||
<FadeInView delay={0.6}>
|
||||
<GlassCard hover={false} className="p-4">
|
||||
<div className="flex items-center justify-center gap-8 flex-wrap">
|
||||
{securityFeatures.map((feat, idx) => {
|
||||
const Icon = feat.icon
|
||||
return (
|
||||
<div key={idx} className="flex items-center gap-2">
|
||||
<Icon className="w-4 h-4 text-emerald-400" />
|
||||
<span className="text-xs text-white/60">{feat.label}</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</GlassCard>
|
||||
</FadeInView>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
198
pitch-deck/components/slides/AssumptionsSlide.tsx
Normal file
198
pitch-deck/components/slides/AssumptionsSlide.tsx
Normal file
@@ -0,0 +1,198 @@
|
||||
'use client'
|
||||
|
||||
import { Language } from '@/lib/types'
|
||||
import { useFinancialModel } from '@/lib/hooks/useFinancialModel'
|
||||
import { t } from '@/lib/i18n'
|
||||
import GradientText from '../ui/GradientText'
|
||||
import FadeInView from '../ui/FadeInView'
|
||||
import GlassCard from '../ui/GlassCard'
|
||||
import { SlidersHorizontal, TrendingUp, TrendingDown, Minus } from 'lucide-react'
|
||||
|
||||
interface AssumptionsSlideProps {
|
||||
lang: Language
|
||||
}
|
||||
|
||||
interface SensitivityResult {
|
||||
label: string
|
||||
base: string
|
||||
bull: string
|
||||
bear: string
|
||||
}
|
||||
|
||||
export default function AssumptionsSlide({ lang }: AssumptionsSlideProps) {
|
||||
const i = t(lang)
|
||||
const fm = useFinancialModel()
|
||||
const de = lang === 'de'
|
||||
|
||||
const baseScenario = fm.scenarios.find(s => s.name === 'Base Case')
|
||||
const bullScenario = fm.scenarios.find(s => s.name === 'Bull Case')
|
||||
const bearScenario = fm.scenarios.find(s => s.name === 'Bear Case')
|
||||
|
||||
function getVal(scenario: typeof baseScenario, key: string): string {
|
||||
if (!scenario) return '-'
|
||||
const a = scenario.assumptions.find(a => a.key === key)
|
||||
if (!a) return '-'
|
||||
const v = a.value
|
||||
if (typeof v === 'number') return String(v)
|
||||
return String(v)
|
||||
}
|
||||
|
||||
const rows: SensitivityResult[] = [
|
||||
{
|
||||
label: de ? 'Monatliches Wachstum' : 'Monthly Growth Rate',
|
||||
base: getVal(baseScenario, 'monthly_growth_rate') + '%',
|
||||
bull: getVal(bullScenario, 'monthly_growth_rate') + '%',
|
||||
bear: getVal(bearScenario, 'monthly_growth_rate') + '%',
|
||||
},
|
||||
{
|
||||
label: de ? 'Monatliche Churn Rate' : 'Monthly Churn Rate',
|
||||
base: getVal(baseScenario, 'churn_rate_monthly') + '%',
|
||||
bull: getVal(bullScenario, 'churn_rate_monthly') + '%',
|
||||
bear: getVal(bearScenario, 'churn_rate_monthly') + '%',
|
||||
},
|
||||
{
|
||||
label: de ? 'Startkunden' : 'Initial Customers',
|
||||
base: getVal(baseScenario, 'initial_customers'),
|
||||
bull: getVal(bullScenario, 'initial_customers'),
|
||||
bear: getVal(bearScenario, 'initial_customers'),
|
||||
},
|
||||
{
|
||||
label: 'ARPU Mini',
|
||||
base: getVal(baseScenario, 'arpu_mini') + ' EUR',
|
||||
bull: getVal(bullScenario, 'arpu_mini') + ' EUR',
|
||||
bear: getVal(bearScenario, 'arpu_mini') + ' EUR',
|
||||
},
|
||||
{
|
||||
label: 'ARPU Studio',
|
||||
base: getVal(baseScenario, 'arpu_studio') + ' EUR',
|
||||
bull: getVal(bullScenario, 'arpu_studio') + ' EUR',
|
||||
bear: getVal(bearScenario, 'arpu_studio') + ' EUR',
|
||||
},
|
||||
{
|
||||
label: 'ARPU Cloud',
|
||||
base: getVal(baseScenario, 'arpu_cloud') + ' EUR',
|
||||
bull: getVal(bullScenario, 'arpu_cloud') + ' EUR',
|
||||
bear: getVal(bearScenario, 'arpu_cloud') + ' EUR',
|
||||
},
|
||||
{
|
||||
label: 'CAC',
|
||||
base: getVal(baseScenario, 'cac') + ' EUR',
|
||||
bull: getVal(bullScenario, 'cac') + ' EUR',
|
||||
bear: getVal(bearScenario, 'cac') + ' EUR',
|
||||
},
|
||||
{
|
||||
label: de ? 'Produktmix Mini/Studio/Cloud' : 'Product Mix Mini/Studio/Cloud',
|
||||
base: `${getVal(baseScenario, 'product_mix_mini')}/${getVal(baseScenario, 'product_mix_studio')}/${getVal(baseScenario, 'product_mix_cloud')}`,
|
||||
bull: `${getVal(bullScenario, 'product_mix_mini')}/${getVal(bullScenario, 'product_mix_studio')}/${getVal(bullScenario, 'product_mix_cloud')}`,
|
||||
bear: `${getVal(bearScenario, 'product_mix_mini')}/${getVal(bearScenario, 'product_mix_studio')}/${getVal(bearScenario, 'product_mix_cloud')}`,
|
||||
},
|
||||
{
|
||||
label: de ? 'Marketing / Monat' : 'Marketing / Month',
|
||||
base: getVal(baseScenario, 'marketing_monthly') + ' EUR',
|
||||
bull: getVal(bullScenario, 'marketing_monthly') + ' EUR',
|
||||
bear: getVal(bearScenario, 'marketing_monthly') + ' EUR',
|
||||
},
|
||||
]
|
||||
|
||||
// Summary KPIs from computed results
|
||||
const baseSummary = fm.results.get(baseScenario?.id || '')?.summary
|
||||
const bullSummary = fm.results.get(bullScenario?.id || '')?.summary
|
||||
const bearSummary = fm.results.get(bearScenario?.id || '')?.summary
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FadeInView className="text-center mb-8">
|
||||
<p className="text-xs font-mono text-indigo-400/60 uppercase tracking-widest mb-2">
|
||||
{de ? 'Anhang' : 'Appendix'}
|
||||
</p>
|
||||
<h2 className="text-4xl md:text-5xl font-bold mb-3">
|
||||
<GradientText>{i.annex.assumptions.title}</GradientText>
|
||||
</h2>
|
||||
<p className="text-lg text-white/50 max-w-2xl mx-auto">{i.annex.assumptions.subtitle}</p>
|
||||
</FadeInView>
|
||||
|
||||
{/* Sensitivity Table */}
|
||||
<FadeInView delay={0.2}>
|
||||
<GlassCard hover={false} className="p-5 mb-6 overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-white/10">
|
||||
<th className="text-left py-2 pr-4 text-white/40 font-medium min-w-[200px]">
|
||||
{de ? 'Annahme' : 'Assumption'}
|
||||
</th>
|
||||
<th className="text-center py-2 px-3 min-w-[100px]">
|
||||
<span className="flex items-center justify-center gap-1 text-red-400 font-medium">
|
||||
<TrendingDown className="w-3 h-3" /> Bear
|
||||
</span>
|
||||
</th>
|
||||
<th className="text-center py-2 px-3 min-w-[100px]">
|
||||
<span className="flex items-center justify-center gap-1 text-indigo-400 font-medium">
|
||||
<Minus className="w-3 h-3" /> Base
|
||||
</span>
|
||||
</th>
|
||||
<th className="text-center py-2 px-3 min-w-[100px]">
|
||||
<span className="flex items-center justify-center gap-1 text-emerald-400 font-medium">
|
||||
<TrendingUp className="w-3 h-3" /> Bull
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((row, idx) => (
|
||||
<tr key={idx} className={idx % 2 === 0 ? 'bg-white/[0.02]' : ''}>
|
||||
<td className="py-2 pr-4 text-white/60">{row.label}</td>
|
||||
<td className="py-2 px-3 text-center font-mono text-red-400/70">{row.bear}</td>
|
||||
<td className="py-2 px-3 text-center font-mono text-white font-semibold">{row.base}</td>
|
||||
<td className="py-2 px-3 text-center font-mono text-emerald-400/70">{row.bull}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</GlassCard>
|
||||
</FadeInView>
|
||||
|
||||
{/* Outcome Summary */}
|
||||
{baseSummary && (
|
||||
<div className="grid md:grid-cols-3 gap-4">
|
||||
{[
|
||||
{ label: 'Bear', summary: bearSummary, color: 'text-red-400', bg: 'bg-red-500/5 border-red-500/10' },
|
||||
{ label: 'Base', summary: baseSummary, color: 'text-indigo-400', bg: 'bg-indigo-500/5 border-indigo-500/10' },
|
||||
{ label: 'Bull', summary: bullSummary, color: 'text-emerald-400', bg: 'bg-emerald-500/5 border-emerald-500/10' },
|
||||
].map((s, idx) => (
|
||||
<FadeInView key={s.label} delay={0.4 + idx * 0.1}>
|
||||
<div className={`border rounded-xl p-4 ${s.bg}`}>
|
||||
<p className={`text-sm font-bold ${s.color} mb-3`}>{s.label} Case</p>
|
||||
<div className="space-y-2 text-xs">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-white/40">ARR 2030</span>
|
||||
<span className="text-white font-mono">
|
||||
{s.summary ? `${(s.summary.final_arr / 1_000_000).toFixed(1)}M` : '-'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-white/40">{de ? 'Kunden 2030' : 'Customers 2030'}</span>
|
||||
<span className="text-white font-mono">
|
||||
{s.summary?.final_customers?.toLocaleString('de-DE') || '-'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-white/40">Break-Even</span>
|
||||
<span className="text-white font-mono">
|
||||
{s.summary?.break_even_month ? `${de ? 'Monat' : 'Month'} ${s.summary.break_even_month}` : (de ? 'Nicht erreicht' : 'Not reached')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-white/40">LTV/CAC</span>
|
||||
<span className="text-white font-mono">
|
||||
{s.summary?.final_ltv_cac ? `${s.summary.final_ltv_cac.toFixed(1)}x` : '-'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FadeInView>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -13,8 +13,48 @@ interface BusinessModelSlideProps {
|
||||
products: PitchProduct[]
|
||||
}
|
||||
|
||||
const AMORT_MONTHS = 24
|
||||
|
||||
function computeKPIs(products: PitchProduct[]) {
|
||||
if (!products.length) return { weightedMarginDuring: 0, weightedMarginAfter: 0, amortMonths: AMORT_MONTHS }
|
||||
|
||||
// Compute weighted margin based on product mix (equal weight per product as proxy)
|
||||
const n = products.length
|
||||
let sumMarginDuring = 0
|
||||
let sumMarginAfter = 0
|
||||
let maxAmortMonths = 0
|
||||
|
||||
for (const p of products) {
|
||||
const price = p.monthly_price_eur
|
||||
if (price <= 0) continue
|
||||
const amort = p.hardware_cost_eur > 0 ? p.hardware_cost_eur / AMORT_MONTHS : 0
|
||||
const opex = p.operating_cost_eur > 0 ? p.operating_cost_eur : 0
|
||||
|
||||
// Margin during amortization
|
||||
const marginDuring = (price - amort - opex) / price
|
||||
sumMarginDuring += marginDuring
|
||||
|
||||
// Margin after amortization (no more HW cost)
|
||||
const marginAfter = (price - opex) / price
|
||||
sumMarginAfter += marginAfter
|
||||
|
||||
// Payback period in months
|
||||
if (p.hardware_cost_eur > 0 && price - opex > 0) {
|
||||
const payback = Math.ceil(p.hardware_cost_eur / (price - opex))
|
||||
if (payback > maxAmortMonths) maxAmortMonths = payback
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
weightedMarginDuring: Math.round((sumMarginDuring / n) * 100),
|
||||
weightedMarginAfter: Math.round((sumMarginAfter / n) * 100),
|
||||
amortMonths: maxAmortMonths || AMORT_MONTHS,
|
||||
}
|
||||
}
|
||||
|
||||
export default function BusinessModelSlide({ lang, products }: BusinessModelSlideProps) {
|
||||
const i = t(lang)
|
||||
const { weightedMarginDuring, weightedMarginAfter, amortMonths } = computeKPIs(products)
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -25,7 +65,7 @@ export default function BusinessModelSlide({ lang, products }: BusinessModelSlid
|
||||
<p className="text-lg text-white/50 max-w-2xl mx-auto">{i.businessModel.subtitle}</p>
|
||||
</FadeInView>
|
||||
|
||||
{/* Key Metrics */}
|
||||
{/* Key Metrics — dynamisch berechnet */}
|
||||
<div className="grid md:grid-cols-3 gap-4 mb-8">
|
||||
<GlassCard delay={0.2} className="text-center">
|
||||
<Repeat className="w-6 h-6 text-indigo-400 mx-auto mb-2" />
|
||||
@@ -36,14 +76,18 @@ export default function BusinessModelSlide({ lang, products }: BusinessModelSlid
|
||||
<GlassCard delay={0.3} className="text-center">
|
||||
<DollarSign className="w-6 h-6 text-green-400 mx-auto mb-2" />
|
||||
<p className="text-sm text-white/50 mb-1">{i.businessModel.margin}</p>
|
||||
<p className="text-2xl font-bold text-white">>70%</p>
|
||||
<p className="text-xs text-white/30">{lang === 'de' ? 'nach Amortisation' : 'post amortization'}</p>
|
||||
<p className="text-2xl font-bold text-white">>{weightedMarginAfter}%</p>
|
||||
<p className="text-xs text-white/30">
|
||||
{lang === 'de' ? 'nach Amortisation' : 'post amortization'}
|
||||
{' · '}
|
||||
{weightedMarginDuring}% {lang === 'de' ? 'waehrend' : 'during'}
|
||||
</p>
|
||||
</GlassCard>
|
||||
<GlassCard delay={0.4} className="text-center">
|
||||
<TrendingUp className="w-6 h-6 text-purple-400 mx-auto mb-2" />
|
||||
<p className="text-sm text-white/50 mb-1">{i.businessModel.amortization}</p>
|
||||
<p className="text-2xl font-bold text-white">24 {i.businessModel.months}</p>
|
||||
<p className="text-xs text-white/30">{lang === 'de' ? 'Hardware-Amortisation' : 'Hardware Amortization'}</p>
|
||||
<p className="text-2xl font-bold text-white">{amortMonths} {i.businessModel.months}</p>
|
||||
<p className="text-xs text-white/30">{lang === 'de' ? 'max. Hardware-Amortisation' : 'max. Hardware Amortization'}</p>
|
||||
</GlassCard>
|
||||
</div>
|
||||
|
||||
@@ -52,7 +96,7 @@ export default function BusinessModelSlide({ lang, products }: BusinessModelSlid
|
||||
<h3 className="text-lg font-semibold mb-4 text-white/70">{i.businessModel.unitEconomics}</h3>
|
||||
<div className="grid md:grid-cols-3 gap-4">
|
||||
{products.map((p, idx) => {
|
||||
const amort = p.hardware_cost_eur > 0 ? Math.round(p.hardware_cost_eur / 24) : 0
|
||||
const amort = p.hardware_cost_eur > 0 ? Math.round(p.hardware_cost_eur / AMORT_MONTHS) : 0
|
||||
const monthlyMargin = p.monthly_price_eur - amort - (p.operating_cost_eur > 0 ? p.operating_cost_eur : 0)
|
||||
const marginPct = Math.round((monthlyMargin / p.monthly_price_eur) * 100)
|
||||
|
||||
@@ -79,7 +123,7 @@ export default function BusinessModelSlide({ lang, products }: BusinessModelSlid
|
||||
{p.operating_cost_eur > 0 && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-white/50">{i.businessModel.operatingCost}</span>
|
||||
<span className="text-white/70">-{p.operating_cost_eur.toLocaleString('de-DE')} EUR/Mo</span>
|
||||
<span className="text-white/70">-{p.operating_cost_eur} EUR/Mo</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="border-t border-white/10 pt-2 flex justify-between">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from 'framer-motion'
|
||||
import { Language } from '@/lib/types'
|
||||
import { Language, PitchFunding } from '@/lib/types'
|
||||
import { t } from '@/lib/i18n'
|
||||
import { ArrowRight } from 'lucide-react'
|
||||
import GradientText from '../ui/GradientText'
|
||||
@@ -10,11 +10,36 @@ import BrandName from '../ui/BrandName'
|
||||
interface CoverSlideProps {
|
||||
lang: Language
|
||||
onNext: () => void
|
||||
funding?: PitchFunding
|
||||
}
|
||||
|
||||
export default function CoverSlide({ lang, onNext }: CoverSlideProps) {
|
||||
function formatRoundLabel(funding: PitchFunding | undefined): string {
|
||||
if (!funding) return 'Pre-Seed'
|
||||
// Extract a short round label from round_name
|
||||
const name = funding.round_name || ''
|
||||
if (name.toLowerCase().includes('seed')) return 'Pre-Seed'
|
||||
if (name.toLowerCase().includes('series a')) return 'Series A'
|
||||
return 'Pre-Seed'
|
||||
}
|
||||
|
||||
function formatQuarter(dateStr: string | undefined): string {
|
||||
if (!dateStr) return ''
|
||||
try {
|
||||
const d = new Date(dateStr)
|
||||
const quarter = Math.ceil((d.getMonth() + 1) / 3)
|
||||
return `Q${quarter} ${d.getFullYear()}`
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export default function CoverSlide({ lang, onNext, funding }: CoverSlideProps) {
|
||||
const i = t(lang)
|
||||
|
||||
const roundLabel = formatRoundLabel(funding)
|
||||
const quarter = formatQuarter(funding?.target_date)
|
||||
const subtitle = quarter ? `${roundLabel} · ${quarter}` : roundLabel
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center text-center min-h-[70vh]">
|
||||
{/* Logo / Brand */}
|
||||
@@ -63,14 +88,14 @@ export default function CoverSlide({ lang, onNext }: CoverSlideProps) {
|
||||
{i.cover.tagline}
|
||||
</motion.p>
|
||||
|
||||
{/* Subtitle */}
|
||||
{/* Subtitle — dynamisch aus Funding */}
|
||||
<motion.p
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.5, delay: 0.8 }}
|
||||
className="text-sm text-white/30 font-mono tracking-wider mb-12"
|
||||
>
|
||||
{i.cover.subtitle}
|
||||
{subtitle}
|
||||
</motion.p>
|
||||
|
||||
{/* CTA */}
|
||||
|
||||
274
pitch-deck/components/slides/EngineeringSlide.tsx
Normal file
274
pitch-deck/components/slides/EngineeringSlide.tsx
Normal file
@@ -0,0 +1,274 @@
|
||||
'use client'
|
||||
|
||||
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 {
|
||||
Code2,
|
||||
Container,
|
||||
GitBranch,
|
||||
Layers,
|
||||
ShieldCheck,
|
||||
Terminal,
|
||||
Cpu,
|
||||
Database,
|
||||
Braces,
|
||||
FileCode2,
|
||||
Server,
|
||||
Workflow,
|
||||
} from 'lucide-react'
|
||||
|
||||
interface EngineeringSlideProps {
|
||||
lang: Language
|
||||
}
|
||||
|
||||
export default function EngineeringSlide({ lang }: EngineeringSlideProps) {
|
||||
const i = t(lang)
|
||||
const de = lang === 'de'
|
||||
|
||||
const heroStats = [
|
||||
{
|
||||
value: '691K',
|
||||
label: de ? 'Zeilen Code' : 'Lines of Code',
|
||||
sub: 'Go · Python · TypeScript',
|
||||
color: 'text-indigo-400',
|
||||
borderColor: 'border-indigo-500/30',
|
||||
},
|
||||
{
|
||||
value: '45',
|
||||
label: de ? 'Docker Container' : 'Docker Containers',
|
||||
sub: de ? 'Produktiv auf einem Mac Studio' : 'Production on one Mac Studio',
|
||||
color: 'text-emerald-400',
|
||||
borderColor: 'border-emerald-500/30',
|
||||
},
|
||||
{
|
||||
value: '27',
|
||||
label: de ? 'Microservices' : 'Microservices',
|
||||
sub: '10 Go · 9 Python · 8 Next.js',
|
||||
color: 'text-purple-400',
|
||||
borderColor: 'border-purple-500/30',
|
||||
},
|
||||
{
|
||||
value: '37',
|
||||
label: 'Dockerfiles',
|
||||
sub: de ? 'Vollstaendig containerisiert' : 'Fully containerized',
|
||||
color: 'text-amber-400',
|
||||
borderColor: 'border-amber-500/30',
|
||||
},
|
||||
]
|
||||
|
||||
const languageBreakdown = [
|
||||
{ lang: 'TypeScript / TSX', pct: 58, loc: '403K', color: 'bg-blue-500', icon: Braces },
|
||||
{ lang: 'Python', pct: 23, loc: '160K', color: 'bg-yellow-500', icon: Terminal },
|
||||
{ lang: 'Go', pct: 18, loc: '127K', color: 'bg-cyan-500', icon: Code2 },
|
||||
]
|
||||
|
||||
const devopsStack = [
|
||||
{
|
||||
icon: GitBranch,
|
||||
label: 'Gitea',
|
||||
desc: de ? 'Self-hosted Git · 4 Repos · Code Review' : 'Self-hosted Git · 4 Repos · Code Review',
|
||||
},
|
||||
{
|
||||
icon: Workflow,
|
||||
label: 'Woodpecker CI',
|
||||
desc: de ? 'Self-hosted CI/CD · Lint · Test · Build · Deploy' : 'Self-hosted CI/CD · Lint · Test · Build · Deploy',
|
||||
},
|
||||
{
|
||||
icon: Container,
|
||||
label: 'Docker Compose',
|
||||
desc: de ? '66 Service-Definitionen · Multi-Stage Builds' : '66 Service Definitions · Multi-Stage Builds',
|
||||
},
|
||||
{
|
||||
icon: ShieldCheck,
|
||||
label: 'DevSecOps',
|
||||
desc: 'Semgrep · Trivy · Gitleaks · CycloneDX SBOM',
|
||||
},
|
||||
{
|
||||
icon: Database,
|
||||
label: 'HashiCorp Vault',
|
||||
desc: de ? 'Secrets Management · Auto-Rotation · PKI' : 'Secrets Management · Auto-Rotation · PKI',
|
||||
},
|
||||
{
|
||||
icon: Server,
|
||||
label: de ? 'Infrastruktur' : 'Infrastructure',
|
||||
desc: 'Nginx · PostgreSQL · Qdrant · MinIO · Valkey',
|
||||
},
|
||||
]
|
||||
|
||||
const serviceArchitecture = [
|
||||
{
|
||||
project: 'breakpilot-core',
|
||||
color: 'text-indigo-400',
|
||||
dotColor: 'bg-indigo-400',
|
||||
services: de
|
||||
? ['Admin Dashboard', 'Consent Service (Go)', 'Billing Service (Go)', 'RAG Pipeline', 'Embedding Service', 'Voice Service', 'Pitch Deck', 'Nginx Reverse Proxy']
|
||||
: ['Admin Dashboard', 'Consent Service (Go)', 'Billing Service (Go)', 'RAG Pipeline', 'Embedding Service', 'Voice Service', 'Pitch Deck', 'Nginx Reverse Proxy'],
|
||||
},
|
||||
{
|
||||
project: 'breakpilot-lehrer',
|
||||
color: 'text-purple-400',
|
||||
dotColor: 'bg-purple-400',
|
||||
services: de
|
||||
? ['Lehrer Dashboard', 'Studio v2', 'Website', 'Klausur Service', 'School Service (Go)', 'Edu Search (Go)']
|
||||
: ['Teacher Dashboard', 'Studio v2', 'Website', 'Exam Service', 'School Service (Go)', 'Edu Search (Go)'],
|
||||
},
|
||||
{
|
||||
project: 'breakpilot-compliance',
|
||||
color: 'text-emerald-400',
|
||||
dotColor: 'bg-emerald-400',
|
||||
services: de
|
||||
? ['Compliance Dashboard', 'Developer Portal', 'AI SDK (Go)', 'Document Crawler', 'DSMS Gateway', 'Security Scanner (Go)']
|
||||
: ['Compliance Dashboard', 'Developer Portal', 'AI SDK (Go)', 'Document Crawler', 'DSMS Gateway', 'Security Scanner (Go)'],
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FadeInView className="text-center mb-6">
|
||||
<p className="text-xs font-mono text-indigo-400/60 uppercase tracking-widest mb-2">
|
||||
{de ? 'Anhang' : 'Appendix'}
|
||||
</p>
|
||||
<h2 className="text-4xl md:text-5xl font-bold mb-2">
|
||||
<GradientText>{i.annex.engineering.title}</GradientText>
|
||||
</h2>
|
||||
<p className="text-lg text-white/50 max-w-2xl mx-auto">{i.annex.engineering.subtitle}</p>
|
||||
</FadeInView>
|
||||
|
||||
{/* Hero Stats */}
|
||||
<FadeInView delay={0.1}>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-5">
|
||||
{heroStats.map((stat, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className={`border rounded-xl p-3 bg-white/[0.03] text-center ${stat.borderColor}`}
|
||||
>
|
||||
<p className={`text-3xl font-black tracking-tight ${stat.color}`}>{stat.value}</p>
|
||||
<p className="text-xs font-semibold text-white/70 mt-0.5">{stat.label}</p>
|
||||
<p className="text-[10px] text-white/30 mt-0.5">{stat.sub}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</FadeInView>
|
||||
|
||||
<div className="grid md:grid-cols-12 gap-4">
|
||||
{/* Left Column: Language Breakdown + Service Map */}
|
||||
<div className="md:col-span-5 space-y-4">
|
||||
{/* Language Breakdown */}
|
||||
<FadeInView delay={0.2}>
|
||||
<GlassCard hover={false} className="p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<FileCode2 className="w-4 h-4 text-white/40" />
|
||||
<p className="text-xs font-semibold text-white/40 uppercase tracking-wider">
|
||||
{de ? 'Sprachen-Mix' : 'Language Mix'}
|
||||
</p>
|
||||
</div>
|
||||
{/* Stacked bar */}
|
||||
<div className="flex rounded-full overflow-hidden h-3 mb-3">
|
||||
{languageBreakdown.map((l, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className={`${l.color} transition-all`}
|
||||
style={{ width: `${l.pct}%` }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
{languageBreakdown.map((l, idx) => {
|
||||
const Icon = l.icon
|
||||
return (
|
||||
<div key={idx} className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`w-2 h-2 rounded-full ${l.color}`} />
|
||||
<Icon className="w-3 h-3 text-white/40" />
|
||||
<span className="text-xs text-white/60">{l.lang}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs font-mono text-white/40">{l.loc}</span>
|
||||
<span className="text-xs font-bold text-white/70">{l.pct}%</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</GlassCard>
|
||||
</FadeInView>
|
||||
|
||||
{/* Service Map */}
|
||||
<FadeInView delay={0.3}>
|
||||
<GlassCard hover={false} className="p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Layers className="w-4 h-4 text-white/40" />
|
||||
<p className="text-xs font-semibold text-white/40 uppercase tracking-wider">
|
||||
{de ? 'Service-Architektur' : 'Service Architecture'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{serviceArchitecture.map((proj, idx) => (
|
||||
<div key={idx}>
|
||||
<p className={`text-[10px] font-bold uppercase tracking-wider ${proj.color} mb-1`}>
|
||||
{proj.project}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{proj.services.map((svc, sidx) => (
|
||||
<span
|
||||
key={sidx}
|
||||
className="text-[10px] px-1.5 py-0.5 rounded bg-white/[0.05] text-white/50 border border-white/[0.06]"
|
||||
>
|
||||
{svc}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</GlassCard>
|
||||
</FadeInView>
|
||||
</div>
|
||||
|
||||
{/* Right Column: DevOps Stack */}
|
||||
<div className="md:col-span-7">
|
||||
<FadeInView delay={0.25}>
|
||||
<GlassCard hover={false} className="p-4 h-full">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Cpu className="w-4 h-4 text-white/40" />
|
||||
<p className="text-xs font-semibold text-white/40 uppercase tracking-wider">
|
||||
{de ? 'DevOps & Toolchain' : 'DevOps & Toolchain'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{devopsStack.map((tool, idx) => {
|
||||
const Icon = tool.icon
|
||||
return (
|
||||
<div
|
||||
key={idx}
|
||||
className="flex items-start gap-3 p-2.5 rounded-lg bg-white/[0.03] border border-white/5"
|
||||
>
|
||||
<div className="w-8 h-8 rounded-lg bg-indigo-500/10 border border-indigo-500/20 flex items-center justify-center shrink-0">
|
||||
<Icon className="w-4 h-4 text-indigo-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-white/80">{tool.label}</p>
|
||||
<p className="text-xs text-white/40 mt-0.5">{tool.desc}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{/* Footer note */}
|
||||
<div className="mt-3 pt-3 border-t border-white/5">
|
||||
<p className="text-[10px] text-white/20 text-center">
|
||||
{de
|
||||
? '100% Self-Hosted · Kein externer Cloud-Anbieter · Vollstaendige Kontrolle ueber Code und Daten'
|
||||
: '100% Self-Hosted · No External Cloud Provider · Full Control Over Code and Data'}
|
||||
</p>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</FadeInView>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
141
pitch-deck/components/slides/GTMSlide.tsx
Normal file
141
pitch-deck/components/slides/GTMSlide.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
'use client'
|
||||
|
||||
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 { Target, Users, Handshake, Megaphone, Building2, GraduationCap } from 'lucide-react'
|
||||
|
||||
interface GTMSlideProps {
|
||||
lang: Language
|
||||
}
|
||||
|
||||
export default function GTMSlide({ lang }: GTMSlideProps) {
|
||||
const i = t(lang)
|
||||
const de = lang === 'de'
|
||||
|
||||
const phases = [
|
||||
{
|
||||
phase: de ? 'Phase 1: Pilot (2026)' : 'Phase 1: Pilot (2026)',
|
||||
color: 'border-indigo-500/30 bg-indigo-500/5',
|
||||
textColor: 'text-indigo-400',
|
||||
items: [
|
||||
de ? 'Direktvertrieb an 5-20 KMU in DACH' : 'Direct sales to 5-20 SMEs in DACH',
|
||||
de ? 'Fokus: Gesundheitswesen, Finanzdienstleister, Rechtsanwaelte' : 'Focus: Healthcare, Financial Services, Law Firms',
|
||||
de ? 'Persoenliches Onboarding, White-Glove-Service' : 'Personal onboarding, white-glove service',
|
||||
de ? 'Case Studies und Referenzkunden aufbauen' : 'Build case studies and reference customers',
|
||||
],
|
||||
},
|
||||
{
|
||||
phase: de ? 'Phase 2: Skalierung (2027)' : 'Phase 2: Scale (2027)',
|
||||
color: 'border-purple-500/30 bg-purple-500/5',
|
||||
textColor: 'text-purple-400',
|
||||
items: [
|
||||
de ? 'Channel-Partnerschaften mit IT-Systemhaeusern' : 'Channel partnerships with IT system integrators',
|
||||
de ? 'IHK- und Handwerkskammer-Kooperationen' : 'Chamber of Commerce & Industry partnerships',
|
||||
de ? 'Content Marketing: Compliance-Webinare, Whitepaper' : 'Content marketing: Compliance webinars, whitepapers',
|
||||
de ? 'Zielkunden: 50-200 in regulierten Branchen' : 'Target: 50-200 customers in regulated industries',
|
||||
],
|
||||
},
|
||||
{
|
||||
phase: de ? 'Phase 3: Expansion (2028+)' : 'Phase 3: Expansion (2028+)',
|
||||
color: 'border-emerald-500/30 bg-emerald-500/5',
|
||||
textColor: 'text-emerald-400',
|
||||
items: [
|
||||
de ? 'Cloud-Tier fuer groessere Unternehmen (50-500 MA)' : 'Cloud tier for larger companies (50-500 employees)',
|
||||
de ? 'EU-Expansion: Oesterreich, Schweiz, Benelux, Nordics' : 'EU expansion: Austria, Switzerland, Benelux, Nordics',
|
||||
de ? 'OEM/Whitelabel fuer Steuerberater und Wirtschaftspruefer' : 'OEM/whitelabel for tax advisors and auditors',
|
||||
de ? 'Self-Service-Onboarding und PLG-Motion' : 'Self-service onboarding and PLG motion',
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const channels = [
|
||||
{ icon: Target, label: de ? 'Direktvertrieb' : 'Direct Sales', pct: '40%', desc: de ? 'Outbound + Inbound, 2 AEs ab 2027' : 'Outbound + Inbound, 2 AEs from 2027' },
|
||||
{ icon: Handshake, label: de ? 'Channel-Partner' : 'Channel Partners', pct: '30%', desc: de ? 'IT-Haendler, Systemhaeuser, MSPs' : 'IT resellers, system integrators, MSPs' },
|
||||
{ icon: Megaphone, label: de ? 'Content & Events' : 'Content & Events', pct: '20%', desc: de ? 'Webinare, Messen (it-sa), SEO' : 'Webinars, trade shows (it-sa), SEO' },
|
||||
{ icon: Users, label: de ? 'Empfehlungen' : 'Referrals', pct: '10%', desc: de ? 'Bestandskunden-Empfehlungsprogramm' : 'Customer referral program' },
|
||||
]
|
||||
|
||||
const idealCustomer = [
|
||||
{ icon: Building2, label: de ? '10-250 Mitarbeiter' : '10-250 Employees' },
|
||||
{ icon: GraduationCap, label: de ? 'Regulierte Branche (Gesundheit, Finanzen, Energie, KRITIS)' : 'Regulated Industry (Healthcare, Finance, Energy, Critical Infrastructure)' },
|
||||
{ icon: Target, label: de ? 'Kein interner Compliance-Officer oder DSB' : 'No Internal Compliance Officer or DPO' },
|
||||
]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FadeInView className="text-center mb-8">
|
||||
<p className="text-xs font-mono text-indigo-400/60 uppercase tracking-widest mb-2">
|
||||
{de ? 'Anhang' : 'Appendix'}
|
||||
</p>
|
||||
<h2 className="text-4xl md:text-5xl font-bold mb-3">
|
||||
<GradientText>{i.annex.gtm.title}</GradientText>
|
||||
</h2>
|
||||
<p className="text-lg text-white/50 max-w-2xl mx-auto">{i.annex.gtm.subtitle}</p>
|
||||
</FadeInView>
|
||||
|
||||
{/* ICP */}
|
||||
<FadeInView delay={0.15}>
|
||||
<GlassCard hover={false} className="p-4 mb-6">
|
||||
<p className="text-xs font-semibold text-white/40 uppercase tracking-wider mb-3">
|
||||
{de ? 'Ideales Kundenprofil (ICP)' : 'Ideal Customer Profile (ICP)'}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
{idealCustomer.map((ic, idx) => {
|
||||
const Icon = ic.icon
|
||||
return (
|
||||
<div key={idx} className="flex items-center gap-2">
|
||||
<Icon className="w-4 h-4 text-indigo-400" />
|
||||
<span className="text-sm text-white/70">{ic.label}</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</GlassCard>
|
||||
</FadeInView>
|
||||
|
||||
{/* Phases */}
|
||||
<div className="grid md:grid-cols-3 gap-4 mb-6">
|
||||
{phases.map((phase, idx) => (
|
||||
<FadeInView key={idx} delay={0.2 + idx * 0.1}>
|
||||
<div className={`border rounded-xl p-4 h-full ${phase.color}`}>
|
||||
<p className={`text-sm font-bold ${phase.textColor} mb-3`}>{phase.phase}</p>
|
||||
<ul className="space-y-2">
|
||||
{phase.items.map((item, iidx) => (
|
||||
<li key={iidx} className="flex items-start gap-2 text-xs text-white/60">
|
||||
<span className={`w-1 h-1 rounded-full mt-1.5 ${phase.textColor} bg-current`} />
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</FadeInView>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Channel Mix */}
|
||||
<FadeInView delay={0.5}>
|
||||
<GlassCard hover={false} className="p-4">
|
||||
<p className="text-xs font-semibold text-white/40 uppercase tracking-wider mb-3">
|
||||
{de ? 'Vertriebskanalmix (Ziel 2028)' : 'Channel Mix (Target 2028)'}
|
||||
</p>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
{channels.map((ch, idx) => {
|
||||
const Icon = ch.icon
|
||||
return (
|
||||
<div key={idx} className="text-center">
|
||||
<Icon className="w-5 h-5 text-indigo-400 mx-auto mb-1" />
|
||||
<p className="text-lg font-bold text-white">{ch.pct}</p>
|
||||
<p className="text-xs font-semibold text-white/60 mb-0.5">{ch.label}</p>
|
||||
<p className="text-[10px] text-white/30">{ch.desc}</p>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</GlassCard>
|
||||
</FadeInView>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from 'framer-motion'
|
||||
import { useState } from 'react'
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
import { Language, PitchMarket } from '@/lib/types'
|
||||
import { t, formatEur } from '@/lib/i18n'
|
||||
import { t } from '@/lib/i18n'
|
||||
import { ExternalLink, X, TrendingUp } from 'lucide-react'
|
||||
import GradientText from '../ui/GradientText'
|
||||
import FadeInView from '../ui/FadeInView'
|
||||
import AnimatedCounter from '../ui/AnimatedCounter'
|
||||
@@ -12,14 +14,133 @@ interface MarketSlideProps {
|
||||
market: PitchMarket[]
|
||||
}
|
||||
|
||||
interface MarketSourceInfo {
|
||||
name: string
|
||||
url: string
|
||||
date: string
|
||||
excerpt_de: string
|
||||
excerpt_en: string
|
||||
}
|
||||
|
||||
// Quellenangaben fuer die Marktzahlen
|
||||
const marketSources: Record<string, MarketSourceInfo[]> = {
|
||||
TAM: [
|
||||
{
|
||||
name: 'Grand View Research — GRC Market Report',
|
||||
url: 'https://www.grandviewresearch.com/industry-analysis/governance-risk-management-compliance-market',
|
||||
date: '2024',
|
||||
excerpt_de: 'Der globale GRC-Software-Markt wurde 2023 auf rund 11,8 Mrd. USD bewertet und soll bis 2030 mit einer CAGR von 14,3% auf ca. 35 Mrd. USD wachsen. Compliance-Management ist das am schnellsten wachsende Segment.',
|
||||
excerpt_en: 'The global GRC software market was valued at approximately USD 11.8B in 2023 and is projected to grow at a CAGR of 14.3% to reach ~USD 35B by 2030. Compliance management is the fastest-growing segment.',
|
||||
},
|
||||
],
|
||||
SAM: [
|
||||
{
|
||||
name: 'Statista / IDC — European Compliance Software',
|
||||
url: 'https://www.statista.com/outlook/tmo/software/enterprise-software/compliance-software/europe',
|
||||
date: '2024',
|
||||
excerpt_de: 'Der europaeische Compliance-Software-Markt wird auf ca. 4,2 Mrd. EUR geschaetzt, wobei die DACH-Region (Deutschland, Oesterreich, Schweiz) mit rund 2,1 Mrd. EUR etwa die Haelfte ausmacht. Der Markt waechst mit 18% p.a. — getrieben durch DSGVO, NIS2 und den AI Act.',
|
||||
excerpt_en: 'The European compliance software market is estimated at approx. EUR 4.2B, with the DACH region (Germany, Austria, Switzerland) accounting for roughly EUR 2.1B. The market is growing at 18% p.a. — driven by GDPR, NIS2, and the AI Act.',
|
||||
},
|
||||
],
|
||||
SOM: [
|
||||
{
|
||||
name: 'Eigene Analyse auf Basis von Destatis und KfW-Mittelstandspanel',
|
||||
url: 'https://www.destatis.de/DE/Themen/Branchen-Unternehmen/Unternehmen/Kleine-Unternehmen-Mittlere-Unternehmen/_inhalt.html',
|
||||
date: '2024-2025',
|
||||
excerpt_de: 'In Deutschland gibt es ca. 3,5 Mio. KMU (Destatis). Davon sind geschaetzt 150.000-200.000 in regulierten Branchen (Gesundheit, Finanzen, Energie, KRITIS) mit erhoehtem Compliance-Bedarf. Bei einem durchschnittlichen Jahresumsatz von 900-1.200 EUR pro Kunde ergibt sich ein adressierbarer Markt von ca. 180 Mio. EUR fuer Self-Hosted-Compliance-Loesungen.',
|
||||
excerpt_en: 'Germany has approx. 3.5M SMEs (Destatis). Of these, an estimated 150,000-200,000 operate in regulated industries (healthcare, finance, energy, critical infrastructure) with elevated compliance needs. At an average annual revenue of EUR 900-1,200 per customer, this yields an addressable market of approx. EUR 180M for self-hosted compliance solutions.',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const sizes = [280, 200, 130]
|
||||
const colors = ['border-indigo-500/30 bg-indigo-500/5', 'border-purple-500/30 bg-purple-500/5', 'border-blue-500/30 bg-blue-500/5']
|
||||
const textColors = ['text-indigo-400', 'text-purple-400', 'text-blue-400']
|
||||
|
||||
function SourceModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
segment,
|
||||
lang,
|
||||
}: {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
segment: string
|
||||
lang: Language
|
||||
}) {
|
||||
if (!isOpen) return null
|
||||
const sources = marketSources[segment] || []
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 z-50 flex items-center justify-center p-4"
|
||||
onClick={onClose}
|
||||
>
|
||||
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" />
|
||||
<motion.div
|
||||
initial={{ scale: 0.9, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
exit={{ scale: 0.9, opacity: 0 }}
|
||||
className="relative bg-slate-900/95 border border-white/10 rounded-2xl p-6 max-w-2xl w-full max-h-[80vh] overflow-y-auto"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute top-4 right-4 p-1 text-white/40 hover:text-white/80 transition-colors"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
<h3 className="text-xl font-bold text-white mb-1">{segment}</h3>
|
||||
<p className="text-sm text-white/40 mb-6">
|
||||
{lang === 'de' ? 'Quellenangaben' : 'Sources'}
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
{sources.map((src, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="bg-white/[0.05] border border-white/10 rounded-xl p-4"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3 mb-2">
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-white">{src.name}</p>
|
||||
<p className="text-xs text-white/30">{src.date}</p>
|
||||
</div>
|
||||
<a
|
||||
href={src.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex-shrink-0 p-1.5 text-indigo-400 hover:text-indigo-300 hover:bg-indigo-500/10 rounded-lg transition-colors"
|
||||
title={lang === 'de' ? 'Quelle oeffnen' : 'Open source'}
|
||||
>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
</a>
|
||||
</div>
|
||||
<p className="text-sm text-white/60 leading-relaxed">
|
||||
{lang === 'de' ? src.excerpt_de : src.excerpt_en}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
|
||||
export default function MarketSlide({ lang, market }: MarketSlideProps) {
|
||||
const i = t(lang)
|
||||
const labels = [i.market.tamLabel, i.market.samLabel, i.market.somLabel]
|
||||
const segments = [i.market.tam, i.market.sam, i.market.som]
|
||||
const segmentKeys = ['TAM', 'SAM', 'SOM']
|
||||
const [activeModal, setActiveModal] = useState<string | null>(null)
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -56,36 +177,67 @@ export default function MarketSlide({ lang, market }: MarketSlideProps) {
|
||||
|
||||
{/* Labels */}
|
||||
<div className="space-y-6">
|
||||
{market.map((m, idx) => (
|
||||
<motion.div
|
||||
key={m.id}
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.5 + idx * 0.15 }}
|
||||
className="flex items-center gap-4"
|
||||
>
|
||||
<div className={`w-3 h-3 rounded-full ${textColors[idx]} bg-current`} />
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`text-sm font-bold ${textColors[idx]}`}>{segments[idx]}</span>
|
||||
<span className="text-xs text-white/30">{labels[idx]}</span>
|
||||
{market.map((m, idx) => {
|
||||
const segKey = segmentKeys[idx] || m.market_segment
|
||||
const sourceCount = marketSources[segKey]?.length || 0
|
||||
return (
|
||||
<motion.div
|
||||
key={m.id}
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.5 + idx * 0.15 }}
|
||||
className="group cursor-pointer"
|
||||
onClick={() => setActiveModal(segKey)}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className={`w-3 h-3 rounded-full ${textColors[idx]} bg-current`} />
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`text-sm font-bold ${textColors[idx]}`}>{segments[idx]}</span>
|
||||
<span className="text-xs text-white/30">{labels[idx]}</span>
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-white">
|
||||
<AnimatedCounter
|
||||
target={m.value_eur / 1_000_000_000}
|
||||
suffix={lang === 'de' ? ' Mrd. EUR' : 'B EUR'}
|
||||
decimals={1}
|
||||
duration={1500}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-xs">
|
||||
{m.growth_rate_pct > 0 && (
|
||||
<span className="flex items-center gap-1 text-emerald-400">
|
||||
<TrendingUp className="w-3 h-3" />
|
||||
{m.growth_rate_pct}% p.a.
|
||||
</span>
|
||||
)}
|
||||
<span className="text-white/40">
|
||||
{i.market.source}: {m.source}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-[10px] text-indigo-400/60 group-hover:text-indigo-400 transition-colors mt-0.5">
|
||||
{sourceCount} {lang === 'de' ? (sourceCount === 1 ? 'Quelle' : 'Quellen') : (sourceCount === 1 ? 'source' : 'sources')}
|
||||
{' · '}
|
||||
{lang === 'de' ? 'Klicken fuer Details' : 'Click for details'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-white">
|
||||
<AnimatedCounter
|
||||
target={m.value_eur / 1_000_000_000}
|
||||
suffix={lang === 'de' ? ' Mrd. EUR' : 'B EUR'}
|
||||
decimals={1}
|
||||
duration={1500}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-xs text-white/40">
|
||||
{i.market.growth}: {m.growth_rate_pct}% · {i.market.source}: {m.source}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Source Modals */}
|
||||
{segmentKeys.map((seg) => (
|
||||
<SourceModal
|
||||
key={seg}
|
||||
isOpen={activeModal === seg}
|
||||
onClose={() => setActiveModal(null)}
|
||||
segment={seg}
|
||||
lang={lang}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from 'framer-motion'
|
||||
import { useState } from 'react'
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
import { Language } from '@/lib/types'
|
||||
import { t } from '@/lib/i18n'
|
||||
import { AlertTriangle, Scale, Shield } from 'lucide-react'
|
||||
import { AlertTriangle, Scale, Shield, ExternalLink, X } from 'lucide-react'
|
||||
import GlassCard from '../ui/GlassCard'
|
||||
import GradientText from '../ui/GradientText'
|
||||
import FadeInView from '../ui/FadeInView'
|
||||
@@ -12,10 +13,150 @@ interface ProblemSlideProps {
|
||||
lang: Language
|
||||
}
|
||||
|
||||
interface SourceInfo {
|
||||
name: string
|
||||
url: string
|
||||
date: string
|
||||
excerpt_de: string
|
||||
excerpt_en: string
|
||||
}
|
||||
|
||||
interface ProblemCardData {
|
||||
sources: SourceInfo[]
|
||||
}
|
||||
|
||||
// Quellenangaben fuer jede Behauptung
|
||||
const cardSources: ProblemCardData[] = [
|
||||
{
|
||||
// DSGVO: 4.1 Mrd EUR Bussgelder, 83% KMU
|
||||
sources: [
|
||||
{
|
||||
name: 'GDPR Enforcement Tracker (CMS Law)',
|
||||
url: 'https://www.enforcementtracker.com/',
|
||||
date: '2025',
|
||||
excerpt_de: 'Der GDPR Enforcement Tracker dokumentiert alle oeffentlich bekannten DSGVO-Bussgelder in der EU. Kumuliert belaufen sich die Bussgelder auf ueber 4,1 Mrd. EUR seit Inkrafttreten der DSGVO im Mai 2018.',
|
||||
excerpt_en: 'The GDPR Enforcement Tracker documents all publicly known GDPR fines across the EU. Cumulative fines exceed EUR 4.1 billion since the GDPR took effect in May 2018.',
|
||||
},
|
||||
{
|
||||
name: 'DIHK Digitalisierungsumfrage 2024',
|
||||
url: 'https://www.dihk.de/de/themen-und-positionen/wirtschaft-digital/digitalisierung',
|
||||
date: '2024',
|
||||
excerpt_de: 'Laut der DIHK-Digitalisierungsumfrage 2024 geben 83% der befragten KMU an, die DSGVO-Anforderungen nicht vollstaendig umgesetzt zu haben. Hauptgruende sind mangelnde Ressourcen, fehlendes Know-how und die Komplexitaet der Vorschriften.',
|
||||
excerpt_en: 'According to the DIHK Digitization Survey 2024, 83% of surveyed SMEs report not having fully implemented GDPR requirements. Main reasons cited are lack of resources, missing expertise, and regulatory complexity.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// AI Act: August 2025
|
||||
sources: [
|
||||
{
|
||||
name: 'EU AI Act — Verordnung (EU) 2024/1689',
|
||||
url: 'https://eur-lex.europa.eu/eli/reg/2024/1689',
|
||||
date: '2024-08-01',
|
||||
excerpt_de: 'Die EU-KI-Verordnung (AI Act) trat am 1. August 2024 in Kraft. Ab dem 2. August 2025 gelten die Verbote fuer KI-Systeme mit unannehmbarem Risiko (Art. 5) sowie die Verpflichtungen fuer Anbieter von KI-Modellen mit allgemeinem Verwendungszweck (Art. 51-56). Ab August 2026 gelten die Anforderungen fuer Hochrisiko-KI-Systeme.',
|
||||
excerpt_en: 'The EU AI Act (Regulation 2024/1689) entered into force on August 1, 2024. From August 2, 2025, prohibitions on unacceptable-risk AI systems (Art. 5) and obligations for general-purpose AI model providers (Art. 51-56) apply. Requirements for high-risk AI systems apply from August 2026.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// NIS2: 30.000+ Unternehmen
|
||||
sources: [
|
||||
{
|
||||
name: 'BSI — NIS-2-Umsetzungs- und Cybersicherheitsstaerkungsgesetz',
|
||||
url: 'https://www.bsi.bund.de/DE/Themen/Regulierte-Wirtschaft/NIS-2-regulierte-Unternehmen/nis-2-regulierte-unternehmen_node.html',
|
||||
date: '2025',
|
||||
excerpt_de: 'Das NIS-2-Umsetzungsgesetz (NIS2UmsuCG) erweitert den Kreis der regulierten Unternehmen in Deutschland erheblich. Nach Schaetzungen des BSI und des BMI sind kuenftig mehr als 30.000 Unternehmen und Einrichtungen von den neuen Cybersicherheitsanforderungen betroffen — gegenueber bisher ca. 4.500 unter der NIS-1-Richtlinie.',
|
||||
excerpt_en: 'The NIS-2 Implementation Act significantly expands the scope of regulated entities in Germany. According to BSI and BMI estimates, more than 30,000 companies and institutions will be affected by the new cybersecurity requirements — compared to approximately 4,500 under NIS-1.',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const icons = [AlertTriangle, Scale, Shield]
|
||||
|
||||
function SourceModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
cardIndex,
|
||||
lang,
|
||||
cardTitle,
|
||||
}: {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
cardIndex: number
|
||||
lang: Language
|
||||
cardTitle: string
|
||||
}) {
|
||||
if (!isOpen) return null
|
||||
const sources = cardSources[cardIndex]?.sources || []
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 z-50 flex items-center justify-center p-4"
|
||||
onClick={onClose}
|
||||
>
|
||||
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" />
|
||||
<motion.div
|
||||
initial={{ scale: 0.9, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
exit={{ scale: 0.9, opacity: 0 }}
|
||||
className="relative bg-slate-900/95 border border-white/10 rounded-2xl p-6 max-w-2xl w-full max-h-[80vh] overflow-y-auto"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute top-4 right-4 p-1 text-white/40 hover:text-white/80 transition-colors"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
<h3 className="text-xl font-bold text-white mb-1">{cardTitle}</h3>
|
||||
<p className="text-sm text-white/40 mb-6">
|
||||
{lang === 'de' ? 'Quellenangaben' : 'Sources'}
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
{sources.map((src, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="bg-white/[0.05] border border-white/10 rounded-xl p-4"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3 mb-2">
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-white">{src.name}</p>
|
||||
<p className="text-xs text-white/30">{src.date}</p>
|
||||
</div>
|
||||
<a
|
||||
href={src.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex-shrink-0 p-1.5 text-indigo-400 hover:text-indigo-300 hover:bg-indigo-500/10 rounded-lg transition-colors"
|
||||
title={lang === 'de' ? 'Quelle oeffnen' : 'Open source'}
|
||||
>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
</a>
|
||||
</div>
|
||||
<p className="text-sm text-white/60 leading-relaxed">
|
||||
{lang === 'de' ? src.excerpt_de : src.excerpt_en}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
|
||||
export default function ProblemSlide({ lang }: ProblemSlideProps) {
|
||||
const i = t(lang)
|
||||
const [activeModal, setActiveModal] = useState<number | null>(null)
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -29,14 +170,25 @@ export default function ProblemSlide({ lang }: ProblemSlideProps) {
|
||||
<div className="grid md:grid-cols-3 gap-6 mb-12">
|
||||
{i.problem.cards.map((card, idx) => {
|
||||
const Icon = icons[idx]
|
||||
const sourceCount = cardSources[idx]?.sources.length || 0
|
||||
return (
|
||||
<GlassCard key={idx} delay={0.2 + idx * 0.15} className="text-center">
|
||||
<GlassCard
|
||||
key={idx}
|
||||
delay={0.2 + idx * 0.15}
|
||||
className="text-center cursor-pointer group"
|
||||
onClick={() => setActiveModal(idx)}
|
||||
>
|
||||
<div className="w-12 h-12 mx-auto mb-4 rounded-xl bg-red-500/10 flex items-center justify-center">
|
||||
<Icon className="w-6 h-6 text-red-400" />
|
||||
</div>
|
||||
<h3 className="text-lg font-bold mb-2 text-white">{card.title}</h3>
|
||||
<p className="text-3xl font-bold text-red-400 mb-3">{card.stat}</p>
|
||||
<p className="text-sm text-white/50 leading-relaxed">{card.desc}</p>
|
||||
<p className="text-sm text-white/50 leading-relaxed mb-3">{card.desc}</p>
|
||||
<p className="text-[10px] text-indigo-400/60 group-hover:text-indigo-400 transition-colors">
|
||||
{sourceCount} {lang === 'de' ? (sourceCount === 1 ? 'Quelle' : 'Quellen') : (sourceCount === 1 ? 'source' : 'sources')}
|
||||
{' · '}
|
||||
{lang === 'de' ? 'Klicken fuer Details' : 'Click for details'}
|
||||
</p>
|
||||
</GlassCard>
|
||||
)
|
||||
})}
|
||||
@@ -49,6 +201,18 @@ export default function ProblemSlide({ lang }: ProblemSlideProps) {
|
||||
</p>
|
||||
</blockquote>
|
||||
</FadeInView>
|
||||
|
||||
{/* Source Modals */}
|
||||
{i.problem.cards.map((card, idx) => (
|
||||
<SourceModal
|
||||
key={idx}
|
||||
isOpen={activeModal === idx}
|
||||
onClose={() => setActiveModal(null)}
|
||||
cardIndex={idx}
|
||||
lang={lang}
|
||||
cardTitle={card.title}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
271
pitch-deck/components/slides/RegulatorySlide.tsx
Normal file
271
pitch-deck/components/slides/RegulatorySlide.tsx
Normal file
@@ -0,0 +1,271 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
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 { Shield, Scale, Wifi, Calendar, AlertTriangle, CheckCircle2, Clock } from 'lucide-react'
|
||||
|
||||
interface RegulatorySlideProps {
|
||||
lang: Language
|
||||
}
|
||||
|
||||
type RegTab = 'dsgvo' | 'aiact' | 'nis2'
|
||||
|
||||
export default function RegulatorySlide({ lang }: RegulatorySlideProps) {
|
||||
const i = t(lang)
|
||||
const de = lang === 'de'
|
||||
const [activeTab, setActiveTab] = useState<RegTab>('dsgvo')
|
||||
|
||||
const tabs: { id: RegTab; label: string; icon: typeof Shield }[] = [
|
||||
{ id: 'dsgvo', label: de ? 'DSGVO / GDPR' : 'GDPR', icon: Shield },
|
||||
{ id: 'aiact', label: 'AI Act', icon: Scale },
|
||||
{ id: 'nis2', label: 'NIS2', icon: Wifi },
|
||||
]
|
||||
|
||||
const regulations: Record<RegTab, {
|
||||
fullName: string
|
||||
status: string
|
||||
statusColor: string
|
||||
statusIcon: typeof CheckCircle2
|
||||
deadline: string
|
||||
affectedCompanies: string
|
||||
keyRequirements: string[]
|
||||
fines: string
|
||||
howWeHelp: string[]
|
||||
}> = {
|
||||
dsgvo: {
|
||||
fullName: de ? 'Datenschutz-Grundverordnung (EU 2016/679)' : 'General Data Protection Regulation (EU 2016/679)',
|
||||
status: de ? 'In Kraft seit Mai 2018' : 'In effect since May 2018',
|
||||
statusColor: 'text-emerald-400',
|
||||
statusIcon: CheckCircle2,
|
||||
deadline: de ? 'Bereits anzuwenden' : 'Already applicable',
|
||||
affectedCompanies: de ? 'Alle Unternehmen die personenbezogene Daten verarbeiten' : 'All companies processing personal data',
|
||||
keyRequirements: de
|
||||
? [
|
||||
'Verzeichnis von Verarbeitungstaetigkeiten (VVT)',
|
||||
'Datenschutz-Folgenabschaetzung (DSFA)',
|
||||
'Technische und organisatorische Massnahmen (TOM)',
|
||||
'Betroffenenrechte (Auskunft, Loeschung, Portabilitaet)',
|
||||
'Auftragsverarbeitungsvertraege (AVV)',
|
||||
'Datenschutzbeauftragter (ab 20 MA)',
|
||||
'Meldepflicht bei Datenpannen (72h)',
|
||||
]
|
||||
: [
|
||||
'Records of Processing Activities (RoPA)',
|
||||
'Data Protection Impact Assessment (DPIA)',
|
||||
'Technical & Organizational Measures (TOMs)',
|
||||
'Data Subject Rights (Access, Erasure, Portability)',
|
||||
'Data Processing Agreements (DPA)',
|
||||
'Data Protection Officer (from 20 employees)',
|
||||
'Breach Notification (72h)',
|
||||
],
|
||||
fines: de ? 'Bis zu 20 Mio. EUR oder 4% des weltweiten Jahresumsatzes' : 'Up to EUR 20M or 4% of global annual revenue',
|
||||
howWeHelp: de
|
||||
? [
|
||||
'Automatische VVT-Erstellung aus Unternehmensdaten',
|
||||
'KI-gestuetzte DSFA-Durchfuehrung',
|
||||
'TOM-Generator mit Branchenvorlagen',
|
||||
'Self-Service-Portal fuer Betroffenenanfragen',
|
||||
'Automatische Dokumentation und Audit-Trail',
|
||||
]
|
||||
: [
|
||||
'Automatic RoPA generation from company data',
|
||||
'AI-powered DPIA execution',
|
||||
'TOM generator with industry templates',
|
||||
'Self-service portal for data subject requests',
|
||||
'Automatic documentation and audit trail',
|
||||
],
|
||||
},
|
||||
aiact: {
|
||||
fullName: de ? 'KI-Verordnung (EU 2024/1689)' : 'AI Act (EU 2024/1689)',
|
||||
status: de ? 'Schrittweise ab Aug 2025' : 'Phased from Aug 2025',
|
||||
statusColor: 'text-amber-400',
|
||||
statusIcon: Clock,
|
||||
deadline: de ? 'Aug 2025: Verbote · Aug 2026: Hochrisiko · Aug 2027: Vollstaendig' : 'Aug 2025: Prohibitions · Aug 2026: High-Risk · Aug 2027: Full',
|
||||
affectedCompanies: de ? 'Anbieter und Betreiber von KI-Systemen in der EU' : 'Providers and deployers of AI systems in the EU',
|
||||
keyRequirements: de
|
||||
? [
|
||||
'Risikoklassifizierung aller KI-Systeme (Art. 6)',
|
||||
'Konformitaetsbewertung fuer Hochrisiko-KI (Art. 43)',
|
||||
'Technische Dokumentation und Transparenz (Art. 11-13)',
|
||||
'Menschliche Aufsicht (Art. 14)',
|
||||
'Registrierung in EU-Datenbank (Art. 49)',
|
||||
'GPAI-Modell-Pflichten (Art. 51-56)',
|
||||
'Grundrechte-Folgenabschaetzung (Art. 27)',
|
||||
]
|
||||
: [
|
||||
'Risk classification of all AI systems (Art. 6)',
|
||||
'Conformity assessment for high-risk AI (Art. 43)',
|
||||
'Technical documentation and transparency (Art. 11-13)',
|
||||
'Human oversight (Art. 14)',
|
||||
'Registration in EU database (Art. 49)',
|
||||
'GPAI model obligations (Art. 51-56)',
|
||||
'Fundamental rights impact assessment (Art. 27)',
|
||||
],
|
||||
fines: de ? 'Bis zu 35 Mio. EUR oder 7% des weltweiten Jahresumsatzes' : 'Up to EUR 35M or 7% of global annual revenue',
|
||||
howWeHelp: de
|
||||
? [
|
||||
'Automatische Risikoklassifizierung von KI-Systemen',
|
||||
'Konformitaets-Checklisten mit KI-Unterstuetzung',
|
||||
'Technische Dokumentation per Template-Engine',
|
||||
'Audit-Vorbereitung fuer Hochrisiko-Systeme',
|
||||
'Monitoring von Rechtsaenderungen',
|
||||
]
|
||||
: [
|
||||
'Automatic AI system risk classification',
|
||||
'Conformity checklists with AI assistance',
|
||||
'Technical documentation via template engine',
|
||||
'Audit preparation for high-risk systems',
|
||||
'Regulatory change monitoring',
|
||||
],
|
||||
},
|
||||
nis2: {
|
||||
fullName: de ? 'NIS-2-Richtlinie (EU 2022/2555)' : 'NIS2 Directive (EU 2022/2555)',
|
||||
status: de ? 'Umsetzung in nationales Recht laeuft' : 'National transposition in progress',
|
||||
statusColor: 'text-amber-400',
|
||||
statusIcon: Clock,
|
||||
deadline: de ? 'NIS2UmsuCG: voraussichtlich 2025/2026' : 'NIS2 Implementation Act: expected 2025/2026',
|
||||
affectedCompanies: de ? '30.000+ Unternehmen in DE (Energie, Transport, Gesundheit, Digital, KRITIS)' : '30,000+ companies in DE (Energy, Transport, Healthcare, Digital, Critical Infrastructure)',
|
||||
keyRequirements: de
|
||||
? [
|
||||
'Risikomanagement-Massnahmen (Art. 21)',
|
||||
'Incident-Meldepflichten: 24h Fruehwarnung, 72h Bericht (Art. 23)',
|
||||
'Business Continuity und Krisenmanagement',
|
||||
'Supply-Chain-Security (Lieferkettenrisiken)',
|
||||
'Geschaeftsleiterhaftung (persoenliche Haftung)',
|
||||
'Registrierung beim BSI',
|
||||
'Regelmaessige Audits und Nachweise',
|
||||
]
|
||||
: [
|
||||
'Risk management measures (Art. 21)',
|
||||
'Incident reporting: 24h early warning, 72h report (Art. 23)',
|
||||
'Business continuity and crisis management',
|
||||
'Supply chain security',
|
||||
'Management liability (personal liability)',
|
||||
'Registration with national authority (BSI)',
|
||||
'Regular audits and evidence',
|
||||
],
|
||||
fines: de ? 'Bis zu 10 Mio. EUR oder 2% des weltweiten Jahresumsatzes' : 'Up to EUR 10M or 2% of global annual revenue',
|
||||
howWeHelp: de
|
||||
? [
|
||||
'Cybersecurity-Policy-Generator nach BSI-Grundschutz',
|
||||
'Incident-Response-Plaene mit KI-Unterstuetzung',
|
||||
'Supply-Chain-Risikoanalyse',
|
||||
'Automatische Audit-Dokumentation',
|
||||
'NIS2-Readiness-Assessment',
|
||||
]
|
||||
: [
|
||||
'Cybersecurity policy generator based on BSI standards',
|
||||
'AI-assisted incident response plans',
|
||||
'Supply chain risk analysis',
|
||||
'Automatic audit documentation',
|
||||
'NIS2 readiness assessment',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
const reg = regulations[activeTab]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FadeInView className="text-center mb-8">
|
||||
<p className="text-xs font-mono text-indigo-400/60 uppercase tracking-widest mb-2">
|
||||
{de ? 'Anhang' : 'Appendix'}
|
||||
</p>
|
||||
<h2 className="text-4xl md:text-5xl font-bold mb-3">
|
||||
<GradientText>{i.annex.regulatory.title}</GradientText>
|
||||
</h2>
|
||||
<p className="text-lg text-white/50 max-w-2xl mx-auto">{i.annex.regulatory.subtitle}</p>
|
||||
</FadeInView>
|
||||
|
||||
{/* Tab Navigation */}
|
||||
<FadeInView delay={0.15}>
|
||||
<div className="flex items-center justify-center gap-2 mb-6">
|
||||
{tabs.map((tab) => {
|
||||
const Icon = tab.icon
|
||||
return (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-xl text-sm transition-all
|
||||
${activeTab === tab.id
|
||||
? 'bg-indigo-500/20 text-indigo-300 border border-indigo-500/30'
|
||||
: 'bg-white/[0.04] text-white/40 border border-transparent hover:text-white/60 hover:bg-white/[0.06]'
|
||||
}`}
|
||||
>
|
||||
<Icon className="w-4 h-4" />
|
||||
{tab.label}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</FadeInView>
|
||||
|
||||
{/* Content */}
|
||||
<FadeInView delay={0.2} key={activeTab}>
|
||||
<div className="grid md:grid-cols-12 gap-4">
|
||||
{/* Left: Overview */}
|
||||
<div className="md:col-span-5 space-y-4">
|
||||
<GlassCard hover={false} className="p-4">
|
||||
<h3 className="text-sm font-bold text-white mb-1">{reg.fullName}</h3>
|
||||
<div className="space-y-2 mt-3 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<reg.statusIcon className={`w-4 h-4 ${reg.statusColor}`} />
|
||||
<span className="text-white/60">{reg.status}</span>
|
||||
</div>
|
||||
<div className="flex items-start gap-2">
|
||||
<Calendar className="w-4 h-4 text-white/30 mt-0.5" />
|
||||
<span className="text-white/60">{reg.deadline}</span>
|
||||
</div>
|
||||
<div className="flex items-start gap-2">
|
||||
<AlertTriangle className="w-4 h-4 text-red-400 mt-0.5" />
|
||||
<span className="text-red-400/80">{reg.fines}</span>
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
<GlassCard hover={false} className="p-4">
|
||||
<p className="text-xs font-semibold text-emerald-400 uppercase tracking-wider mb-2">
|
||||
{de ? 'Wie ComplAI hilft' : 'How ComplAI Helps'}
|
||||
</p>
|
||||
<ul className="space-y-1.5">
|
||||
{reg.howWeHelp.map((item, idx) => (
|
||||
<li key={idx} className="flex items-start gap-2 text-xs text-white/60">
|
||||
<CheckCircle2 className="w-3 h-3 text-emerald-400 mt-0.5 shrink-0" />
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</GlassCard>
|
||||
</div>
|
||||
|
||||
{/* Right: Requirements */}
|
||||
<div className="md:col-span-7">
|
||||
<GlassCard hover={false} className="p-4 h-full">
|
||||
<p className="text-xs font-semibold text-white/40 uppercase tracking-wider mb-3">
|
||||
{de ? 'Kernanforderungen' : 'Key Requirements'}
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
{reg.keyRequirements.map((req, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="flex items-start gap-3 p-2 rounded-lg bg-white/[0.03] border border-white/5"
|
||||
>
|
||||
<span className="text-xs font-mono text-indigo-400/60 mt-0.5 shrink-0 w-4 text-right">{idx + 1}</span>
|
||||
<span className="text-xs text-white/70">{req}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-[10px] text-white/20 mt-3">
|
||||
{de ? 'Betroffene Unternehmen' : 'Affected companies'}: {reg.affectedCompanies}
|
||||
</p>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</div>
|
||||
</FadeInView>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -3,9 +3,10 @@
|
||||
import { motion } from 'framer-motion'
|
||||
import { Language, PitchTeamMember } from '@/lib/types'
|
||||
import { t } from '@/lib/i18n'
|
||||
import { User } from 'lucide-react'
|
||||
import { User, Linkedin } from 'lucide-react'
|
||||
import GradientText from '../ui/GradientText'
|
||||
import FadeInView from '../ui/FadeInView'
|
||||
import Image from 'next/image'
|
||||
|
||||
interface TeamSlideProps {
|
||||
lang: Language
|
||||
@@ -34,14 +35,39 @@ export default function TeamSlide({ lang, team }: TeamSlideProps) {
|
||||
className="bg-white/[0.08] backdrop-blur-xl border border-white/10 rounded-3xl p-8"
|
||||
>
|
||||
<div className="flex items-start gap-5">
|
||||
{/* Avatar */}
|
||||
<div className="w-20 h-20 rounded-2xl bg-gradient-to-br from-indigo-500 to-purple-600
|
||||
flex items-center justify-center shrink-0 shadow-lg">
|
||||
<User className="w-10 h-10 text-white" />
|
||||
</div>
|
||||
{/* Avatar — Foto oder Fallback */}
|
||||
{member.photo_url ? (
|
||||
<div className="w-20 h-20 rounded-2xl overflow-hidden shrink-0 shadow-lg">
|
||||
<Image
|
||||
src={member.photo_url}
|
||||
alt={member.name}
|
||||
width={80}
|
||||
height={80}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-20 h-20 rounded-2xl bg-gradient-to-br from-indigo-500 to-purple-600
|
||||
flex items-center justify-center shrink-0 shadow-lg">
|
||||
<User className="w-10 h-10 text-white" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex-1">
|
||||
<h3 className="text-xl font-bold text-white mb-1">{member.name}</h3>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<h3 className="text-xl font-bold text-white">{member.name}</h3>
|
||||
{member.linkedin_url && (
|
||||
<a
|
||||
href={member.linkedin_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="p-1 text-white/30 hover:text-[#0A66C2] transition-colors"
|
||||
title="LinkedIn"
|
||||
>
|
||||
<Linkedin className="w-4 h-4" />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-indigo-400 text-sm font-medium mb-3">
|
||||
{lang === 'de' ? member.role_de : member.role_en}
|
||||
</p>
|
||||
|
||||
@@ -17,9 +17,32 @@ interface TheAskSlideProps {
|
||||
|
||||
const COLORS = ['#6366f1', '#a78bfa', '#60a5fa', '#34d399', '#fbbf24']
|
||||
|
||||
function formatFundingAmount(amount: number): { target: number; suffix: string } {
|
||||
if (amount >= 1_000_000) {
|
||||
return { target: Math.round(amount / 100_000) / 10, suffix: ' Mio.' }
|
||||
}
|
||||
if (amount >= 1_000) {
|
||||
return { target: Math.round(amount / 1_000), suffix: 'k' }
|
||||
}
|
||||
return { target: amount, suffix: '' }
|
||||
}
|
||||
|
||||
function formatTargetDate(dateStr: string, lang: Language): string {
|
||||
if (!dateStr) return 'TBD'
|
||||
try {
|
||||
const d = new Date(dateStr)
|
||||
const quarter = Math.ceil((d.getMonth() + 1) / 3)
|
||||
return `Q${quarter} ${d.getFullYear()}`
|
||||
} catch {
|
||||
return dateStr
|
||||
}
|
||||
}
|
||||
|
||||
export default function TheAskSlide({ lang, funding }: TheAskSlideProps) {
|
||||
const i = t(lang)
|
||||
const useOfFunds = funding?.use_of_funds || []
|
||||
const amount = funding?.amount_eur || 0
|
||||
const { target, suffix } = formatFundingAmount(amount)
|
||||
|
||||
const pieData = useOfFunds.map((item) => ({
|
||||
name: lang === 'de' ? item.label_de : item.label_en,
|
||||
@@ -35,7 +58,7 @@ export default function TheAskSlide({ lang, funding }: TheAskSlideProps) {
|
||||
<p className="text-lg text-white/50 max-w-2xl mx-auto">{i.theAsk.subtitle}</p>
|
||||
</FadeInView>
|
||||
|
||||
{/* Main Number */}
|
||||
{/* Main Number — dynamisch aus funding.amount_eur */}
|
||||
<FadeInView delay={0.2} className="text-center mb-10">
|
||||
<motion.div
|
||||
initial={{ scale: 0.5, opacity: 0 }}
|
||||
@@ -43,13 +66,13 @@ export default function TheAskSlide({ lang, funding }: TheAskSlideProps) {
|
||||
transition={{ delay: 0.4, type: 'spring', stiffness: 200 }}
|
||||
>
|
||||
<p className="text-6xl md:text-8xl font-bold text-white mb-2">
|
||||
<AnimatedCounter target={200} suffix="k" duration={2000} />
|
||||
<AnimatedCounter target={target} suffix={suffix} duration={2000} decimals={suffix === ' Mio.' ? 1 : 0} />
|
||||
<span className="text-3xl md:text-4xl text-white/50 ml-2">EUR</span>
|
||||
</p>
|
||||
</motion.div>
|
||||
</FadeInView>
|
||||
|
||||
{/* Details */}
|
||||
{/* Details — dynamisch aus funding-Objekt */}
|
||||
<div className="grid md:grid-cols-3 gap-4 mb-8">
|
||||
<GlassCard delay={0.5} className="text-center p-5">
|
||||
<FileText className="w-6 h-6 text-indigo-400 mx-auto mb-2" />
|
||||
@@ -59,12 +82,14 @@ export default function TheAskSlide({ lang, funding }: TheAskSlideProps) {
|
||||
<GlassCard delay={0.6} className="text-center p-5">
|
||||
<Calendar className="w-6 h-6 text-purple-400 mx-auto mb-2" />
|
||||
<p className="text-xs text-white/40 mb-1">{i.theAsk.targetDate}</p>
|
||||
<p className="text-lg font-bold text-white">Q3 2026</p>
|
||||
<p className="text-lg font-bold text-white">
|
||||
{formatTargetDate(funding?.target_date, lang)}
|
||||
</p>
|
||||
</GlassCard>
|
||||
<GlassCard delay={0.7} className="text-center p-5">
|
||||
<Target className="w-6 h-6 text-blue-400 mx-auto mb-2" />
|
||||
<p className="text-xs text-white/40 mb-1">{lang === 'de' ? 'Runway' : 'Runway'}</p>
|
||||
<p className="text-lg font-bold text-white">18 {lang === 'de' ? 'Monate' : 'Months'}</p>
|
||||
<p className="text-xs text-white/40 mb-1">{lang === 'de' ? 'Runde' : 'Round'}</p>
|
||||
<p className="text-lg font-bold text-white">{funding?.round_name || 'Pre-Seed'}</p>
|
||||
</GlassCard>
|
||||
</div>
|
||||
|
||||
@@ -114,7 +139,7 @@ export default function TheAskSlide({ lang, funding }: TheAskSlideProps) {
|
||||
</span>
|
||||
<span className="text-sm font-bold text-white">{item.percentage}%</span>
|
||||
<span className="text-xs text-white/30">
|
||||
{((funding.amount_eur * item.percentage) / 100).toLocaleString('de-DE')} EUR
|
||||
{((amount * item.percentage) / 100).toLocaleString('de-DE')} EUR
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user