[split-required] [guardrail-change] Enforce 500 LOC budget across all services

Install LOC guardrails (check-loc.sh, architecture.md, pre-commit hook)
and split all 44 files exceeding 500 LOC into domain-focused modules:

- consent-service (Go): models, handlers, services, database splits
- backend-core (Python): security_api, rbac_api, pdf_service, auth splits
- admin-core (TypeScript): 5 page.tsx + sidebar extractions
- pitch-deck (TypeScript): 6 slides, 3 UI components, engine.ts splits
- voice-service (Python): enhanced_task_orchestrator split

Result: 0 violations, 36 exempted (pipeline, tests, pure-data files).
Go build verified clean. No behavior changes — pure structural splits.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-27 00:09:30 +02:00
parent 5ef039a6bc
commit 92c86ec6ba
162 changed files with 23853 additions and 23034 deletions

View File

@@ -1,11 +1,9 @@
'use client'
import { useState, useEffect, useRef, useMemo } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { useState } from 'react'
import { Language } from '@/lib/types'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import { X } from 'lucide-react'
interface USPSlideProps { lang: Language }
@@ -32,592 +30,6 @@ const CSS_KF = `
`
// ── 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)
ref.current = fn
useEffect(() => {
let t: ReturnType<typeof setTimeout>
const loop = () => {
if (Math.random() > skip) ref.current()
t = setTimeout(loop, min + Math.random() * (max - min))
}
loop()
return () => clearTimeout(t)
}, [min, max, skip])
}
function TickerShell({ tint, isLight, children }: { tint: string; isLight: boolean; children: React.ReactNode }) {
return (
<div style={{
...MONO, marginTop: 10, padding: '5px 9px',
background: isLight ? '#f1f5f9' : 'rgba(0,0,0,.38)',
border: `1px solid ${tint}55`,
borderRadius: 6, fontSize: 10.5,
color: isLight ? '#475569' : 'rgba(236,233,247,.88)',
display: 'flex', alignItems: 'center', gap: 7,
whiteSpace: 'nowrap', overflow: 'hidden', height: 22,
}}>{children}</div>
)
}
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 (
<TickerShell tint={tint} isLight={isLight}>
<span style={{ color: isLight ? '#16a34a' : '#4ade80' }}></span>
<span style={{ color: tint, opacity: .85 }}>trace</span>
<span style={{ color: isLight ? '#1a1a2e' : '#f5f3fc', fontWeight: 600 }}>{n.toLocaleString()}</span>
<span style={{ color: isLight ? '#94a3b8' : 'rgba(236,233,247,.45)' }}>evidence-chain</span>
</TickerShell>
)
}
function TickEngine({ tint, isLight }: { tint: string; isLight: boolean }) {
const [v, setV] = useState(428)
const [rate, setRate] = useState(99.4)
useTicker(() => {
setV(x => x + 1 + Math.floor(Math.random() * 4))
setRate(r => Math.max(97, Math.min(99.9, r + (Math.random() - 0.5) * 0.3)))
}, 220, 420)
return (
<TickerShell tint={tint} isLight={isLight}>
<span style={{ color: isLight ? '#16a34a' : '#4ade80' }}></span>
<span style={{ color: tint, opacity: .85 }}>validate</span>
<span style={{ color: isLight ? '#1a1a2e' : '#f5f3fc', fontWeight: 600 }}>{v.toLocaleString()}</span>
<span style={{ color: isLight ? '#16a34a' : '#4ade80' }}>{rate.toFixed(1)}%</span>
</TickerShell>
)
}
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 (
<TickerShell tint={tint} isLight={isLight}>
<span style={{ color: '#fbbf24' }}></span>
<span style={{ color: tint, opacity: .85 }}>optimize</span>
<span style={{ color: isLight ? '#1a1a2e' : '#f5f3fc', overflow: 'hidden', textOverflow: 'ellipsis', flex: 1 }}>{ops[i]}</span>
</TickerShell>
)
}
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 (
<TickerShell tint={tint} isLight={isLight}>
<span style={{ color: isLight ? '#16a34a' : '#4ade80' }}></span>
<span style={{ color: tint, opacity: .85 }}>check</span>
<span style={{ color: isLight ? '#1a1a2e' : '#f5f3fc', fontWeight: 600 }}>{regs[i]}</span>
<span style={{ color: isLight ? '#94a3b8' : 'rgba(236,233,247,.4)' }}>·</span>
<span style={{ color: isLight ? '#1a1a2e' : '#f5f3fc' }}>{c.toLocaleString()}</span>
</TickerShell>
)
}
// ── Data ──────────────────────────────────────────────────────────────────────
interface DetailItem {
tint: string
icon: string
kicker: string
title: string
body: string
bullets?: string[]
stat?: { k: string; v: string }
}
function getDetails(de: boolean): Record<string, DetailItem> {
return {
rfq: {
tint: '#a78bfa', icon: '⇄',
kicker: de ? 'Säule · Compliance' : 'Pillar · Compliance',
title: de ? 'RFQ-Prüfung' : 'RFQ Verification',
body: de
? 'Kunden-Anforderungsdokumente werden automatisch gegen den aktuellen Source-Code geprüft. Abweichungen werden erkannt, Änderungen vorgeschlagen und auf Wunsch direkt im Code umgesetzt — ohne manuelles Nacharbeiten.'
: 'Customer requirement documents are automatically verified against current source code. Deviations are detected, changes proposed and implemented directly in code on request — no manual rework needed.',
bullets: de
? ['Klauseln automatisch gegen SBOM, SAST-Findings und Policy-Docs abgeglichen', 'Lücken mit konkreten Implementierungsvorschlägen markiert', 'RFQ-Antworten in Stunden statt Wochen']
: ['Auto-match clauses against SBOM, SAST findings and policy docs', 'Flag gaps with concrete implementation proposals', 'Win-ready RFQ replies in hours, not weeks'],
stat: { k: de ? 'Ø Antwortzeit' : 'avg response time', v: de ? '4,2h (war 12 Tage)' : '4.2h (was 12 days)' },
},
process: {
tint: '#c084fc', icon: '⟲',
kicker: de ? 'Säule · Compliance' : 'Pillar · Compliance',
title: de ? 'Prozess-Compliance' : 'Process Compliance',
body: de
? 'Vom Audit-Finding über das Ticket bis zur Code-Änderung läuft der gesamte Prozess automatisiert durch. Rollen, Fristen und Eskalation werden End-to-End verwaltet. Nachweise werden automatisch generiert und archiviert.'
: 'From audit finding to ticket to code change, the entire process runs automatically. Roles, deadlines and escalation are managed end-to-end. Evidence is automatically generated and archived.',
bullets: de
? ['Finding → Ticket → PR → Nachweis in einem Thread', 'SLA-Tracking pro Control mit Auto-Eskalation', 'Unveränderliches Audit-Log, pro Änderung signiert']
: ['Finding → ticket → PR → evidence in one thread', 'SLA tracking per control with auto-escalation', 'Immutable audit log signed per change'],
stat: { k: de ? 'automatisierte Prozessschritte' : 'process steps automated', v: '87%' },
},
bidir: {
tint: '#fbbf24', icon: '⟷',
kicker: de ? 'Säule · Code' : 'Pillar · Code',
title: de ? 'Bidirektional' : 'Bidirectional Sync',
body: de
? 'Compliance-Anforderungen fliessen direkt in den Code. Umgekehrt aktualisieren Code-Änderungen automatisch die Compliance-Dokumentation. Beide Seiten sind immer synchron — kein Informationsverlust zwischen Audit und Entwicklung.'
: 'Compliance requirements flow directly into code. Conversely, code changes automatically update compliance documentation. Both sides always stay in sync — no information loss between audit and development.',
bullets: de
? ['Policy ↔ Code-Mapping via semantischem Diff', 'Git-nativ: jede Änderung als PR', 'Zero Drift zwischen Audit-Artefakten und Realität']
: ['Policy ↔ code mapping via semantic diff', 'Git-native: every change shipped as a PR', 'Zero drift between audit artefacts and reality'],
stat: { k: de ? 'Drift-Vorfälle' : 'drift incidents', v: de ? '0 seit März 2024' : '0 since Mar-2024' },
},
cont: {
tint: '#f59e0b', icon: '◎',
kicker: de ? 'Säule · Code' : 'Pillar · Code',
title: de ? 'Kontinuierlich' : 'Continuous, Not Yearly',
body: de
? 'Klassische Compliance prüft einmal im Jahr und hofft auf das Beste. Unsere Plattform prüft bei jeder Code-Änderung. Findings werden sofort zu Tickets mit konkreten Implementierungsvorschlägen im Issue-Tracker der Wahl.'
: 'Traditional compliance checks once a year and hopes for the best. Our platform checks on every code change. Findings immediately become tickets with concrete implementation proposals in the issue tracker of choice.',
bullets: de
? ['CI-integrierte Validierung bei jedem Push', 'Fix-Vorschläge generiert, nicht nur gemeldet', 'Compliance-Frische: Minuten statt Monate']
: ['CI-integrated validation on each push', 'Fix suggestions generated, not just reported', 'Compliance freshness: minutes, not months'],
stat: { k: de ? 'Validierungen / Tag' : 'validations / day', v: '~2.400 / repo' },
},
trace: {
tint: '#a78bfa', icon: '⇄',
kicker: de ? 'Under the Hood' : 'Under the Hood',
title: de ? 'End-to-End Rückverfolgbarkeit' : 'End-to-End Traceability',
body: de
? 'Regulatorische Anforderungen (Gesetz → Obligation → Control) deterministisch mit realem Systemzustand und Code verknüpft — inklusive revisionssicherem Evidence-Layer.'
: 'Regulatory requirements (law → obligation → control) deterministically linked to real system state and code — including audit-proof evidence layer.',
bullets: de
? ['Versionierter Evidence-Chain, unveränderlich gespeichert', 'Ein Klick von Klausel bis Codezeile', 'Signierte Attestierungen pro Build']
: ['Versioned evidence chain stored immutably', 'One-click drill from clause to line of code', 'Signed attestations per build'],
},
engine: {
tint: '#c084fc', icon: '◉',
kicker: de ? 'Under the Hood' : 'Under the Hood',
title: de ? 'Continuous Compliance Engine' : 'Continuous Compliance Engine',
body: de
? 'Statt punktueller Audits: Validierung bei jeder Änderung (Code, Infrastruktur, Prozesse) mit auditierbaren Nachweisen in Echtzeit.'
: 'Instead of point-in-time audits: validation on every change (code, infrastructure, processes) with auditable evidence in real time.',
bullets: de
? ['Rule-Packs pro Framework (NIS-2, DORA, …)', 'Verarbeitet Code, IaC und Prozess-Events', 'Findings automatisch ans richtige Team geroutet']
: ['Rule packs per framework (NIS-2, DORA, …)', 'Handles code, infra-as-code, and process events', 'Findings routed to the right team automatically'],
},
opt: {
tint: '#fbbf24', icon: '✦',
kicker: de ? 'Under the Hood' : 'Under the Hood',
title: de ? 'Compliance Optimizer' : 'Compliance Optimizer',
body: de
? 'Nicht nur „erlaubt/verboten", sondern die maximal zulässige Ausgestaltung jedes KI-Use-Cases. Deterministische Constraint-Optimierung zeigt den Sweet Spot zwischen Regulierung und Innovation — ersetzt 20200k EUR Anwaltskosten.'
: 'Not just "allowed/forbidden" but the maximum permissible configuration of every AI use case. Deterministic constraint optimization shows the sweet spot between regulation and innovation — replaces EUR 20200k in legal fees.',
bullets: de
? ['ROI-Ranking jedes offenen Findings', 'Abwägung zwischen Liefergeschwindigkeit und Restrisiko', 'Low-Hanging-Wins zuerst']
: ['ROI-ranks every open finding', 'Balances speed of delivery with residual risk', 'Highlights low-hanging wins first'],
},
stack: {
tint: '#f59e0b', icon: '◎',
kicker: de ? 'Under the Hood' : 'Under the Hood',
title: de ? 'EU-Trust & Governance Stack' : 'EU Trust & Governance Stack',
body: de
? 'Souveräne, DSGVO-/AI-Act-konforme Architektur (EU-Hosting, Isolation, Betriebsrat-Fähigkeit) — Marktzugang, den US-Lösungen strukturell nicht erreichen.'
: 'Sovereign, GDPR/AI Act compliant architecture (EU hosting, isolation, works council capability) — market access that US solutions structurally cannot achieve.',
bullets: de
? ['DSGVO · NIS-2 · DORA · EU AI Act · ISO 27001 · BSI C5', 'EU-souveränes Hosting und Key-Management', 'Eine Plattform, ein Audit, eine Rechnung']
: ['DSGVO · NIS-2 · DORA · EU AI Act · ISO 27001 · BSI C5', 'EU-sovereign hosting and key-management', 'One platform, one audit, one bill'],
},
hub: {
tint: '#a78bfa', icon: '∞',
kicker: de ? 'Die Schleife' : 'The Loop',
title: de ? 'Compliance ↔ Code · Immer in Sync' : 'Compliance ↔ Code · Always in sync',
body: de
? 'Die Plattform ist eine einzige geschlossene Schleife. Jede Policy-Änderung fliesst in den Code; jede Code-Änderung fliesst in die Policy zurück.'
: 'The platform is a single closed loop. Every policy change ripples into code; every code change ripples back into policy. That\'s the USP in one diagram.',
bullets: de
? ['Single Source of Truth, zwei Oberflächen', 'Echtzeit-Sync, kein Batch-Abgleich', 'Auditoren, Entwickler und Sales fragen denselben Graphen ab']
: ['Single source of truth, two surfaces', 'Real-time sync, not batch reconciliation', 'Auditors, engineers and sales all query the same graph'],
},
}
}
// ── Pillar row ────────────────────────────────────────────────────────────────
function PillarRow({ side, title, body, tint, onClick, active, isLight }: {
side: 'left' | 'right'
title: string; body: string; tint: string
onClick: () => void; active: boolean; isLight: boolean
}) {
const [hover, setHover] = useState(false)
const lit = hover || active
const isLeft = side === 'left'
return (
<div
onClick={onClick}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
style={{
display: 'flex', alignItems: 'flex-start', gap: 12,
flexDirection: isLeft ? 'row-reverse' : 'row',
textAlign: isLeft ? 'right' : 'left',
padding: '10px 14px', borderRadius: 10, cursor: 'pointer',
transition: 'transform .25s, background .25s, box-shadow .25s',
background: lit
? `linear-gradient(${isLeft ? '270deg' : '90deg'}, ${tint}24 0%, ${tint}0a 70%, transparent 100%)`
: 'transparent',
boxShadow: lit
? `0 10px 30px ${tint}26, inset 0 0 0 1px ${tint}44`
: 'inset 0 0 0 1px transparent',
transform: lit ? (isLeft ? 'translateX(-3px)' : 'translateX(3px)') : 'translateX(0)',
}}
>
<div style={{
flex: '0 0 30px', width: 30, height: 30, borderRadius: 9,
background: lit ? `${tint}3a` : `${tint}22`,
border: `1px solid ${lit ? tint : tint + '66'}`,
display: 'flex', alignItems: 'center', justifyContent: 'center',
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',
}}></div>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{
fontSize: 13, fontWeight: 700,
color: isLight ? '#1a1a2e' : '#f7f5fc',
letterSpacing: -0.15, marginBottom: 3,
display: 'flex', alignItems: 'center', gap: 6,
justifyContent: isLeft ? 'flex-end' : 'flex-start',
}}>
<span>{title}</span>
<span style={{
fontSize: 10, color: tint, opacity: lit ? 1 : 0,
transform: `translateX(${lit ? 0 : (isLeft ? 4 : -4)}px)`,
transition: 'all .25s',
}}>{isLeft ? '' : ''}</span>
</div>
<div style={{
fontSize: 11, lineHeight: 1.55,
color: isLight
? `rgba(71,85,105,${lit ? 1 : .78})`
: `rgba(236,233,247,${lit ? .82 : .62})`,
transition: 'color .25s',
}}>{body}</div>
</div>
</div>
)
}
// ── Column header ─────────────────────────────────────────────────────────────
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 (
<div style={{
display: 'flex', alignItems: 'center', gap: 10,
flexDirection: isLeft ? 'row-reverse' : 'row',
paddingBottom: 10, borderBottom: `1px solid ${color}35`,
}}>
<div style={{
width: 34, height: 34, borderRadius: 9,
background: `linear-gradient(135deg, ${color}55, ${color}20)`,
border: `1px solid ${color}88`,
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: isLight ? color : '#fff', fontSize: 15, fontWeight: 700,
boxShadow: `0 0 18px ${color}55, inset 0 1px 0 ${color}aa`,
}}>{icon}</div>
<div>
<div style={{ fontSize: 18, fontWeight: 700, color: isLight ? '#1a1a2e' : '#f7f5fc', letterSpacing: -0.3, lineHeight: 1 }}>{label}</div>
<div style={{ ...MONO, fontSize: 9.5, letterSpacing: 2, color, opacity: .75, marginTop: 3, textTransform: 'uppercase' as const }}>{sub}</div>
</div>
</div>
)
}
// ── Central hub ───────────────────────────────────────────────────────────────
function CentralHub({ caption, isLight }: { caption: string; isLight: boolean }) {
return (
<div style={{ position: 'relative', width: 260, height: 320, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<div style={{
position: 'relative', width: 120, height: 120, borderRadius: '50%',
background: 'radial-gradient(circle at 32% 28%, #f0e9ff 0%, #c4aaff 26%, #7b5cd6 58%, #2a1560 100%)',
border: '1.5px solid rgba(216,202,255,.7)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
boxShadow: isLight
? '0 0 30px rgba(167,139,250,.4), 0 0 60px rgba(167,139,250,.15), inset 0 3px 0 rgba(255,255,255,.5), inset 0 -8px 14px rgba(0,0,0,.2)'
: '0 0 50px rgba(167,139,250,.65), 0 0 100px rgba(167,139,250,.25), inset 0 3px 0 rgba(255,255,255,.35), inset 0 -8px 14px rgba(0,0,0,.35)',
animation: isLight ? 'uspPulseLight 2.6s ease-in-out infinite' : 'uspPulse 2.6s ease-in-out infinite',
zIndex: 3,
}}>
<div style={{ position: 'absolute', inset: -14, borderRadius: '50%', border: `1px dashed ${isLight ? 'rgba(167,139,250,.5)' : 'rgba(216,202,255,.42)'}`, animation: 'uspSpin 14s linear infinite' }} />
<div style={{ position: 'absolute', inset: -30, borderRadius: '50%', border: `1px dashed ${isLight ? 'rgba(167,139,250,.3)' : 'rgba(216,202,255,.2)'}`, animation: 'uspSpin 22s linear infinite reverse' }} />
<svg width="54" height="26" viewBox="0 0 54 26" fill="none" stroke="#fff" strokeWidth="2.8" strokeLinecap="round" strokeLinejoin="round"
style={{ filter: 'drop-shadow(0 1px 3px rgba(0,0,0,.5))' }}>
<path d="M 10 13 C 10 5, 22 5, 27 13 C 32 21, 44 21, 44 13 C 44 5, 32 5, 27 13 C 22 21, 10 21, 10 13 Z" />
</svg>
</div>
<div style={{
position: 'absolute', left: 0, right: 0, bottom: 24, textAlign: 'center',
...MONO, fontSize: 9.5, letterSpacing: 2.5,
color: isLight ? 'rgba(109,77,194,.75)' : 'rgba(216,202,255,.75)',
textTransform: 'uppercase' as const, fontWeight: 600,
}}>{caption}</div>
</div>
)
}
// ── Bridge SVG connectors ─────────────────────────────────────────────────────
function BridgeConnectors({ isLight }: { isLight: boolean }) {
const rfpY = 130
const sub2Y = 250
const hubCx = 500
const hubR = 72
return (
<svg viewBox="0 0 1000 400" preserveAspectRatio="none"
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', pointerEvents: 'none', zIndex: 1 }}>
<defs>
<linearGradient id="uspFromL" x1="0" x2="1">
<stop offset="0" stopColor="#a78bfa" stopOpacity="0" />
<stop offset=".3" stopColor="#a78bfa" stopOpacity={isLight ? '.6' : '.85'} />
<stop offset="1" stopColor="#c084fc" stopOpacity={isLight ? '.2' : '.3'} />
</linearGradient>
<linearGradient id="uspToR" x1="0" x2="1">
<stop offset="0" stopColor="#c084fc" stopOpacity={isLight ? '.2' : '.3'} />
<stop offset=".7" stopColor="#fbbf24" stopOpacity={isLight ? '.6' : '.85'} />
<stop offset="1" stopColor="#fbbf24" stopOpacity="0" />
</linearGradient>
</defs>
<line x1="40" y1={rfpY} x2={hubCx - hubR} y2={rfpY}
stroke="url(#uspFromL)" strokeWidth="2" strokeDasharray="4 5"
style={{ animation: 'uspFlowR 1.6s linear infinite' }} />
<line x1={hubCx + hubR} y1={rfpY} x2="960" y2={rfpY}
stroke="url(#uspToR)" strokeWidth="2" strokeDasharray="4 5"
style={{ animation: 'uspFlowR 1.6s linear infinite' }} />
<line x1="40" y1={sub2Y} x2={hubCx - hubR} y2={sub2Y}
stroke="url(#uspFromL)" strokeWidth="2" strokeDasharray="4 5"
style={{ animation: 'uspFlowR 1.6s linear infinite' }} />
<line x1={hubCx + hubR} y1={sub2Y} x2="960" y2={sub2Y}
stroke="url(#uspToR)" strokeWidth="2" strokeDasharray="4 5"
style={{ animation: 'uspFlowR 1.6s linear infinite' }} />
{([rfpY, sub2Y] as number[]).map(y => (
<g key={y}>
<circle cx={hubCx - hubR} cy={y} r="4" fill={isLight ? '#eef2ff' : '#1a0f34'} stroke="#a78bfa" strokeWidth="1.2" />
<circle cx={hubCx - hubR} cy={y} r="1.5" fill="#a78bfa" />
<circle cx={hubCx + hubR} cy={y} r="4" fill={isLight ? '#eef2ff' : '#1a0f34'} stroke="#fbbf24" strokeWidth="1.2" />
<circle cx={hubCx + hubR} cy={y} r="1.5" fill="#fbbf24" />
</g>
))}
<circle r="3" fill="#c4aaff" style={{ filter: 'drop-shadow(0 0 6px #a78bfa)' }}>
<animate attributeName="cx" from="40" to="960" dur="3.5s" repeatCount="indefinite" />
<animate attributeName="cy" values={`${rfpY};${rfpY}`} dur="3.5s" repeatCount="indefinite" />
</circle>
<circle r="3" fill="#fde68a" style={{ filter: 'drop-shadow(0 0 6px #fbbf24)' }}>
<animate attributeName="cx" from="960" to="40" dur="3.5s" repeatCount="indefinite" />
<animate attributeName="cy" values={`${sub2Y};${sub2Y}`} dur="3.5s" repeatCount="indefinite" />
</circle>
</svg>
)
}
// ── Under-the-hood feature card ───────────────────────────────────────────────
function FeatureCard({ icon, title, body, tint, Ticker, onClick, active, isLight }: {
icon: string; title: string; body: string; tint: string
Ticker: React.ComponentType<{ tint: string; isLight: boolean }>
onClick: () => void; active: boolean; isLight: boolean
}) {
const [hover, setHover] = useState(false)
const lit = hover || active
return (
<div
onClick={onClick}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
style={{
position: 'relative', padding: '13px 15px',
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`
: 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',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
<span style={{
width: 22, height: 22, borderRadius: 6,
background: lit ? `${tint}44` : `${tint}22`,
border: `1px solid ${lit ? tint : tint + '66'}`,
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
color: lit ? (isLight ? tint : '#fff') : tint, fontSize: 12,
boxShadow: lit ? `0 0 12px ${tint}88` : 'none',
transition: 'all .25s',
}}>{icon}</span>
<span style={{ fontSize: 12.5, fontWeight: 700, color: isLight ? '#1a1a2e' : '#f7f5fc', letterSpacing: -0.15, flex: 1 }}>{title}</span>
<span style={{ fontSize: 10, color: tint, opacity: lit ? 1 : 0.5, transform: `translateX(${lit ? 0 : -3}px)`, transition: 'all .25s' }}></span>
</div>
<div style={{
fontSize: 11, lineHeight: 1.45,
color: isLight
? `rgba(71,85,105,${lit ? 1 : .78})`
: `rgba(236,233,247,${lit ? .82 : .65})`,
transition: 'color .25s',
}}>{body}</div>
<Ticker tint={tint} isLight={isLight} />
</div>
)
}
// ── Detail modal ──────────────────────────────────────────────────────────────
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() }
window.addEventListener('keydown', onKey)
return () => window.removeEventListener('keydown', onKey)
}, [item, onClose])
return (
<AnimatePresence>
{item && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
onClick={onClose}
style={{
position: 'absolute', inset: 0, zIndex: 50,
background: isLight ? 'rgba(240,244,255,.72)' : 'rgba(5,2,16,.72)',
backdropFilter: 'blur(6px)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}
>
<motion.div
initial={{ opacity: 0, scale: 0.94 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.94 }}
transition={{ duration: 0.22 }}
onClick={e => e.stopPropagation()}
style={{
width: 560, maxWidth: '88%',
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: 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',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 14 }}>
<div style={{
width: 38, height: 38, borderRadius: 10,
background: `linear-gradient(135deg, ${item.tint}66, ${item.tint}22)`,
border: `1px solid ${item.tint}`,
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: isLight ? item.tint : '#fff', fontSize: 16, fontWeight: 700,
boxShadow: `0 0 18px ${item.tint}66`,
}}>{item.icon}</div>
<div style={{ flex: 1 }}>
<div style={{ ...MONO, fontSize: 9.5, letterSpacing: 2.5, color: item.tint, textTransform: 'uppercase' as const, fontWeight: 600, marginBottom: 2 }}>
{item.kicker}
</div>
<div style={{ fontSize: 19, fontWeight: 700, color: isLight ? '#1a1a2e' : '#f7f5fc', letterSpacing: -0.3 }}>{item.title}</div>
</div>
<button onClick={onClose} style={{
background: 'transparent', border: `1px solid ${item.tint}55`,
borderRadius: 8, cursor: 'pointer', width: 30, height: 30,
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: isLight ? '#64748b' : 'rgba(236,233,247,.6)',
}}>
<X style={{ width: 14, height: 14 }} />
</button>
</div>
<div style={{ fontSize: 13, lineHeight: 1.6, color: isLight ? '#475569' : 'rgba(236,233,247,.82)', marginBottom: 16 }}>
{item.body}
</div>
{item.bullets && (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 14 }}>
{item.bullets.map((b, i) => (
<div key={i} style={{
display: 'flex', alignItems: 'flex-start', gap: 10,
padding: '8px 12px', borderRadius: 8,
background: isLight ? 'rgba(0,0,0,.04)' : 'rgba(0,0,0,.3)',
border: `1px solid ${item.tint}${isLight ? '22' : '33'}`,
}}>
<span style={{ color: item.tint, fontSize: 12, marginTop: 1 }}></span>
<span style={{ fontSize: 12, lineHeight: 1.5, color: isLight ? '#475569' : 'rgba(236,233,247,.78)' }}>{b}</span>
</div>
))}
</div>
)}
{item.stat && (
<div style={{
...MONO, padding: '10px 14px', borderRadius: 8,
background: isLight ? 'rgba(0,0,0,.04)' : 'rgba(0,0,0,.45)',
border: `1px solid ${item.tint}${isLight ? '33' : '55'}`,
fontSize: 12, color: isLight ? '#475569' : 'rgba(236,233,247,.9)',
display: 'flex', alignItems: 'center', gap: 10,
}}>
<span style={{ color: isLight ? '#16a34a' : '#4ade80' }}></span>
<span style={{ color: item.tint }}>{item.stat.k}</span>
<span style={{ color: isLight ? '#1a1a2e' : '#f5f3fc', fontWeight: 600 }}>{item.stat.v}</span>
</div>
)}
</motion.div>
</motion.div>
)}
</AnimatePresence>
)
}
// ── Star field ────────────────────────────────────────────────────────────────
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 (
<div style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>
{stars.map((st, i) => (
<div key={i} style={{
position: 'absolute', left: `${st.x}%`, top: `${st.y}%`,
width: st.size, height: st.size, borderRadius: '50%',
background: '#fff', opacity: st.op,
boxShadow: `0 0 ${st.size * 3}px rgba(180,160,255,.7)`,
}} />
))}
</div>
)
}
// ── Main slide ────────────────────────────────────────────────────────────────
export default function USPSlide({ lang }: USPSlideProps) {
const de = lang === 'de'
const isLight = useIsLight()