fix(pitch-print): USP overflow, How It Works rail, Assumptions, Architecture layer cards
Build pitch-deck / build-push-deploy (push) Successful in 2m4s
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 40s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 30s
Build pitch-deck / build-push-deploy (push) Successful in 2m4s
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 40s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 30s
Five fixes per user review:
1. USP p1 overflow (stats were getting clipped). Tightened card spacing:
- icon tile 12mm → 9mm, moved inline next to title
- mono kicker for "SÄULE · COMPLIANCE" tags
- reduced paddings, title 13pt → 12pt, body 8.5pt → 8pt
- violet replaces indigo (already by alias, but explicit here)
2. USP p2 closing loop: was a plain tinted callout, now a 2-col hero panel
- left: violet circle around ∞, mono "DIE SCHLEIFE · ALWAYS IN SYNC",
bold headline (14pt), body
- right: white card containing the LoopDiagram with violet outline
- gradient violet→white→violet background for the panel
3. How It Works: replaced the floating-arrow StepStrip with a real
horizontal-rail timeline:
- Violet gradient connector line behind 4 numbered circles
- Each circle is a 14mm violet disc with the step number
- Title + body below each circle
Replaced the Time-to-Value callout with a dotted-rail timeline:
- 5 day markers (Tag 0/3/7/14/30) as violet pill chips on a dashed rail
- Stop label below each
- Mono header reads "Time-to-Value · Median 14 Tage · Worst Case 28 Tage"
4. Assumptions slide:
- "Skalare Annahmen" → "Treibervariablen des Finanzplans" (plain language)
- subtitle rewritten to explain the three-scenario sensitivity setup
instead of referencing internal fp_assumptions tables
- each category now a violet-bordered card with mono kicker + variable
count, italic instead of bare table
- sensitivity callout expanded with concrete runway impact numbers
5. Architecture diagram: layer chips per Claude Design pattern.
- Each tier wrapped in a tinted rounded card (violet for product +
inference, amber for gateway)
- "01 · APPLICATION LAYER" mono pill with italic sub-label
("User-facing services") next to it
- Gateway layer carries the LiteLLM Proxy title inline with subtitle
- Connector arrows kept between layers
Also fixes "Kleinstunternehmen" → "Kleinunternehmen" typo in solution
pillar 03 and the product pricing-logic callout.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -127,59 +127,61 @@ export function ArchitectureDiagram({
|
||||
lang: 'de' | 'en'
|
||||
}) {
|
||||
const de = lang === 'de'
|
||||
const MONO = "'JetBrains Mono', ui-monospace, monospace"
|
||||
|
||||
/** Layer pill: "01 · APPLICATION LAYER" + sub-label */
|
||||
const LayerChip = ({ n, label, sub, tint }: { n: string; label: string; sub: string; tint: string }) => (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '4mm', marginBottom: '3mm' }}>
|
||||
<span style={{ fontFamily: MONO, fontSize: '7.5pt', fontWeight: 700, color: tint, textTransform: 'uppercase', letterSpacing: '0.2em', background: '#ffffff', padding: '1.5mm 4mm', borderRadius: '99pt', border: `1px solid ${tint}55`, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>{n} · {label}</span>
|
||||
<span style={{ fontSize: '8.5pt', color: COLORS.slate600, fontStyle: 'italic' }}>{sub}</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
const productLayerBg = `linear-gradient(135deg, ${COLORS.violet50} 0%, #ffffff 50%, ${COLORS.violet50} 100%)`
|
||||
const proxyLayerBg = `linear-gradient(135deg, ${COLORS.amber50} 0%, #fffaf0 50%, ${COLORS.amber50} 100%)`
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
{/* PRODUCT TIER */}
|
||||
<div style={{ marginBottom: '2mm' }}>
|
||||
<div style={{ fontSize: '7pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.14em', marginBottom: '2mm' }}>{de ? 'Produkt-Schicht' : 'Product Tier'}</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '2mm' }}>
|
||||
{/* APPLICATION (PRODUCT) LAYER */}
|
||||
<div style={{ background: productLayerBg, border: `1px solid ${COLORS.violet200}`, borderRadius: '4pt', padding: '4mm 5mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
<LayerChip n="01" label={de ? 'Application Layer' : 'Application Layer'} sub={de ? 'Kundenseitige Services' : 'User-facing services'} tint={COLORS.violet600} />
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '4mm' }}>
|
||||
{product.map((p, i) => (
|
||||
<FlowNode key={i} kicker={p.kicker} title={p.title} subtitle={p.subtitle} items={p.services} accent={COLORS.indigo600} footer={p.tech} />
|
||||
<FlowNode key={i} kicker={p.kicker} title={p.title} subtitle={p.subtitle} items={p.services} accent={COLORS.violet600} footer={p.tech} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Down arrows between product and proxy */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '4mm', marginBottom: '1mm' }}>
|
||||
{[0, 1, 2].map(i => <VArrow key={i} color={COLORS.indigo500} />)}
|
||||
{/* connectors */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '4mm', padding: '0 5mm' }}>
|
||||
{[0, 1, 2].map(i => <VArrow key={i} color={COLORS.violet500} />)}
|
||||
</div>
|
||||
|
||||
{/* PROXY */}
|
||||
<div style={{ marginBottom: '1mm' }}>
|
||||
<div style={{
|
||||
border: `1.5px solid ${COLORS.amber600}`,
|
||||
borderTop: `3px solid ${COLORS.amber600}`,
|
||||
background: COLORS.amber50,
|
||||
padding: '3mm 5mm',
|
||||
WebkitPrintColorAdjust: 'exact',
|
||||
printColorAdjust: 'exact',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: '2mm' }}>
|
||||
<div>
|
||||
<span style={{ fontSize: '6.5pt', fontWeight: 700, color: COLORS.amber700, textTransform: 'uppercase', letterSpacing: '0.14em', marginRight: '3mm' }}>{de ? 'Gateway' : 'Gateway'}</span>
|
||||
<span style={{ fontSize: '12pt', fontWeight: 800, color: COLORS.slate900 }}>{proxy.title}</span>
|
||||
<span style={{ fontSize: '8.5pt', color: COLORS.amber700, marginLeft: '3mm', fontWeight: 600 }}>{proxy.subtitle}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: '3mm', fontSize: '7.5pt', color: COLORS.slate700 }}>
|
||||
{proxy.features.map((f, i) => (
|
||||
<div key={i} style={{ paddingLeft: '2mm', borderLeft: `1px solid ${COLORS.amber600}`, lineHeight: 1.3 }}>{f}</div>
|
||||
))}
|
||||
</div>
|
||||
{/* GATEWAY LAYER */}
|
||||
<div style={{ background: proxyLayerBg, border: `1.5px solid ${COLORS.amber600}`, borderRadius: '4pt', padding: '4mm 5mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
<LayerChip n="02" label={de ? 'Gateway Layer' : 'Gateway Layer'} sub={de ? 'Routing & Guardrails' : 'Routing & guardrails'} tint={COLORS.amber700} />
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', gap: '4mm', marginBottom: '3mm' }}>
|
||||
<span style={{ fontSize: '13pt', fontWeight: 800, color: COLORS.slate900, letterSpacing: '-0.005em' }}>{proxy.title}</span>
|
||||
<span style={{ fontFamily: MONO, fontSize: '7.5pt', color: COLORS.amber700, fontWeight: 600, letterSpacing: '0.04em' }}>{proxy.subtitle}</span>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: '3mm', fontSize: '7.5pt', color: COLORS.slate700 }}>
|
||||
{proxy.features.map((f, i) => (
|
||||
<div key={i} style={{ paddingLeft: '2mm', borderLeft: `1px solid ${COLORS.amber600}`, lineHeight: 1.35 }}>{f}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Down arrows between proxy and inference */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '4mm', marginBottom: '1mm' }}>
|
||||
{/* connectors */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '4mm', padding: '0 5mm' }}>
|
||||
{[0, 1, 2].map(i => <VArrow key={i} color={COLORS.amber600} />)}
|
||||
</div>
|
||||
|
||||
{/* INFERENCE TIER */}
|
||||
<div>
|
||||
<div style={{ fontSize: '7pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.14em', marginBottom: '2mm' }}>{de ? 'Inferenz-Schicht (lokal, air-gap-fähig)' : 'Inference Tier (local, air-gap capable)'}</div>
|
||||
{/* INFRASTRUCTURE (INFERENCE) LAYER */}
|
||||
<div style={{ background: productLayerBg, border: `1px solid ${COLORS.violet200}`, borderRadius: '4pt', padding: '4mm 5mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
<LayerChip n="03" label={de ? 'Infrastructure Layer' : 'Infrastructure Layer'} sub={de ? 'Compute & Daten · lokal · air-gap-fähig' : 'Compute & data · local · air-gap capable'} tint={COLORS.violet600} />
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '4mm' }}>
|
||||
{inference.map((p, i) => (
|
||||
<FlowNode key={i} title={p.title} subtitle={p.subtitle} items={[p.desc]} accent={COLORS.slate700} footer={p.tech} />
|
||||
<FlowNode key={i} title={p.title} subtitle={p.subtitle} items={[p.desc]} accent={COLORS.violet500} footer={p.tech} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -219,21 +219,27 @@ export function PrintAssumptionsPage({ assumptions, lang, pageNum, totalPages, v
|
||||
}, {})
|
||||
const categories = Object.entries(byCategory)
|
||||
|
||||
const MONO = "'JetBrains Mono', ui-monospace, monospace"
|
||||
return (
|
||||
<Page kicker="18" section={de ? 'ANHANG · ANNAHMEN' : 'APPENDIX · ASSUMPTIONS'} title={de ? 'Skalare Annahmen, Base-Case-Szenario.' : 'Scalar assumptions, base-case scenario.'} subtitle={de ? 'Alle Treibervariablen aus den fp_assumptions-Tabellen. Drei Szenarien (Best/Base/Worst) für Sensitivitätsanalyse verfügbar.' : 'All driver variables from the fp_assumptions tables. Three scenarios (best/base/worst) available for sensitivity analysis.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
|
||||
<Page kicker="18" section={de ? 'ANHANG · ANNAHMEN' : 'APPENDIX · ASSUMPTIONS'} title={de ? 'Treibervariablen des Finanzplans.' : 'Financial plan driver variables.'} subtitle={de ? 'Die Annahmen hinter dem Base-Case. Drei Szenarien (Best/Base/Worst) für Sensitivitätsanalyse verfügbar; Base-Case ist absichtlich konservativ angesetzt.' : 'The assumptions behind the base case. Three scenarios (best/base/worst) available for sensitivity analysis; base case is deliberately conservative.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8mm', flex: 1, minHeight: 0 }}>
|
||||
{categories.slice(0, 6).map(([cat, items]) => (
|
||||
<div key={cat}>
|
||||
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.indigo600, textTransform: 'uppercase', letterSpacing: '0.1em', borderBottom: `1px solid ${COLORS.indigo600}`, paddingBottom: '1mm', marginBottom: '2mm' }}>{cat}</div>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '8pt', fontVariantNumeric: 'tabular-nums' }}>
|
||||
{/* Visual category cards: each category as a violet-bordered panel with
|
||||
its assumptions laid out as a clean two-col list. */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gridAutoRows: 'min-content', gap: '5mm', flex: 1, minHeight: 0, alignContent: 'start' }}>
|
||||
{categories.slice(0, 6).map(([cat, items], i) => (
|
||||
<div key={cat} style={{ border: `1px solid ${COLORS.slate200}`, borderTop: `3px solid ${COLORS.violet600}`, background: '#ffffff', padding: '3.5mm 5mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: '2.5mm' }}>
|
||||
<span style={{ fontFamily: MONO, fontSize: '7.5pt', fontWeight: 700, color: COLORS.violet700, textTransform: 'uppercase', letterSpacing: '0.18em' }}>{cat}</span>
|
||||
<span style={{ fontFamily: MONO, fontSize: '6.5pt', color: COLORS.slate400, fontVariantNumeric: 'tabular-nums' }}>{String(i + 1).padStart(2, '0')} · {items.length} {de ? 'Variablen' : 'variables'}</span>
|
||||
</div>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '8.5pt', fontVariantNumeric: 'tabular-nums' }}>
|
||||
<tbody>
|
||||
{items.map(a => (
|
||||
<tr key={a.key}>
|
||||
{items.map((a, j) => (
|
||||
<tr key={a.key} style={{ borderTop: j > 0 ? `1px solid ${COLORS.slate100}` : 'none' }}>
|
||||
<td style={{ padding: '1.5mm 0', color: COLORS.slate700, paddingRight: '4mm', lineHeight: 1.35 }}>{de ? a.label_de : a.label_en}</td>
|
||||
<td style={{ padding: '1.5mm 0', textAlign: 'right', fontWeight: 700, color: COLORS.slate900, whiteSpace: 'nowrap' }}>
|
||||
{typeof a.value === 'number' ? a.value.toLocaleString('de-DE') : String(a.value)}
|
||||
{a.unit && <span style={{ color: COLORS.slate500, marginLeft: '1mm', fontWeight: 400, fontSize: '7pt' }}>{a.unit}</span>}
|
||||
{a.unit && <span style={{ fontFamily: MONO, color: COLORS.slate500, marginLeft: '1.5mm', fontWeight: 400, fontSize: '7pt' }}>{a.unit}</span>}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
@@ -243,11 +249,11 @@ export function PrintAssumptionsPage({ assumptions, lang, pageNum, totalPages, v
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: '4mm', flexShrink: 0 }}>
|
||||
<Callout tone="accent" label={de ? 'Sensitivität' : 'Sensitivity'}>
|
||||
<div style={{ marginTop: '5mm', flexShrink: 0 }}>
|
||||
<Callout tone="accent" label={de ? 'Sensitivität · Drei Szenarien' : 'Sensitivity · Three Scenarios'}>
|
||||
{de
|
||||
? 'Drei Szenarien, Best/Base/Worst, variieren um Wachstumsrate, Churn, ARPU und CAC. Sensitivitäts-Tornado verfügbar im Live-Modell. Base-Case ist absichtlich konservativ angesetzt.'
|
||||
: 'Three scenarios, best/base/worst, vary growth rate, churn, ARPU and CAC. Sensitivity tornado available in live model. Base case is deliberately conservative.'}
|
||||
? 'Best, Base und Worst Case variieren die kritischen Treiber (Wachstumsrate, Churn, ARPU, CAC). Sensitivitäts-Tornado verfügbar im Live-Modell. Der Base-Case ist absichtlich konservativ — Worst Case noch 8 Monate Runway, Best Case Break-Even 6 Monate früher.'
|
||||
: 'Best, base and worst case vary the critical drivers (growth rate, churn, ARPU, CAC). Sensitivity tornado available in the live model. The base case is deliberately conservative — worst case still gives 8 months runway, best case breaks even 6 months earlier.'}
|
||||
</Callout>
|
||||
</div>
|
||||
</Page>
|
||||
|
||||
@@ -450,7 +450,7 @@ const DE_PILLARS = [
|
||||
'BSI C5 zertifizierte Cloud-Hoster in DE und FR',
|
||||
'Self-Hosted Matrix (Chat) + Jitsi (Video) für Support-Calls',
|
||||
'Lokale LLM-Inferenz (Qwen3, DeepSeek) — air-gap-fähig',
|
||||
'Optional: Mac Mini/Studio on-premise für Kleinstunternehmen',
|
||||
'Optional: Mac Mini/Studio on-premise für Kleinunternehmen',
|
||||
'Vault, Keycloak, OPA für Secrets, SSO, Policies',
|
||||
] },
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Language, PitchProduct } from '@/lib/types'
|
||||
import { Page, Bullets, Callout, COLORS, DataTable, StatLine } from './PrintLayout'
|
||||
import { StepStrip, LoopDiagram } from './PrintDiagrams'
|
||||
import { LoopDiagram } from './PrintDiagrams'
|
||||
import { getDetails } from '@/components/slides/USPSlide.data'
|
||||
import {
|
||||
ScanLine, ShieldCheck, FileText, ClipboardCheck, Users, UserCheck,
|
||||
@@ -26,28 +26,31 @@ export function PrintUSPPage1({ lang, pageNum, totalPages, versionName }: SlideB
|
||||
return (
|
||||
<Page kicker="05" section={de ? 'USP · 1 / 2' : 'USP · 1 / 2'} title={de ? 'Compliance ↔ Code, immer in Sync.' : 'Compliance ↔ Code, always in sync.'} subtitle={de ? 'Vier Säulen, die kein anderer Anbieter geschlossen liefert: RFQ-Prüfung, Prozess-Compliance, bidirektionale Sync, kontinuierliche Engine.' : 'Four pillars no other vendor delivers end-to-end: RFQ verification, process compliance, bidirectional sync, continuous engine.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6mm', flex: 1, minHeight: 0 }}>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '5mm', flex: 1, minHeight: 0 }}>
|
||||
{pillars.map((k, i) => {
|
||||
const p = d[k]
|
||||
const Icon = USP_ICON[k]
|
||||
return (
|
||||
<div key={k} style={{ border: `1px solid ${COLORS.slate200}`, borderTop: `3px solid ${COLORS.indigo600}`, padding: '4mm 5mm', display: 'flex', flexDirection: 'column', minHeight: 0, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', marginBottom: '2mm' }}>
|
||||
<div style={{ width: '12mm', height: '12mm', background: COLORS.indigo50, display: 'flex', alignItems: 'center', justifyContent: 'center', color: COLORS.indigo600, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
{Icon && <Icon size={26} strokeWidth={1.5} />}
|
||||
<div key={k} style={{ border: `1px solid ${COLORS.slate200}`, borderTop: `3px solid ${COLORS.violet600}`, padding: '3mm 4mm', display: 'flex', flexDirection: 'column', minHeight: 0, overflow: 'hidden', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
{/* Header: icon + title inline, kicker top-right */}
|
||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '3mm', marginBottom: '2mm' }}>
|
||||
<div style={{ width: '9mm', height: '9mm', flexShrink: 0, background: COLORS.violet50, display: 'flex', alignItems: 'center', justifyContent: 'center', color: COLORS.violet600, borderRadius: '2pt', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
{Icon && <Icon size={18} strokeWidth={1.5} />}
|
||||
</div>
|
||||
<div style={{ textAlign: 'right' }}>
|
||||
<div style={{ fontSize: '7pt', fontWeight: 700, color: COLORS.indigo600, textTransform: 'uppercase', letterSpacing: '0.12em' }}>{p.kicker}</div>
|
||||
<div style={{ fontSize: '7pt', color: COLORS.slate400, fontVariantNumeric: 'tabular-nums', marginTop: '1mm' }}>{String(i + 1).padStart(2, '0')} / 04</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: '2mm' }}>
|
||||
<span style={{ fontFamily: "'JetBrains Mono', ui-monospace, monospace", fontSize: '6.5pt', fontWeight: 700, color: COLORS.violet600, textTransform: 'uppercase', letterSpacing: '0.16em' }}>{p.kicker}</span>
|
||||
<span style={{ fontFamily: "'JetBrains Mono', ui-monospace, monospace", fontSize: '6.5pt', color: COLORS.slate400, fontVariantNumeric: 'tabular-nums' }}>{String(i + 1).padStart(2, '0')} / 04</span>
|
||||
</div>
|
||||
<div style={{ fontSize: '12pt', fontWeight: 700, color: COLORS.slate900, lineHeight: 1.15, letterSpacing: '-0.005em', marginTop: '1mm' }}>{p.title}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ fontSize: '13pt', fontWeight: 700, color: COLORS.slate900, lineHeight: 1.2, letterSpacing: '-0.005em', marginBottom: '3mm', marginTop: '1mm' }}>{p.title}</div>
|
||||
<div style={{ fontSize: '8.5pt', color: COLORS.slate700, lineHeight: 1.5, marginBottom: '3mm' }}>{p.body}</div>
|
||||
<div style={{ fontSize: '8pt', color: COLORS.slate700, lineHeight: 1.45, marginBottom: '2mm' }}>{p.body}</div>
|
||||
{p.bullets && <Bullets dense items={p.bullets} />}
|
||||
{p.stat && (
|
||||
<div style={{ marginTop: 'auto', paddingTop: '3mm', borderTop: `1px solid ${COLORS.slate200}`, display: 'flex', alignItems: 'baseline', justifyContent: 'space-between' }}>
|
||||
<span style={{ fontSize: '7.5pt', color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.08em', fontWeight: 600 }}>{p.stat.k}</span>
|
||||
<span style={{ fontSize: '12pt', fontWeight: 800, color: COLORS.emerald700, fontVariantNumeric: 'tabular-nums' }}>{p.stat.v}</span>
|
||||
<div style={{ marginTop: 'auto', paddingTop: '2mm', borderTop: `1px solid ${COLORS.slate200}`, display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: '3mm' }}>
|
||||
<span style={{ fontFamily: "'JetBrains Mono', ui-monospace, monospace", fontSize: '6.5pt', color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.12em', fontWeight: 700 }}>{p.stat.k}</span>
|
||||
<span style={{ fontSize: '11pt', fontWeight: 800, color: COLORS.emerald700, fontVariantNumeric: 'tabular-nums', whiteSpace: 'nowrap' }}>{p.stat.v}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -86,14 +89,21 @@ export function PrintUSPPage2({ lang, pageNum, totalPages, versionName }: SlideB
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: '5mm', border: `1px solid ${COLORS.indigo600}`, background: COLORS.indigo50, padding: '4mm 5mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '3mm', marginBottom: '2mm' }}>
|
||||
<Infinity size={20} strokeWidth={1.5} color={COLORS.indigo700} />
|
||||
<span style={{ fontSize: '7.5pt', fontWeight: 700, color: COLORS.indigo700, textTransform: 'uppercase', letterSpacing: '0.14em' }}>{de ? 'Die Schleife' : 'The Loop'}</span>
|
||||
{/* Closing loop: violet-tinted hero panel with the diagram on the right */}
|
||||
<div style={{ marginTop: '6mm', border: `1px solid ${COLORS.violet300}`, background: `linear-gradient(135deg, ${COLORS.violet50} 0%, #ffffff 50%, ${COLORS.violet50} 100%)`, borderRadius: '3pt', padding: '5mm 6mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact', display: 'grid', gridTemplateColumns: '1fr 1.2fr', gap: '6mm', alignItems: 'center' }}>
|
||||
<div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '3mm', marginBottom: '3mm' }}>
|
||||
<div style={{ width: '10mm', height: '10mm', borderRadius: '50%', background: COLORS.violet600, color: '#ffffff', display: 'flex', alignItems: 'center', justifyContent: 'center', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
<Infinity size={18} strokeWidth={2} />
|
||||
</div>
|
||||
<span style={{ fontFamily: "'JetBrains Mono', ui-monospace, monospace", fontSize: '7.5pt', fontWeight: 700, color: COLORS.violet700, textTransform: 'uppercase', letterSpacing: '0.2em' }}>{de ? 'Die Schleife · Always in Sync' : 'The Loop · Always in Sync'}</span>
|
||||
</div>
|
||||
<div style={{ fontSize: '14pt', fontWeight: 800, color: COLORS.slate900, marginBottom: '2mm', lineHeight: 1.15, letterSpacing: '-0.01em' }}>{d.hub.title}</div>
|
||||
<div style={{ fontSize: '8.5pt', color: COLORS.slate700, lineHeight: 1.55 }}>{d.hub.body}</div>
|
||||
</div>
|
||||
<div style={{ background: '#ffffff', border: `1px solid ${COLORS.violet200}`, borderRadius: '3pt', padding: '3mm 4mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
<LoopDiagram lang={lang} />
|
||||
</div>
|
||||
<div style={{ fontSize: '11pt', fontWeight: 700, color: COLORS.slate900, marginBottom: '2mm' }}>{d.hub.title}</div>
|
||||
<div style={{ fontSize: '8.5pt', color: COLORS.slate700, lineHeight: 1.5, marginBottom: '3mm' }}>{d.hub.body}</div>
|
||||
<LoopDiagram lang={lang} />
|
||||
</div>
|
||||
</Page>
|
||||
)
|
||||
@@ -252,7 +262,7 @@ export function PrintProductPage({ products, lang, pageNum, totalPages, versionN
|
||||
<div style={{ marginTop: '4mm', flexShrink: 0 }}>
|
||||
<Callout tone="positive" label={de ? 'Pricing-Logik' : 'Pricing logic'}>
|
||||
{de
|
||||
? 'Starter <10 MA: 3.600 €/J · Professional 10–250: 15–40k €/J · Enterprise 250+: ab 50k €/J. Mitarbeiterbasiert. Standard: BSI-Cloud DE. Optional: Mac Mini/Studio für absolute Privacy bei Kleinstunternehmen.'
|
||||
? 'Starter <10 MA: 3.600 €/J · Professional 10–250: 15–40k €/J · Enterprise 250+: ab 50k €/J. Mitarbeiterbasiert. Standard: BSI-Cloud DE. Optional: Mac Mini/Studio für absolute Privacy bei Kleinunternehmen.'
|
||||
: 'Starter <10 emp: €3,600/yr · Professional 10–250: €15–40k/yr · Enterprise 250+: from €50k/yr. Employee-based. Standard: BSI cloud DE. Optional: Mac Mini/Studio for absolute privacy for micro businesses.'}
|
||||
</Callout>
|
||||
</div>
|
||||
@@ -275,22 +285,62 @@ const STEPS_EN = [
|
||||
{ n: '04', t: 'Prepare for audit', d: 'All evidence, documents and risk assessments at the push of a button. Post-audit deviations automatically tracked, deadlines, tickets, escalation.' },
|
||||
]
|
||||
|
||||
const TIMELINE_DAYS_DE = ['Tag 0', 'Tag 3', 'Tag 7', 'Tag 14', 'Tag 30']
|
||||
const TIMELINE_DAYS_EN = ['Day 0', 'Day 3', 'Day 7', 'Day 14', 'Day 30']
|
||||
const TIMELINE_LABELS_DE = ['Vertrag', 'Onboarding-Call', 'Repos angebunden', 'VVT/TOMs auto', 'audit-ready']
|
||||
const TIMELINE_LABELS_EN = ['Contract', 'Onboarding call', 'Repos connected', 'RoPA/TOMs auto', 'audit-ready']
|
||||
|
||||
export function PrintHowItWorksPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
|
||||
const de = lang === 'de'
|
||||
const steps = de ? STEPS_DE : STEPS_EN
|
||||
const days = de ? TIMELINE_DAYS_DE : TIMELINE_DAYS_EN
|
||||
const labels = de ? TIMELINE_LABELS_DE : TIMELINE_LABELS_EN
|
||||
const MONO = "'JetBrains Mono', ui-monospace, monospace"
|
||||
|
||||
return (
|
||||
<Page kicker="08" section={de ? 'SO FUNKTIONIERT\'S' : 'HOW IT WORKS'} title={de ? 'In 4 Schritten zur kontinuierlichen Compliance.' : 'Continuous compliance in 4 steps.'} subtitle={de ? 'Vom Vertrag bis zur Audit-Bereitschaft in der Regel <30 Tage. Kein Excel, kein Pentest-Vendor, keine manuelle Dokumentenpflege.' : 'From contract to audit-ready typically in <30 days. No Excel, no pentest vendor, no manual document maintenance.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
|
||||
|
||||
<div style={{ flex: 1, minHeight: 0, display: 'flex', alignItems: 'stretch' }}>
|
||||
<StepStrip steps={steps} />
|
||||
{/* 4-step rail: numbered violet circles on a horizontal connector line,
|
||||
title + body underneath each. Replaces the floating-arrow StepStrip. */}
|
||||
<div style={{ position: 'relative', marginTop: '4mm', flexShrink: 0 }}>
|
||||
{/* connector line (behind the circles) */}
|
||||
<div style={{ position: 'absolute', top: '7mm', left: '7mm', right: '7mm', height: '2px', background: `linear-gradient(90deg, ${COLORS.violet600} 0%, ${COLORS.violet400} 100%)`, opacity: 0.85, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
|
||||
<div style={{ position: 'relative', display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '5mm' }}>
|
||||
{steps.map((s, i) => (
|
||||
<div key={i} style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
|
||||
{/* number circle on the rail */}
|
||||
<div style={{ width: '14mm', height: '14mm', borderRadius: '50%', background: COLORS.violet600, color: '#ffffff', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '14pt', fontWeight: 800, letterSpacing: '-0.01em', boxShadow: `0 0 0 4px ${COLORS.violet50}`, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>{s.n}</div>
|
||||
<div style={{ marginTop: '3mm', fontSize: '12pt', fontWeight: 700, color: COLORS.slate900, lineHeight: 1.15, letterSpacing: '-0.005em' }}>{s.t}</div>
|
||||
<div style={{ marginTop: '2mm', fontSize: '8.5pt', color: COLORS.slate700, lineHeight: 1.5 }}>{s.d}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: '6mm', flexShrink: 0 }}>
|
||||
<Callout tone="accent" label={de ? 'Typische Time-to-Value' : 'Typical time-to-value'}>
|
||||
{de
|
||||
? 'Tag 0: Vertrag · Tag 3: Onboarding-Call · Tag 7: erste Repos angebunden · Tag 14: erste automatische VVT/TOMs · Tag 30: audit-ready Status erreicht.'
|
||||
: 'Day 0: contract · Day 3: onboarding call · Day 7: first repos connected · Day 14: first automated RoPA/TOMs · Day 30: audit-ready status achieved.'}
|
||||
</Callout>
|
||||
{/* Fill space between steps and footer with a visual timeline */}
|
||||
<div style={{ flex: 1 }} />
|
||||
|
||||
<div style={{ flexShrink: 0, marginBottom: '2mm' }}>
|
||||
<div style={{ fontFamily: MONO, fontSize: '7.5pt', fontWeight: 700, color: COLORS.violet600, textTransform: 'uppercase', letterSpacing: '0.18em', marginBottom: '4mm' }}>
|
||||
{de ? 'Time-to-Value · Median 14 Tage · Worst Case 28 Tage' : 'Time-to-Value · Median 14 days · Worst case 28 days'}
|
||||
</div>
|
||||
{/* dotted timeline with 5 day markers */}
|
||||
<div style={{ position: 'relative', height: '14mm' }}>
|
||||
{/* the rail */}
|
||||
<div style={{ position: 'absolute', left: '7mm', right: '7mm', top: '5mm', height: '1.5px', background: `repeating-linear-gradient(90deg, ${COLORS.violet400} 0, ${COLORS.violet400} 2mm, transparent 2mm, transparent 4mm)`, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', height: '100%' }}>
|
||||
{days.map((d, i) => (
|
||||
<div key={i} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', position: 'relative' }}>
|
||||
{/* day marker pill */}
|
||||
<div style={{ fontFamily: MONO, fontSize: '7pt', fontWeight: 700, color: COLORS.violet700, background: COLORS.violet50, padding: '1mm 3mm', borderRadius: '99pt', border: `1px solid ${COLORS.violet300}`, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>{d}</div>
|
||||
{/* dot on rail */}
|
||||
<div style={{ width: '3mm', height: '3mm', borderRadius: '50%', background: COLORS.violet600, marginTop: '1mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
|
||||
{/* label below */}
|
||||
<div style={{ marginTop: '2mm', fontSize: '7.5pt', color: COLORS.slate700, textAlign: 'center', fontWeight: 600 }}>{labels[i]}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Page>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user