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) { "
- +