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>
255 lines
12 KiB
TypeScript
255 lines
12 KiB
TypeScript
'use client'
|
|
|
|
import { useState, Fragment } from 'react'
|
|
import { motion, AnimatePresence } from 'framer-motion'
|
|
import { Language } from '@/lib/types'
|
|
import { t } from '@/lib/i18n'
|
|
import GradientText from '../ui/GradientText'
|
|
import FadeInView from '../ui/FadeInView'
|
|
import { X, Users, Lock, Server, BadgeCheck } from 'lucide-react'
|
|
import { type NodeId, type NodeDef, getNodes, LAYERS } from './ArchitectureSlide.data'
|
|
import { CSS_KF, MONO, useIsLight, LayerConnector, LayerSlab } from './ArchitectureSlide.parts'
|
|
|
|
interface ArchitectureSlideProps { lang: Language }
|
|
|
|
// ── Main slide ────────────────────────────────────────────────────────────────
|
|
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<NodeId, NodeDef>
|
|
|
|
const [activeId, setActiveId] = useState<NodeId | null>(null)
|
|
function toggle(id: NodeId) { setActiveId(prev => prev === id ? null : id) }
|
|
const active = activeId ? nodeMap[activeId] : null
|
|
|
|
const tenants = de
|
|
? ['Mandant A', 'Mandant B', 'Mandant C', 'Mandant N…']
|
|
: ['Namespace A', 'Namespace B', 'Namespace C', 'Namespace N…']
|
|
|
|
const layerLabels = de
|
|
? ['01 · Anwendung', '02 · Gateway', '03 · Infrastruktur']
|
|
: ['01 · Application', '02 · Gateway', '03 · Infrastructure']
|
|
const layerSublabels = de
|
|
? ['Benutzeroberflächen', 'Routing & Guardrails', 'Compute & Daten']
|
|
: ['User-facing services', 'Routing & guardrails', 'Compute & data']
|
|
|
|
return (
|
|
<div className="space-y-3">
|
|
<style>{CSS_KF}</style>
|
|
|
|
<FadeInView className="text-center mb-3">
|
|
<p className="text-[10px] font-mono text-indigo-400/50 uppercase tracking-widest mb-1.5">
|
|
{de ? 'Anhang' : 'Appendix'}
|
|
</p>
|
|
<h2 className="text-3xl md:text-4xl font-bold mb-1.5">
|
|
<GradientText>{i.annex.architecture.title}</GradientText>
|
|
</h2>
|
|
<p className="text-xs text-white/35">
|
|
{de ? 'Klicke auf eine Station für Details' : 'Click any node to explore'}
|
|
</p>
|
|
</FadeInView>
|
|
|
|
<FadeInView delay={0.15}>
|
|
<div className="flex items-center justify-center gap-2 flex-wrap mb-3 px-[4%]">
|
|
<Users className="w-3 h-3 text-white/25 flex-shrink-0" />
|
|
<span className="text-[9px] font-mono text-white/25 uppercase tracking-widest mr-1">
|
|
{de ? 'Kundenmandanten' : 'Customer Namespaces'}
|
|
</span>
|
|
{tenants.map(tn => (
|
|
<span key={tn} className="text-[9px] px-2 py-0.5 rounded-full border border-white/[0.08] bg-white/[0.03] text-white/35 font-mono">
|
|
{tn}
|
|
</span>
|
|
))}
|
|
</div>
|
|
|
|
{/* ── Main canvas ── */}
|
|
<div style={{
|
|
position: 'relative',
|
|
background: isLight
|
|
? 'linear-gradient(180deg, #f0f4ff 0%, #eef2ff 50%, #f0f4ff 100%)'
|
|
: 'linear-gradient(180deg, #0a0618 0%, #140a28 50%, #1a0f34 100%)',
|
|
borderRadius: 16, overflow: 'hidden',
|
|
padding: '22px 16px 20px',
|
|
fontFamily: '"Inter", system-ui, -apple-system, sans-serif',
|
|
WebkitFontSmoothing: 'antialiased',
|
|
MozOsxFontSmoothing: 'grayscale',
|
|
} as React.CSSProperties}>
|
|
|
|
{/* Ambient glows */}
|
|
{!isLight && (
|
|
<>
|
|
<div style={{
|
|
position: 'absolute', top: -80, left: '25%',
|
|
width: 400, height: 400, borderRadius: '50%',
|
|
background: 'radial-gradient(circle, rgba(167,139,250,.2), transparent 65%)',
|
|
filter: 'blur(50px)', pointerEvents: 'none',
|
|
}} />
|
|
<div style={{
|
|
position: 'absolute', bottom: -100, right: '15%',
|
|
width: 500, height: 500, borderRadius: '50%',
|
|
background: 'radial-gradient(circle, rgba(139,92,246,.15), transparent 65%)',
|
|
filter: 'blur(50px)', pointerEvents: 'none',
|
|
}} />
|
|
</>
|
|
)}
|
|
|
|
{/* Slabs + connectors */}
|
|
<div style={{
|
|
display: 'flex', flexDirection: 'column', alignItems: 'center',
|
|
position: 'relative', zIndex: 1,
|
|
perspective: '2000px', perspectiveOrigin: '50% 0%',
|
|
}}>
|
|
{LAYERS.map((layer, li) => {
|
|
const nodes = layer.nodeIds.map(id => nodeMap[id])
|
|
return (
|
|
<Fragment key={layer.id}>
|
|
<LayerSlab
|
|
label={layerLabels[li]}
|
|
sublabel={layerSublabels[li]}
|
|
nodes={nodes}
|
|
tint={layer.tint}
|
|
depth={layer.depth}
|
|
selectedId={activeId}
|
|
onSelect={toggle}
|
|
isLight={isLight}
|
|
/>
|
|
{li < LAYERS.length - 1 && <LayerConnector tint={layer.tint} />}
|
|
</Fragment>
|
|
)
|
|
})}
|
|
</div>
|
|
|
|
{/* Footer badges */}
|
|
<div style={{
|
|
display: 'flex', justifyContent: 'center', gap: 8,
|
|
flexWrap: 'wrap', marginTop: 20, position: 'relative', zIndex: 1,
|
|
}}>
|
|
{([
|
|
{ Icon: Lock, label: de ? 'Kein US-Anbieter · 100% DSGVO' : 'No US providers · 100% GDPR' },
|
|
{ Icon: Server, label: de ? 'BSI-zertifiziertes Rechenzentrum' : 'BSI-certified data center' },
|
|
{ Icon: BadgeCheck, label: de ? 'EU-souveräne Inferenz' : 'EU-sovereign inference' },
|
|
] as { Icon: React.ElementType; label: string }[]).map(({ Icon, label }) => (
|
|
<div key={label} style={{
|
|
display: 'flex', alignItems: 'center', gap: 6,
|
|
padding: '5px 11px', borderRadius: 99,
|
|
background: isLight ? '#ffffff' : 'rgba(10,6,24,.82)',
|
|
border: `1px solid ${isLight ? 'rgba(0,0,0,.1)' : 'rgba(167,139,250,.28)'}`,
|
|
fontSize: 10.5,
|
|
color: isLight ? '#64748b' : 'rgba(236,233,247,.7)',
|
|
whiteSpace: 'nowrap',
|
|
boxShadow: isLight ? '0 1px 3px rgba(0,0,0,.06)' : 'none',
|
|
}}>
|
|
<Icon style={{ width: 12, height: 12, color: '#a78bfa' }} />
|
|
{label}
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Detail panel */}
|
|
<AnimatePresence>
|
|
{active && (
|
|
<motion.div
|
|
initial={{ y: '100%' }}
|
|
animate={{ y: 0 }}
|
|
exit={{ y: '100%' }}
|
|
transition={{ duration: 0.3, ease: [0.2, 0.7, 0.2, 1] }}
|
|
style={{
|
|
position: 'absolute', left: 0, right: 0, bottom: 0,
|
|
background: isLight ? 'rgba(255,255,255,.98)' : 'rgba(15,10,31,.97)',
|
|
borderTop: `1px solid ${active.color}${isLight ? '30' : '40'}`,
|
|
zIndex: 50,
|
|
padding: '18px 24px 20px',
|
|
boxShadow: isLight ? '0 -8px 30px rgba(0,0,0,.08)' : '0 -20px 60px rgba(0,0,0,.55)',
|
|
}}
|
|
>
|
|
<div style={{ maxWidth: 900, margin: '0 auto' }}>
|
|
<div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16, marginBottom: 14 }}>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
|
<div style={{
|
|
width: 38, height: 38, borderRadius: 11, flexShrink: 0,
|
|
background: `linear-gradient(135deg, ${active.color}3a, ${active.color}10)`,
|
|
border: `1px solid ${active.color}66`,
|
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
color: active.color,
|
|
}}>
|
|
<active.icon style={{ width: 19, height: 19 }} />
|
|
</div>
|
|
<div>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
|
|
<span style={{ fontSize: 15, fontWeight: 600, color: isLight ? '#1a1a2e' : '#f5f3fc', letterSpacing: -0.2 }}>
|
|
{active.title}
|
|
</span>
|
|
<span style={{
|
|
fontSize: 9, padding: '2px 7px', borderRadius: 4,
|
|
background: `${active.color}18`, color: active.color,
|
|
border: `1px solid ${active.color}40`,
|
|
letterSpacing: 0.8, textTransform: 'uppercase' as const, fontWeight: 600,
|
|
}}>
|
|
{active.tier === 'product' ? (de ? 'Anwendung' : 'Application') :
|
|
active.tier === 'proxy' ? 'Gateway' :
|
|
(de ? 'Inferenz' : 'Inference')}
|
|
</span>
|
|
</div>
|
|
<div style={{ fontSize: 11.5, color: isLight ? '#64748b' : 'rgba(236,233,247,.5)', marginTop: 2 }}>
|
|
{active.subtitle}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<button
|
|
onClick={() => setActiveId(null)}
|
|
style={{
|
|
background: 'transparent',
|
|
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,
|
|
}}
|
|
>
|
|
<X style={{ width: 13, height: 13 }} />
|
|
</button>
|
|
</div>
|
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20 }}>
|
|
<div>
|
|
<div style={{ fontSize: 8.5, letterSpacing: 1.5, textTransform: 'uppercase' as const, color: isLight ? '#94a3b8' : 'rgba(236,233,247,.32)', marginBottom: 7, fontWeight: 600 }}>
|
|
{de ? 'Stack' : 'Tech Stack'}
|
|
</div>
|
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 5 }}>
|
|
{active.tech.map(tk => (
|
|
<span key={tk} style={{
|
|
...MONO,
|
|
fontSize: 10, padding: '3px 8px', borderRadius: 5,
|
|
background: isLight ? '#f1f5f9' : 'rgba(255,255,255,.05)',
|
|
border: `1px solid ${isLight ? 'rgba(0,0,0,.1)' : 'rgba(255,255,255,.1)'}`,
|
|
color: isLight ? '#334155' : 'rgba(236,233,247,.65)',
|
|
}}>{tk}</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div style={{ fontSize: 8.5, letterSpacing: 1.5, textTransform: 'uppercase' as const, color: isLight ? '#94a3b8' : 'rgba(236,233,247,.32)', marginBottom: 7, fontWeight: 600 }}>
|
|
{de ? 'Funktionen' : 'Capabilities'}
|
|
</div>
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
|
|
{active.services.map(s => (
|
|
<div key={s.name} style={{ display: 'flex', alignItems: 'baseline', gap: 6 }}>
|
|
<div style={{ width: 3, height: 3, borderRadius: '50%', background: active.color, opacity: 0.7, flexShrink: 0, marginTop: 6 }} />
|
|
<span style={{ fontSize: 11.5, fontWeight: 600, color: isLight ? '#1a1a2e' : 'rgba(245,243,252,.82)' }}>{s.name}</span>
|
|
<span style={{ fontSize: 10, color: isLight ? '#64748b' : 'rgba(236,233,247,.38)' }}>{s.desc}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
</FadeInView>
|
|
</div>
|
|
)
|
|
}
|