fix(pitch-deck): center mandants strip and fix LiteLLM overlap
All checks were successful
Build pitch-deck / build-push-deploy (push) Successful in 1m12s
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 33s
CI / test-python-voice (push) Successful in 31s
CI / test-bqas (push) Successful in 30s

- Remove flex-1 trailing divider that was pushing pills left
- Increase map height 420→480px to give clearance between app labels and gateway circle
- Update SVG viewBox to 0 0 1100 480 (consistent with CONNS coordinates)
- Update cy% for all nodes to match new 480px coordinate space (app 22.1%, gateway 50%, inference 80%)
- Update SEP1/SEP2 to 36%/65% for new height
- Update all SVG element y-coords: track lines, tick marks, junction dots, gateway stub

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-04-20 23:48:33 +02:00
parent e37aecab18
commit ac8ef371ff

View File

@@ -39,30 +39,31 @@ interface ConnDef {
from: NodeId from: NodeId
to: NodeId to: NodeId
type: ConnType type: ConnType
d: string // SVG path in viewBox "0 0 1100 420" — right-angle only d: string // SVG path in viewBox "0 0 1100 480" — right-angle only
revD?: string revD?: string
} }
// All right-angle paths — safe with preserveAspectRatio="none" + vectorEffect="non-scaling-stroke" // All right-angle paths — safe with preserveAspectRatio="none" + vectorEffect="non-scaling-stroke"
// viewBox: 0 0 1100 480 | app y=106 gateway y=240 inference y=384
const CONNS: ConnDef[] = [ const CONNS: ConnDef[] = [
{ from: 'certifai', to: 'litellm', type: 'api', { from: 'certifai', to: 'litellm', type: 'api',
d: 'M 195 105 L 195 158 L 522 158 L 522 210' }, d: 'M 195 106 L 195 178 L 522 178 L 522 240' },
{ from: 'complai', to: 'litellm', type: 'api', { from: 'complai', to: 'litellm', type: 'api',
d: 'M 550 105 L 550 210' }, d: 'M 550 106 L 550 240' },
{ from: 'scanner', to: 'litellm', type: 'api', { from: 'scanner', to: 'litellm', type: 'api',
d: 'M 905 105 L 905 158 L 578 158 L 578 210' }, d: 'M 905 106 L 905 178 L 578 178 L 578 240' },
{ from: 'complai', to: 'scanner', type: 'mcp', { from: 'complai', to: 'scanner', type: 'mcp',
d: 'M 550 105 Q 727 52 905 105', d: 'M 550 106 Q 727 55 905 106',
revD: 'M 905 105 Q 727 52 550 105' }, revD: 'M 905 106 Q 727 55 550 106' },
{ from: 'litellm', to: 'llm', type: 'api', { from: 'litellm', to: 'llm', type: 'api',
d: 'M 550 210 L 550 268 L 218 268 L 218 340' }, d: 'M 550 240 L 550 308 L 218 308 L 218 384' },
{ from: 'litellm', to: 'embeddings', type: 'embed', { from: 'litellm', to: 'embeddings', type: 'embed',
d: 'M 550 210 L 550 340' }, d: 'M 550 240 L 550 384' },
{ from: 'litellm', to: 'tools', type: 'tool', { from: 'litellm', to: 'tools', type: 'tool',
d: 'M 550 210 L 550 268 L 882 268 L 882 340' }, d: 'M 550 240 L 550 308 L 882 308 L 882 384' },
] ]
// cx = svgX/1100*100, cy = svgY/420*100 (exact match between SVG coords and CSS %) // cx = svgX/1100*100, cy = svgY/480*100 (exact match between SVG coords and CSS %)
function getNodes(de: boolean): NodeDef[] { function getNodes(de: boolean): NodeDef[] {
return [ return [
{ {
@@ -71,7 +72,7 @@ function getNodes(de: boolean): NodeDef[] {
subtitle: de ? 'GenAI Mandantenportal' : 'GenAI Tenant Portal', subtitle: de ? 'GenAI Mandantenportal' : 'GenAI Tenant Portal',
color: '#c084fc', twColor: 'text-purple-400', color: '#c084fc', twColor: 'text-purple-400',
twBorder: 'border-purple-500/50', twBg: 'bg-purple-500/10', twDot: 'bg-purple-400', twBorder: 'border-purple-500/50', twBg: 'bg-purple-500/10', twDot: 'bg-purple-400',
cx: 17.7, cy: 25, tier: 'product', cx: 17.7, cy: 22.1, tier: 'product',
badge: 'Rust · Dioxus', badge: 'Rust · Dioxus',
tech: ['Rust', 'Dioxus', 'MongoDB', 'Keycloak', 'SearXNG', 'LangGraph'], tech: ['Rust', 'Dioxus', 'MongoDB', 'Keycloak', 'SearXNG', 'LangGraph'],
services: [ services: [
@@ -87,7 +88,7 @@ function getNodes(de: boolean): NodeDef[] {
subtitle: de ? 'Compliance & Audit' : 'Compliance & Audit', subtitle: de ? 'Compliance & Audit' : 'Compliance & Audit',
color: '#818cf8', twColor: 'text-indigo-400', color: '#818cf8', twColor: 'text-indigo-400',
twBorder: 'border-indigo-500/50', twBg: 'bg-indigo-500/10', twDot: 'bg-indigo-400', twBorder: 'border-indigo-500/50', twBg: 'bg-indigo-500/10', twDot: 'bg-indigo-400',
cx: 50, cy: 25, tier: 'product', cx: 50, cy: 22.1, tier: 'product',
badge: 'Next.js · FastAPI', badge: 'Next.js · FastAPI',
tech: ['Next.js 15', 'FastAPI', 'Go/Gin', 'PostgreSQL', 'Qdrant', 'Valkey'], tech: ['Next.js 15', 'FastAPI', 'Go/Gin', 'PostgreSQL', 'Qdrant', 'Valkey'],
services: [ services: [
@@ -103,7 +104,7 @@ function getNodes(de: boolean): NodeDef[] {
subtitle: de ? 'Code-Sicherheit' : 'Code Security', subtitle: de ? 'Code-Sicherheit' : 'Code Security',
color: '#34d399', twColor: 'text-emerald-400', color: '#34d399', twColor: 'text-emerald-400',
twBorder: 'border-emerald-500/50', twBg: 'bg-emerald-500/10', twDot: 'bg-emerald-400', twBorder: 'border-emerald-500/50', twBg: 'bg-emerald-500/10', twDot: 'bg-emerald-400',
cx: 82.3, cy: 25, tier: 'product', cx: 82.3, cy: 22.1, tier: 'product',
badge: 'Rust · Axum', badge: 'Rust · Axum',
tech: ['Rust', 'Axum', 'MongoDB', 'Semgrep', 'Gitleaks', 'Syft'], tech: ['Rust', 'Axum', 'MongoDB', 'Semgrep', 'Gitleaks', 'Syft'],
services: [ services: [
@@ -136,7 +137,7 @@ function getNodes(de: boolean): NodeDef[] {
subtitle: de ? 'Lokale Sprachmodelle' : 'Local Language Models', subtitle: de ? 'Lokale Sprachmodelle' : 'Local Language Models',
color: '#60a5fa', twColor: 'text-blue-400', color: '#60a5fa', twColor: 'text-blue-400',
twBorder: 'border-blue-500/50', twBg: 'bg-blue-500/10', twDot: 'bg-blue-400', twBorder: 'border-blue-500/50', twBg: 'bg-blue-500/10', twDot: 'bg-blue-400',
cx: 17.7, cy: 81, tier: 'inference', cx: 17.7, cy: 80, tier: 'inference',
badge: 'On-Premise · BSI', badge: 'On-Premise · BSI',
tech: ['Qwen3-32B', 'Qwen3-Coder-30B', 'DeepSeek-R1-8B', 'Ollama'], tech: ['Qwen3-32B', 'Qwen3-Coder-30B', 'DeepSeek-R1-8B', 'Ollama'],
services: [ services: [
@@ -151,7 +152,7 @@ function getNodes(de: boolean): NodeDef[] {
subtitle: de ? 'Semantische Suche' : 'Semantic Search', subtitle: de ? 'Semantische Suche' : 'Semantic Search',
color: '#a78bfa', twColor: 'text-violet-400', color: '#a78bfa', twColor: 'text-violet-400',
twBorder: 'border-violet-500/50', twBg: 'bg-violet-500/10', twDot: 'bg-violet-400', twBorder: 'border-violet-500/50', twBg: 'bg-violet-500/10', twDot: 'bg-violet-400',
cx: 50, cy: 81, tier: 'inference', cx: 50, cy: 80, tier: 'inference',
badge: de ? 'EU-Souverän' : 'EU Sovereign', badge: de ? 'EU-Souverän' : 'EU Sovereign',
tech: ['bge-m3', 'Qdrant Vector DB', 'Sentence-Transformers'], tech: ['bge-m3', 'Qdrant Vector DB', 'Sentence-Transformers'],
services: [ services: [
@@ -166,7 +167,7 @@ function getNodes(de: boolean): NodeDef[] {
subtitle: de ? 'Web-Suche & MCP' : 'Web Search & MCP', subtitle: de ? 'Web-Suche & MCP' : 'Web Search & MCP',
color: '#2dd4bf', twColor: 'text-teal-400', color: '#2dd4bf', twColor: 'text-teal-400',
twBorder: 'border-teal-500/50', twBg: 'bg-teal-500/10', twDot: 'bg-teal-400', twBorder: 'border-teal-500/50', twBg: 'bg-teal-500/10', twDot: 'bg-teal-400',
cx: 82.3, cy: 81, tier: 'inference', cx: 82.3, cy: 80, tier: 'inference',
badge: de ? 'EU-Souverän' : 'EU Sovereign', badge: de ? 'EU-Souverän' : 'EU Sovereign',
tech: ['SearXNG', 'MCP Protocol', 'Semgrep API', 'Gitleaks API'], tech: ['SearXNG', 'MCP Protocol', 'Semgrep API', 'Gitleaks API'],
services: [ services: [
@@ -377,10 +378,10 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
? ['Mandant A', 'Mandant B', 'Mandant C', 'Mandant N…'] ? ['Mandant A', 'Mandant B', 'Mandant C', 'Mandant N…']
: ['Namespace A', 'Namespace B', 'Namespace C', 'Namespace N…'] : ['Namespace A', 'Namespace B', 'Namespace C', 'Namespace N…']
// Tier separator positions (% of 420px container height) // Tier separator positions (% of 480px container height)
// App: 038.1% (y=0160), Gateway: 38.164.3% (y=160270), Inference: 64.3100% // App: 036% (y=0173), Gateway: 3665% (y=173312), Inference: 65100%
const SEP1 = '38.1%' const SEP1 = '36%'
const SEP2 = '64.3%' const SEP2 = '65%'
return ( return (
<div className="space-y-3"> <div className="space-y-3">
@@ -410,11 +411,10 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
{tn} {tn}
</span> </span>
))} ))}
<div className="flex-1 h-px bg-gradient-to-r from-white/10 to-transparent ml-1" />
</div> </div>
{/* ── METRO MAP ──────────────────────────────────────────────── */} {/* ── METRO MAP ──────────────────────────────────────────────── */}
<div className="relative w-full rounded-xl overflow-hidden" style={{ height: '420px' }}> <div className="relative w-full rounded-xl overflow-hidden" style={{ height: '480px' }}>
{/* Background */} {/* Background */}
<div className="absolute inset-0" <div className="absolute inset-0"
@@ -432,7 +432,7 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
<div className="absolute inset-x-0 top-0 pointer-events-none" <div className="absolute inset-x-0 top-0 pointer-events-none"
style={{ height: SEP1, background: 'rgba(129,140,248,0.028)' }} /> style={{ height: SEP1, background: 'rgba(129,140,248,0.028)' }} />
<div className="absolute inset-x-0 pointer-events-none" <div className="absolute inset-x-0 pointer-events-none"
style={{ top: SEP1, height: '26.2%', background: 'rgba(251,191,36,0.032)' }} /> style={{ top: SEP1, height: '29%', background: 'rgba(251,191,36,0.032)' }} />
<div className="absolute inset-x-0 pointer-events-none" <div className="absolute inset-x-0 pointer-events-none"
style={{ top: SEP2, bottom: 0, background: 'rgba(96,165,250,0.022)' }} /> style={{ top: SEP2, bottom: 0, background: 'rgba(96,165,250,0.022)' }} />
@@ -451,7 +451,7 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
{de ? 'ANWENDUNG' : 'APP LAYER'} {de ? 'ANWENDUNG' : 'APP LAYER'}
</span> </span>
</div> </div>
<div className="flex items-center justify-center" style={{ height: '26.2%' }}> <div className="flex items-center justify-center" style={{ height: '29%' }}>
<span className="text-[6.5px] font-mono tracking-[0.22em] uppercase" <span className="text-[6.5px] font-mono tracking-[0.22em] uppercase"
style={{ writingMode: 'vertical-rl', transform: 'rotate(180deg)', color: 'rgba(251,191,36,0.38)' }}> style={{ writingMode: 'vertical-rl', transform: 'rotate(180deg)', color: 'rgba(251,191,36,0.38)' }}>
GATEWAY GATEWAY
@@ -468,7 +468,7 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
{/* BSI badges — right strip */} {/* BSI badges — right strip */}
<div className="absolute right-0 top-0 bottom-0 w-20 flex flex-col pointer-events-none select-none" style={{ zIndex: 2 }}> <div className="absolute right-0 top-0 bottom-0 w-20 flex flex-col pointer-events-none select-none" style={{ zIndex: 2 }}>
<div style={{ height: SEP1 }} /> <div style={{ height: SEP1 }} />
<div className="flex items-center justify-end pr-2" style={{ height: '26.2%' }}> <div className="flex items-center justify-end pr-2" style={{ height: '29%' }}>
<div className="flex items-center gap-1 opacity-45"> <div className="flex items-center gap-1 opacity-45">
<BadgeCheck className="w-2.5 h-2.5 text-amber-400" /> <BadgeCheck className="w-2.5 h-2.5 text-amber-400" />
<span className="text-[6.5px] font-mono tracking-wider text-amber-400">BSI DC</span> <span className="text-[6.5px] font-mono tracking-wider text-amber-400">BSI DC</span>
@@ -494,40 +494,40 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
{/* ── SVG: metro tracks + animated data flows ── */} {/* ── SVG: metro tracks + animated data flows ── */}
<svg className="absolute inset-0 w-full h-full pointer-events-none" <svg className="absolute inset-0 w-full h-full pointer-events-none"
viewBox="0 0 1100 420" preserveAspectRatio="none"> viewBox="0 0 1100 480" preserveAspectRatio="none">
{/* Horizontal metro line — Application layer (y=105) */} {/* Horizontal metro line — Application layer (y=106) */}
<line x1="165" y1="105" x2="935" y2="105" <line x1="165" y1="106" x2="935" y2="106"
stroke="rgba(129,140,248,0.14)" strokeWidth="4" stroke="rgba(129,140,248,0.14)" strokeWidth="4"
vectorEffect="non-scaling-stroke" /> vectorEffect="non-scaling-stroke" />
{/* Station tick marks on app line */} {/* Station tick marks on app line */}
{[195, 550, 905].map(x => ( {[195, 550, 905].map(x => (
<line key={x} x1={x} y1="97" x2={x} y2="113" <line key={x} x1={x} y1="98" x2={x} y2="114"
stroke="rgba(129,140,248,0.45)" strokeWidth="2" stroke="rgba(129,140,248,0.45)" strokeWidth="2"
vectorEffect="non-scaling-stroke" /> vectorEffect="non-scaling-stroke" />
))} ))}
{/* Horizontal metro line — Inference layer (y=340) */} {/* Horizontal metro line — Inference layer (y=384) */}
<line x1="165" y1="340" x2="935" y2="340" <line x1="165" y1="384" x2="935" y2="384"
stroke="rgba(96,165,250,0.14)" strokeWidth="4" stroke="rgba(96,165,250,0.14)" strokeWidth="4"
vectorEffect="non-scaling-stroke" /> vectorEffect="non-scaling-stroke" />
{/* Station tick marks on inference line */} {/* Station tick marks on inference line */}
{[195, 550, 905].map(x => ( {[195, 550, 905].map(x => (
<line key={x} x1={x} y1="332" x2={x} y2="348" <line key={x} x1={x} y1="376" x2={x} y2="392"
stroke="rgba(96,165,250,0.45)" strokeWidth="2" stroke="rgba(96,165,250,0.45)" strokeWidth="2"
vectorEffect="non-scaling-stroke" /> vectorEffect="non-scaling-stroke" />
))} ))}
{/* Gateway stub lines (short horizontal stubs from hub) */} {/* Gateway stub lines (short horizontal stubs from hub) */}
<line x1="440" y1="210" x2="660" y2="210" <line x1="440" y1="240" x2="660" y2="240"
stroke="rgba(251,191,36,0.10)" strokeWidth="3" stroke="rgba(251,191,36,0.10)" strokeWidth="3"
vectorEffect="non-scaling-stroke" /> vectorEffect="non-scaling-stroke" />
{/* Junction corner dots */} {/* Junction corner dots */}
{[ {[
{ cx: 195, cy: 158 }, { cx: 522, cy: 158 }, { cx: 195, cy: 178 }, { cx: 522, cy: 178 },
{ cx: 905, cy: 158 }, { cx: 578, cy: 158 }, { cx: 905, cy: 178 }, { cx: 578, cy: 178 },
{ cx: 218, cy: 268 }, { cx: 550, cy: 268 }, { cx: 882, cy: 268 }, { cx: 218, cy: 308 }, { cx: 550, cy: 308 }, { cx: 882, cy: 308 },
].map(({ cx, cy }) => ( ].map(({ cx, cy }) => (
<circle key={`${cx}-${cy}`} cx={cx} cy={cy} r="3.5" <circle key={`${cx}-${cy}`} cx={cx} cy={cy} r="3.5"
fill="rgba(160,170,200,0.22)" vectorEffect="non-scaling-stroke" /> fill="rgba(160,170,200,0.22)" vectorEffect="non-scaling-stroke" />