This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/pitch-deck/components/ui/RunwayGauge.tsx
BreakPilot Dev b464366341
Some checks failed
ci/woodpecker/push/integration Pipeline failed
ci/woodpecker/push/main Pipeline failed
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
feat: Add staged funding model, financial compute engine, annex slides and UI enhancements
Restructure financial plan from single 200k SAFE to realistic staged funding
(25k Stammkapital, 25k Angel, 200k Wandeldarlehen, 1M Series A = 1.25M total).
Add 60-month compute engine with CAPEX/OPEX accounting, cash constraints,
hardware financing (30% upfront / 70% leasing), and revenue-based hiring caps.
Rebuild TheAskSlide with 4-event funding timeline, update i18n (DE/EN),
chat agent core messages, and add 15 new annex/technology slides with
supporting UI components (KPICard, RunwayGauge, WaterfallChart, etc.).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 21:20:02 +01:00

134 lines
4.5 KiB
TypeScript

'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 (
<motion.div
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.6, delay: 0.3 }}
className="flex flex-col items-center"
>
<div className={`relative ${shouldPulse ? 'animate-pulse' : ''}`} style={{ width: size, height: size * 0.8 }}>
<svg width={size} height={size * 0.8} viewBox={`0 0 ${size} ${size * 0.8}`}>
{/* Background arc */}
<path d={arcPath} fill="none" stroke="rgba(255,255,255,0.1)" strokeWidth="8" strokeLinecap="round" />
{/* Filled arc */}
<motion.path
d={filledPath}
fill="none"
stroke={color}
strokeWidth="8"
strokeLinecap="round"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 1.5, ease: 'easeOut' }}
/>
{/* 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 (
<g key={tick}>
<line x1={inner.x} y1={inner.y} x2={outer.x} y2={outer.y} stroke="rgba(255,255,255,0.3)" strokeWidth="1.5" />
<text
x={polarToCartesian(cx, cy, radius - 22, tickAngle).x}
y={polarToCartesian(cx, cy, radius - 22, tickAngle).y}
fill="rgba(255,255,255,0.3)"
fontSize="8"
textAnchor="middle"
dominantBaseline="central"
>
{tick}
</text>
</g>
)
})}
{/* Needle */}
<motion.line
x1={cx}
y1={cy}
x2={needleX}
y2={needleY}
stroke="white"
strokeWidth="2"
strokeLinecap="round"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.5 }}
/>
{/* Center circle */}
<circle cx={cx} cy={cy} r="4" fill={color} />
<circle cx={cx} cy={cy} r="2" fill="white" />
</svg>
</div>
<div className="text-center -mt-2">
<p className="text-lg font-bold" style={{ color }}>{Math.round(months)}</p>
<p className="text-[10px] text-white/40 uppercase tracking-wider">{label}</p>
</div>
</motion.div>
)
}