[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:
216
pitch-deck/components/slides/CompetitionSlide.parts.tsx
Normal file
216
pitch-deck/components/slides/CompetitionSlide.parts.tsx
Normal file
@@ -0,0 +1,216 @@
|
||||
'use client'
|
||||
|
||||
import { Language } from '@/lib/types'
|
||||
import { Users, DollarSign, Globe, Cpu, Star, Check, X, Minus } from 'lucide-react'
|
||||
import BrandName from '../ui/BrandName'
|
||||
import {
|
||||
type FeatureStatus, type ExtendedCompetitor, type ComparisonFeature,
|
||||
type AppSecCompetitor, type AppSecFeature, GROUP_LABELS,
|
||||
} from './CompetitionSlide.data'
|
||||
|
||||
export function StatusIcon({ value }: { value: FeatureStatus }) {
|
||||
if (value === true) return <Check className="w-3.5 h-3.5 text-green-400 mx-auto" />
|
||||
if (value === 'partial') return <Minus className="w-3.5 h-3.5 text-yellow-400 mx-auto" />
|
||||
return <X className="w-3.5 h-3.5 text-white/15 mx-auto" />
|
||||
}
|
||||
|
||||
export function AiBadge({ level, lang }: { level: 'full' | 'partial' | 'none'; lang: Language }) {
|
||||
const colors = { full: 'bg-green-500/15 text-green-400', partial: 'bg-yellow-500/15 text-yellow-400', none: 'bg-white/5 text-white/30' }
|
||||
const labels = { full: { de: 'KI', en: 'AI' }, partial: { de: 'Basis', en: 'Basic' }, none: { de: 'Keine', en: 'None' } }
|
||||
return (
|
||||
<span className={`text-[10px] px-1.5 py-0.5 rounded-full font-medium ${colors[level]}`}>
|
||||
<Cpu className="w-2.5 h-2.5 inline mr-0.5 -mt-px" />
|
||||
{labels[level][lang]}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export function ratio(a: number, b: number): string {
|
||||
if (b === 0) return '\u2014'
|
||||
const r = a / b
|
||||
if (r >= 1_000_000) return `${(r / 1_000_000).toFixed(1)}M`
|
||||
if (r >= 1_000) return `${(r / 1_000).toFixed(0)}k`
|
||||
return r.toFixed(0)
|
||||
}
|
||||
|
||||
export function CompetitorCard({ competitor: c, lang }: { competitor: ExtendedCompetitor; lang: Language }) {
|
||||
return (
|
||||
<div className="bg-white/[0.04] border border-white/5 rounded-xl p-2.5 text-[11px]">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-sm">{c.flag}</span>
|
||||
<span className="font-semibold text-white/80 text-xs">{c.name}</span>
|
||||
</div>
|
||||
<AiBadge level={c.aiUsage} lang={lang} />
|
||||
</div>
|
||||
<div className="text-[10px] text-white/40 mb-1.5 truncate" title={`HQ: ${c.hq}, ${c.hqCountry}` + (c.offices.length > 1 ? ` | Offices: ${c.offices.join(', ')}` : '')}>
|
||||
<span className="text-white/55">{c.hq}, {c.hqCountry}</span>
|
||||
{c.offices.length > 1 && (
|
||||
<span className="ml-1">+ {c.offices.join(', ')}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-x-3 gap-y-0.5 text-white/50">
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-white/30">{lang === 'de' ? 'Gr.' : 'Est.'}</span>
|
||||
<span className="text-white/70">{c.founded}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Users className="w-2.5 h-2.5 text-white/30" />
|
||||
<span className="text-white/70">{c.employees.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<DollarSign className="w-2.5 h-2.5 text-white/30" />
|
||||
<span className="text-white/70">{c.revenue}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Globe className="w-2.5 h-2.5 text-white/30" />
|
||||
<span className="text-white/70">{c.customers.toLocaleString()} {lang === 'de' ? 'Kd.' : 'cust.'} ({c.customerCountries})</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-1.5 pt-1.5 border-t border-white/5">
|
||||
<div className="text-white/40">
|
||||
<span className="text-white/60 font-medium">{c.fundingTotal}</span>
|
||||
<span className="ml-1 text-[10px]">{c.fundingRound}</span>
|
||||
</div>
|
||||
{c.investors.length > 0 && (
|
||||
<div className="text-[10px] text-white/30 mt-0.5 truncate" title={c.investors.join(', ')}>
|
||||
{c.investors.slice(0, 3).join(', ')}{c.investors.length > 3 ? ' +' + (c.investors.length - 3) : ''}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-1 text-[10px] text-white/35 truncate" title={c.market[lang]}>
|
||||
{c.market[lang]}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function FeatureTable({
|
||||
features, lang, cols, labels,
|
||||
}: {
|
||||
features: ComparisonFeature[]
|
||||
lang: Language
|
||||
cols: readonly string[]
|
||||
labels: string[]
|
||||
highlight?: boolean
|
||||
}) {
|
||||
const rowElements: React.ReactNode[] = []
|
||||
let lastGroup = ''
|
||||
features.forEach((f, i) => {
|
||||
const grp = f.group || ''
|
||||
if (grp && grp !== lastGroup) {
|
||||
const gl = GROUP_LABELS[grp]
|
||||
if (gl) {
|
||||
rowElements.push(
|
||||
<tr key={`grp-${grp}`} className="bg-white/[0.02]">
|
||||
<td colSpan={cols.length + 1} className={`py-1.5 px-2 text-[10px] font-bold uppercase tracking-wider ${gl.color}`}>
|
||||
{lang === 'de' ? gl.de : gl.en}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
lastGroup = grp
|
||||
}
|
||||
rowElements.push(
|
||||
<tr key={i} className={`border-b border-white/5 ${f.isDiff ? 'bg-indigo-500/5' : ''}`}>
|
||||
<td className="py-1.5 px-2 flex items-center gap-1.5">
|
||||
{f.isDiff && <Star className="w-3 h-3 text-yellow-400 shrink-0" />}
|
||||
<span className={f.isDiff ? 'text-white font-medium' : 'text-white/60'}>
|
||||
{lang === 'de' ? f.de : f.en}
|
||||
</span>
|
||||
</td>
|
||||
{cols.map(col => (
|
||||
<td key={col} className="py-1.5 px-1.5 text-center">
|
||||
<StatusIcon value={f[col as keyof ComparisonFeature] as FeatureStatus} />
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="overflow-x-auto mt-1 mb-1">
|
||||
<table className="w-full text-[11px]">
|
||||
<thead>
|
||||
<tr className="border-b border-white/10">
|
||||
<th className="text-left py-1.5 px-2 text-white/40 font-medium min-w-[180px]">Feature</th>
|
||||
{labels.map((l, idx) => (
|
||||
<th key={l} className={`py-1.5 px-1.5 font-medium text-center whitespace-nowrap ${idx === 0 ? 'text-indigo-400' : 'text-white/50'}`}>
|
||||
{idx === 0 ? <BrandName className="text-[11px]" /> : l}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{rowElements}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function AppSecCard({ competitor: c, lang }: { competitor: AppSecCompetitor; lang: Language }) {
|
||||
return (
|
||||
<div className="bg-white/[0.04] border border-white/5 rounded-xl p-2 text-[11px]">
|
||||
<div className="flex items-center gap-1.5 mb-1">
|
||||
<span className="text-sm">{c.flag}</span>
|
||||
<span className="font-semibold text-white/80 text-xs">{c.name}</span>
|
||||
</div>
|
||||
<div className="text-[10px] text-white/40 mb-1 truncate">{c.hq} · {c.founded}</div>
|
||||
<div className="grid grid-cols-2 gap-x-2 gap-y-0.5 text-white/50">
|
||||
<div className="flex items-center gap-1">
|
||||
<Users className="w-2.5 h-2.5 text-white/30" />
|
||||
<span className="text-white/70">{c.employees.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<DollarSign className="w-2.5 h-2.5 text-white/30" />
|
||||
<span className="text-white/70 truncate">{c.revenue}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-1 pt-1 border-t border-white/5 text-[10px]">
|
||||
<div className="text-white/40 truncate">{c.funding}</div>
|
||||
<div className="text-white/50 mt-0.5">{c.pricing}</div>
|
||||
</div>
|
||||
<div className="mt-1 text-[10px] text-white/35 truncate" title={c.focus[lang]}>
|
||||
{c.focus[lang]}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function AppSecFeatureTable({ features, lang, highlight }: { features: AppSecFeature[]; lang: Language; highlight?: boolean }) {
|
||||
const cols = ['bp', 'snyk', 'veracode', 'checkmarx', 'sonar', 'semgrep', 'pentera', 'invicti', 'intruder'] as const
|
||||
const labels = ['ComplAI', 'Snyk', 'Veracode', 'Checkmarx', 'Sonar', 'Semgrep', 'Pentera', 'Invicti', 'Intruder']
|
||||
|
||||
return (
|
||||
<div className="overflow-x-auto mt-1 mb-1">
|
||||
<table className="w-full text-[11px]">
|
||||
<thead>
|
||||
<tr className="border-b border-white/10">
|
||||
<th className="text-left py-1.5 px-2 text-white/40 font-medium min-w-[160px]">Feature</th>
|
||||
{labels.map((l, idx) => (
|
||||
<th key={l} className={`py-1.5 px-1 font-medium text-center whitespace-nowrap ${idx === 0 ? 'text-indigo-400' : 'text-white/50'}`}>
|
||||
{idx === 0 ? <BrandName className="text-[11px]" /> : l}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{features.map((f, i) => (
|
||||
<tr key={i} className={`border-b border-white/5 ${highlight && f.isUSP ? 'bg-indigo-500/5' : ''}`}>
|
||||
<td className="py-1.5 px-2 flex items-center gap-1.5">
|
||||
{f.isUSP && highlight && <Star className="w-3 h-3 text-yellow-400 shrink-0" />}
|
||||
<span className={f.isUSP && highlight ? 'text-white font-medium' : 'text-white/60'}>
|
||||
{lang === 'de' ? f.de : f.en}
|
||||
</span>
|
||||
</td>
|
||||
{cols.map(col => (
|
||||
<td key={col} className="py-1.5 px-1 text-center">
|
||||
<StatusIcon value={f[col] as FeatureStatus} />
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user