feat: Add DevSecOps tools, Woodpecker proxy, Vault persistent storage, pitch-deck annex slides
All checks were successful
CI / test-bqas (push) Successful in 32s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 46s
CI / test-python-voice (push) Successful in 38s
All checks were successful
CI / test-bqas (push) Successful in 32s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 46s
CI / test-python-voice (push) Successful in 38s
- Install Gitleaks, Trivy, Grype, Syft, Semgrep, Bandit in backend-core Dockerfile - Add Woodpecker SQLite proxy API (fallback without API token) - Mount woodpecker_data volume read-only to backend-core - Add backend proxy fallback in admin-core Woodpecker route - Add Vault file-based persistent storage (config.hcl, init-vault.sh) - Auto-init, unseal and root-token persistence for Vault - Add 6 pitch-deck annex slides (Assumptions, Architecture, GTM, Regulatory, Engineering, AI Pipeline) - Dynamic margin/amortization KPIs in BusinessModelSlide - Market sources modal with citations in MarketSlide - Redesign nginx landing page to 3-column layout (Lehrer/Compliance/Core) - Extend MkDocs nav with Services and SDK documentation sections - Add SDK Protection architecture doc Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,9 +17,32 @@ interface TheAskSlideProps {
|
||||
|
||||
const COLORS = ['#6366f1', '#a78bfa', '#60a5fa', '#34d399', '#fbbf24']
|
||||
|
||||
function formatFundingAmount(amount: number): { target: number; suffix: string } {
|
||||
if (amount >= 1_000_000) {
|
||||
return { target: Math.round(amount / 100_000) / 10, suffix: ' Mio.' }
|
||||
}
|
||||
if (amount >= 1_000) {
|
||||
return { target: Math.round(amount / 1_000), suffix: 'k' }
|
||||
}
|
||||
return { target: amount, suffix: '' }
|
||||
}
|
||||
|
||||
function formatTargetDate(dateStr: string, lang: Language): string {
|
||||
if (!dateStr) return 'TBD'
|
||||
try {
|
||||
const d = new Date(dateStr)
|
||||
const quarter = Math.ceil((d.getMonth() + 1) / 3)
|
||||
return `Q${quarter} ${d.getFullYear()}`
|
||||
} catch {
|
||||
return dateStr
|
||||
}
|
||||
}
|
||||
|
||||
export default function TheAskSlide({ lang, funding }: TheAskSlideProps) {
|
||||
const i = t(lang)
|
||||
const useOfFunds = funding?.use_of_funds || []
|
||||
const amount = funding?.amount_eur || 0
|
||||
const { target, suffix } = formatFundingAmount(amount)
|
||||
|
||||
const pieData = useOfFunds.map((item) => ({
|
||||
name: lang === 'de' ? item.label_de : item.label_en,
|
||||
@@ -35,7 +58,7 @@ export default function TheAskSlide({ lang, funding }: TheAskSlideProps) {
|
||||
<p className="text-lg text-white/50 max-w-2xl mx-auto">{i.theAsk.subtitle}</p>
|
||||
</FadeInView>
|
||||
|
||||
{/* Main Number */}
|
||||
{/* Main Number — dynamisch aus funding.amount_eur */}
|
||||
<FadeInView delay={0.2} className="text-center mb-10">
|
||||
<motion.div
|
||||
initial={{ scale: 0.5, opacity: 0 }}
|
||||
@@ -43,13 +66,13 @@ export default function TheAskSlide({ lang, funding }: TheAskSlideProps) {
|
||||
transition={{ delay: 0.4, type: 'spring', stiffness: 200 }}
|
||||
>
|
||||
<p className="text-6xl md:text-8xl font-bold text-white mb-2">
|
||||
<AnimatedCounter target={200} suffix="k" duration={2000} />
|
||||
<AnimatedCounter target={target} suffix={suffix} duration={2000} decimals={suffix === ' Mio.' ? 1 : 0} />
|
||||
<span className="text-3xl md:text-4xl text-white/50 ml-2">EUR</span>
|
||||
</p>
|
||||
</motion.div>
|
||||
</FadeInView>
|
||||
|
||||
{/* Details */}
|
||||
{/* Details — dynamisch aus funding-Objekt */}
|
||||
<div className="grid md:grid-cols-3 gap-4 mb-8">
|
||||
<GlassCard delay={0.5} className="text-center p-5">
|
||||
<FileText className="w-6 h-6 text-indigo-400 mx-auto mb-2" />
|
||||
@@ -59,12 +82,14 @@ export default function TheAskSlide({ lang, funding }: TheAskSlideProps) {
|
||||
<GlassCard delay={0.6} className="text-center p-5">
|
||||
<Calendar className="w-6 h-6 text-purple-400 mx-auto mb-2" />
|
||||
<p className="text-xs text-white/40 mb-1">{i.theAsk.targetDate}</p>
|
||||
<p className="text-lg font-bold text-white">Q3 2026</p>
|
||||
<p className="text-lg font-bold text-white">
|
||||
{formatTargetDate(funding?.target_date, lang)}
|
||||
</p>
|
||||
</GlassCard>
|
||||
<GlassCard delay={0.7} className="text-center p-5">
|
||||
<Target className="w-6 h-6 text-blue-400 mx-auto mb-2" />
|
||||
<p className="text-xs text-white/40 mb-1">{lang === 'de' ? 'Runway' : 'Runway'}</p>
|
||||
<p className="text-lg font-bold text-white">18 {lang === 'de' ? 'Monate' : 'Months'}</p>
|
||||
<p className="text-xs text-white/40 mb-1">{lang === 'de' ? 'Runde' : 'Round'}</p>
|
||||
<p className="text-lg font-bold text-white">{funding?.round_name || 'Pre-Seed'}</p>
|
||||
</GlassCard>
|
||||
</div>
|
||||
|
||||
@@ -114,7 +139,7 @@ export default function TheAskSlide({ lang, funding }: TheAskSlideProps) {
|
||||
</span>
|
||||
<span className="text-sm font-bold text-white">{item.percentage}%</span>
|
||||
<span className="text-xs text-white/30">
|
||||
{((funding.amount_eur * item.percentage) / 100).toLocaleString('de-DE')} EUR
|
||||
{((amount * item.percentage) / 100).toLocaleString('de-DE')} EUR
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user