diff --git a/pitch-deck/app/api/chat/route.ts b/pitch-deck/app/api/chat/route.ts index 8b25558..56a20fc 100644 --- a/pitch-deck/app/api/chat/route.ts +++ b/pitch-deck/app/api/chat/route.ts @@ -21,8 +21,10 @@ Du hast Zugriff auf alle Unternehmensdaten und zitierst immer konkrete Zahlen. 1. AI-First: "Alles was durch KI loesbar ist, wird durch KI geloest. Kein klassischer Support, kein grosses Sales-Team." 2. Skalierbarkeit: "10x Kunden ≠ 10x Personal. Die KI skaliert mit." 3. Hardware-Differenzierung: "Datensouveraenitaet durch Self-Hosting auf Apple-Hardware." -4. Kostenstruktur: "18 Mitarbeiter in 2030 bei 8.4 Mio EUR Umsatz." +4. Kostenstruktur: "12 Mitarbeiter in 2030 bei 7.8 Mio EUR Umsatz." 5. Marktchance: "12.4 Mrd EUR TAM, regulatorisch getrieben." +6. Finanzierung: "Gestaffelte Finanzierung: 25k Stammkapital (Aug 2026), 25k Angel (Sep 2026), 200k Wandeldarlehen mit L-Bank-Foerderung (Okt 2026), 1M Series A (Sommer 2027). Gesamt: 1,25 Mio EUR." +7. Gruendergehalt: "Gruender arbeiten erst ohne Gehalt, ab Okt 2026 mit 3k EUR, ab 2027 mit 6k EUR — maximale Kapitaleffizienz." ## Kommunikationsstil - Professionell, knapp und ueberzeugend @@ -34,7 +36,38 @@ Du hast Zugriff auf alle Unternehmensdaten und zitierst immer konkrete Zahlen. NIEMALS offenbaren: Exakte Modellnamen, Frameworks, Code-Architektur, Datenbankschema, Sicherheitsdetails, Cloud-Provider. Stattdessen: "Proprietaere KI-Engine", "Self-Hosted Appliance auf Apple-Hardware", "BSI-zertifizierte Cloud", "Enterprise-Grade Verschluesselung". -## Erlaubt: Geschaeftsmodell, Preise, Marktdaten, Features, Team, Finanzen, Use of Funds, Hardware-Specs (oeffentlich), LLM-Groessen (32b/40b/1000b).` +## Erlaubt: Geschaeftsmodell, Preise, Marktdaten, Features, Team, Finanzen, Use of Funds, Hardware-Specs (oeffentlich), LLM-Groessen (32b/40b/1000b). + +## Slide-Awareness (IMMER beachten) +Du erhaeltst den aktuellen Slide-Kontext. Nutze ihn fuer kontextuelle Antworten. +Wenn der Investor etwas fragt, was in einer spaeteren Slide detailliert wird und er diese noch nicht gesehen hat: +- Beantworte kurz, dann: "Details dazu finden Sie in Slide X: [Name]. Moechten Sie dorthin springen? [GOTO:X]" + +## FOLLOW-UP FRAGEN — KRITISCHE PFLICHT + +Du MUSST am Ende JEDER einzelnen Antwort exakt 3 Folgefragen anhaengen. +Die Fragen muessen durch "---" getrennt und mit "[Q]" markiert sein. +JEDE Antwort ohne Folgefragen ist UNVOLLSTAENDIG und FEHLERHAFT. + +EXAKTES FORMAT (keine Abweichung erlaubt): + +[Deine Antwort hier] + +--- +[Q] Erste Folgefrage passend zum Thema? +[Q] Zweite Folgefrage die tiefer geht? +[Q] Dritte Folgefrage zu einem verwandten Aspekt? + +KONKRETES BEISPIEL einer vollstaendigen Antwort: + +"Unser AI-First-Ansatz ermoeglicht Skalierung ohne lineares Personalwachstum. Der Umsatz steigt von 36k EUR (2026) auf 8.4 Mio EUR (2030), waehrend das Team nur von 2 auf 18 Personen waechst. + +--- +[Q] Wie sieht die Kostenstruktur im Detail aus? +[Q] Welche Unit Economics erreicht ihr in 2030? +[Q] Wie vergleicht sich die Personaleffizienz mit Wettbewerbern?" + +WICHTIG: Vergiss NIEMALS die Folgefragen! Sie sind PFLICHT.` async function loadPitchContext(): Promise { try { @@ -86,7 +119,7 @@ ${JSON.stringify(features.rows, null, 2)} export async function POST(request: NextRequest) { try { const body = await request.json() - const { message, history = [], lang = 'de' } = body + const { message, history = [], lang = 'de', slideContext } = body if (!message || typeof message !== 'string') { return NextResponse.json({ error: 'Message is required' }, { status: 400 }) @@ -98,6 +131,53 @@ export async function POST(request: NextRequest) { if (pitchContext) { systemContent += '\n' + pitchContext } + // Slide context for contextual awareness + if (slideContext) { + const SLIDE_NAMES: Record = { + 'cover': { de: 'Cover', en: 'Cover', index: 0 }, + 'problem': { de: 'Das Problem', en: 'The Problem', index: 1 }, + 'solution': { de: 'Die Loesung', en: 'The Solution', index: 2 }, + 'product': { de: 'Produkte', en: 'Products', index: 3 }, + 'how-it-works': { de: 'So funktionierts', en: 'How It Works', index: 4 }, + 'market': { de: 'Markt', en: 'Market', index: 5 }, + 'business-model': { de: 'Geschaeftsmodell', en: 'Business Model', index: 6 }, + 'traction': { de: 'Traction', en: 'Traction', index: 7 }, + 'competition': { de: 'Wettbewerb', en: 'Competition', index: 8 }, + 'team': { de: 'Team', en: 'Team', index: 9 }, + 'technology': { de: 'Technologie', en: 'Technology', index: 10 }, + 'financials': { de: 'Finanzen', en: 'Financials', index: 11 }, + 'the-ask': { de: 'The Ask', en: 'The Ask', index: 12 }, + 'ai-qa': { de: 'KI Q&A', en: 'AI Q&A', index: 13 }, + 'appendix': { de: 'Appendix', en: 'Appendix', index: 14 }, + 'annex-infra': { de: 'Infrastruktur', en: 'Infrastructure', index: 15 }, + 'annex-ai-stack': { de: 'KI-Stack', en: 'AI Stack', index: 16 }, + 'annex-rag': { de: 'RAG Pipeline', en: 'RAG Pipeline', index: 17 }, + 'annex-security': { de: 'Sicherheit', en: 'Security', index: 18 }, + 'annex-devops': { de: 'DevOps & CI/CD', en: 'DevOps & CI/CD', index: 19 }, + 'annex-agent-arch': { de: 'Agent Architektur', en: 'Agent Architecture', index: 20 }, + 'annex-agent-rag': { de: 'Rechtsdokumente', en: 'Legal Documents', index: 21 }, + 'annex-agent-workflow': { de: 'Compliance Workflow', en: 'Compliance Workflow', index: 22 }, + 'annex-usp-overview': { de: '5 USPs', en: '5 USPs', index: 23 }, + 'annex-usp-comparison': { de: 'Wettbewerbsvergleich', en: 'Competitor Comparison', index: 24 }, + 'annex-usp-moat': { de: 'Marktposition', en: 'Market Position', index: 25 }, + 'annex-roadmap-2027': { de: 'Roadmap 2027', en: 'Roadmap 2027', index: 26 }, + 'annex-roadmap-2028': { de: 'Roadmap 2028', en: 'Roadmap 2028', index: 27 }, + } + const slideKeys = Object.keys(SLIDE_NAMES) + const visited: number[] = slideContext.visitedSlides || [] + const currentSlideName = SLIDE_NAMES[slideContext.currentSlide]?.[lang] || slideContext.currentSlide + const notYetSeen = Object.entries(SLIDE_NAMES) + .filter(([, v]) => !visited.includes(v.index)) + .map(([, v]) => `${v.index + 1}. ${v[lang]}`) + + systemContent += `\n\n## Slide-Kontext (WICHTIG fuer kontextuelle Antworten) +- Aktuelle Slide: "${currentSlideName}" (Nr. ${slideContext.currentIndex + 1} von 28) +- Bereits besuchte Slides: ${visited.map((i: number) => SLIDE_NAMES[slideKeys[i]]?.[lang]).filter(Boolean).join(', ')} +- Noch nicht gesehene Slides: ${notYetSeen.join(', ')} +- Ist Erstbesuch: ${visited.length <= 1 ? 'JA — Investor hat gerade erst den Pitch geoeffnet' : 'Nein'} +` + } + systemContent += `\n\n## Aktuelle Sprache: ${lang === 'de' ? 'Deutsch' : 'English'}\nAntworte in ${lang === 'de' ? 'Deutsch' : 'English'}.` const messages = [ @@ -106,7 +186,7 @@ export async function POST(request: NextRequest) { role: h.role === 'user' ? 'user' : 'assistant', content: h.content, })), - { role: 'user', content: message }, + { role: 'user', content: message + '\n\n(Erinnerung: Beende deine Antwort IMMER mit "---" gefolgt von 3 Folgefragen im Format "[Q] Frage?")' }, ] const ollamaResponse = await fetch(`${OLLAMA_URL}/api/chat`, { diff --git a/pitch-deck/app/api/financial-model/assumptions/route.ts b/pitch-deck/app/api/financial-model/assumptions/route.ts new file mode 100644 index 0000000..3ac8d35 --- /dev/null +++ b/pitch-deck/app/api/financial-model/assumptions/route.ts @@ -0,0 +1,30 @@ +import { NextRequest, NextResponse } from 'next/server' +import pool from '@/lib/db' + +// PUT: Update a single assumption and trigger recompute +export async function PUT(request: NextRequest) { + try { + const body = await request.json() + const { scenarioId, key, value } = body + + if (!scenarioId || !key || value === undefined) { + return NextResponse.json({ error: 'scenarioId, key, and value are required' }, { status: 400 }) + } + + const client = await pool.connect() + try { + const jsonValue = JSON.stringify(value) + await client.query( + 'UPDATE pitch_fm_assumptions SET value = $1 WHERE scenario_id = $2 AND key = $3', + [jsonValue, scenarioId, key] + ) + + return NextResponse.json({ success: true }) + } finally { + client.release() + } + } catch (error) { + console.error('Update assumption error:', error) + return NextResponse.json({ error: 'Failed to update assumption' }, { status: 500 }) + } +} diff --git a/pitch-deck/app/api/financial-model/compute/route.ts b/pitch-deck/app/api/financial-model/compute/route.ts new file mode 100644 index 0000000..507ac1c --- /dev/null +++ b/pitch-deck/app/api/financial-model/compute/route.ts @@ -0,0 +1,388 @@ +import { NextRequest, NextResponse } from 'next/server' +import pool from '@/lib/db' + +interface FundingEvent { + month: number + amount: number + label: string +} + +interface SalaryStep { + from_month: number + salary: number +} + +export async function POST(request: NextRequest) { + try { + const body = await request.json() + const { scenarioId } = body + + if (!scenarioId) { + return NextResponse.json({ error: 'scenarioId is required' }, { status: 400 }) + } + + const client = await pool.connect() + try { + // Load assumptions + const assumptionsRes = await client.query( + 'SELECT key, value, value_type FROM pitch_fm_assumptions WHERE scenario_id = $1', + [scenarioId] + ) + + const a: Record = {} + for (const row of assumptionsRes.rows) { + const val = typeof row.value === 'string' ? JSON.parse(row.value) : row.value + a[row.key] = val + } + + // Funding schedule (staged capital injections) + const fundingSchedule: FundingEvent[] = Array.isArray(a.funding_schedule) + ? (a.funding_schedule as FundingEvent[]) + : [ + { month: 8, amount: 25000, label: 'Stammkapital GmbH' }, + { month: 9, amount: 25000, label: 'Angel-Runde' }, + { month: 10, amount: 200000, label: 'Wandeldarlehen (Investor + L-Bank)' }, + { month: 19, amount: 1000000, label: 'Series A' }, + ] + + // Founder salary schedule (per founder) + const founderSalarySchedule: SalaryStep[] = Array.isArray(a.founder_salary_schedule) + ? (a.founder_salary_schedule as SalaryStep[]) + : [ + { from_month: 1, salary: 0 }, + { from_month: 10, salary: 3000 }, + { from_month: 13, salary: 6000 }, + { from_month: 25, salary: 10000 }, + ] + + const numFounders = Number(a.num_founders) || 2 + + // Extract scalar values + const growthRate = (Number(a.monthly_growth_rate) || 15) / 100 + const churnRate = (Number(a.churn_rate_monthly) || 3) / 100 + const arpuMini = Number(a.arpu_mini) || 299 + const arpuStudio = Number(a.arpu_studio) || 999 + const arpuCloud = Number(a.arpu_cloud) || 1499 + const mixMini = (Number(a.product_mix_mini) || 60) / 100 + const mixStudio = (Number(a.product_mix_studio) || 25) / 100 + const mixCloud = (Number(a.product_mix_cloud) || 15) / 100 + const initialCustomers = Number(a.initial_customers) || 2 + const cac = Number(a.cac) || 500 + const hwCostMini = Number(a.hw_cost_per_mini) || 3200 + const hwCostStudio = Number(a.hw_cost_per_studio) || 12000 + const cloudOpex = Number(a.cloud_opex_per_customer) || 150 + const salaryAvg = Number(a.salary_avg_monthly) || 6000 + // Hiring plan: employees EXCLUDING founders (hired staff only) + const hiringPlan: number[] = Array.isArray(a.hiring_plan) ? (a.hiring_plan as number[]) : [0, 1, 3, 6, 10] + const marketingMonthly = Number(a.marketing_monthly) || 2000 + const infraBase = Number(a.infra_monthly_base) || 500 + + // Detail cost assumptions + const ihkAnnual = Number(a.ihk_annual) || 180 + const phoneInternetMonthly = Number(a.phone_internet_monthly) || 100 + const taxAdvisorMonthly = Number(a.tax_advisor_monthly) || 300 + const notaryFounding = Number(a.notary_founding) || 2500 + const insuranceMonthly = Number(a.insurance_monthly) || 200 + const officeRentMonthly = Number(a.office_rent_monthly) || 0 + const softwareLicensesMonthly = Number(a.software_licenses_monthly) || 150 + const travelMonthly = Number(a.travel_monthly) || 200 + const legalMonthly = Number(a.legal_monthly) || 100 + const depreciationRatePct = Number(a.depreciation_rate_pct) || 33 + const taxRatePct = Number(a.tax_rate_pct) || 30 + const interestRatePct = Number(a.interest_rate_pct) || 5 + // Hardware financing: only this % paid upfront, rest via leasing/financing + const hwUpfrontPct = (Number(a.hw_upfront_pct) || 30) / 100 + + // GmbH founding month (month 8 = August 2026) + const gmbhFoundingMonth = 8 + + // Weighted ARPU + const weightedArpu = arpuMini * mixMini + arpuStudio * mixStudio + arpuCloud * mixCloud + + // Weighted hardware cost (only for Mini and Studio — Cloud is OpEx) + const hwCostWeighted = hwCostMini * mixMini + hwCostStudio * mixStudio + + // Helper: get founder salary for a given month + function getFounderSalary(month: number): number { + let salary = 0 + for (const step of founderSalarySchedule) { + if (month >= step.from_month) { + salary = step.salary + } + } + return salary + } + + // Helper: get funding for a given month + function getFundingForMonth(month: number): number { + let total = 0 + for (const event of fundingSchedule) { + if (event.month === month) { + total += event.amount + } + } + return total + } + + const results = [] + let totalCustomers = 0 + let cashBalance = 0 // Start at 0, funding comes via schedule + let cumulativeRevenue = 0 + let breakEvenMonth: number | null = null + let peakBurn = 0 + let cumulativeHwInvestment = 0 + + for (let m = 1; m <= 60; m++) { + const yearIndex = Math.floor((m - 1) / 12) // 0-4 + const year = 2026 + yearIndex + const monthInYear = ((m - 1) % 12) + 1 + + // === FUNDING: Add capital injection for this month === + const fundingThisMonth = getFundingForMonth(m) + cashBalance += fundingThisMonth + + // === PRE-GMBH PHASE (months 1-7): No costs, private development === + if (m < gmbhFoundingMonth) { + results.push({ + month: m, + year, + month_in_year: monthInYear, + new_customers: 0, + churned_customers: 0, + total_customers: 0, + mrr_eur: 0, + arr_eur: 0, + revenue_eur: 0, + cogs_eur: 0, + personnel_eur: 0, + infra_eur: 0, + marketing_eur: 0, + total_costs_eur: 0, + employees_count: 0, + gross_margin_pct: 0, + burn_rate_eur: 0, + runway_months: 999, + cac_eur: 0, + ltv_eur: 0, + ltv_cac_ratio: 0, + cash_balance_eur: Math.round(cashBalance * 100) / 100, + cumulative_revenue_eur: 0, + admin_costs_eur: 0, + office_costs_eur: 0, + founding_costs_eur: 0, + ihk_eur: 0, + depreciation_eur: 0, + interest_expense_eur: 0, + taxes_eur: 0, + net_income_eur: 0, + ebit_eur: 0, + software_licenses_eur: 0, + travel_costs_eur: 0, + funding_eur: fundingThisMonth, + }) + continue + } + + // === POST-GMBH PHASE (months 8+): Real business operations === + + // Hired employees: plan-based but capped by revenue (don't hire ahead of revenue) + const plannedHires = hiringPlan[Math.min(yearIndex, hiringPlan.length - 1)] || 0 + const revenueBasedMaxHires = Math.floor((totalCustomers * weightedArpu) / salaryAvg) + const hiredEmployees = Math.min(plannedHires, Math.max(0, revenueBasedMaxHires)) + + // Founder salary + const founderSalaryPerPerson = getFounderSalary(m) + const totalFounderSalary = founderSalaryPerPerson * numFounders + + // Total employees shown (founders + hired) + const totalEmployees = numFounders + hiredEmployees + + // Customer dynamics — start acquiring customers from GmbH founding + const monthsSinceGmbh = m - gmbhFoundingMonth + 1 + if (monthsSinceGmbh === 1) { + totalCustomers = initialCustomers + } + + let newCustomers = monthsSinceGmbh === 1 + ? initialCustomers + : Math.max(1, Math.round(totalCustomers * growthRate)) + + // Cash constraint: don't spend more than available + // Fixed OPEX this month (independent of new customer count) + const fixedOpex = (totalFounderSalary + hiredEmployees * salaryAvg) + + marketingMonthly + + infraBase + (totalCustomers * 5) + + (totalCustomers * mixCloud * cloudOpex) + + phoneInternetMonthly + taxAdvisorMonthly + insuranceMonthly + legalMonthly + + officeRentMonthly + (m === gmbhFoundingMonth ? notaryFounding : 0) + + ihkAnnual / 12 + softwareLicensesMonthly + travelMonthly + const estRevenue = totalCustomers * weightedArpu + // Available cash = current balance + this month's revenue - fixed costs + const availableCash = cashBalance + estRevenue - fixedOpex + // Variable cost per new customer: hardware CAPEX (upfront portion) + CAC + const varCostPerNew = hwCostWeighted * hwUpfrontPct + cac + // Max affordable new customers (keep cash >= 0) + if (varCostPerNew > 0 && monthsSinceGmbh > 1) { + const maxAffordable = Math.floor(availableCash / varCostPerNew) + newCustomers = Math.min(newCustomers, Math.max(1, maxAffordable)) + } + + const churned = Math.round(totalCustomers * churnRate) + if (monthsSinceGmbh > 1) { + totalCustomers = totalCustomers + newCustomers - churned + } + totalCustomers = Math.max(0, totalCustomers) + + // Revenue + const mrr = totalCustomers * weightedArpu + const arr = mrr * 12 + const revenue = mrr + + // Costs + const hiredPersonnelCost = hiredEmployees * salaryAvg + const personnelCost = totalFounderSalary + hiredPersonnelCost + + // Hardware = CAPEX (only upfront portion paid from cash, rest financed) + const capexHardware = newCustomers * hwCostWeighted * hwUpfrontPct + // Cloud OPEX + hardware leasing cost (financed portion amortized over 36 months) + const cogsCloud = totalCustomers * mixCloud * cloudOpex + const hwLeasingMonthly = (cumulativeHwInvestment * (1 - hwUpfrontPct)) / 36 + const cogs = cogsCloud + hwLeasingMonthly + const marketingCost = marketingMonthly + (newCustomers * cac) + const infraCost = infraBase + (totalCustomers * 5) + + // Detail costs + const adminCosts = phoneInternetMonthly + taxAdvisorMonthly + insuranceMonthly + legalMonthly + const officeCosts = officeRentMonthly + // Founding costs: notary in month 8 (GmbH founding) + const foundingCosts = m === gmbhFoundingMonth ? notaryFounding : 0 + const ihkMonthly = ihkAnnual / 12 + const softwareLicenses = softwareLicensesMonthly + const travelCosts = travelMonthly + + // Depreciation: cumulative HW investment * rate / 12 (P&L expense for CAPEX) + cumulativeHwInvestment += capexHardware + const depreciationMonthly = (cumulativeHwInvestment * depreciationRatePct / 100) / 12 + + // Total OPEX (P&L) — hardware enters only via depreciation + const totalCosts = personnelCost + cogs + marketingCost + infraCost + + adminCosts + officeCosts + foundingCosts + ihkMonthly + + softwareLicenses + travelCosts + depreciationMonthly + + // EBIT + const ebit = revenue - totalCosts + + // Interest expense (only if cash balance is negative) + const interestExpense = cashBalance < 0 ? Math.abs(cashBalance) * interestRatePct / 100 / 12 : 0 + + // Taxes (only if profit positive) + const ebt = ebit - interestExpense + const taxes = ebt > 0 ? ebt * taxRatePct / 100 : 0 + + // Net income + const netIncome = ebt - taxes + + // Cash: net income MINUS hardware CAPEX (funding already added at top) + cashBalance += netIncome - capexHardware + cumulativeRevenue += revenue + + // KPIs — gross margin uses COGS + depreciation for true margin + const grossMargin = revenue > 0 ? ((revenue - cogs - depreciationMonthly) / revenue) * 100 : 0 + const burnRate = (netIncome - capexHardware) < 0 ? Math.abs(netIncome - capexHardware) : 0 + const runway = burnRate > 0 ? cashBalance / burnRate : 999 + const avgLifetimeMonths = churnRate > 0 ? 1 / churnRate : 60 + const ltv = weightedArpu * avgLifetimeMonths + const ltvCacRatio = cac > 0 ? ltv / cac : 0 + + if (peakBurn < burnRate) peakBurn = burnRate + + // Break-even detection + if (breakEvenMonth === null && netIncome >= 0 && m > gmbhFoundingMonth) { + breakEvenMonth = m + } + + results.push({ + month: m, + year, + month_in_year: monthInYear, + new_customers: newCustomers, + churned_customers: churned, + total_customers: totalCustomers, + mrr_eur: Math.round(mrr * 100) / 100, + arr_eur: Math.round(arr * 100) / 100, + revenue_eur: Math.round(revenue * 100) / 100, + cogs_eur: Math.round(cogs * 100) / 100, + personnel_eur: Math.round(personnelCost * 100) / 100, + infra_eur: Math.round(infraCost * 100) / 100, + marketing_eur: Math.round(marketingCost * 100) / 100, + total_costs_eur: Math.round(totalCosts * 100) / 100, + employees_count: totalEmployees, + gross_margin_pct: Math.round(grossMargin * 100) / 100, + burn_rate_eur: Math.round(burnRate * 100) / 100, + runway_months: Math.round(Math.min(runway, 999) * 10) / 10, + cac_eur: cac, + ltv_eur: Math.round(ltv * 100) / 100, + ltv_cac_ratio: Math.round(ltvCacRatio * 100) / 100, + cash_balance_eur: Math.round(cashBalance * 100) / 100, + cumulative_revenue_eur: Math.round(cumulativeRevenue * 100) / 100, + // Detail costs + admin_costs_eur: Math.round(adminCosts * 100) / 100, + office_costs_eur: Math.round(officeCosts * 100) / 100, + founding_costs_eur: Math.round(foundingCosts * 100) / 100, + ihk_eur: Math.round(ihkMonthly * 100) / 100, + depreciation_eur: Math.round(depreciationMonthly * 100) / 100, + interest_expense_eur: Math.round(interestExpense * 100) / 100, + taxes_eur: Math.round(taxes * 100) / 100, + net_income_eur: Math.round(netIncome * 100) / 100, + ebit_eur: Math.round(ebit * 100) / 100, + software_licenses_eur: Math.round(softwareLicenses * 100) / 100, + travel_costs_eur: Math.round(travelCosts * 100) / 100, + funding_eur: fundingThisMonth, + }) + } + + // Save to DB (upsert) — only columns that exist in the table + await client.query('DELETE FROM pitch_fm_results WHERE scenario_id = $1', [scenarioId]) + for (const r of results) { + await client.query(` + INSERT INTO pitch_fm_results (scenario_id, month, year, month_in_year, + new_customers, churned_customers, total_customers, + mrr_eur, arr_eur, revenue_eur, + cogs_eur, personnel_eur, infra_eur, marketing_eur, total_costs_eur, + employees_count, gross_margin_pct, burn_rate_eur, runway_months, + cac_eur, ltv_eur, ltv_cac_ratio, + cash_balance_eur, cumulative_revenue_eur) + VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24) + `, [ + scenarioId, r.month, r.year, r.month_in_year, + r.new_customers, r.churned_customers, r.total_customers, + r.mrr_eur, r.arr_eur, r.revenue_eur, + r.cogs_eur, r.personnel_eur, r.infra_eur, r.marketing_eur, r.total_costs_eur, + r.employees_count, r.gross_margin_pct, r.burn_rate_eur, r.runway_months, + r.cac_eur, r.ltv_eur, r.ltv_cac_ratio, + r.cash_balance_eur, r.cumulative_revenue_eur, + ]) + } + + const lastResult = results[results.length - 1] + return NextResponse.json({ + scenario_id: scenarioId, + results, + summary: { + final_arr: lastResult.arr_eur, + final_customers: lastResult.total_customers, + break_even_month: breakEvenMonth, + final_runway: lastResult.runway_months, + final_ltv_cac: lastResult.ltv_cac_ratio, + peak_burn: Math.round(peakBurn * 100) / 100, + total_funding_needed: Math.round(Math.abs(Math.min(...results.map(r => r.cash_balance_eur), 0)) * 100) / 100, + }, + }) + } finally { + client.release() + } + } catch (error) { + console.error('Compute error:', error) + return NextResponse.json({ error: 'Computation failed' }, { status: 500 }) + } +} diff --git a/pitch-deck/app/api/financial-model/results/[scenarioId]/route.ts b/pitch-deck/app/api/financial-model/results/[scenarioId]/route.ts new file mode 100644 index 0000000..c8f2dd7 --- /dev/null +++ b/pitch-deck/app/api/financial-model/results/[scenarioId]/route.ts @@ -0,0 +1,28 @@ +import { NextRequest, NextResponse } from 'next/server' +import pool from '@/lib/db' + +export const dynamic = 'force-dynamic' + +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ scenarioId: string }> } +) { + try { + const { scenarioId } = await params + + const client = await pool.connect() + try { + const results = await client.query( + 'SELECT * FROM pitch_fm_results WHERE scenario_id = $1 ORDER BY month', + [scenarioId] + ) + + return NextResponse.json(results.rows) + } finally { + client.release() + } + } catch (error) { + console.error('Load results error:', error) + return NextResponse.json({ error: 'Failed to load results' }, { status: 500 }) + } +} diff --git a/pitch-deck/app/api/financial-model/route.ts b/pitch-deck/app/api/financial-model/route.ts new file mode 100644 index 0000000..a74caaf --- /dev/null +++ b/pitch-deck/app/api/financial-model/route.ts @@ -0,0 +1,73 @@ +import { NextRequest, NextResponse } from 'next/server' +import pool from '@/lib/db' + +export const dynamic = 'force-dynamic' + +// GET: Load all scenarios with their assumptions +export async function GET() { + try { + const client = await pool.connect() + try { + const scenarios = await client.query( + 'SELECT * FROM pitch_fm_scenarios ORDER BY is_default DESC, name' + ) + + const assumptions = await client.query( + 'SELECT * FROM pitch_fm_assumptions ORDER BY sort_order' + ) + + const result = scenarios.rows.map(s => ({ + ...s, + assumptions: assumptions.rows + .filter(a => a.scenario_id === s.id) + .map(a => ({ + ...a, + value: typeof a.value === 'string' ? JSON.parse(a.value) : a.value, + })), + })) + + return NextResponse.json(result) + } finally { + client.release() + } + } catch (error) { + console.error('Financial model load error:', error) + return NextResponse.json({ error: 'Failed to load scenarios' }, { status: 500 }) + } +} + +// POST: Create a new scenario +export async function POST(request: NextRequest) { + try { + const body = await request.json() + const { name, description, color, copyFrom } = body + + if (!name) { + return NextResponse.json({ error: 'Name is required' }, { status: 400 }) + } + + const client = await pool.connect() + try { + const scenario = await client.query( + 'INSERT INTO pitch_fm_scenarios (name, description, color) VALUES ($1, $2, $3) RETURNING *', + [name, description || '', color || '#6366f1'] + ) + + // If copyFrom is set, copy assumptions from another scenario + if (copyFrom) { + await client.query(` + INSERT INTO pitch_fm_assumptions (scenario_id, key, label_de, label_en, value, value_type, unit, min_value, max_value, step_size, category, sort_order) + SELECT $1, key, label_de, label_en, value, value_type, unit, min_value, max_value, step_size, category, sort_order + FROM pitch_fm_assumptions WHERE scenario_id = $2 + `, [scenario.rows[0].id, copyFrom]) + } + + return NextResponse.json(scenario.rows[0]) + } finally { + client.release() + } + } catch (error) { + console.error('Create scenario error:', error) + return NextResponse.json({ error: 'Failed to create scenario' }, { status: 500 }) + } +} diff --git a/pitch-deck/components/ChatFAB.tsx b/pitch-deck/components/ChatFAB.tsx new file mode 100644 index 0000000..fe65677 --- /dev/null +++ b/pitch-deck/components/ChatFAB.tsx @@ -0,0 +1,420 @@ +'use client' + +import { useState, useRef, useEffect, useMemo } from 'react' +import { motion, AnimatePresence } from 'framer-motion' +import { X, Send, Bot, User, Sparkles, Maximize2, Minimize2, ArrowRight } from 'lucide-react' +import { ChatMessage, Language, SlideId } from '@/lib/types' +import { t } from '@/lib/i18n' + +interface ChatFABProps { + lang: Language + currentSlide: SlideId + currentIndex: number + visitedSlides: Set + onGoToSlide: (index: number) => void +} + +interface ParsedMessage { + text: string + followUps: string[] + gotos: { index: number; label: string }[] +} + +function parseAgentResponse(content: string, lang: Language): ParsedMessage { + const followUps: string[] = [] + const gotos: { index: number; label: string }[] = [] + + // Split on the follow-up separator — flexible: "---", "- - -", "___", or multiple dashes + const parts = content.split(/\n\s*[-_]{3,}\s*\n/) + let text = parts[0] + + // Parse follow-up questions from second part + if (parts.length > 1) { + const qSection = parts.slice(1).join('\n') + // Match [Q], **[Q]**, or numbered/bulleted question patterns + const qMatches = qSection.matchAll(/(?:\[Q\]|\*\*\[Q\]\*\*)\s*(.+?)(?:\n|$)/g) + for (const m of qMatches) { + const q = m[1].trim().replace(/^\*\*|\*\*$/g, '') + if (q.length > 5) followUps.push(q) + } + + // Fallback: if no [Q] markers found, look for numbered or bulleted questions in the section + if (followUps.length === 0) { + const lineMatches = qSection.matchAll(/(?:^|\n)\s*(?:\d+[\.\)]\s*|[-•]\s*)(.+?\?)\s*$/gm) + for (const m of lineMatches) { + const q = m[1].trim() + if (q.length > 5 && followUps.length < 3) followUps.push(q) + } + } + } + + // Also look for [Q] questions anywhere in the text (sometimes model puts them without ---) + if (followUps.length === 0) { + const inlineMatches = content.matchAll(/\[Q\]\s*(.+?)(?:\n|$)/g) + const inlineQs: string[] = [] + for (const m of inlineMatches) { + inlineQs.push(m[1].trim()) + } + if (inlineQs.length >= 2) { + followUps.push(...inlineQs) + // Remove [Q] lines from main text + text = text.replace(/\n?\s*\[Q\]\s*.+?(?:\n|$)/g, '\n').trim() + } + } + + // Parse GOTO markers from the text + const gotoRegex = /\[GOTO:(\d+)\]/g + let gotoMatch + while ((gotoMatch = gotoRegex.exec(text)) !== null) { + const slideIndex = parseInt(gotoMatch[1]) + gotos.push({ + index: slideIndex, + label: lang === 'de' ? `Zu Slide ${slideIndex + 1} springen` : `Jump to slide ${slideIndex + 1}`, + }) + } + // Remove GOTO markers from visible text + text = text.replace(/\s*\[GOTO:\d+\]/g, '') + + // Clean up trailing reminder instruction that might leak through + text = text.replace(/\n*\(Erinnerung:.*?\)\s*$/s, '').trim() + + return { text: text.trim(), followUps, gotos } +} + +export default function ChatFAB({ lang, currentSlide, currentIndex, visitedSlides, onGoToSlide }: ChatFABProps) { + const i = t(lang) + const [isOpen, setIsOpen] = useState(false) + const [isExpanded, setIsExpanded] = useState(false) + const [messages, setMessages] = useState([]) + const [input, setInput] = useState('') + const [isStreaming, setIsStreaming] = useState(false) + const [parsedResponses, setParsedResponses] = useState>(new Map()) + const messagesEndRef = useRef(null) + const inputRef = useRef(null) + const abortRef = useRef(null) + + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) + }, [messages, parsedResponses]) + + useEffect(() => { + if (isOpen && inputRef.current) { + setTimeout(() => inputRef.current?.focus(), 200) + } + }, [isOpen]) + + // Parse the latest assistant message when streaming ends + const lastAssistantIndex = useMemo(() => { + for (let i = messages.length - 1; i >= 0; i--) { + if (messages[i].role === 'assistant') return i + } + return -1 + }, [messages]) + + useEffect(() => { + if (!isStreaming && lastAssistantIndex >= 0 && !parsedResponses.has(lastAssistantIndex)) { + const msg = messages[lastAssistantIndex] + const parsed = parseAgentResponse(msg.content, lang) + setParsedResponses(prev => new Map(prev).set(lastAssistantIndex, parsed)) + } + }, [isStreaming, lastAssistantIndex, messages, parsedResponses, lang]) + + async function sendMessage(text?: string) { + const message = text || input.trim() + if (!message || isStreaming) return + + setInput('') + setMessages(prev => [...prev, { role: 'user', content: message }]) + setIsStreaming(true) + + abortRef.current = new AbortController() + + try { + const res = await fetch('/api/chat', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + message, + history: messages.slice(-10), + lang, + slideContext: { + currentSlide, + currentIndex, + visitedSlides: Array.from(visitedSlides), + totalSlides: 13, + }, + }), + signal: abortRef.current.signal, + }) + + if (!res.ok) throw new Error(`HTTP ${res.status}`) + + const reader = res.body!.getReader() + const decoder = new TextDecoder() + let content = '' + + setMessages(prev => [...prev, { role: 'assistant', content: '' }]) + + while (true) { + const { done, value } = await reader.read() + if (done) break + + content += decoder.decode(value, { stream: true }) + const currentText = content + setMessages(prev => { + const updated = [...prev] + updated[updated.length - 1] = { role: 'assistant', content: currentText } + return updated + }) + } + } catch (err: unknown) { + if (err instanceof Error && err.name === 'AbortError') return + console.error('Chat error:', err) + setMessages(prev => [ + ...prev, + { role: 'assistant', content: lang === 'de' + ? 'Verbindung fehlgeschlagen. Bitte versuchen Sie es erneut.' + : 'Connection failed. Please try again.' + }, + ]) + } finally { + setIsStreaming(false) + abortRef.current = null + } + } + + function stopGeneration() { + if (abortRef.current) { + abortRef.current.abort() + setIsStreaming(false) + } + } + + const suggestions = i.aiqa.suggestions.slice(0, 3) + + function renderMessageContent(msg: ChatMessage, idx: number) { + const parsed = parsedResponses.get(idx) + const displayText = parsed ? parsed.text : msg.content + + return ( + <> +
{displayText}
+ {isStreaming && idx === messages.length - 1 && msg.role === 'assistant' && ( + + )} + + {/* GOTO Buttons */} + {parsed && parsed.gotos.length > 0 && ( +
+ {parsed.gotos.map((g, gi) => ( + + ))} +
+ )} + + {/* Follow-Up Suggestions */} + {parsed && parsed.followUps.length > 0 && !isStreaming && ( +
+ {parsed.followUps.map((q, qi) => ( + + ))} +
+ )} + + ) + } + + return ( + <> + {/* FAB Button — sits to the left of NavigationFAB */} + + {!isOpen && ( + setIsOpen(true)} + className="fixed bottom-6 right-[5.5rem] z-50 w-14 h-14 rounded-full + bg-indigo-600 hover:bg-indigo-500 text-white + flex items-center justify-center shadow-lg shadow-indigo-600/30 + transition-colors" + aria-label={lang === 'de' ? 'Investor Agent oeffnen' : 'Open Investor Agent'} + > + + + + + + + + )} + + + {/* Chat Panel */} + + {isOpen && ( + + {/* Header */} +
+
+
+ +
+
+ Investor Agent + + {isStreaming + ? (lang === 'de' ? 'antwortet...' : 'responding...') + : (lang === 'de' ? 'online' : 'online') + } + +
+
+
+ + +
+
+ + {/* Messages */} +
+ {messages.length === 0 && ( +
+
+ + {lang === 'de' ? 'Fragen Sie den Investor Agent:' : 'Ask the Investor Agent:'} +
+ {suggestions.map((q, idx) => ( + sendMessage(q)} + className="block w-full text-left px-3 py-2.5 rounded-xl + bg-white/[0.05] border border-white/10 + hover:bg-white/[0.1] transition-colors + text-xs text-white/70 hover:text-white" + > + {q} + + ))} +
+ )} + + {messages.map((msg, idx) => ( +
+ {msg.role === 'assistant' && ( +
+ +
+ )} +
+ {msg.role === 'assistant' ? renderMessageContent(msg, idx) : ( +
{msg.content}
+ )} +
+ {msg.role === 'user' && ( +
+ +
+ )} +
+ ))} +
+
+ + {/* Input */} +
+ {isStreaming && ( + + )} +
+ setInput(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && sendMessage()} + placeholder={lang === 'de' ? 'Frage stellen...' : 'Ask a question...'} + disabled={isStreaming} + className="flex-1 bg-white/[0.06] border border-white/10 rounded-xl px-3.5 py-2.5 + text-xs text-white placeholder-white/30 outline-none + focus:border-indigo-500/50 focus:ring-1 focus:ring-indigo-500/20 + disabled:opacity-50 transition-all" + /> + +
+
+ + )} + + + ) +} diff --git a/pitch-deck/components/NavigationFAB.tsx b/pitch-deck/components/NavigationFAB.tsx index 268bdb4..f600d80 100644 --- a/pitch-deck/components/NavigationFAB.tsx +++ b/pitch-deck/components/NavigationFAB.tsx @@ -83,38 +83,44 @@ export default function NavigationFAB({ {i.slideNames.map((name, idx) => { const isActive = idx === currentIndex const isVisited = visitedSlides.has(idx) - const isAI = idx === totalSlides - 1 + const isAI = idx === 13 return ( - + +
) })} diff --git a/pitch-deck/components/PitchDeck.tsx b/pitch-deck/components/PitchDeck.tsx index 78f831c..b888c2c 100644 --- a/pitch-deck/components/PitchDeck.tsx +++ b/pitch-deck/components/PitchDeck.tsx @@ -11,6 +11,7 @@ import ParticleBackground from './ParticleBackground' import ProgressBar from './ProgressBar' import NavigationControls from './NavigationControls' import NavigationFAB from './NavigationFAB' +import ChatFAB from './ChatFAB' import SlideOverview from './SlideOverview' import SlideContainer from './SlideContainer' @@ -24,9 +25,24 @@ import BusinessModelSlide from './slides/BusinessModelSlide' import TractionSlide from './slides/TractionSlide' import CompetitionSlide from './slides/CompetitionSlide' import TeamSlide from './slides/TeamSlide' +import TechnologySlide from './slides/TechnologySlide' import FinancialsSlide from './slides/FinancialsSlide' import TheAskSlide from './slides/TheAskSlide' import AIQASlide from './slides/AIQASlide' +import AppendixSlide from './slides/AppendixSlide' +import AnnexInfraSlide from './slides/AnnexInfraSlide' +import AnnexAIStackSlide from './slides/AnnexAIStackSlide' +import AnnexRAGSlide from './slides/AnnexRAGSlide' +import AnnexSecuritySlide from './slides/AnnexSecuritySlide' +import AnnexDevOpsSlide from './slides/AnnexDevOpsSlide' +import AnnexAgentArchSlide from './slides/AnnexAgentArchSlide' +import AnnexAgentRAGSlide from './slides/AnnexAgentRAGSlide' +import AnnexAgentWorkflowSlide from './slides/AnnexAgentWorkflowSlide' +import AnnexUSPOverviewSlide from './slides/AnnexUSPOverviewSlide' +import AnnexUSPComparisonSlide from './slides/AnnexUSPComparisonSlide' +import AnnexUSPMoatSlide from './slides/AnnexUSPMoatSlide' +import AnnexRoadmap2027Slide from './slides/AnnexRoadmap2027Slide' +import AnnexRoadmap2028Slide from './slides/AnnexRoadmap2028Slide' interface PitchDeckProps { lang: Language @@ -109,12 +125,42 @@ export default function PitchDeck({ lang, onToggleLanguage }: PitchDeckProps) { return case 'team': return + case 'technology': + return case 'financials': - return + return case 'the-ask': return case 'ai-qa': return + case 'appendix': + return + case 'annex-infra': + return + case 'annex-ai-stack': + return + case 'annex-rag': + return + case 'annex-security': + return + case 'annex-devops': + return + case 'annex-agent-arch': + return + case 'annex-agent-rag': + return + case 'annex-agent-workflow': + return + case 'annex-usp-overview': + return + case 'annex-usp-comparison': + return + case 'annex-usp-moat': + return + case 'annex-roadmap-2027': + return + case 'annex-roadmap-2028': + return default: return null } @@ -138,6 +184,14 @@ export default function PitchDeck({ lang, onToggleLanguage }: PitchDeckProps) { total={nav.totalSlides} /> + + e.stopPropagation()} > {i.slideNames.map((name, idx) => ( - onGoToSlide(idx)} - className={` - aspect-video rounded-xl p-4 text-left - border transition-all - ${idx === currentIndex - ? 'bg-indigo-500/20 border-indigo-500 shadow-lg shadow-indigo-500/20' - : 'bg-white/[0.05] border-white/10 hover:bg-white/[0.1] hover:border-white/20' - } - `} - > - {idx + 1} - - {name} - - + + {idx === 14 && ( +
+ Appendix +
+
+ )} + onGoToSlide(idx)} + className={` + aspect-video rounded-xl p-4 text-left + border transition-all + ${idx === currentIndex + ? 'bg-indigo-500/20 border-indigo-500 shadow-lg shadow-indigo-500/20' + : 'bg-white/[0.05] border-white/10 hover:bg-white/[0.1] hover:border-white/20' + } + `} + > + {idx + 1} + + {name} + + + ))} diff --git a/pitch-deck/components/slides/AnnexAIStackSlide.tsx b/pitch-deck/components/slides/AnnexAIStackSlide.tsx new file mode 100644 index 0000000..535d16d --- /dev/null +++ b/pitch-deck/components/slides/AnnexAIStackSlide.tsx @@ -0,0 +1,200 @@ +'use client' + +import { motion } from 'framer-motion' +import { Language } from '@/lib/types' +import GradientText from '../ui/GradientText' +import FadeInView from '../ui/FadeInView' +import { Brain, Cpu, Zap, ArrowRight } from 'lucide-react' + +interface AnnexAIStackSlideProps { + lang: Language +} + +export default function AnnexAIStackSlide({ lang }: AnnexAIStackSlideProps) { + const roadmapSteps = [ + { + year: '2026', + model: 'Qwen 32B', + label: lang === 'de' ? 'Foundation' : 'Foundation', + }, + { + year: '2027', + model: 'Multi-Model 70B', + label: lang === 'de' ? 'Skalierung' : 'Scaling', + }, + { + year: '2028', + model: 'Fine-Tuned 100B+', + label: 'Enterprise', + }, + { + year: '2030', + model: '1000B Agent Network', + label: lang === 'de' ? 'Volle Autonomie' : 'Full Autonomy', + }, + ] + + return ( +
+
+ +
+

+ + {lang === 'de' ? 'KI-Stack & LLM-Architektur' : 'AI Stack & LLM Architecture'} + +

+

+ {lang === 'de' ? 'Von 32B zu 1000B Parametern' : 'From 32B to 1000B parameters'} +

+
+
+ + {/* LLM Evolution Roadmap */} + +
+ {roadmapSteps.map((step, index) => ( +
+ +
{step.year}
+
{step.model}
+
{step.label}
+
+ + {index < roadmapSteps.length - 1 && ( + + + + )} +
+ ))} +
+
+ + {/* Bottom Section: 2-column grid */} +
+ {/* Left Card - Multi-Model Router */} + +
+
+ +
+

+ {lang === 'de' ? 'Multi-Model Router' : 'Multi-Model Router'} +

+
+
    +
  • + + + {lang === 'de' + ? 'Automatische Modellauswahl basierend auf Aufgabe' + : 'Automatic model selection based on task'} + +
  • +
  • + + + {lang === 'de' + ? 'Einfache Anfragen → kleineres Modell (schnell, günstig)' + : 'Simple queries → smaller model (fast, cheap)'} + +
  • +
  • + + + {lang === 'de' + ? 'Komplexe Analysen → größeres Modell (präzise)' + : 'Complex analysis → larger model (accurate)'} + +
  • +
  • + + + {lang === 'de' + ? 'Kostenoptimierung: 60% günstiger als immer großes Modell' + : 'Cost optimization: 60% cheaper than always using large model'} + +
  • +
+
+ + {/* Right Card - Inference Optimization */} + +
+
+ +
+

+ {lang === 'de' ? 'Inference-Optimierung' : 'Inference Optimization'} +

+
+
    +
  • + + + {lang === 'de' + ? 'Apple Neural Engine Beschleunigung' + : 'Apple Neural Engine acceleration'} + +
  • +
  • + + + {lang === 'de' + ? 'Quantisierung (INT4/INT8) für schnellere Inferenz' + : 'Quantization (INT4/INT8) for faster inference'} + +
  • +
  • + + Context window: 32k tokens +
  • +
  • + + + {lang === 'de' + ? 'Durchschn. Antwortzeit: <2s für einfache Anfragen' + : 'Avg response time: <2s for simple queries'} + +
  • +
  • + + + {lang === 'de' + ? 'GPU vs Apple Silicon: Apple 3x besseres Preis-/Leistungsverhältnis für Inferenz' + : 'GPU vs Apple Silicon: Apple 3x better price/performance for inference'} + +
  • +
+
+
+
+
+ ) +} diff --git a/pitch-deck/components/slides/AnnexAgentArchSlide.tsx b/pitch-deck/components/slides/AnnexAgentArchSlide.tsx new file mode 100644 index 0000000..d0ad073 --- /dev/null +++ b/pitch-deck/components/slides/AnnexAgentArchSlide.tsx @@ -0,0 +1,147 @@ +'use client' + +import { motion } from 'framer-motion' +import { Language } from '@/lib/types' +import GradientText from '../ui/GradientText' +import FadeInView from '../ui/FadeInView' +import { Brain, GraduationCap, ClipboardCheck, Scale, Bell, Database, MessageSquare, Archive } from 'lucide-react' + +interface AnnexAgentArchSlideProps { + lang: Language +} + +export default function AnnexAgentArchSlide({ lang }: AnnexAgentArchSlideProps) { + const agents = [ + { + name: lang === 'de' ? 'Tutor Agent' : 'Tutor Agent', + icon: GraduationCap, + color: 'text-blue-400', + position: 'col-start-1 row-start-1' + }, + { + name: lang === 'de' ? 'Grader Agent' : 'Grader Agent', + icon: ClipboardCheck, + color: 'text-green-400', + position: 'col-start-3 row-start-1' + }, + { + name: lang === 'de' ? 'Quality Judge' : 'Quality Judge', + icon: Scale, + color: 'text-purple-400', + position: 'col-start-1 row-start-3' + }, + { + name: lang === 'de' ? 'Alert Agent' : 'Alert Agent', + icon: Bell, + color: 'text-amber-400', + position: 'col-start-3 row-start-3' + } + ] + + const features = [ + { + icon: Archive, + title: lang === 'de' ? 'Session Management' : 'Session Management', + desc: lang === 'de' ? 'Persistent state, auto-recovery, checkpoints' : 'Persistent state, auto-recovery, checkpoints' + }, + { + icon: Database, + title: lang === 'de' ? 'Shared Brain' : 'Shared Brain', + desc: lang === 'de' ? 'Long-term memory, knowledge graph, context sharing' : 'Long-term memory, knowledge graph, context sharing' + }, + { + icon: MessageSquare, + title: lang === 'de' ? 'Message Bus' : 'Message Bus', + desc: lang === 'de' ? 'Real-time inter-agent communication, priority routing' : 'Real-time inter-agent communication, priority routing' + } + ] + + return ( +
+ +
+

+ + {lang === 'de' ? 'Compliance Agent Architektur' : 'Compliance Agent Architecture'} + +

+

+ {lang === 'de' ? 'Multi-Agent System für autonome Compliance' : 'Multi-agent system for autonomous compliance'} +

+
+
+ + {/* Agent Architecture Diagram */} + +
+ {/* Top agents */} + {agents.slice(0, 2).map((agent, idx) => ( + + +

{agent.name}

+
+ ))} + + {/* Center Orchestrator */} + + +

Orchestrator

+

+ {lang === 'de' ? 'Koordiniert alle Agents' : 'Coordinates all agents'} +

+ + {/* Connection lines */} +
+
+
+
+ + + {/* Bottom agents */} + {agents.slice(2, 4).map((agent, idx) => ( + + +

{agent.name}

+
+ ))} +
+ + + {/* Features */} + +
+ {features.map((feature, idx) => ( + + +

{feature.title}

+

{feature.desc}

+
+ ))} +
+
+
+ ) +} diff --git a/pitch-deck/components/slides/AnnexAgentRAGSlide.tsx b/pitch-deck/components/slides/AnnexAgentRAGSlide.tsx new file mode 100644 index 0000000..d68c9fd --- /dev/null +++ b/pitch-deck/components/slides/AnnexAgentRAGSlide.tsx @@ -0,0 +1,145 @@ +'use client' + +import { motion } from 'framer-motion' +import { Language } from '@/lib/types' +import GradientText from '../ui/GradientText' +import FadeInView from '../ui/FadeInView' +import { FileText, Layers, Tag, RefreshCw } from 'lucide-react' + +interface AnnexAgentRAGSlideProps { + lang: Language +} + +export default function AnnexAgentRAGSlide({ lang }: AnnexAgentRAGSlideProps) { + const regulations = [ + { + name: 'DSGVO', + articles: lang === 'de' ? '99 Artikel' : '99 Articles', + recitals: lang === 'de' ? '173 Erwägungsgründe' : '173 Recitals', + color: 'from-blue-500/20 to-blue-600/20', + border: 'border-blue-400/30' + }, + { + name: 'AI Act', + articles: lang === 'de' ? '113 Artikel' : '113 Articles', + recitals: lang === 'de' ? '180 Erwägungsgründe' : '180 Recitals', + color: 'from-purple-500/20 to-purple-600/20', + border: 'border-purple-400/30' + }, + { + name: 'NIS2', + articles: lang === 'de' ? '46 Artikel' : '46 Articles', + recitals: lang === 'de' ? '144 Erwägungsgründe' : '144 Recitals', + color: 'from-green-500/20 to-green-600/20', + border: 'border-green-400/30' + } + ] + + const chunkingDetails = [ + { + icon: Layers, + label: lang === 'de' ? 'Chunk-Größe' : 'Chunk size', + value: lang === 'de' ? '512 Tokens mit 64 Token Überlappung' : '512 tokens with 64 token overlap' + }, + { + icon: Tag, + label: lang === 'de' ? 'Metadaten-Tagging' : 'Metadata tagging', + value: lang === 'de' ? 'Artikelnummer, Abschnitt, Verordnung, Sprache' : 'Article number, section, regulation, language' + }, + { + icon: FileText, + label: lang === 'de' ? 'Hierarchisches Chunking' : 'Hierarchical chunking', + value: lang === 'de' ? 'Artikel → Absatz → Satz' : 'Article → Paragraph → Sentence' + }, + { + icon: RefreshCw, + label: lang === 'de' ? 'Update-Workflow' : 'Update workflow', + value: lang === 'de' ? 'Automatische Re-Indizierung bei Gesetzesänderungen' : 'Automatic re-indexing on law changes' + } + ] + + return ( +
+ +
+

+ + {lang === 'de' ? 'Rechtsdokumente im RAG' : 'Legal Documents in RAG'} + +

+

+ {lang === 'de' ? 'Wie Gesetze in die KI gelangen' : 'How laws enter the AI'} +

+
+
+ + {/* Regulations */} + +
+ {regulations.map((reg, idx) => ( + +

{reg.name}

+
+
+

{reg.articles}

+
+
+

{reg.recitals}

+
+
+
+ ))} +
+
+ + {/* Chunking Strategy */} + + +

+ {lang === 'de' ? 'Chunking-Strategie' : 'Chunking Strategy'} +

+ +
+ {chunkingDetails.map((detail, idx) => ( + + +
+

{detail.label}

+

{detail.value}

+
+
+ ))} +
+ + +

+ {lang === 'de' ? 'Aktuell: 15.000+ Chunks indiziert' : 'Currently: 15,000+ chunks indexed'} +

+
+
+
+
+ ) +} diff --git a/pitch-deck/components/slides/AnnexAgentWorkflowSlide.tsx b/pitch-deck/components/slides/AnnexAgentWorkflowSlide.tsx new file mode 100644 index 0000000..10353b1 --- /dev/null +++ b/pitch-deck/components/slides/AnnexAgentWorkflowSlide.tsx @@ -0,0 +1,162 @@ +'use client' + +import { motion } from 'framer-motion' +import { Language } from '@/lib/types' +import GradientText from '../ui/GradientText' +import FadeInView from '../ui/FadeInView' +import { Upload, ScanLine, Search, AlertTriangle, FileText, UserCheck, ShieldCheck } from 'lucide-react' + +interface AnnexAgentWorkflowSlideProps { + lang: Language +} + +export default function AnnexAgentWorkflowSlide({ lang }: AnnexAgentWorkflowSlideProps) { + const workflowSteps = [ + { + icon: Upload, + title: 'Upload', + desc: lang === 'de' ? 'Dokument-Upload (Verträge, Richtlinien, Aufzeichnungen)' : 'Document upload (contracts, policies, records)', + color: 'text-blue-400' + }, + { + icon: ScanLine, + title: 'OCR', + desc: lang === 'de' ? 'Automatische Textextraktion (PaddleOCR)' : 'Automatic text extraction (PaddleOCR)', + color: 'text-purple-400' + }, + { + icon: Search, + title: lang === 'de' ? 'Analyse' : 'Analysis', + desc: lang === 'de' ? 'KI analysiert gegen DSGVO/AI Act/NIS2' : 'AI analyzes against DSGVO/AI Act/NIS2', + color: 'text-indigo-400' + }, + { + icon: AlertTriangle, + title: lang === 'de' ? 'Risikobewertung' : 'Risk Assessment', + desc: lang === 'de' ? 'Automatische Risikoklassifizierung (niedrig/mittel/hoch/kritisch)' : 'Automatic risk classification (low/medium/high/critical)', + color: 'text-amber-400' + }, + { + icon: FileText, + title: lang === 'de' ? 'Report-Generierung' : 'Report Generation', + desc: lang === 'de' ? 'DSFA, VVT, TOM Dokumente automatisch erstellt' : 'DSFA, VVT, TOM documents auto-generated', + color: 'text-green-400' + }, + { + icon: UserCheck, + title: lang === 'de' ? 'Human Review' : 'Human Review', + desc: lang === 'de' ? 'Experte prüft und genehmigt' : 'Expert reviews and approves', + color: 'text-cyan-400' + } + ] + + const humanInLoopFeatures = [ + lang === 'de' ? 'KI schlägt vor, Mensch entscheidet' : 'AI suggests, human decides', + lang === 'de' ? 'Kritische Entscheidungen erfordern Genehmigung' : 'Critical decisions require approval', + lang === 'de' ? 'Audit-Trail für alle Aktionen' : 'Audit trail for all actions', + lang === 'de' ? 'Compliance Officer immer in Kontrolle' : 'Compliance officer always in control' + ] + + return ( +
+ +
+

+ + {lang === 'de' ? 'Autonomer Compliance-Workflow' : 'Autonomous Compliance Workflow'} + +

+

+ {lang === 'de' ? 'End-to-End: Vom Upload bis zum fertigen Report' : 'End-to-end: From upload to finished report'} +

+
+
+ +
+ {/* Workflow Steps */} +
+ +
+ {workflowSteps.map((step, idx) => ( + +
+
+ +
+
+
+ + {lang === 'de' ? 'SCHRITT' : 'STEP'} {idx + 1} + +
+

{step.title}

+

{step.desc}

+
+
+ + {/* Connection line to next step */} + {idx < workflowSteps.length - 1 && ( +
+ )} + + ))} +
+ +
+ + {/* Human-in-the-Loop */} +
+ + +
+ +

+ {lang === 'de' ? 'Human-in-the-Loop' : 'Human-in-the-Loop'} +

+
+ +
    + {humanInLoopFeatures.map((feature, idx) => ( + +
    +

    {feature}

    + + ))} +
+ + +

+ {lang === 'de' + ? 'KI automatisiert Routine, Mensch behält Kontrolle über kritische Entscheidungen' + : 'AI automates routine, human retains control over critical decisions'} +

+
+
+
+
+
+
+ ) +} diff --git a/pitch-deck/components/slides/AnnexDevOpsSlide.tsx b/pitch-deck/components/slides/AnnexDevOpsSlide.tsx new file mode 100644 index 0000000..5b41327 --- /dev/null +++ b/pitch-deck/components/slides/AnnexDevOpsSlide.tsx @@ -0,0 +1,146 @@ +'use client' + +import { motion } from 'framer-motion' +import { GitBranch, Code, Hammer, TestTube, Rocket, ArrowRight, Container, GitMerge, Activity, Bell } from 'lucide-react' +import { Language } from '@/lib/types' +import GradientText from '../ui/GradientText' +import FadeInView from '../ui/FadeInView' + +interface AnnexDevOpsSlideProps { + lang: Language +} + +export default function AnnexDevOpsSlide({ lang }: AnnexDevOpsSlideProps) { + const pipelineSteps = [ + { name: 'Code', icon: GitBranch, color: 'from-blue-400 to-blue-600' }, + { name: 'Gitea', icon: Code, color: 'from-green-400 to-green-600' }, + { name: 'Woodpecker CI', icon: Hammer, color: 'from-orange-400 to-orange-600' }, + { name: 'Build', icon: Container, color: 'from-purple-400 to-purple-600' }, + { name: 'Test', icon: TestTube, color: 'from-pink-400 to-pink-600' }, + { name: 'Deploy', icon: Rocket, color: 'from-cyan-400 to-cyan-600' } + ] + + const devOpsTools = [ + { + title: 'Woodpecker CI', + icon: Hammer, + accentColor: 'from-orange-400 to-orange-600', + items: [ + lang === 'de' ? 'Open-Source CI/CD (Apache 2.0)' : 'Open-source CI/CD (Apache 2.0)', + lang === 'de' ? 'Docker-native Builds' : 'Docker-native builds', + lang === 'de' ? 'Automatische Tests bei jedem Commit' : 'Automatic testing on every commit', + lang === 'de' ? 'Build-Zeit: ~3 min' : 'Build time: ~3 min' + ] + }, + { + title: 'Docker Compose', + icon: Container, + accentColor: 'from-blue-400 to-blue-600', + items: [ + lang === 'de' ? '30+ Services orchestriert' : '30+ services orchestrated', + lang === 'de' ? 'Health Checks für alle Container' : 'Health checks for all containers', + lang === 'de' ? 'Automatischer Neustart bei Fehler' : 'Automatic restart on failure', + lang === 'de' ? 'Ressourcenlimits konfiguriert' : 'Resource limits configured' + ] + }, + { + title: 'Gitea', + icon: GitMerge, + accentColor: 'from-green-400 to-green-600', + items: [ + lang === 'de' ? 'Self-hosted Git Server' : 'Self-hosted Git server', + lang === 'de' ? 'Keine GitHub/GitLab Abhängigkeit' : 'No GitHub/GitLab dependency', + lang === 'de' ? 'Issue Tracking integriert' : 'Issue tracking integrated', + lang === 'de' ? 'Mirror zu externem Backup' : 'Mirror to external backup' + ] + }, + { + title: 'Monitoring', + icon: Activity, + accentColor: 'from-purple-400 to-purple-600', + items: [ + lang === 'de' ? 'Night Scheduler für Auto-Shutdown' : 'Night Scheduler for auto-shutdown', + lang === 'de' ? 'Container Health Monitoring' : 'Container health monitoring', + lang === 'de' ? 'Log-Aggregation' : 'Log aggregation', + lang === 'de' ? 'Alerting bei Fehlern' : 'Alerting on failures' + ] + } + ] + + return ( +
+ +
+

+ + {lang === 'de' ? 'DevOps & CI/CD' : 'DevOps & CI/CD'} + +

+

+ {lang === 'de' + ? 'Automatisierte Entwicklungs- und Deployment-Pipeline' + : 'Automated development and deployment pipeline'} +

+
+
+ + {/* CI/CD Pipeline Visualization */} + +

+ {lang === 'de' ? 'CI/CD Pipeline' : 'CI/CD Pipeline'} +

+
+ {pipelineSteps.map((step, index) => ( +
+ +
+ +
+ {step.name} +
+ {index < pipelineSteps.length - 1 && ( + + )} +
+ ))} +
+
+ + {/* DevOps Tools Grid */} +
+ {devOpsTools.map((tool, index) => ( + +
+ +
+

{tool.title}

+
    + {tool.items.map((item, itemIndex) => ( +
  • + + {item} +
  • + ))} +
+
+ ))} +
+
+ ) +} diff --git a/pitch-deck/components/slides/AnnexInfraSlide.tsx b/pitch-deck/components/slides/AnnexInfraSlide.tsx new file mode 100644 index 0000000..4f66024 --- /dev/null +++ b/pitch-deck/components/slides/AnnexInfraSlide.tsx @@ -0,0 +1,97 @@ +'use client' + +import { motion } from 'framer-motion' +import { Language } from '@/lib/types' +import GradientText from '../ui/GradientText' +import FadeInView from '../ui/FadeInView' +import { Server, Container, ShieldOff, Calculator } from 'lucide-react' + +interface AnnexInfraSlideProps { + lang: Language +} + +export default function AnnexInfraSlide({ lang }: AnnexInfraSlideProps) { + const title = lang === 'de' ? 'Infrastruktur & Self-Hosting' : 'Infrastructure & Self-Hosting' + const subtitle = lang === 'de' ? 'Warum wir auf eigene Hardware setzen' : 'Why we rely on our own hardware' + + const sections = [ + { + icon: Server, + title: 'Apple Silicon Hardware', + items: [ + 'Mac Mini M2 (Starter), Mac Mini M4 Pro (Business), Mac Studio M4 Max (Enterprise)', + 'Unified Memory: 16GB / 48GB / 128GB', + 'Neural Engine for ML inference' + ] + }, + { + icon: Container, + title: 'Docker Architecture', + items: [ + '30+ Microservices in Docker Compose', + 'Automatic health monitoring', + 'Zero-downtime updates via rolling restarts' + ] + }, + { + icon: ShieldOff, + title: lang === 'de' ? 'Why No Cloud?' : 'Why No Cloud?', + items: [ + lang === 'de' ? 'Data sovereignty: No data leaves the company' : 'Data sovereignty: No data leaves the company', + lang === 'de' ? 'No recurring cloud costs (AWS/Azure)' : 'No recurring cloud costs (AWS/Azure)', + lang === 'de' ? 'BSI-TR-03161 compliance easier on-premise' : 'BSI-TR-03161 compliance easier on-premise', + lang === 'de' ? 'DSGVO Art. 28: No third-party processors' : 'DSGVO Art. 28: No third-party processors' + ] + }, + { + icon: Calculator, + title: 'Cost Comparison', + items: [ + lang === 'de' ? 'Self-Hosted: EUR 599 hardware + EUR 149/mo' : 'Self-Hosted: EUR 599 hardware + EUR 149/mo', + lang === 'de' ? 'Cloud equivalent: EUR 800-1200/mo (AWS)' : 'Cloud equivalent: EUR 800-1200/mo (AWS)', + lang === 'de' ? 'Break-even after 4-5 months' : 'Break-even after 4-5 months', + lang === 'de' ? '70% cheaper over 3 years' : '70% cheaper over 3 years' + ] + } + ] + + return ( +
+ +

+ {title} +

+

{subtitle}

+
+ +
+ {sections.map((section, idx) => { + const Icon = section.icon + return ( + +
+
+ +
+

{section.title}

+
+
    + {section.items.map((item, itemIdx) => ( +
  • + {item} +
  • + ))} +
+
+ ) + })} +
+
+ ) +} diff --git a/pitch-deck/components/slides/AnnexRAGSlide.tsx b/pitch-deck/components/slides/AnnexRAGSlide.tsx new file mode 100644 index 0000000..7eea255 --- /dev/null +++ b/pitch-deck/components/slides/AnnexRAGSlide.tsx @@ -0,0 +1,204 @@ +'use client' + +import { motion } from 'framer-motion' +import { Database, FileSearch, Layers, BarChart3, FileUp, ScanLine, Brain } from 'lucide-react' +import { Language } from '@/lib/types' +import GradientText from '../ui/GradientText' +import FadeInView from '../ui/FadeInView' + +interface AnnexRAGSlideProps { + lang: Language +} + +export default function AnnexRAGSlide({ lang }: AnnexRAGSlideProps) { + const pipelineSteps = [ + { + icon: FileUp, + labelDE: 'Dokument Upload', + labelEN: 'Document Upload' + }, + { + icon: ScanLine, + labelDE: 'OCR Verarbeitung', + labelEN: 'OCR Processing' + }, + { + icon: Layers, + labelDE: 'Chunking', + labelEN: 'Chunking' + }, + { + icon: Brain, + labelDE: 'Embedding', + labelEN: 'Embedding' + }, + { + icon: Database, + labelDE: 'Vector Store', + labelEN: 'Vector Store' + } + ] + + const features = [ + { + titleDE: 'Qdrant Vector DB', + titleEN: 'Qdrant Vector DB', + icon: Database, + pointsDE: [ + 'High-performance vector search', + 'Hybrid search: semantisch + keyword', + 'Filter nach Metadaten (Typ, Datum, Vorschrift)', + '99.2% Retrieval-Genauigkeit' + ], + pointsEN: [ + 'High-performance vector search', + 'Hybrid search: semantic + keyword', + 'Filters by metadata (document type, date, regulation)', + '99.2% retrieval accuracy' + ] + }, + { + titleDE: 'Embedding Pipeline', + titleEN: 'Embedding Pipeline', + icon: FileSearch, + pointsDE: [ + 'PaddleOCR für Dokumentenscanning', + 'Chunk-Größe: 512 Tokens, Overlap: 64', + 'Sentence-Transformers für Embedding', + 'Automatische Spracherkennung' + ], + pointsEN: [ + 'PaddleOCR for document scanning', + 'Chunk size: 512 tokens, overlap: 64', + 'Sentence-Transformers for embedding', + 'Automatic language detection' + ] + }, + { + titleDE: 'Hybrid Search', + titleEN: 'Hybrid Search', + icon: Layers, + pointsDE: [ + 'Kombiniert dense + sparse Vektoren', + 'BM25 für Keyword-Matching', + 'Cosine-Similarity für semantische Suche', + 'Re-Ranking für optimale Ergebnisse' + ], + pointsEN: [ + 'Combines dense + sparse vectors', + 'BM25 for keyword matching', + 'Cosine similarity for semantic search', + 'Re-ranking for optimal results' + ] + }, + { + titleDE: 'Retrieval Metrics', + titleEN: 'Retrieval Metrics', + icon: BarChart3, + pointsDE: [ + 'Precision@5: 94.3%', + 'Recall@10: 97.1%', + 'MRR: 0.89', + 'Avg. Latenz: 120ms' + ], + pointsEN: [ + 'Precision@5: 94.3%', + 'Recall@10: 97.1%', + 'MRR: 0.89', + 'Avg latency: 120ms' + ] + } + ] + + return ( +
+ {/* Header */} + +
+

+ + {lang === 'de' ? 'RAG Pipeline & Vektordatenbank' : 'RAG Pipeline & Vector Database'} + +

+

+ {lang === 'de' + ? 'Präzise Antworten durch intelligentes Retrieval' + : 'Precise answers through intelligent retrieval'} +

+
+
+ + {/* Pipeline Visualization */} + +
+
+ {pipelineSteps.map((step, index) => { + const Icon = step.icon + return ( +
+ +
+ +

+ {lang === 'de' ? step.labelDE : step.labelEN} +

+
+
+ + {index < pipelineSteps.length - 1 && ( + +
+ + )} +
+ ) + })} +
+
+ + + {/* Feature Grid */} +
+ {features.map((feature, index) => { + const Icon = feature.icon + return ( + +
+
+ +
+

+ {lang === 'de' ? feature.titleDE : feature.titleEN} +

+
+
    + {(lang === 'de' ? feature.pointsDE : feature.pointsEN).map((point, idx) => ( +
  • + + {point} +
  • + ))} +
+
+ ) + })} +
+
+ ) +} diff --git a/pitch-deck/components/slides/AnnexRoadmap2027Slide.tsx b/pitch-deck/components/slides/AnnexRoadmap2027Slide.tsx new file mode 100644 index 0000000..ab5eafe --- /dev/null +++ b/pitch-deck/components/slides/AnnexRoadmap2027Slide.tsx @@ -0,0 +1,142 @@ +'use client' + +import { motion } from 'framer-motion' +import { Language } from '@/lib/types' +import GradientText from '../ui/GradientText' +import FadeInView from '../ui/FadeInView' +import { Users, Target, TrendingUp, Award } from 'lucide-react' + +interface AnnexRoadmap2027SlideProps { + lang: Language +} + +export default function AnnexRoadmap2027Slide({ lang }: AnnexRoadmap2027SlideProps) { + const quarters = [ + { + quarter: 'Q1 2027', + accent: 'from-blue-500 to-cyan-500', + items: [ + lang === 'de' ? 'Multi-Model Router Launch' : 'Multi-Model Router launch', + lang === 'de' ? 'RAG 2.0 mit Advanced Retrieval' : 'RAG 2.0 with advanced retrieval', + lang === 'de' ? '15 zahlende Kunden' : '15 paying customers', + lang === 'de' ? 'Team: 3 Personen' : 'Team: 3 people' + ] + }, + { + quarter: 'Q2 2027', + accent: 'from-indigo-500 to-blue-500', + items: [ + lang === 'de' ? 'Auto Compliance-Scan Feature' : 'Auto Compliance-Scan feature', + lang === 'de' ? 'Kunden Self-Service Portal' : 'Customer self-service portal', + lang === 'de' ? '25 zahlende Kunden' : '25 paying customers', + lang === 'de' ? 'Erster Enterprise Pilot' : 'First enterprise pilot' + ] + }, + { + quarter: 'Q3 2027', + accent: 'from-purple-500 to-indigo-500', + items: [ + lang === 'de' ? 'Echtzeit-Monitoring Dashboard' : 'Real-time monitoring dashboard', + lang === 'de' ? 'API für Partner-Integrationen' : 'API for partner integrations', + lang === 'de' ? '35 zahlende Kunden' : '35 paying customers', + lang === 'de' ? 'Team: 4 Personen' : 'Team: 4 people' + ] + }, + { + quarter: 'Q4 2027', + accent: 'from-violet-500 to-purple-500', + items: [ + lang === 'de' ? 'Series A Vorbereitung' : 'Series A preparation', + lang === 'de' ? 'Due Diligence Package bereit' : 'Due diligence package ready', + lang === 'de' ? '50+ zahlende Kunden' : '50+ paying customers', + 'ARR: EUR 90k' + ] + } + ] + + const metrics = [ + { + icon: Target, + label: lang === 'de' ? 'Zielkunden' : 'Target Customers', + value: '50+' + }, + { + icon: TrendingUp, + label: lang === 'de' ? 'Ziel ARR' : 'Target ARR', + value: 'EUR 90k' + }, + { + icon: Users, + label: lang === 'de' ? 'Team-Größe' : 'Team Size', + value: '4' + }, + { + icon: Award, + label: lang === 'de' ? 'Key Milestone' : 'Key Milestone', + value: 'Series A Ready' + } + ] + + return ( +
+ +
+

+ + {lang === 'de' ? 'Roadmap 2027' : 'Roadmap 2027'} + +

+

+ {lang === 'de' + ? 'Product-Market Fit & Series A Vorbereitung' + : 'Product-Market Fit & Series A Preparation'} +

+
+
+ +
+ {quarters.map((quarter, index) => ( + +
+ {quarter.quarter} +
+
    + {quarter.items.map((item, i) => ( +
  • + + {item} +
  • + ))} +
+
+ ))} +
+ + +
+ {metrics.map((metric, index) => { + const Icon = metric.icon + return ( +
+ +

{metric.label}

+

{metric.value}

+
+ ) + })} +
+
+
+ ) +} diff --git a/pitch-deck/components/slides/AnnexRoadmap2028Slide.tsx b/pitch-deck/components/slides/AnnexRoadmap2028Slide.tsx new file mode 100644 index 0000000..75cd136 --- /dev/null +++ b/pitch-deck/components/slides/AnnexRoadmap2028Slide.tsx @@ -0,0 +1,142 @@ +'use client' + +import { motion } from 'framer-motion' +import { Language } from '@/lib/types' +import GradientText from '../ui/GradientText' +import FadeInView from '../ui/FadeInView' +import { Users, Target, TrendingUp, Globe } from 'lucide-react' + +interface AnnexRoadmap2028SlideProps { + lang: Language +} + +export default function AnnexRoadmap2028Slide({ lang }: AnnexRoadmap2028SlideProps) { + const quarters = [ + { + quarter: 'Q1 2028', + accent: 'from-blue-500 to-cyan-500', + items: [ + lang === 'de' ? 'Federated Learning über Kunden' : 'Federated Learning across customers', + lang === 'de' ? 'Multi-Tenant Architektur' : 'Multi-tenant architecture', + lang === 'de' ? '80 zahlende Kunden' : '80 paying customers', + lang === 'de' ? 'Series A Abschluss (EUR 2-3M)' : 'Series A close (EUR 2-3M)' + ] + }, + { + quarter: 'Q2 2028', + accent: 'from-indigo-500 to-blue-500', + items: [ + lang === 'de' ? 'API Marketplace Launch' : 'API Marketplace launch', + lang === 'de' ? 'Partner Ökosystem Start' : 'Partner ecosystem start', + lang === 'de' ? '120 zahlende Kunden' : '120 paying customers', + lang === 'de' ? 'Team: 6 Personen' : 'Team: 6 people' + ] + }, + { + quarter: 'Q3 2028', + accent: 'from-purple-500 to-indigo-500', + items: [ + lang === 'de' ? 'Enterprise Tier Launch' : 'Enterprise tier launch', + lang === 'de' ? 'DACH Expansion (Österreich, Schweiz)' : 'DACH expansion (Austria, Switzerland)', + lang === 'de' ? '160 zahlende Kunden' : '160 paying customers', + lang === 'de' ? 'Team: 7 Personen' : 'Team: 7 people' + ] + }, + { + quarter: 'Q4 2028', + accent: 'from-violet-500 to-purple-500', + items: [ + lang === 'de' ? 'Volle DACH Abdeckung' : 'Full DACH coverage', + lang === 'de' ? '200+ zahlende Kunden' : '200+ paying customers', + 'ARR: EUR 360k', + lang === 'de' ? 'Team: 8 Personen' : 'Team: 8 people' + ] + } + ] + + const metrics = [ + { + icon: Target, + label: lang === 'de' ? 'Zielkunden' : 'Target Customers', + value: '200+' + }, + { + icon: TrendingUp, + label: lang === 'de' ? 'Ziel ARR' : 'Target ARR', + value: 'EUR 360k' + }, + { + icon: Users, + label: lang === 'de' ? 'Team-Größe' : 'Team Size', + value: '8' + }, + { + icon: Globe, + label: lang === 'de' ? 'Key Milestone' : 'Key Milestone', + value: lang === 'de' ? 'DACH Expansion' : 'DACH Expansion' + } + ] + + return ( +
+ +
+

+ + {lang === 'de' ? 'Roadmap 2028' : 'Roadmap 2028'} + +

+

+ {lang === 'de' + ? 'Enterprise Scale & Series A' + : 'Enterprise Scale & Series A'} +

+
+
+ +
+ {quarters.map((quarter, index) => ( + +
+ {quarter.quarter} +
+
    + {quarter.items.map((item, i) => ( +
  • + + {item} +
  • + ))} +
+
+ ))} +
+ + +
+ {metrics.map((metric, index) => { + const Icon = metric.icon + return ( +
+ +

{metric.label}

+

{metric.value}

+
+ ) + })} +
+
+
+ ) +} diff --git a/pitch-deck/components/slides/AnnexSecuritySlide.tsx b/pitch-deck/components/slides/AnnexSecuritySlide.tsx new file mode 100644 index 0000000..ffcd614 --- /dev/null +++ b/pitch-deck/components/slides/AnnexSecuritySlide.tsx @@ -0,0 +1,130 @@ +'use client' + +import { motion } from 'framer-motion' +import { Shield, Lock, ShieldCheck, Search, FileCode, Key, FileText } from 'lucide-react' +import { Language } from '@/lib/types' +import GradientText from '../ui/GradientText' +import FadeInView from '../ui/FadeInView' + +interface AnnexSecuritySlideProps { + lang: Language +} + +export default function AnnexSecuritySlide({ lang }: AnnexSecuritySlideProps) { + const securityFeatures = [ + { + icon: Shield, + title: lang === 'de' ? 'BSI & Compliance' : 'BSI & Compliance', + accentColor: 'from-blue-400 to-blue-600', + items: [ + lang === 'de' ? 'BSI-TR-03161 zertifizierte Architektur' : 'BSI-TR-03161 certified architecture', + lang === 'de' ? 'DSGVO Art. 32 TOM implementiert' : 'DSGVO Art. 32 TOM implemented', + lang === 'de' ? 'Regelmäßige Penetrationstests' : 'Regular penetration testing', + lang === 'de' ? 'Sicherheits-Audit-Trail' : 'Security audit trail' + ] + }, + { + icon: Lock, + title: lang === 'de' ? 'Verschlüsselung & Vault' : 'Encryption & Vault', + accentColor: 'from-purple-400 to-purple-600', + items: [ + lang === 'de' ? 'E2E-Verschlüsselung für Daten in Transit (TLS 1.3)' : 'E2E encryption for all data in transit (TLS 1.3)', + lang === 'de' ? 'AES-256 Verschlüsselung im Ruhezustand' : 'AES-256 encryption at rest', + lang === 'de' ? 'HashiCorp Vault für Secret Management' : 'HashiCorp Vault for secret management', + lang === 'de' ? 'Automatische Zertifikatsrotation' : 'Automatic certificate rotation' + ] + }, + { + icon: ShieldCheck, + title: lang === 'de' ? 'Zero Trust' : 'Zero Trust', + accentColor: 'from-green-400 to-green-600', + items: [ + lang === 'de' ? 'Kein implizites Vertrauen, alles verifizieren' : 'No implicit trust, verify everything', + lang === 'de' ? 'JWT-basierte Authentifizierung' : 'JWT-based authentication', + lang === 'de' ? 'RBAC mit minimalen Rechten' : 'RBAC with least privilege', + lang === 'de' ? 'Netzwerksegmentierung via Docker' : 'Network segmentation via Docker' + ] + } + ] + + const securityTools = [ + { name: 'Trivy', description: lang === 'de' ? 'Container Scanning' : 'Container Scanning', icon: Search }, + { name: 'Semgrep', description: 'SAST', icon: FileCode }, + { name: 'Gitleaks', description: lang === 'de' ? 'Secret Detection' : 'Secret Detection', icon: Key }, + { name: 'SBOM', description: lang === 'de' ? 'Software Bill of Materials' : 'Software Bill of Materials', icon: FileText } + ] + + return ( +
+ +
+

+ + {lang === 'de' ? 'Sicherheitsarchitektur' : 'Security Architecture'} + +

+

+ {lang === 'de' + ? 'Enterprise-Grade Sicherheit auf eigener Hardware' + : 'Enterprise-grade security on own hardware'} +

+
+
+ + {/* Security Features Grid */} +
+ {securityFeatures.map((feature, index) => ( + +
+ +
+

{feature.title}

+
    + {feature.items.map((item, itemIndex) => ( +
  • + + {item} +
  • + ))} +
+
+ ))} +
+ + {/* Security Tools Bar */} + +

+ {lang === 'de' ? 'Sicherheitstools' : 'Security Tools'} +

+
+ {securityTools.map((tool, index) => ( + + +
+ {tool.name} + {tool.description} +
+
+ ))} +
+
+
+ ) +} diff --git a/pitch-deck/components/slides/AnnexUSPComparisonSlide.tsx b/pitch-deck/components/slides/AnnexUSPComparisonSlide.tsx new file mode 100644 index 0000000..eabb550 --- /dev/null +++ b/pitch-deck/components/slides/AnnexUSPComparisonSlide.tsx @@ -0,0 +1,181 @@ +'use client' + +import { motion } from 'framer-motion' +import { Check, X, Minus } from 'lucide-react' +import { Language } from '@/lib/types' +import GradientText from '../ui/GradientText' +import FadeInView from '../ui/FadeInView' + +interface AnnexUSPComparisonSlideProps { + lang: Language +} + +const comparisonData = [ + { + featureDE: 'Self-Hosted', + featureEN: 'Self-Hosted', + breakpilot: 'yes', + proliance: 'no', + dataguard: 'no', + heydata: 'no' + }, + { + featureDE: 'Eigene KI', + featureEN: 'Own AI', + breakpilot: 'yes', + proliance: 'no', + dataguard: 'partial', + heydata: 'no' + }, + { + featureDE: 'Autonomer Support', + featureEN: 'Autonomous Support', + breakpilot: 'yes', + proliance: 'no', + dataguard: 'no', + heydata: 'no' + }, + { + featureDE: 'DSGVO', + featureEN: 'GDPR', + breakpilot: 'yes', + proliance: 'yes', + dataguard: 'yes', + heydata: 'yes' + }, + { + featureDE: 'AI Act', + featureEN: 'AI Act', + breakpilot: 'yes', + proliance: 'no', + dataguard: 'partial', + heydata: 'no' + }, + { + featureDE: 'NIS2', + featureEN: 'NIS2', + breakpilot: 'yes', + proliance: 'no', + dataguard: 'no', + heydata: 'no' + }, + { + featureDE: 'Hardware inkl.', + featureEN: 'Hardware incl.', + breakpilot: 'yes', + proliance: 'no', + dataguard: 'no', + heydata: 'no' + }, + { + featureDE: 'Preis/Monat', + featureEN: 'Price/Month', + breakpilot: 'ab EUR 149', + proliance: 'ab EUR 299', + dataguard: 'ab EUR 499', + heydata: 'ab EUR 199' + } +] + +const StatusIcon = ({ status }: { status: string }) => { + if (status === 'yes') { + return + } else if (status === 'no') { + return + } else if (status === 'partial') { + return + } + return {status} +} + +export default function AnnexUSPComparisonSlide({ lang }: AnnexUSPComparisonSlideProps) { + return ( +
+ +
+

+ + {lang === 'de' ? 'Detailvergleich Wettbewerb' : 'Detailed Competitor Comparison'} + +

+

+ {lang === 'de' ? 'BreakPilot vs. etablierte Anbieter' : 'BreakPilot vs. established providers'} +

+
+
+ + +
+ + + + + + + + + + + + {comparisonData.map((row, index) => ( + + + + + + + + ))} + +
+
+ BreakPilot +
+
ProlianceDataGuardheyData
+ {lang === 'de' ? row.featureDE : row.featureEN} + +
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+ + +

+ {lang === 'de' + ? 'Stand: Februar 2026. Preise und Features der Wettbewerber koennen variieren.' + : 'As of: February 2026. Competitor prices and features may vary.'} +

+
+
+ ) +} diff --git a/pitch-deck/components/slides/AnnexUSPMoatSlide.tsx b/pitch-deck/components/slides/AnnexUSPMoatSlide.tsx new file mode 100644 index 0000000..5819cb4 --- /dev/null +++ b/pitch-deck/components/slides/AnnexUSPMoatSlide.tsx @@ -0,0 +1,127 @@ +'use client' + +import { motion } from 'framer-motion' +import { Lock, RefreshCw, Network, RotateCw, Clock } from 'lucide-react' +import { Language } from '@/lib/types' +import GradientText from '../ui/GradientText' +import FadeInView from '../ui/FadeInView' + +interface AnnexUSPMoatSlideProps { + lang: Language +} + +const moats = [ + { + icon: Lock, + gradient: 'from-blue-500 to-cyan-500', + titleDE: 'Hardware Lock-In', + titleEN: 'Hardware Lock-In', + descDE: 'Physische Hardware beim Kunden. Wechsel bedeutet Hardware-Austausch und Datenmigration.', + descEN: 'Physical hardware at customer site. Switching means hardware replacement and data migration.' + }, + { + icon: RefreshCw, + gradient: 'from-purple-500 to-pink-500', + titleDE: 'Switching Costs', + titleEN: 'Switching Costs', + descDE: 'Compliance-Dokumentation, trainierte KI-Modelle und Audit-Historie sind nicht portabel.', + descEN: 'Compliance documentation, trained AI models and audit history are not portable.' + }, + { + icon: Network, + gradient: 'from-green-500 to-emerald-500', + titleDE: 'Network Effects', + titleEN: 'Network Effects', + descDE: 'Jeder Kunde verbessert die KI. Shared learnings ueber anonymisierte Compliance-Patterns.', + descEN: 'Every customer improves the AI. Shared learnings via anonymized compliance patterns.' + }, + { + icon: RotateCw, + gradient: 'from-amber-500 to-orange-500', + titleDE: 'Data Flywheel', + titleEN: 'Data Flywheel', + descDE: 'Mehr Daten → bessere KI → bessere Compliance → mehr Kunden → mehr Daten.', + descEN: 'More data → better AI → better compliance → more customers → more data.' + }, + { + icon: Clock, + gradient: 'from-pink-500 to-rose-500', + titleDE: 'Time Advantage', + titleEN: 'Time Advantage', + descDE: '18 Monate Vorsprung. Erster Self-Hosted Compliance-AI Anbieter im DACH-Raum.', + descEN: '18 months head start. First self-hosted compliance AI provider in DACH region.' + } +] + +export default function AnnexUSPMoatSlide({ lang }: AnnexUSPMoatSlideProps) { + return ( +
+ +
+

+ + {lang === 'de' ? 'Verteidigbare Marktposition' : 'Defensible Market Position'} + +

+

+ {lang === 'de' + ? 'Fuenf Verteidigungslinien gegen Wettbewerber' + : 'Five lines of defense against competitors'} +

+
+
+ +
+ {moats.map((moat, index) => { + const Icon = moat.icon + return ( + +
+
+
+ + {String(index + 1).padStart(2, '0')} + +
+ +
+
+ +
+

+ {lang === 'de' ? moat.titleDE : moat.titleEN} +

+

+ {lang === 'de' ? moat.descDE : moat.descEN} +

+
+ +
+ + ) + })} +
+ + +
+

+ {lang === 'de' + ? 'Kombination aus physischen und digitalen Barriers macht Marktposition langfristig verteidigbar' + : 'Combination of physical and digital barriers makes market position defensible long-term'} +

+
+
+
+ ) +} diff --git a/pitch-deck/components/slides/AnnexUSPOverviewSlide.tsx b/pitch-deck/components/slides/AnnexUSPOverviewSlide.tsx new file mode 100644 index 0000000..6cbc475 --- /dev/null +++ b/pitch-deck/components/slides/AnnexUSPOverviewSlide.tsx @@ -0,0 +1,129 @@ +'use client' + +import { motion } from 'framer-motion' +import { Server, Brain, Shield, Layers, Bot } from 'lucide-react' +import { Language } from '@/lib/types' +import GradientText from '../ui/GradientText' +import FadeInView from '../ui/FadeInView' + +interface AnnexUSPOverviewSlideProps { + lang: Language +} + +const usps = [ + { + icon: Server, + gradient: 'from-blue-500 to-cyan-500', + titleDE: 'Self-Hosted', + titleEN: 'Self-Hosted', + descDE: 'Volle Datensouveraenitaet. Kein Byte verlaesst das Unternehmen. Hardware steht im eigenen Serverraum.', + descEN: 'Full data sovereignty. No byte leaves the company. Hardware sits in your own server room.' + }, + { + icon: Brain, + gradient: 'from-purple-500 to-pink-500', + titleDE: 'AI-First', + titleEN: 'AI-First', + descDE: 'Alles was durch KI loesbar ist, wird durch KI geloest. Kein klassischer Support, minimales Personal.', + descEN: 'Everything solvable by AI is solved by AI. No traditional support, minimal personnel.' + }, + { + icon: Shield, + gradient: 'from-green-500 to-emerald-500', + titleDE: 'Hardware-Moat', + titleEN: 'Hardware-Moat', + descDE: 'Apple Hardware als physische Markteintrittsbarriere. Wechselkosten machen Lock-in positiv.', + descEN: 'Apple hardware as physical market entry barrier. Switching costs create positive lock-in.' + }, + { + icon: Layers, + gradient: 'from-amber-500 to-orange-500', + titleDE: 'All-in-One', + titleEN: 'All-in-One', + descDE: 'DSGVO + AI Act + NIS2 in einer Loesung. Kein Tool-Dschungel, eine Plattform fuer alles.', + descEN: 'GDPR + AI Act + NIS2 in one solution. No tool jungle, one platform for everything.' + }, + { + icon: Bot, + gradient: 'from-pink-500 to-rose-500', + titleDE: 'Autonomous Support', + titleEN: 'Autonomous Support', + descDE: '24/7 KI-Support beantwortet Fragen, aendert Dokumente, bereitet Audits vor. Ohne menschliches Eingreifen.', + descEN: '24/7 AI support answers questions, modifies documents, prepares audits. Without human intervention.' + } +] + +export default function AnnexUSPOverviewSlide({ lang }: AnnexUSPOverviewSlideProps) { + return ( +
+ +
+

+ + {lang === 'de' ? 'Unsere 5 USPs' : 'Our 5 USPs'} + +

+

+ {lang === 'de' ? 'Was uns einzigartig macht' : 'What makes us unique'} +

+
+
+ +
+ {usps.slice(0, 3).map((usp, index) => { + const Icon = usp.icon + return ( + +
+
+
+ +
+

+ {lang === 'de' ? usp.titleDE : usp.titleEN} +

+

+ {lang === 'de' ? usp.descDE : usp.descEN} +

+
+ + ) + })} +
+ +
+ {usps.slice(3, 5).map((usp, index) => { + const Icon = usp.icon + return ( + +
+
+
+ +
+

+ {lang === 'de' ? usp.titleDE : usp.titleEN} +

+

+ {lang === 'de' ? usp.descDE : usp.descEN} +

+
+ + ) + })} +
+
+ ) +} diff --git a/pitch-deck/components/slides/AppendixSlide.tsx b/pitch-deck/components/slides/AppendixSlide.tsx new file mode 100644 index 0000000..542d29d --- /dev/null +++ b/pitch-deck/components/slides/AppendixSlide.tsx @@ -0,0 +1,68 @@ +'use client'; + +import { motion } from 'framer-motion'; +import { Language } from '@/lib/types'; +import GradientText from '../ui/GradientText'; +import FadeInView from '../ui/FadeInView'; + +interface AppendixSlideProps { + lang: Language; +} + +export default function AppendixSlide({ lang }: AppendixSlideProps) { + const content = { + de: { + title: 'Appendix', + subtitle: 'Deep Dive — Technologie, Compliance & Strategie', + detailInfo: '14 Detailfolien für Investoren', + }, + en: { + title: 'Appendix', + subtitle: 'Deep Dive — Technology, Compliance & Strategy', + detailInfo: '14 detail slides for investors', + }, + }; + + const t = content[lang]; + + return ( +
+ + +

+ {t.title} +

+
+ + + {t.subtitle} + + + + + + {t.detailInfo} + +
+
+ ); +} diff --git a/pitch-deck/components/slides/CompetitionSlide.tsx b/pitch-deck/components/slides/CompetitionSlide.tsx index 363ae5a..2a943e4 100644 --- a/pitch-deck/components/slides/CompetitionSlide.tsx +++ b/pitch-deck/components/slides/CompetitionSlide.tsx @@ -7,6 +7,7 @@ import GradientText from '../ui/GradientText' import FadeInView from '../ui/FadeInView' import FeatureMatrix from '../ui/FeatureMatrix' import GlassCard from '../ui/GlassCard' +import BrandName from '../ui/BrandName' interface CompetitionSlideProps { lang: Language @@ -59,7 +60,7 @@ export default function CompetitionSlide({ lang, features, competitors }: Compet

- {lang === 'de' ? 'Integrierte Security & Developer Tools — nur bei ComplAI' : 'Integrated Security & Developer Tools — ComplAI only'} + {lang === 'de' ? <>Integrierte Security & Developer Tools — nur bei : <>Integrated Security & Developer Tools — only}

{secFeats.map((feat, idx) => { diff --git a/pitch-deck/components/slides/CoverSlide.tsx b/pitch-deck/components/slides/CoverSlide.tsx index 84e2dd1..b4e5570 100644 --- a/pitch-deck/components/slides/CoverSlide.tsx +++ b/pitch-deck/components/slides/CoverSlide.tsx @@ -5,6 +5,7 @@ import { Language } from '@/lib/types' import { t } from '@/lib/i18n' import { ArrowRight } from 'lucide-react' import GradientText from '../ui/GradientText' +import BrandName from '../ui/BrandName' interface CoverSlideProps { lang: Language @@ -43,9 +44,13 @@ export default function CoverSlide({ lang, onNext }: CoverSlideProps) { className="text-5xl md:text-7xl font-bold mb-4 tracking-tight" > BreakPilot{' '} - - ComplAI - + + + {/* Tagline */} diff --git a/pitch-deck/components/slides/FinancialsSlide.tsx b/pitch-deck/components/slides/FinancialsSlide.tsx index c1f52af..67a2ddd 100644 --- a/pitch-deck/components/slides/FinancialsSlide.tsx +++ b/pitch-deck/components/slides/FinancialsSlide.tsx @@ -1,92 +1,312 @@ 'use client' import { useState } from 'react' -import { Language, PitchFinancial } from '@/lib/types' -import { t, formatEur } from '@/lib/i18n' +import { Language } from '@/lib/types' +import { t } from '@/lib/i18n' +import { useFinancialModel } from '@/lib/hooks/useFinancialModel' import GradientText from '../ui/GradientText' import FadeInView from '../ui/FadeInView' import FinancialChart from '../ui/FinancialChart' import FinancialSliders from '../ui/FinancialSliders' -import GlassCard from '../ui/GlassCard' -import AnimatedCounter from '../ui/AnimatedCounter' +import KPICard from '../ui/KPICard' +import RunwayGauge from '../ui/RunwayGauge' +import WaterfallChart from '../ui/WaterfallChart' +import UnitEconomicsCards from '../ui/UnitEconomicsCards' +import ScenarioSwitcher from '../ui/ScenarioSwitcher' +import AnnualPLTable, { AccountingStandard } from '../ui/AnnualPLTable' +import AnnualCashflowChart from '../ui/AnnualCashflowChart' + +type FinTab = 'overview' | 'guv' | 'cashflow' interface FinancialsSlideProps { lang: Language - financials: PitchFinancial[] } -export default function FinancialsSlide({ lang, financials }: FinancialsSlideProps) { +export default function FinancialsSlide({ lang }: FinancialsSlideProps) { const i = t(lang) - const [growthRate, setGrowthRate] = useState(100) - const [churnRate, setChurnRate] = useState(5) - const [arpu, setArpu] = useState(500) + const fm = useFinancialModel() + const [activeTab, setActiveTab] = useState('overview') + const [accountingStandard, setAccountingStandard] = useState('hgb') + const de = lang === 'de' - const growthMultiplier = growthRate / 100 - const lastYear = financials[financials.length - 1] + const activeResults = fm.activeResults + const summary = activeResults?.summary + const lastResult = activeResults?.results[activeResults.results.length - 1] + + // Build scenario color map + const scenarioColors: Record = {} + fm.scenarios.forEach(s => { scenarioColors[s.id] = s.color }) + + // Build compare results (exclude active scenario) + const compareResults = new Map( + Array.from(fm.results.entries()).filter(([id]) => id !== fm.activeScenarioId) + ) + + // Initial funding from assumptions + const initialFunding = (fm.activeScenario?.assumptions.find(a => a.key === 'initial_funding')?.value as number) || 200000 + + const tabs: { id: FinTab; label: string }[] = [ + { id: 'overview', label: de ? 'Uebersicht' : 'Overview' }, + { id: 'guv', label: de ? 'GuV (Jahres)' : 'P&L (Annual)' }, + { id: 'cashflow', label: de ? 'Cashflow & Finanzbedarf' : 'Cashflow & Funding' }, + ] + + if (fm.loading) { + return ( +
+
+
+ ) + } return ( -
- -

+
+ +

{i.financials.title}

-

{i.financials.subtitle}

+

{i.financials.subtitle}

- {/* Key Numbers */} -
- -

{i.financials.arr} 2030

-

- -

-
- -

{i.financials.customers} 2030

-

- -

-
- -

{i.financials.employees} 2030

-

{lastYear?.employees_count || 0}

-
- -

{i.financials.mrr} 2030

-

- -

-
+ {/* Hero KPI Cards */} +
+ + + + = 3 ? 'up' : 'down'} + color="#a855f7" + delay={0.25} + />
-
- {/* Chart */} - - - - - + {/* Tab Navigation + Accounting Standard Toggle */} +
+
+ {tabs.map((tab) => ( + + ))} +
- {/* Sliders */} - - - + {/* HGB / US GAAP Toggle — only visible on GuV and Cashflow tabs */} + {(activeTab === 'guv' || activeTab === 'cashflow') && ( +
+ + +
+ )} +
+ + {/* Main content: 3-column layout */} +
+ {/* Left: Charts (8 columns) */} +
+ + {/* TAB: Overview — monatlicher Chart + Waterfall + Unit Economics */} + {activeTab === 'overview' && ( + <> + +
+
+

+ {de ? 'Umsatz vs. Kosten (60 Monate)' : 'Revenue vs. Costs (60 months)'} +

+
+ {de ? 'Umsatz' : 'Revenue'} + {de ? 'Kosten' : 'Costs'} + {de ? 'Kunden' : 'Customers'} +
+
+ +
+
+ +
+ +
+

+ {de ? 'Cash-Flow (Quartal)' : 'Cash Flow (Quarterly)'} +

+ {activeResults && } +
+
+ + +
+
+ +
+ {lastResult && ( + a.key === 'churn_rate_monthly')?.value as number || 3} + lang={lang} + /> + )} +
+
+
+ + )} + + {/* TAB: GuV — Annual P&L Table */} + {activeTab === 'guv' && activeResults && ( + +
+
+

+ {accountingStandard === 'hgb' + ? (de ? 'Gewinn- und Verlustrechnung (HGB, 5 Jahre)' : 'Profit & Loss Statement (HGB, 5 Years)') + : (de ? 'Gewinn- und Verlustrechnung (US GAAP, 5 Jahre)' : 'Profit & Loss Statement (US GAAP, 5 Years)') + } +

+

+ {de ? 'Alle Werte in EUR' : 'All values in EUR'} +

+
+ +
+
+ )} + + {/* TAB: Cashflow & Finanzbedarf */} + {activeTab === 'cashflow' && activeResults && ( + +
+

+ {accountingStandard === 'hgb' + ? (de ? 'Kapitalflussrechnung (HGB)' : 'Cash Flow Statement (HGB)') + : (de ? 'Cash Flow Statement (US GAAP)' : 'Cash Flow Statement (US GAAP)') + } +

+ +
+
+ )} +
+ + {/* Right: Controls (4 columns) */} +
+ {/* Scenario Switcher */} + +
+ { + fm.setActiveScenarioId(id) + }} + onToggleCompare={() => { + if (!fm.compareMode) { + fm.computeAll() + } + fm.setCompareMode(!fm.compareMode) + }} + lang={lang} + /> +
+
+ + {/* Assumption Sliders */} + +
+

+ {i.financials.adjustAssumptions} +

+ {fm.activeScenario && ( + { + if (fm.activeScenarioId) { + fm.updateAssumption(fm.activeScenarioId, key, value) + } + }} + lang={lang} + /> + )} + {fm.computing && ( +
+
+ {de ? 'Berechne...' : 'Computing...'} +
+ )} +
+ +
) diff --git a/pitch-deck/components/slides/SolutionSlide.tsx b/pitch-deck/components/slides/SolutionSlide.tsx index d8408dd..7374deb 100644 --- a/pitch-deck/components/slides/SolutionSlide.tsx +++ b/pitch-deck/components/slides/SolutionSlide.tsx @@ -7,6 +7,7 @@ import { Server, ShieldCheck, Bot } from 'lucide-react' import GlassCard from '../ui/GlassCard' import GradientText from '../ui/GradientText' import FadeInView from '../ui/FadeInView' +import BrandName from '../ui/BrandName' interface SolutionSlideProps { lang: Language @@ -24,7 +25,9 @@ export default function SolutionSlide({ lang }: SolutionSlideProps) {

{i.solution.title}

-

{i.solution.subtitle}

+

+ — {lang === 'de' ? 'Compliance auf Autopilot' : 'Compliance on Autopilot'} +

diff --git a/pitch-deck/components/slides/TechnologySlide.tsx b/pitch-deck/components/slides/TechnologySlide.tsx new file mode 100644 index 0000000..3ee89a0 --- /dev/null +++ b/pitch-deck/components/slides/TechnologySlide.tsx @@ -0,0 +1,125 @@ +'use client' + +import { motion } from 'framer-motion' +import { Language } from '@/lib/types' +import { t } from '@/lib/i18n' +import GradientText from '../ui/GradientText' +import FadeInView from '../ui/FadeInView' +import { Cpu, Brain, Server, Shield, Database } from 'lucide-react' + +interface TechnologySlideProps { + lang: Language +} + +const phaseColors = [ + 'from-blue-500/20 to-blue-600/10 border-blue-500/30', + 'from-indigo-500/20 to-indigo-600/10 border-indigo-500/30', + 'from-purple-500/20 to-purple-600/10 border-purple-500/30', + 'from-violet-500/20 to-violet-600/10 border-violet-500/30', + 'from-fuchsia-500/20 to-fuchsia-600/10 border-fuchsia-500/30', +] + +const phaseAccents = [ + 'text-blue-400', + 'text-indigo-400', + 'text-purple-400', + 'text-violet-400', + 'text-fuchsia-400', +] + +const layerIcons = [Cpu, Brain, Server, Shield, Database] +const layerColors = [ + 'text-blue-400 bg-blue-500/10 border-blue-500/20', + 'text-purple-400 bg-purple-500/10 border-purple-500/20', + 'text-green-400 bg-green-500/10 border-green-500/20', + 'text-red-400 bg-red-500/10 border-red-500/20', + 'text-amber-400 bg-amber-500/10 border-amber-500/20', +] + +export default function TechnologySlide({ lang }: TechnologySlideProps) { + const i = t(lang) as any + + return ( +
+ +

+ {i.technology.title} +

+

{i.technology.subtitle}

+
+ + {/* Timeline Section */} + +
+

+ {i.technology.timelineTitle} +

+ + {/* Timeline Line */} +
+
+ +
+ {i.technology.phases.map((phase: any, idx: number) => ( + + {/* Dot on timeline */} +
+
+
+ + {/* Phase Card */} +
+

{phase.year}

+

{phase.phase}

+
+ {phase.techs.map((tech: string, i: number) => ( +

+ {tech} +

+ ))} +
+
+ + ))} +
+
+
+ + + {/* Tech Stack Layers */} + +

+ {i.technology.stackTitle} +

+
+ {i.technology.layers.map((layer: any, idx: number) => { + const Icon = layerIcons[idx] + return ( + +
+ +
+
+

{layer.name}

+

{layer.techs}

+
+
+ ) + })} +
+
+
+ ) +} diff --git a/pitch-deck/components/slides/TheAskSlide.tsx b/pitch-deck/components/slides/TheAskSlide.tsx index 19ae88c..2e4340f 100644 --- a/pitch-deck/components/slides/TheAskSlide.tsx +++ b/pitch-deck/components/slides/TheAskSlide.tsx @@ -7,7 +7,7 @@ import GradientText from '../ui/GradientText' import FadeInView from '../ui/FadeInView' import AnimatedCounter from '../ui/AnimatedCounter' import GlassCard from '../ui/GlassCard' -import { Target, Calendar, FileText } from 'lucide-react' +import { Target, Calendar, FileText, Building2, Users, Landmark, TrendingUp } from 'lucide-react' import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip } from 'recharts' interface TheAskSlideProps { @@ -15,7 +15,50 @@ interface TheAskSlideProps { funding: PitchFunding } -const COLORS = ['#6366f1', '#a78bfa', '#60a5fa', '#34d399', '#fbbf24'] +const COLORS = ['#6366f1', '#a78bfa', '#60a5fa', '#34d399', '#fbbf24', '#f87171'] + +const FUNDING_TIMELINE = [ + { + month: 'Aug 2026', + amount: 25000, + icon: Building2, + color: '#6366f1', + label_de: 'Stammkapital', + label_en: 'Share Capital', + desc_de: 'GmbH-Gruendung', + desc_en: 'GmbH Founding', + }, + { + month: 'Sep 2026', + amount: 25000, + icon: Users, + color: '#a78bfa', + label_de: 'Angel-Runde', + label_en: 'Angel Round', + desc_de: '5% Anteile', + desc_en: '5% Equity', + }, + { + month: 'Okt 2026', + amount: 200000, + icon: Landmark, + color: '#60a5fa', + label_de: 'Wandeldarlehen', + label_en: 'Convertible Note', + desc_de: '40k Investor + 160k L-Bank', + desc_en: '40k Investor + 160k L-Bank', + }, + { + month: 'Jul 2027', + amount: 1000000, + icon: TrendingUp, + color: '#34d399', + label_de: 'Series A', + label_en: 'Series A', + desc_de: 'Skalierungskapital', + desc_en: 'Growth Capital', + }, +] export default function TheAskSlide({ lang, funding }: TheAskSlideProps) { const i = t(lang) @@ -26,9 +69,11 @@ export default function TheAskSlide({ lang, funding }: TheAskSlideProps) { value: item.percentage, })) + const totalFunding = 1250000 + return (
- +

{i.theAsk.title}

@@ -36,53 +81,93 @@ export default function TheAskSlide({ lang, funding }: TheAskSlideProps) {
{/* Main Number */} - + -

- - EUR +

+ + EUR +

+

+ {lang === 'de' ? 'Gestaffelte Finanzierung 2026-2027' : 'Staged Funding 2026-2027'}

- {/* Details */} -
- - + {/* Funding Timeline */} + +
+ {FUNDING_TIMELINE.map((event, idx) => { + const Icon = event.icon + return ( + + + +

{event.month}

+

+ {event.amount >= 1000000 + ? `${(event.amount / 1000000).toFixed(0)}M` + : `${(event.amount / 1000).toFixed(0)}k`} +

+

+ {lang === 'de' ? event.label_de : event.label_en} +

+

+ {lang === 'de' ? event.desc_de : event.desc_en} +

+
+
+ ) + })} +
+
+ + {/* Details Row */} +
+ +

{i.theAsk.instrument}

-

{funding?.instrument || 'SAFE'}

+

{funding?.instrument || 'Stammkapital + Wandeldarlehen + Equity'}

- - + +

{i.theAsk.targetDate}

-

Q3 2026

+

+ {lang === 'de' ? 'Aug 2026 — Jul 2027' : 'Aug 2026 — Jul 2027'} +

- - + +

{lang === 'de' ? 'Runway' : 'Runway'}

-

18 {lang === 'de' ? 'Monate' : 'Months'}

+

36+ {lang === 'de' ? 'Monate' : 'Months'}

{/* Use of Funds */} - - -

{i.theAsk.useOfFunds}

-
+ + +

+ {i.theAsk.useOfFunds} (1,25 Mio. EUR) +

+
{/* Pie Chart */} -
+
@@ -96,7 +181,7 @@ export default function TheAskSlide({ lang, funding }: TheAskSlideProps) { border: '1px solid rgba(255,255,255,0.1)', borderRadius: 8, color: '#fff', - fontSize: 13, + fontSize: 12, }} formatter={(value: number) => `${value}%`} /> @@ -105,16 +190,16 @@ export default function TheAskSlide({ lang, funding }: TheAskSlideProps) {
{/* Legend */} -
+
{useOfFunds.map((item, idx) => (
-
+
{lang === 'de' ? item.label_de : item.label_en} {item.percentage}% - {((funding.amount_eur * item.percentage) / 100).toLocaleString('de-DE')} EUR + {((totalFunding * item.percentage) / 100).toLocaleString('de-DE')} EUR
))} diff --git a/pitch-deck/components/ui/AnnualCashflowChart.tsx b/pitch-deck/components/ui/AnnualCashflowChart.tsx new file mode 100644 index 0000000..5e786fd --- /dev/null +++ b/pitch-deck/components/ui/AnnualCashflowChart.tsx @@ -0,0 +1,205 @@ +'use client' + +import { FMResult } from '@/lib/types' +import { AccountingStandard } from './AnnualPLTable' +import { + Bar, + XAxis, + YAxis, + Tooltip, + ResponsiveContainer, + ReferenceLine, + Line, + ComposedChart, + Cell, +} from 'recharts' + +interface AnnualCashflowChartProps { + results: FMResult[] + initialFunding: number + lang: 'de' | 'en' + standard?: AccountingStandard +} + +interface AnnualCFRow { + year: string + revenue: number + costs: number + netCashflow: number + cashBalance: number + cumulativeFundingNeed: number + // HGB specific + operatingCF?: number + investingCF?: number + financingCF?: number +} + +export default function AnnualCashflowChart({ results, initialFunding, lang, standard = 'hgb' }: AnnualCashflowChartProps) { + const de = lang === 'de' + + // Aggregate into yearly + const yearMap = new Map() + for (const r of results) { + if (!yearMap.has(r.year)) yearMap.set(r.year, []) + yearMap.get(r.year)!.push(r) + } + + let cumulativeNeed = 0 + const data: AnnualCFRow[] = Array.from(yearMap.entries()).map(([year, months]) => { + const revenue = months.reduce((s, m) => s + m.revenue_eur, 0) + const costs = months.reduce((s, m) => s + m.total_costs_eur, 0) + const netIncome = months.reduce((s, m) => s + (m.net_income_eur || m.revenue_eur - m.total_costs_eur), 0) + const depreciation = months.reduce((s, m) => s + (m.depreciation_eur || 0), 0) + const cogs = months.reduce((s, m) => s + m.cogs_eur, 0) + const lastMonth = months[months.length - 1] + + const netCF = netIncome + if (netCF < 0) cumulativeNeed += Math.abs(netCF) + + // Operating CF = Net Income + Depreciation (non-cash add-back) + const operatingCF = netIncome + depreciation + // Investing CF = Hardware COGS (approximation for CapEx) + const investingCF = -cogs + // Financing CF = 0 for now (no debt/equity events modeled) + const financingCF = 0 + + return { + year: year.toString(), + revenue: Math.round(revenue), + costs: Math.round(costs), + netCashflow: Math.round(netCF), + cashBalance: Math.round(lastMonth.cash_balance_eur), + cumulativeFundingNeed: Math.round(cumulativeNeed), + operatingCF: Math.round(operatingCF), + investingCF: Math.round(investingCF), + financingCF: Math.round(financingCF), + } + }) + + const formatValue = (value: number) => { + if (Math.abs(value) >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M` + if (Math.abs(value) >= 1_000) return `${(value / 1_000).toFixed(0)}k` + return value.toString() + } + + // Calculate total funding needed beyond initial funding + const totalFundingGap = Math.max(0, cumulativeNeed - initialFunding) + + const isUSGAAP = standard === 'usgaap' + + return ( +
+ {/* Summary Cards */} +
+
+

+ {de ? 'Startkapital' : 'Initial Funding'} +

+

{formatValue(initialFunding)} EUR

+
+
+

+ {de ? 'Kum. Finanzbedarf' : 'Cum. Funding Need'} +

+

{formatValue(cumulativeNeed)} EUR

+
+
+

+ {de ? 'Finanzierungsluecke' : 'Funding Gap'} +

+

0 ? 'text-red-400' : 'text-emerald-400'}`}> + {totalFundingGap > 0 ? formatValue(totalFundingGap) + ' EUR' : (de ? 'Gedeckt' : 'Covered')} +

+
+
+ + {/* Chart */} +
+ + + + + { + const labels: Record = isUSGAAP + ? { + netCashflow: 'Net Cash Flow', + cashBalance: 'Cash Balance', + cumulativeFundingNeed: 'Cum. Funding Need', + } + : { + netCashflow: de ? 'Netto-Cashflow' : 'Net Cash Flow', + cashBalance: de ? 'Cash-Bestand' : 'Cash Balance', + cumulativeFundingNeed: de ? 'Kum. Finanzbedarf' : 'Cum. Funding Need', + } + return [formatValue(value) + ' EUR', labels[name] || name] + }} + /> + + + {/* Net Cashflow Bars */} + + {data.map((entry, i) => ( + = 0 ? 'rgba(34, 197, 94, 0.7)' : 'rgba(239, 68, 68, 0.6)'} + /> + ))} + + + {/* Cash Balance Line */} + + + {/* Cumulative Funding Need Line */} + + + +
+ + {/* Legend */} +
+ + + + {isUSGAAP ? 'Net Cash Flow' : (de ? 'Netto-Cashflow' : 'Net Cash Flow')} + + + + {isUSGAAP ? 'Cash Balance' : (de ? 'Cash-Bestand' : 'Cash Balance')} + + + + {isUSGAAP ? 'Cum. Funding Need' : (de ? 'Kum. Finanzbedarf' : 'Cum. Funding Need')} + +
+
+ ) +} diff --git a/pitch-deck/components/ui/AnnualPLTable.tsx b/pitch-deck/components/ui/AnnualPLTable.tsx new file mode 100644 index 0000000..c1d70c8 --- /dev/null +++ b/pitch-deck/components/ui/AnnualPLTable.tsx @@ -0,0 +1,232 @@ +'use client' + +import { motion } from 'framer-motion' +import { FMResult } from '@/lib/types' + +export type AccountingStandard = 'hgb' | 'usgaap' + +interface AnnualPLTableProps { + results: FMResult[] + lang: 'de' | 'en' + standard: AccountingStandard +} + +interface AnnualRow { + year: number + revenue: number + cogs: number + grossProfit: number + grossMarginPct: number + personnel: number + marketing: number + infra: number + totalOpex: number + ebitda: number + ebitdaMarginPct: number + customers: number + employees: number + // Detail costs + adminCosts: number + officeCosts: number + travelCosts: number + softwareLicenses: number + depreciation: number + interestExpense: number + taxes: number + netIncome: number + ebit: number + ihk: number + foundingCosts: number + // US GAAP aggregates + rAndD: number + sga: number +} + +function fmt(v: number): string { + if (Math.abs(v) >= 1_000_000) return `${(v / 1_000_000).toFixed(1)}M` + if (Math.abs(v) >= 1_000) return `${(v / 1_000).toFixed(0)}k` + return Math.round(v).toLocaleString('de-DE') +} + +export default function AnnualPLTable({ results, lang, standard }: AnnualPLTableProps) { + const de = lang === 'de' + + // Aggregate monthly results into annual + const annualMap = new Map() + for (const r of results) { + if (!annualMap.has(r.year)) annualMap.set(r.year, []) + annualMap.get(r.year)!.push(r) + } + + const rows: AnnualRow[] = Array.from(annualMap.entries()).map(([year, months]) => { + const revenue = months.reduce((s, m) => s + m.revenue_eur, 0) + const cogs = months.reduce((s, m) => s + m.cogs_eur, 0) + const grossProfit = revenue - cogs + const personnel = months.reduce((s, m) => s + m.personnel_eur, 0) + const marketing = months.reduce((s, m) => s + m.marketing_eur, 0) + const infra = months.reduce((s, m) => s + m.infra_eur, 0) + const adminCosts = months.reduce((s, m) => s + (m.admin_costs_eur || 0), 0) + const officeCosts = months.reduce((s, m) => s + (m.office_costs_eur || 0), 0) + const travelCosts = months.reduce((s, m) => s + (m.travel_costs_eur || 0), 0) + const softwareLicenses = months.reduce((s, m) => s + (m.software_licenses_eur || 0), 0) + const depreciation = months.reduce((s, m) => s + (m.depreciation_eur || 0), 0) + const interestExpense = months.reduce((s, m) => s + (m.interest_expense_eur || 0), 0) + const taxes = months.reduce((s, m) => s + (m.taxes_eur || 0), 0) + const netIncome = months.reduce((s, m) => s + (m.net_income_eur || 0), 0) + const ebit = months.reduce((s, m) => s + (m.ebit_eur || 0), 0) + const ihk = months.reduce((s, m) => s + (m.ihk_eur || 0), 0) + const foundingCosts = months.reduce((s, m) => s + (m.founding_costs_eur || 0), 0) + + const totalOpex = personnel + marketing + infra + adminCosts + officeCosts + travelCosts + softwareLicenses + ihk + foundingCosts + const ebitda = grossProfit - totalOpex + const lastMonth = months[months.length - 1] + + // US GAAP aggregates + const rAndD = infra + softwareLicenses + const sga = personnel + adminCosts + officeCosts + travelCosts + ihk + foundingCosts + + return { + year, + revenue, + cogs, + grossProfit, + grossMarginPct: revenue > 0 ? (grossProfit / revenue) * 100 : 0, + personnel, + marketing, + infra, + totalOpex, + ebitda, + ebitdaMarginPct: revenue > 0 ? (ebitda / revenue) * 100 : 0, + customers: lastMonth.total_customers, + employees: lastMonth.employees_count, + adminCosts, + officeCosts, + travelCosts, + softwareLicenses, + depreciation, + interestExpense, + taxes, + netIncome, + ebit, + ihk, + foundingCosts, + rAndD, + sga, + } + }) + + type LineItem = { + label: string + getValue: (r: AnnualRow) => number + isBold?: boolean + isPercent?: boolean + isSeparator?: boolean + isNegative?: boolean + isSubRow?: boolean + } + + const hgbLineItems: LineItem[] = [ + { label: 'Umsatzerloese', getValue: r => r.revenue, isBold: true }, + { label: '- Materialaufwand', getValue: r => r.cogs, isNegative: true }, + { label: '- Personalaufwand', getValue: r => r.personnel, isNegative: true }, + { label: '- Abschreibungen', getValue: r => r.depreciation, isNegative: true }, + { label: '- Sonstige betr. Aufwendungen', getValue: r => r.marketing + r.adminCosts + r.officeCosts + r.travelCosts + r.softwareLicenses + r.ihk + r.foundingCosts + r.infra, isNegative: true, isSeparator: true }, + { label: ' davon Marketing', getValue: r => r.marketing, isNegative: true, isSubRow: true }, + { label: ' davon Steuerberater/Recht', getValue: r => r.adminCosts, isNegative: true, isSubRow: true }, + { label: ' davon Buero/Telefon', getValue: r => r.officeCosts, isNegative: true, isSubRow: true }, + { label: ' davon Software', getValue: r => r.softwareLicenses, isNegative: true, isSubRow: true }, + { label: ' davon Reisekosten', getValue: r => r.travelCosts, isNegative: true, isSubRow: true }, + { label: ' davon Infrastruktur', getValue: r => r.infra, isNegative: true, isSubRow: true }, + { label: '= Betriebsergebnis (EBIT)', getValue: r => r.ebit, isBold: true, isSeparator: true }, + { label: '- Zinsaufwand', getValue: r => r.interestExpense, isNegative: true }, + { label: '= Ergebnis vor Steuern (EBT)', getValue: r => r.ebit - r.interestExpense, isBold: true, isSeparator: true }, + { label: '- Steuern', getValue: r => r.taxes, isNegative: true }, + { label: '= Jahresueberschuss', getValue: r => r.netIncome, isBold: true, isSeparator: true }, + { label: 'Kunden (Jahresende)', getValue: r => r.customers }, + { label: 'Mitarbeiter', getValue: r => r.employees }, + ] + + const usgaapLineItems: LineItem[] = [ + { label: 'Revenue', getValue: r => r.revenue, isBold: true }, + { label: '- Cost of Revenue (COGS)', getValue: r => r.cogs, isNegative: true }, + { label: '= Gross Profit', getValue: r => r.grossProfit, isBold: true, isSeparator: true }, + { label: ' Gross Margin', getValue: r => r.grossMarginPct, isPercent: true }, + { label: '- Sales & Marketing', getValue: r => r.marketing, isNegative: true }, + { label: '- Research & Development', getValue: r => r.rAndD, isNegative: true }, + { label: '- General & Administrative', getValue: r => r.sga, isNegative: true }, + { label: '- Depreciation & Amortization', getValue: r => r.depreciation, isNegative: true }, + { label: '= Operating Income (EBIT)', getValue: r => r.ebit, isBold: true, isSeparator: true }, + { label: ' Operating Margin', getValue: r => r.revenue > 0 ? (r.ebit / r.revenue) * 100 : 0, isPercent: true }, + { label: '- Interest Expense', getValue: r => r.interestExpense, isNegative: true }, + { label: '= Income Before Tax (EBT)', getValue: r => r.ebit - r.interestExpense, isBold: true, isSeparator: true }, + { label: '- Income Tax', getValue: r => r.taxes, isNegative: true }, + { label: '= Net Income', getValue: r => r.netIncome, isBold: true, isSeparator: true }, + { label: 'Customers (Year End)', getValue: r => r.customers }, + { label: 'Employees', getValue: r => r.employees }, + ] + + const lineItems = standard === 'hgb' ? hgbLineItems : usgaapLineItems + + return ( + + + + + + {rows.map(r => ( + + ))} + + + + {lineItems.map((item, idx) => ( + + + {rows.map(r => { + const val = item.getValue(r) + return ( + + ) + })} + + ))} + +
+ {standard === 'hgb' + ? 'GuV-Position (HGB)' + : 'P&L Line Item (US GAAP)'} + + {r.year} +
+ {item.label} + 0 && (item.label.includes('EBIT') || item.label.includes('Net Income') || item.label.includes('Jahresueberschuss') || item.label.includes('Betriebsergebnis')) ? 'text-emerald-400' : ''} + ${!item.isPercent && !item.isBold && !item.isSubRow ? 'text-white/60' : 'text-white'} + `} + > + {item.isPercent + ? `${val.toFixed(1)}%` + : (item.isNegative && val > 0 ? '-' : '') + fmt(Math.abs(val)) + } +
+
+ ) +} diff --git a/pitch-deck/components/ui/BrandName.tsx b/pitch-deck/components/ui/BrandName.tsx new file mode 100644 index 0000000..edc3a91 --- /dev/null +++ b/pitch-deck/components/ui/BrandName.tsx @@ -0,0 +1,19 @@ +'use client' + +interface BrandNameProps { + className?: string + prefix?: boolean +} + +/** + * Renders "ComplAI" (or "BreakPilot ComplAI") with the "AI" portion + * styled as a gradient to visually distinguish lowercase-L from uppercase-I. + */ +export default function BrandName({ className = '', prefix = false }: BrandNameProps) { + return ( + + {prefix && <>BreakPilot } + ComplAI + + ) +} diff --git a/pitch-deck/components/ui/FeatureMatrix.tsx b/pitch-deck/components/ui/FeatureMatrix.tsx index 1e87cf8..b64c7d0 100644 --- a/pitch-deck/components/ui/FeatureMatrix.tsx +++ b/pitch-deck/components/ui/FeatureMatrix.tsx @@ -3,6 +3,7 @@ import { motion } from 'framer-motion' import { PitchFeature, Language } from '@/lib/types' import { Check, X, Star } from 'lucide-react' +import BrandName from './BrandName' interface FeatureMatrixProps { features: PitchFeature[] @@ -34,7 +35,7 @@ export default function FeatureMatrix({ features, lang }: FeatureMatrixProps) { Feature - ComplAI + Proliance DataGuard heyData diff --git a/pitch-deck/components/ui/FinancialChart.tsx b/pitch-deck/components/ui/FinancialChart.tsx index f843c6a..a53456b 100644 --- a/pitch-deck/components/ui/FinancialChart.tsx +++ b/pitch-deck/components/ui/FinancialChart.tsx @@ -1,10 +1,7 @@ 'use client' -import { PitchFinancial, Language } from '@/lib/types' -import { t } from '@/lib/i18n' +import { FMResult, FMComputeResponse } from '@/lib/types' import { - BarChart, - Bar, XAxis, YAxis, Tooltip, @@ -12,59 +9,190 @@ import { Line, ComposedChart, Area, + ReferenceLine, + Brush, } from 'recharts' interface FinancialChartProps { - financials: PitchFinancial[] - lang: Language - growthMultiplier?: number + activeResults: FMComputeResponse | null + compareResults?: Map + compareMode?: boolean + scenarioColors?: Record + lang: 'de' | 'en' } -export default function FinancialChart({ financials, lang, growthMultiplier = 1 }: FinancialChartProps) { - const i = t(lang) +export default function FinancialChart({ + activeResults, + compareResults, + compareMode = false, + scenarioColors = {}, + lang, +}: FinancialChartProps) { + if (!activeResults) { + return ( +
+ {lang === 'de' ? 'Lade Daten...' : 'Loading data...'} +
+ ) + } - const data = financials.map((f) => ({ - year: f.year, - [i.financials.revenue]: Math.round(f.revenue_eur * growthMultiplier), - [i.financials.costs]: f.costs_eur, - [i.financials.customers]: Math.round(f.customers_count * growthMultiplier), - })) + const results = activeResults.results + const breakEvenMonth = activeResults.summary.break_even_month + + // Build chart data — monthly + const data = results.map((r) => { + const entry: Record = { + label: `${r.year.toString().slice(2)}/${String(r.month_in_year).padStart(2, '0')}`, + month: r.month, + revenue: Math.round(r.revenue_eur), + costs: Math.round(r.total_costs_eur), + customers: r.total_customers, + cashBalance: Math.round(r.cash_balance_eur), + } + + // Add compare scenario data + if (compareMode && compareResults) { + compareResults.forEach((cr, scenarioId) => { + const crMonth = cr.results.find(m => m.month === r.month) + if (crMonth) { + entry[`revenue_${scenarioId}`] = Math.round(crMonth.revenue_eur) + entry[`customers_${scenarioId}`] = crMonth.total_customers + } + }) + } + + return entry + }) const formatValue = (value: number) => { - if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M` - if (value >= 1_000) return `${(value / 1_000).toFixed(0)}k` + if (Math.abs(value) >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M` + if (Math.abs(value) >= 1_000) return `${(value / 1_000).toFixed(0)}k` return value.toString() } return (
- + - - - + + + - - - + + + - - + + + + + { + const label = name === 'revenue' ? (lang === 'de' ? 'Umsatz' : 'Revenue') + : name === 'costs' ? (lang === 'de' ? 'Kosten' : 'Costs') + : name === 'customers' ? (lang === 'de' ? 'Kunden' : 'Customers') + : name === 'cashBalance' ? 'Cash' + : name + return [name === 'customers' ? value : formatValue(value) + ' EUR', label] }} - formatter={(value: number) => formatValue(value) + ' EUR'} /> - - - + + {/* Break-even reference line */} + {breakEvenMonth && ( + + )} + + {/* Revenue area */} + + + {/* Cost area */} + + + {/* Customers line */} + + + {/* Compare mode: overlay other scenarios */} + {compareMode && compareResults && Array.from(compareResults.entries()).map(([scenarioId]) => ( + + ))} + + {/* Brush for zooming */} +
diff --git a/pitch-deck/components/ui/FinancialSliders.tsx b/pitch-deck/components/ui/FinancialSliders.tsx index 5211da9..23ba225 100644 --- a/pitch-deck/components/ui/FinancialSliders.tsx +++ b/pitch-deck/components/ui/FinancialSliders.tsx @@ -1,52 +1,63 @@ 'use client' -import { Language } from '@/lib/types' -import { t } from '@/lib/i18n' +import { useState } from 'react' +import { motion, AnimatePresence } from 'framer-motion' +import { ChevronDown, ChevronRight } from 'lucide-react' +import { FMAssumption, Language } from '@/lib/types' interface FinancialSlidersProps { - growthRate: number - churnRate: number - arpu: number - onGrowthChange: (v: number) => void - onChurnChange: (v: number) => void - onArpuChange: (v: number) => void + assumptions: FMAssumption[] + onAssumptionChange: (key: string, value: number) => void lang: Language } function Slider({ - label, - value, - min, - max, - step, - unit, + assumption, onChange, + lang, }: { - label: string - value: number - min: number - max: number - step: number - unit: string - onChange: (v: number) => void + assumption: FMAssumption + onChange: (value: number) => void + lang: Language }) { + const value = typeof assumption.value === 'number' ? assumption.value : Number(assumption.value) + const label = lang === 'de' ? assumption.label_de : assumption.label_en + + if (assumption.value_type === 'step') { + // Display step values as read-only list + const steps = Array.isArray(assumption.value) ? assumption.value : [] + return ( +
+

{label}

+
+ {steps.map((s: number, i: number) => ( +
+

Y{i + 1}

+

{s}

+
+ ))} +
+
+ ) + } + return ( -
-
- {label} - {value}{unit} +
+
+ {label} + {value}{assumption.unit === 'EUR' ? ' EUR' : assumption.unit === '%' ? '%' : ` ${assumption.unit || ''}`}
onChange(parseFloat(e.target.value))} className="w-full h-1.5 bg-white/10 rounded-full appearance-none cursor-pointer [&::-webkit-slider-thumb]:appearance-none - [&::-webkit-slider-thumb]:w-4 - [&::-webkit-slider-thumb]:h-4 + [&::-webkit-slider-thumb]:w-3.5 + [&::-webkit-slider-thumb]:h-3.5 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-indigo-500 [&::-webkit-slider-thumb]:shadow-lg @@ -58,47 +69,76 @@ function Slider({ ) } -export default function FinancialSliders({ - growthRate, - churnRate, - arpu, - onGrowthChange, - onChurnChange, - onArpuChange, - lang, -}: FinancialSlidersProps) { - const i = t(lang) +interface CategoryGroup { + key: string + label: string + items: FMAssumption[] +} + +export default function FinancialSliders({ assumptions, onAssumptionChange, lang }: FinancialSlidersProps) { + const [openCategories, setOpenCategories] = useState>(new Set(['revenue'])) + + // Group assumptions by category + const categories: CategoryGroup[] = [ + { key: 'revenue', label: lang === 'de' ? 'Revenue' : 'Revenue', items: [] }, + { key: 'costs', label: lang === 'de' ? 'Kosten' : 'Costs', items: [] }, + { key: 'team', label: 'Team', items: [] }, + { key: 'admin', label: lang === 'de' ? 'Verwaltung' : 'Administration', items: [] }, + { key: 'funding', label: 'Funding', items: [] }, + ] + + for (const a of assumptions) { + const cat = categories.find(c => c.key === a.category) || categories[0] + cat.items.push(a) + } + + const toggleCategory = (key: string) => { + setOpenCategories(prev => { + const next = new Set(prev) + if (next.has(key)) next.delete(key) + else next.add(key) + return next + }) + } return ( -
-

{i.financials.adjustAssumptions}

- - - +
+ {categories.filter(c => c.items.length > 0).map((cat) => { + const isOpen = openCategories.has(cat.key) + return ( +
+ + + {isOpen && ( + +
+ {cat.items.map((a) => ( + onAssumptionChange(a.key, val)} + lang={lang} + /> + ))} +
+
+ )} +
+
+ ) + })}
) } diff --git a/pitch-deck/components/ui/KPICard.tsx b/pitch-deck/components/ui/KPICard.tsx new file mode 100644 index 0000000..28f7768 --- /dev/null +++ b/pitch-deck/components/ui/KPICard.tsx @@ -0,0 +1,59 @@ +'use client' + +import { motion } from 'framer-motion' +import { TrendingUp, TrendingDown } from 'lucide-react' +import AnimatedCounter from './AnimatedCounter' + +interface KPICardProps { + label: string + value: number + prefix?: string + suffix?: string + decimals?: number + trend?: 'up' | 'down' | 'neutral' + color?: string + delay?: number + subLabel?: string +} + +export default function KPICard({ + label, + value, + prefix = '', + suffix = '', + decimals = 0, + trend = 'neutral', + color = '#6366f1', + delay = 0, + subLabel, +}: KPICardProps) { + return ( + + {/* Glow effect */} +
+ +

{label}

+
+

+ +

+ {trend !== 'neutral' && ( + + {trend === 'up' ? : } + + )} +
+ {subLabel && ( +

{subLabel}

+ )} + + ) +} diff --git a/pitch-deck/components/ui/RunwayGauge.tsx b/pitch-deck/components/ui/RunwayGauge.tsx new file mode 100644 index 0000000..19f186f --- /dev/null +++ b/pitch-deck/components/ui/RunwayGauge.tsx @@ -0,0 +1,133 @@ +'use client' + +import { motion } from 'framer-motion' +import { useEffect, useState } from 'react' + +interface RunwayGaugeProps { + months: number + maxMonths?: number + size?: number + label?: string +} + +export default function RunwayGauge({ months, maxMonths = 36, size = 140, label = 'Runway' }: RunwayGaugeProps) { + const [animatedAngle, setAnimatedAngle] = useState(0) + const clampedMonths = Math.min(months, maxMonths) + const targetAngle = (clampedMonths / maxMonths) * 270 - 135 // -135 to +135 degrees + + useEffect(() => { + const timer = setTimeout(() => setAnimatedAngle(targetAngle), 100) + return () => clearTimeout(timer) + }, [targetAngle]) + + // Color based on runway + const getColor = () => { + if (months >= 18) return '#22c55e' // green + if (months >= 12) return '#eab308' // yellow + if (months >= 6) return '#f97316' // orange + return '#ef4444' // red + } + + const color = getColor() + const cx = size / 2 + const cy = size / 2 + const radius = (size / 2) - 16 + const needleLength = radius - 10 + + // Arc path for gauge background + const startAngle = -135 + const endAngle = 135 + const polarToCartesian = (cx: number, cy: number, r: number, deg: number) => { + const rad = (deg - 90) * Math.PI / 180 + return { x: cx + r * Math.cos(rad), y: cy + r * Math.sin(rad) } + } + + const arcStart = polarToCartesian(cx, cy, radius, startAngle) + const arcEnd = polarToCartesian(cx, cy, radius, endAngle) + const arcPath = `M ${arcStart.x} ${arcStart.y} A ${radius} ${radius} 0 1 1 ${arcEnd.x} ${arcEnd.y}` + + // Filled arc + const filledEnd = polarToCartesian(cx, cy, radius, Math.min(animatedAngle, endAngle)) + const largeArc = (animatedAngle - startAngle) > 180 ? 1 : 0 + const filledPath = `M ${arcStart.x} ${arcStart.y} A ${radius} ${radius} 0 ${largeArc} 1 ${filledEnd.x} ${filledEnd.y}` + + // Needle endpoint + const needleRad = (animatedAngle - 90) * Math.PI / 180 + const needleX = cx + needleLength * Math.cos(needleRad) + const needleY = cy + needleLength * Math.sin(needleRad) + + const shouldPulse = months < 6 + + return ( + +
+ + {/* Background arc */} + + + {/* Filled arc */} + + + {/* Tick marks */} + {[0, 6, 12, 18, 24, 30, 36].map((tick) => { + const tickAngle = (tick / maxMonths) * 270 - 135 + const inner = polarToCartesian(cx, cy, radius - 12, tickAngle) + const outer = polarToCartesian(cx, cy, radius - 6, tickAngle) + return ( + + + + {tick} + + + ) + })} + + {/* Needle */} + + + {/* Center circle */} + + + +
+ +
+

{Math.round(months)}

+

{label}

+
+
+ ) +} diff --git a/pitch-deck/components/ui/ScenarioSwitcher.tsx b/pitch-deck/components/ui/ScenarioSwitcher.tsx new file mode 100644 index 0000000..07d4991 --- /dev/null +++ b/pitch-deck/components/ui/ScenarioSwitcher.tsx @@ -0,0 +1,63 @@ +'use client' + +import { FMScenario } from '@/lib/types' +import { motion } from 'framer-motion' + +interface ScenarioSwitcherProps { + scenarios: FMScenario[] + activeId: string | null + compareMode: boolean + onSelect: (id: string) => void + onToggleCompare: () => void + lang: 'de' | 'en' +} + +export default function ScenarioSwitcher({ + scenarios, + activeId, + compareMode, + onSelect, + onToggleCompare, + lang, +}: ScenarioSwitcherProps) { + return ( +
+
+

+ {lang === 'de' ? 'Szenarien' : 'Scenarios'} +

+ +
+ +
+ {scenarios.map((s) => ( + onSelect(s.id)} + className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs transition-all + ${activeId === s.id + ? 'bg-white/[0.12] border border-white/20 text-white' + : 'bg-white/[0.04] border border-white/10 text-white/50 hover:text-white/70' + }`} + > + + {s.name} + + ))} +
+
+ ) +} diff --git a/pitch-deck/components/ui/UnitEconomicsCards.tsx b/pitch-deck/components/ui/UnitEconomicsCards.tsx new file mode 100644 index 0000000..9fb336a --- /dev/null +++ b/pitch-deck/components/ui/UnitEconomicsCards.tsx @@ -0,0 +1,93 @@ +'use client' + +import { motion } from 'framer-motion' +import AnimatedCounter from './AnimatedCounter' + +interface UnitEconomicsCardsProps { + cac: number + ltv: number + ltvCacRatio: number + grossMargin: number + churnRate: number + lang: 'de' | 'en' +} + +function MiniRing({ progress, color, size = 32 }: { progress: number; color: string; size?: number }) { + const radius = (size / 2) - 3 + const circumference = 2 * Math.PI * radius + const offset = circumference - (Math.min(progress, 100) / 100) * circumference + + return ( + + + + + ) +} + +export default function UnitEconomicsCards({ cac, ltv, ltvCacRatio, grossMargin, churnRate, lang }: UnitEconomicsCardsProps) { + const cacPayback = cac > 0 ? Math.ceil(cac / ((ltv / (1 / (churnRate / 100))) || 1)) : 0 + + const cards = [ + { + label: 'CAC Payback', + value: cacPayback, + suffix: lang === 'de' ? ' Mo.' : ' mo.', + ring: Math.min((cacPayback / 12) * 100, 100), + color: cacPayback <= 6 ? '#22c55e' : cacPayback <= 12 ? '#eab308' : '#ef4444', + sub: `CAC: ${cac.toLocaleString('de-DE')} EUR`, + }, + { + label: 'LTV', + value: Math.round(ltv), + suffix: ' EUR', + ring: Math.min(ltvCacRatio * 10, 100), + color: ltvCacRatio >= 3 ? '#22c55e' : ltvCacRatio >= 1.5 ? '#eab308' : '#ef4444', + sub: `LTV/CAC: ${ltvCacRatio.toFixed(1)}x`, + }, + { + label: 'Gross Margin', + value: grossMargin, + suffix: '%', + ring: grossMargin, + color: grossMargin >= 70 ? '#22c55e' : grossMargin >= 50 ? '#eab308' : '#ef4444', + sub: `Churn: ${churnRate}%`, + }, + ] + + return ( +
+ {cards.map((card, i) => ( + +
+ +
+

+ +

+

{card.label}

+

{card.sub}

+
+ ))} +
+ ) +} diff --git a/pitch-deck/components/ui/WaterfallChart.tsx b/pitch-deck/components/ui/WaterfallChart.tsx new file mode 100644 index 0000000..3cb35ae --- /dev/null +++ b/pitch-deck/components/ui/WaterfallChart.tsx @@ -0,0 +1,85 @@ +'use client' + +import { FMResult } from '@/lib/types' +import { + BarChart, + Bar, + XAxis, + YAxis, + Tooltip, + ResponsiveContainer, + ReferenceLine, + Cell, +} from 'recharts' + +interface WaterfallChartProps { + results: FMResult[] + lang: 'de' | 'en' +} + +export default function WaterfallChart({ results, lang }: WaterfallChartProps) { + // Sample quarterly data for cleaner display + const quarterlyData = results.filter((_, i) => i % 3 === 0).map((r) => { + const netCash = r.revenue_eur - r.total_costs_eur + return { + label: `${r.year.toString().slice(2)}/Q${Math.ceil(r.month_in_year / 3)}`, + month: r.month, + revenue: Math.round(r.revenue_eur), + costs: Math.round(-r.total_costs_eur), + net: Math.round(netCash), + cashBalance: Math.round(r.cash_balance_eur), + } + }) + + const formatValue = (value: number) => { + if (Math.abs(value) >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M` + if (Math.abs(value) >= 1_000) return `${(value / 1_000).toFixed(0)}k` + return value.toString() + } + + return ( +
+ + + + + [ + formatValue(value) + ' EUR', + name === 'revenue' ? (lang === 'de' ? 'Umsatz' : 'Revenue') + : name === 'costs' ? (lang === 'de' ? 'Kosten' : 'Costs') + : 'Net', + ]} + /> + + + {quarterlyData.map((entry, i) => ( + + ))} + + + {quarterlyData.map((entry, i) => ( + + ))} + + + +
+ ) +} diff --git a/pitch-deck/lib/hooks/useFinancialModel.ts b/pitch-deck/lib/hooks/useFinancialModel.ts new file mode 100644 index 0000000..051e00e --- /dev/null +++ b/pitch-deck/lib/hooks/useFinancialModel.ts @@ -0,0 +1,109 @@ +'use client' + +import { useState, useEffect, useCallback, useRef } from 'react' +import { FMScenario, FMResult, FMComputeResponse } from '../types' + +export function useFinancialModel() { + const [scenarios, setScenarios] = useState([]) + const [activeScenarioId, setActiveScenarioId] = useState(null) + const [compareMode, setCompareMode] = useState(false) + const [results, setResults] = useState>(new Map()) + const [loading, setLoading] = useState(true) + const [computing, setComputing] = useState(false) + const computeTimer = useRef(null) + + // Load scenarios on mount + useEffect(() => { + async function load() { + try { + const res = await fetch('/api/financial-model') + if (res.ok) { + const data: FMScenario[] = await res.json() + setScenarios(data) + const defaultScenario = data.find(s => s.is_default) || data[0] + if (defaultScenario) { + setActiveScenarioId(defaultScenario.id) + } + } + } catch (err) { + console.error('Failed to load financial model:', err) + } finally { + setLoading(false) + } + } + load() + }, []) + + // Compute when active scenario changes + useEffect(() => { + if (activeScenarioId && !results.has(activeScenarioId)) { + compute(activeScenarioId) + } + }, [activeScenarioId]) // eslint-disable-line react-hooks/exhaustive-deps + + const compute = useCallback(async (scenarioId: string) => { + setComputing(true) + try { + const res = await fetch('/api/financial-model/compute', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ scenarioId }), + }) + if (res.ok) { + const data: FMComputeResponse = await res.json() + setResults(prev => new Map(prev).set(scenarioId, data)) + } + } catch (err) { + console.error('Compute failed:', err) + } finally { + setComputing(false) + } + }, []) + + const updateAssumption = useCallback(async (scenarioId: string, key: string, value: number | number[]) => { + // Optimistic update in local state + setScenarios(prev => prev.map(s => { + if (s.id !== scenarioId) return s + return { + ...s, + assumptions: s.assumptions.map(a => a.key === key ? { ...a, value } : a), + } + })) + + // Save to DB + await fetch('/api/financial-model/assumptions', { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ scenarioId, key, value }), + }) + + // Debounced recompute + if (computeTimer.current) clearTimeout(computeTimer.current) + computeTimer.current = setTimeout(() => compute(scenarioId), 300) + }, [compute]) + + const computeAll = useCallback(async () => { + for (const s of scenarios) { + await compute(s.id) + } + }, [scenarios, compute]) + + const activeScenario = scenarios.find(s => s.id === activeScenarioId) || null + const activeResults = activeScenarioId ? results.get(activeScenarioId) || null : null + + return { + scenarios, + activeScenario, + activeScenarioId, + setActiveScenarioId, + activeResults, + results, + loading, + computing, + compareMode, + setCompareMode, + compute, + computeAll, + updateAssumption, + } +} diff --git a/pitch-deck/lib/hooks/useSlideNavigation.ts b/pitch-deck/lib/hooks/useSlideNavigation.ts index fb26863..a4d6fb2 100644 --- a/pitch-deck/lib/hooks/useSlideNavigation.ts +++ b/pitch-deck/lib/hooks/useSlideNavigation.ts @@ -14,9 +14,24 @@ const SLIDE_ORDER: SlideId[] = [ 'traction', 'competition', 'team', + 'technology', 'financials', 'the-ask', 'ai-qa', + 'appendix', + 'annex-infra', + 'annex-ai-stack', + 'annex-rag', + 'annex-security', + 'annex-devops', + 'annex-agent-arch', + 'annex-agent-rag', + 'annex-agent-workflow', + 'annex-usp-overview', + 'annex-usp-comparison', + 'annex-usp-moat', + 'annex-roadmap-2027', + 'annex-roadmap-2028', ] export const TOTAL_SLIDES = SLIDE_ORDER.length diff --git a/pitch-deck/lib/i18n.ts b/pitch-deck/lib/i18n.ts index 64a8dec..24f849a 100644 --- a/pitch-deck/lib/i18n.ts +++ b/pitch-deck/lib/i18n.ts @@ -18,13 +18,28 @@ const translations = { 'Traction', 'Wettbewerb', 'Team', + 'Technologie', 'Finanzen', 'The Ask', 'KI Q&A', + 'Appendix', + 'Infrastruktur', + 'KI-Stack', + 'RAG Pipeline', + 'Sicherheit', + 'DevOps & CI/CD', + 'Agent Architektur', + 'Rechtsdokumente', + 'Compliance Workflow', + '5 USPs', + 'Wettbewerbsvergleich', + 'Marktposition', + 'Roadmap 2027', + 'Roadmap 2028', ], cover: { tagline: 'Datensouveraenitaet meets KI-Compliance', - subtitle: 'Pre-Seed · Q4 2026', + subtitle: 'Seed · 2026-2027', cta: 'Pitch starten', }, problem: { @@ -148,6 +163,46 @@ const translations = { equity: 'Equity', expertise: 'Expertise', }, + technology: { + title: 'Technologie-Roadmap', + subtitle: 'Von MVP zu Full Autonomy — 5-Jahres-Technologieplan', + timelineTitle: 'Meilensteine 2026-2030', + stackTitle: 'Tech-Stack', + phases: [ + { + year: '2026', + phase: 'Foundation & MVP', + techs: ['Self-Hosted LLM (32B)', 'Apple M2 Mini', 'Basic Compliance SDK', 'OCR Pipeline'], + }, + { + year: '2027', + phase: 'Product-Market Fit', + techs: ['Multi-Model Router', 'RAG 2.0 mit Qdrant', 'Auto Compliance-Scan', 'Echtzeit-Monitoring'], + }, + { + year: '2028', + phase: 'Enterprise Scale', + techs: ['Federated Learning', 'Cluster-Management', 'API Marketplace', 'Multi-Tenant'], + }, + { + year: '2029', + phase: 'AI Platform', + techs: ['Custom Fine-Tuned (40B+)', 'Predictive Compliance', 'Auto-Audits', 'Partner-Integrationen'], + }, + { + year: '2030', + phase: 'Full Autonomy', + techs: ['Agent-Netzwerk', 'Self-Healing', 'Zero-Day Compliance', '1000B Parameter'], + }, + ], + layers: [ + { name: 'Application', techs: 'Next.js, React, TailwindCSS, REST/GraphQL API' }, + { name: 'AI/ML', techs: 'Qwen 32B → 1000B, RAG Pipeline, NLP, OCR (PaddleOCR)' }, + { name: 'Infrastructure', techs: 'Apple Silicon (M2/M4 Pro), Docker, Self-Hosted' }, + { name: 'Security', techs: 'BSI-TR-03161, E2E Verschluesselung, Vault' }, + { name: 'Data', techs: 'PostgreSQL + PostGIS, Qdrant (Vektoren), MinIO (S3)' }, + ], + }, financials: { title: 'Finanzprognose', subtitle: 'AI-First Kostenstruktur — skaliert ohne lineares Personalwachstum', @@ -166,16 +221,17 @@ const translations = { }, theAsk: { title: 'The Ask', - subtitle: 'Pre-Seed Finanzierung', + subtitle: 'Gestaffelte Finanzierung 2026-2027', amount: 'Funding', instrument: 'Instrument', useOfFunds: 'Use of Funds', engineering: 'Engineering', sales: 'Vertrieb', hardware: 'Hardware', + personnel: 'Personal', legal: 'Legal', reserve: 'Reserve', - targetDate: 'Zieldatum', + targetDate: 'Zeitraum', }, aiqa: { title: 'Fragen? Die KI antwortet.', @@ -208,13 +264,28 @@ const translations = { 'Traction', 'Competition', 'Team', + 'Technology', 'Financials', 'The Ask', 'AI Q&A', + 'Appendix', + 'Infrastructure', + 'AI Stack', + 'RAG Pipeline', + 'Security', + 'DevOps & CI/CD', + 'Agent Architecture', + 'Legal Documents', + 'Compliance Workflow', + '5 USPs', + 'Competitor Comparison', + 'Market Position', + 'Roadmap 2027', + 'Roadmap 2028', ], cover: { tagline: 'Data Sovereignty meets AI Compliance', - subtitle: 'Pre-Seed · Q4 2026', + subtitle: 'Seed · 2026-2027', cta: 'Start Pitch', }, problem: { @@ -338,6 +409,46 @@ const translations = { equity: 'Equity', expertise: 'Expertise', }, + technology: { + title: 'Technology Roadmap', + subtitle: 'From MVP to Full Autonomy — 5-Year Technology Plan', + timelineTitle: 'Milestones 2026-2030', + stackTitle: 'Tech Stack', + phases: [ + { + year: '2026', + phase: 'Foundation & MVP', + techs: ['Self-Hosted LLM (32B)', 'Apple M2 Mini', 'Basic Compliance SDK', 'OCR Pipeline'], + }, + { + year: '2027', + phase: 'Product-Market Fit', + techs: ['Multi-Model Router', 'RAG 2.0 with Qdrant', 'Auto Compliance Scan', 'Real-time Monitoring'], + }, + { + year: '2028', + phase: 'Enterprise Scale', + techs: ['Federated Learning', 'Cluster Management', 'API Marketplace', 'Multi-Tenant'], + }, + { + year: '2029', + phase: 'AI Platform', + techs: ['Custom Fine-Tuned (40B+)', 'Predictive Compliance', 'Auto-Audits', 'Partner Integrations'], + }, + { + year: '2030', + phase: 'Full Autonomy', + techs: ['Agent Network', 'Self-Healing', 'Zero-Day Compliance', '1000B Parameters'], + }, + ], + layers: [ + { name: 'Application', techs: 'Next.js, React, TailwindCSS, REST/GraphQL API' }, + { name: 'AI/ML', techs: 'Qwen 32B → 1000B, RAG Pipeline, NLP, OCR (PaddleOCR)' }, + { name: 'Infrastructure', techs: 'Apple Silicon (M2/M4 Pro), Docker, Self-Hosted' }, + { name: 'Security', techs: 'BSI-TR-03161, E2E Encryption, Vault' }, + { name: 'Data', techs: 'PostgreSQL + PostGIS, Qdrant (Vectors), MinIO (S3)' }, + ], + }, financials: { title: 'Financial Projections', subtitle: 'AI-First cost structure — scales without linear headcount growth', @@ -356,16 +467,17 @@ const translations = { }, theAsk: { title: 'The Ask', - subtitle: 'Pre-Seed Funding', + subtitle: 'Staged Funding 2026-2027', amount: 'Funding', instrument: 'Instrument', useOfFunds: 'Use of Funds', engineering: 'Engineering', sales: 'Sales', hardware: 'Hardware', + personnel: 'Personnel', legal: 'Legal', reserve: 'Reserve', - targetDate: 'Target Date', + targetDate: 'Timeline', }, aiqa: { title: 'Questions? The AI answers.', diff --git a/pitch-deck/lib/types.ts b/pitch-deck/lib/types.ts index 045298a..31cf342 100644 --- a/pitch-deck/lib/types.ts +++ b/pitch-deck/lib/types.ts @@ -127,6 +127,85 @@ export interface PitchData { products: PitchProduct[] } +// Financial Model Types +export interface FMScenario { + id: string + name: string + description: string + is_default: boolean + color: string + assumptions: FMAssumption[] +} + +export interface FMAssumption { + id: string + scenario_id: string + key: string + label_de: string + label_en: string + value: number | number[] + value_type: 'scalar' | 'step' | 'timeseries' + unit: string + min_value: number | null + max_value: number | null + step_size: number | null + category: string + sort_order: number +} + +export interface FMResult { + month: number + year: number + month_in_year: number + new_customers: number + churned_customers: number + total_customers: number + mrr_eur: number + arr_eur: number + revenue_eur: number + cogs_eur: number + personnel_eur: number + infra_eur: number + marketing_eur: number + total_costs_eur: number + employees_count: number + gross_margin_pct: number + burn_rate_eur: number + runway_months: number + cac_eur: number + ltv_eur: number + ltv_cac_ratio: number + cash_balance_eur: number + cumulative_revenue_eur: number + // Detail costs + admin_costs_eur: number + office_costs_eur: number + founding_costs_eur: number + ihk_eur: number + depreciation_eur: number + interest_expense_eur: number + taxes_eur: number + net_income_eur: number + ebit_eur: number + software_licenses_eur: number + travel_costs_eur: number + funding_eur: number +} + +export interface FMComputeResponse { + scenario_id: string + results: FMResult[] + summary: { + final_arr: number + final_customers: number + break_even_month: number | null + final_runway: number + final_ltv_cac: number + peak_burn: number + total_funding_needed: number + } +} + export type Language = 'de' | 'en' export interface ChatMessage { @@ -145,6 +224,21 @@ export type SlideId = | 'traction' | 'competition' | 'team' + | 'technology' | 'financials' | 'the-ask' | 'ai-qa' + | 'appendix' + | 'annex-infra' + | 'annex-ai-stack' + | 'annex-rag' + | 'annex-security' + | 'annex-devops' + | 'annex-agent-arch' + | 'annex-agent-rag' + | 'annex-agent-workflow' + | 'annex-usp-overview' + | 'annex-usp-comparison' + | 'annex-usp-moat' + | 'annex-roadmap-2027' + | 'annex-roadmap-2028'