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
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:
@@ -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: 0–38.1% (y=0–160), Gateway: 38.1–64.3% (y=160–270), Inference: 64.3–100%
|
// App: 0–36% (y=0–173), Gateway: 36–65% (y=173–312), Inference: 65–100%
|
||||||
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" />
|
||||||
|
|||||||
Reference in New Issue
Block a user