Merge remote-tracking branch 'gitea/main'
Some checks failed
Build pitch-deck / build-push-deploy (push) Failing after 1m13s
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 49s
CI / test-python-voice (push) Successful in 38s
CI / test-bqas (push) Successful in 31s

# Conflicts:
#	pitch-deck/components/slides/MilestonesSlide.tsx
#	pitch-deck/lib/finanzplan/engine.ts
This commit is contained in:
Benjamin Admin
2026-04-27 13:14:54 +02:00
21 changed files with 624 additions and 354 deletions

View File

@@ -1,13 +1,8 @@
'use client'
import { useState, useEffect, useMemo } from 'react'
import { type Milestone, type StatItem, MILESTONES, TODAY_POSITION } from './MilestonesSlide.data'
import { type Theme } from './MilestonesSlide.themes'
const MONO: React.CSSProperties = {
fontFamily: '"JetBrains Mono","SF Mono",ui-monospace,monospace',
fontVariantNumeric: 'tabular-nums',
}
import { type Milestone, type StatItem, MILESTONES, STATS, TODAY_POSITION } from './MilestonesSlide.data'
import { type Theme, MONO } from './MilestonesSlide.themes'
// ── Star Field ────────────────────────────────────────────────────────────────
export function StarField() {
@@ -150,7 +145,7 @@ function MilestoneNode({ m, onClick, active, t, de }: {
const bgTopA = lit ? m.tint + t.cardTintTopH : m.tint + t.cardTintTop
const bgMidA = lit ? m.tint + t.cardTintMidH : m.tint + t.cardTintMid
const cardBg = `linear-gradient(180deg, ${bgTopA} 0%, ${bgMidA} 55%, ${t.cardBase}${lit ? t.cardBaseAH : t.cardBaseA})`
const badge = m.done ? (de ? 'erledigt' : 'done') : (m.next ? (de ? 'als nächstes' : 'next') : (de ? 'geplant' : 'plan'))
const badge = m.done ? (de ? 'erledigt' : 'done') : (m.next ? (de ? 'als n\u00e4chstes' : 'next') : (de ? 'geplant' : 'plan'))
return (
<>
@@ -175,7 +170,7 @@ function MilestoneNode({ m, onClick, active, t, de }: {
transition: 'all .25s',
transform: lit ? 'scale(1.15)' : 'scale(1)',
}}>
{m.done ? '' : (m.next ? '' : '')}
{m.done ? '\u2713' : (m.next ? '\u25c9' : '\u25cb')}
</div>
{/* card */}
@@ -224,7 +219,7 @@ function MilestoneNode({ m, onClick, active, t, de }: {
opacity: lit ? 1 : 0.55,
transform: `translateX(${lit ? 0 : -4}px)`,
transition: 'all .25s',
}}>{de ? 'Details ' : 'Details '}</span>
}}>{de ? 'Details \u2192' : 'Details \u2192'}</span>
</div>
</div>
</>
@@ -292,7 +287,7 @@ export function DetailModal({ item, onClose, t, de }: {
const tint = item.tint
const badge = item.done
? (de ? 'ABGESCHLOSSEN' : 'COMPLETED')
: (item.next ? (de ? 'ALS NÄCHSTES' : 'NEXT UP') : (de ? 'GEPLANT' : 'PLANNED'))
: (item.next ? (de ? 'ALS N\u00c4CHSTES' : 'NEXT UP') : (de ? 'GEPLANT' : 'PLANNED'))
const badgeColor = item.done ? t.done : tint
return (
@@ -319,7 +314,7 @@ export function DetailModal({ item, onClose, t, de }: {
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: t.key === 'light' ? tint : '#fff', fontSize: 17, fontWeight: 700,
boxShadow: `0 0 20px ${tint}66`,
}}>{item.done ? '' : (item.next ? '' : '')}</div>
}}>{item.done ? '\u2713' : (item.next ? '\u25c9' : '\u25cb')}</div>
<div style={{ flex: 1 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 4 }}>
<span style={{
@@ -337,7 +332,7 @@ export function DetailModal({ item, onClose, t, de }: {
<button onClick={onClose} style={{
background: 'transparent', border: `1px solid ${tint}66`, color: t.fg,
width: 32, height: 32, borderRadius: 8, cursor: 'pointer', fontSize: 14,
}}></button>
}}>{'\u2715'}</button>
</div>
<div style={{ fontSize: 13, lineHeight: 1.6, color: t.fgSoft, marginBottom: 16 }}>
{de ? item.body.de : item.body.en}
@@ -350,7 +345,7 @@ export function DetailModal({ item, onClose, t, de }: {
background: t.bulletBg, border: `1px solid ${tint}44`,
}}>
<span style={{ color: item.done ? t.done : tint, fontSize: 12, marginTop: 1 }}>
{item.done ? '' : ''}
{item.done ? '\u2713' : '\u25b8'}
</span>
<span style={{ fontSize: 12, lineHeight: 1.5, color: t.fgSoft }}>{b}</span>
</div>
@@ -360,3 +355,87 @@ export function DetailModal({ item, onClose, t, de }: {
</div>
)
}
// ── Inner slide (fixed 1280x600) ─────────────────────────────────────────────
export function MilestonesInner({ t, de, sel, setSel }: {
t: Theme; de: boolean
sel: Milestone | null
setSel: (m: Milestone | null) => void
}) {
const doneCnt = useMemo(() => MILESTONES.filter(m => m.done).length, [])
const total = MILESTONES.length
return (
<div style={{
position: 'relative', width: 1280, height: 600, overflow: 'hidden',
background: t.bg, color: t.fg,
fontFamily: '"Inter", system-ui, sans-serif', WebkitFontSmoothing: 'antialiased',
}}>
{/* Ambient glow */}
<div style={{
position: 'absolute', top: -120, left: '50%', transform: 'translateX(-50%)',
width: 800, height: 500, borderRadius: '50%',
background: t.ambient, filter: 'blur(50px)', pointerEvents: 'none',
}} />
{t.stars ? <StarField /> : <SoftGrid t={t} />}
{/* Progress indicator */}
<div style={{
position: 'absolute', top: 36, right: 52, display: 'flex', alignItems: 'center', gap: 10, zIndex: 3,
}}>
<div style={{ ...MONO, fontSize: 10, letterSpacing: 2, color: t.fgMuted, textTransform: 'uppercase' as const, fontWeight: 700 }}>
{de ? 'Fortschritt' : 'Progress'}
</div>
<div style={{
width: 120, height: 6, background: t.progressTrackBg, borderRadius: 3, overflow: 'hidden',
border: `1px solid ${t.progressTrackBorder}`,
}}>
<div style={{
width: `${(doneCnt / total) * 100}%`, height: '100%',
background: `linear-gradient(90deg, ${t.done}, ${t.accent})`,
boxShadow: `0 0 12px ${t.done}99`,
}} />
</div>
<div style={{ ...MONO, fontSize: 11, color: t.fg, fontWeight: 700 }}>
<span style={{ color: t.done }}>{doneCnt}</span>
<span style={{ color: t.fgWhisper }}> / {total}</span>
</div>
</div>
{/* Tip */}
<div style={{
position: 'absolute', top: 36, left: 52, ...MONO, fontSize: 10,
letterSpacing: 2, color: t.fgGhost, textTransform: 'uppercase' as const, fontWeight: 700,
display: 'flex', alignItems: 'center', gap: 8, zIndex: 3,
}}>
<span>{de ? 'Tipp:' : 'Tip:'}</span>
<span style={{ color: t.accent70 }}>{de ? 'Klick auf einen Meilenstein' : 'Click any milestone'}</span>
</div>
{/* Timeline */}
<div style={{ position: 'relative', marginTop: 68 }}>
<Timeline onSelect={setSel} selectedId={sel?.id ?? null} t={t} de={de} />
</div>
{/* Stats */}
<div style={{
position: 'absolute', left: 40, right: 40, bottom: 36,
display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 14,
}}>
{STATS.map(s => <StatCard key={s.tint} item={s} t={t} de={de} />)}
</div>
{/* Footer */}
<div style={{
position: 'absolute', left: 0, right: 0, bottom: 14, textAlign: 'center',
...MONO, fontSize: 9, letterSpacing: 3, color: t.accent40,
textTransform: 'uppercase' as const, fontWeight: 700,
}}>
{de ? 'Stand heute \u00b7 live-Metriken aus der Plattform' : 'As of today \u00b7 live metrics from the platform'}
</div>
<DetailModal item={sel} onClose={() => setSel(null)} t={t} de={de} />
</div>
)
}