Migrated pitch-deck from breakpilot-pwa to breakpilot-core. Container: bp-core-pitch-deck on port 3012. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
172 lines
5.5 KiB
TypeScript
172 lines
5.5 KiB
TypeScript
'use client'
|
|
|
|
import { useCallback, useState } from 'react'
|
|
import { AnimatePresence } from 'framer-motion'
|
|
import { useSlideNavigation } from '@/lib/hooks/useSlideNavigation'
|
|
import { useKeyboard } from '@/lib/hooks/useKeyboard'
|
|
import { usePitchData } from '@/lib/hooks/usePitchData'
|
|
import { Language, PitchData } from '@/lib/types'
|
|
|
|
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'
|
|
|
|
import CoverSlide from './slides/CoverSlide'
|
|
import ProblemSlide from './slides/ProblemSlide'
|
|
import SolutionSlide from './slides/SolutionSlide'
|
|
import ProductSlide from './slides/ProductSlide'
|
|
import HowItWorksSlide from './slides/HowItWorksSlide'
|
|
import MarketSlide from './slides/MarketSlide'
|
|
import BusinessModelSlide from './slides/BusinessModelSlide'
|
|
import TractionSlide from './slides/TractionSlide'
|
|
import CompetitionSlide from './slides/CompetitionSlide'
|
|
import TeamSlide from './slides/TeamSlide'
|
|
import FinancialsSlide from './slides/FinancialsSlide'
|
|
import TheAskSlide from './slides/TheAskSlide'
|
|
import AIQASlide from './slides/AIQASlide'
|
|
|
|
interface PitchDeckProps {
|
|
lang: Language
|
|
onToggleLanguage: () => void
|
|
}
|
|
|
|
export default function PitchDeck({ lang, onToggleLanguage }: PitchDeckProps) {
|
|
const { data, loading, error } = usePitchData()
|
|
const nav = useSlideNavigation()
|
|
const [fabOpen, setFabOpen] = useState(false)
|
|
|
|
const toggleFullscreen = useCallback(() => {
|
|
if (!document.fullscreenElement) {
|
|
document.documentElement.requestFullscreen()
|
|
} else {
|
|
document.exitFullscreen()
|
|
}
|
|
}, [])
|
|
|
|
const toggleMenu = useCallback(() => {
|
|
setFabOpen(prev => !prev)
|
|
}, [])
|
|
|
|
useKeyboard({
|
|
onNext: nav.nextSlide,
|
|
onPrev: nav.prevSlide,
|
|
onFirst: nav.goToFirst,
|
|
onLast: nav.goToLast,
|
|
onOverview: nav.toggleOverview,
|
|
onFullscreen: toggleFullscreen,
|
|
onLanguageToggle: onToggleLanguage,
|
|
onMenuToggle: toggleMenu,
|
|
onGoToSlide: nav.goToSlide,
|
|
enabled: !nav.showOverview,
|
|
})
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="h-screen flex items-center justify-center">
|
|
<div className="text-center">
|
|
<div className="w-12 h-12 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin mx-auto mb-4" />
|
|
<p className="text-white/40 text-sm">{lang === 'de' ? 'Lade Pitch-Daten...' : 'Loading pitch data...'}</p>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (error || !data) {
|
|
return (
|
|
<div className="h-screen flex items-center justify-center">
|
|
<div className="text-center max-w-md">
|
|
<p className="text-red-400 mb-2">{lang === 'de' ? 'Fehler beim Laden' : 'Loading error'}</p>
|
|
<p className="text-white/40 text-sm">{error || 'No data'}</p>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function renderSlide() {
|
|
if (!data) return null
|
|
|
|
switch (nav.currentSlide) {
|
|
case 'cover':
|
|
return <CoverSlide lang={lang} onNext={nav.nextSlide} />
|
|
case 'problem':
|
|
return <ProblemSlide lang={lang} />
|
|
case 'solution':
|
|
return <SolutionSlide lang={lang} />
|
|
case 'product':
|
|
return <ProductSlide lang={lang} products={data.products} />
|
|
case 'how-it-works':
|
|
return <HowItWorksSlide lang={lang} />
|
|
case 'market':
|
|
return <MarketSlide lang={lang} market={data.market} />
|
|
case 'business-model':
|
|
return <BusinessModelSlide lang={lang} products={data.products} />
|
|
case 'traction':
|
|
return <TractionSlide lang={lang} milestones={data.milestones} metrics={data.metrics} />
|
|
case 'competition':
|
|
return <CompetitionSlide lang={lang} features={data.features} competitors={data.competitors} />
|
|
case 'team':
|
|
return <TeamSlide lang={lang} team={data.team} />
|
|
case 'financials':
|
|
return <FinancialsSlide lang={lang} />
|
|
case 'the-ask':
|
|
return <TheAskSlide lang={lang} funding={data.funding} />
|
|
case 'ai-qa':
|
|
return <AIQASlide lang={lang} />
|
|
default:
|
|
return null
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="h-screen relative overflow-hidden bg-gradient-to-br from-slate-950 via-[#0a0a1a] to-slate-950">
|
|
<ParticleBackground />
|
|
<ProgressBar current={nav.currentIndex} total={nav.totalSlides} />
|
|
|
|
<SlideContainer slideKey={nav.currentSlide} direction={nav.direction}>
|
|
{renderSlide()}
|
|
</SlideContainer>
|
|
|
|
<NavigationControls
|
|
onPrev={nav.prevSlide}
|
|
onNext={nav.nextSlide}
|
|
isFirst={nav.isFirst}
|
|
isLast={nav.isLast}
|
|
current={nav.currentIndex}
|
|
total={nav.totalSlides}
|
|
/>
|
|
|
|
<ChatFAB
|
|
lang={lang}
|
|
currentSlide={nav.currentSlide}
|
|
currentIndex={nav.currentIndex}
|
|
visitedSlides={nav.visitedSlides}
|
|
onGoToSlide={nav.goToSlide}
|
|
/>
|
|
|
|
<NavigationFAB
|
|
currentIndex={nav.currentIndex}
|
|
totalSlides={nav.totalSlides}
|
|
visitedSlides={nav.visitedSlides}
|
|
onGoToSlide={nav.goToSlide}
|
|
lang={lang}
|
|
onToggleLanguage={onToggleLanguage}
|
|
/>
|
|
|
|
<AnimatePresence>
|
|
{nav.showOverview && (
|
|
<SlideOverview
|
|
currentIndex={nav.currentIndex}
|
|
onGoToSlide={nav.goToSlide}
|
|
onClose={() => nav.setShowOverview(false)}
|
|
lang={lang}
|
|
/>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
)
|
|
}
|