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>
161 lines
5.9 KiB
TypeScript
161 lines
5.9 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useCallback } from 'react'
|
|
import { motion, AnimatePresence } from 'framer-motion'
|
|
import { Menu, X, Maximize, Minimize, Bot } from 'lucide-react'
|
|
import { Language } from '@/lib/types'
|
|
import { t } from '@/lib/i18n'
|
|
|
|
interface NavigationFABProps {
|
|
currentIndex: number
|
|
totalSlides: number
|
|
visitedSlides: Set<number>
|
|
onGoToSlide: (index: number) => void
|
|
lang: Language
|
|
onToggleLanguage: () => void
|
|
}
|
|
|
|
export default function NavigationFAB({
|
|
currentIndex,
|
|
totalSlides,
|
|
visitedSlides,
|
|
onGoToSlide,
|
|
lang,
|
|
onToggleLanguage,
|
|
}: NavigationFABProps) {
|
|
const [isOpen, setIsOpen] = useState(false)
|
|
const [isFullscreen, setIsFullscreen] = useState(false)
|
|
const i = t(lang)
|
|
|
|
const toggleFullscreen = useCallback(() => {
|
|
if (!document.fullscreenElement) {
|
|
document.documentElement.requestFullscreen()
|
|
setIsFullscreen(true)
|
|
} else {
|
|
document.exitFullscreen()
|
|
setIsFullscreen(false)
|
|
}
|
|
}, [])
|
|
|
|
return (
|
|
<div className="fixed bottom-6 right-6 z-50">
|
|
<AnimatePresence mode="wait">
|
|
{!isOpen ? (
|
|
<motion.button
|
|
key="fab"
|
|
initial={{ scale: 0 }}
|
|
animate={{ scale: 1 }}
|
|
exit={{ scale: 0 }}
|
|
whileHover={{ scale: 1.1 }}
|
|
whileTap={{ scale: 0.95 }}
|
|
onClick={() => setIsOpen(true)}
|
|
className="w-14 h-14 rounded-full bg-indigo-600 hover:bg-indigo-500
|
|
flex items-center justify-center shadow-lg shadow-indigo-600/30
|
|
transition-colors"
|
|
>
|
|
<Menu className="w-6 h-6 text-white" />
|
|
</motion.button>
|
|
) : (
|
|
<motion.div
|
|
key="panel"
|
|
initial={{ opacity: 0, scale: 0.9, y: 20 }}
|
|
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
exit={{ opacity: 0, scale: 0.9, y: 20 }}
|
|
transition={{ duration: 0.2 }}
|
|
className="w-[300px] max-h-[80vh] rounded-2xl overflow-hidden
|
|
bg-black/80 backdrop-blur-xl border border-white/10
|
|
shadow-2xl shadow-black/50"
|
|
>
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between px-4 py-3 border-b border-white/10">
|
|
<span className="text-sm font-semibold text-white">{i.nav.slides}</span>
|
|
<button
|
|
onClick={() => setIsOpen(false)}
|
|
className="w-7 h-7 rounded-full bg-white/10 flex items-center justify-center
|
|
hover:bg-white/20 transition-colors"
|
|
>
|
|
<X className="w-4 h-4 text-white/60" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Slide List */}
|
|
<div className="overflow-y-auto max-h-[55vh] py-2">
|
|
{i.slideNames.map((name, idx) => {
|
|
const isActive = idx === currentIndex
|
|
const isVisited = visitedSlides.has(idx)
|
|
const isAI = idx === totalSlides - 1
|
|
|
|
return (
|
|
<button
|
|
key={idx}
|
|
onClick={() => onGoToSlide(idx)}
|
|
className={`
|
|
w-full flex items-center gap-3 px-4 py-2.5 text-left
|
|
transition-all text-sm
|
|
${isActive
|
|
? 'bg-indigo-500/20 border-l-2 border-indigo-500 text-white'
|
|
: 'hover:bg-white/[0.06] text-white/60 hover:text-white border-l-2 border-transparent'
|
|
}
|
|
`}
|
|
>
|
|
<span className={`
|
|
w-6 h-6 rounded-full flex items-center justify-center text-xs font-mono shrink-0
|
|
${isActive
|
|
? 'bg-indigo-500 text-white'
|
|
: isVisited
|
|
? 'bg-white/10 text-white/60'
|
|
: 'bg-white/5 text-white/30'
|
|
}
|
|
`}>
|
|
{idx + 1}
|
|
</span>
|
|
<span className="flex-1 truncate">{name}</span>
|
|
{isAI && <Bot className="w-4 h-4 text-indigo-400 shrink-0" />}
|
|
{isActive && (
|
|
<span className="w-2 h-2 rounded-full bg-indigo-400 shrink-0" />
|
|
)}
|
|
</button>
|
|
)
|
|
})}
|
|
</div>
|
|
|
|
{/* Footer */}
|
|
<div className="border-t border-white/10 px-4 py-3 space-y-2">
|
|
{/* Language Toggle */}
|
|
<button
|
|
onClick={onToggleLanguage}
|
|
className="w-full flex items-center justify-between px-3 py-2 rounded-lg
|
|
bg-white/[0.05] hover:bg-white/[0.1] transition-colors text-sm"
|
|
>
|
|
<span className="text-white/50">{i.nav.language}</span>
|
|
<div className="flex items-center gap-1">
|
|
<span className={`px-2 py-0.5 rounded text-xs font-medium ${lang === 'de' ? 'bg-indigo-500 text-white' : 'text-white/40'}`}>
|
|
DE
|
|
</span>
|
|
<span className={`px-2 py-0.5 rounded text-xs font-medium ${lang === 'en' ? 'bg-indigo-500 text-white' : 'text-white/40'}`}>
|
|
EN
|
|
</span>
|
|
</div>
|
|
</button>
|
|
|
|
{/* Fullscreen */}
|
|
<button
|
|
onClick={toggleFullscreen}
|
|
className="w-full flex items-center justify-between px-3 py-2 rounded-lg
|
|
bg-white/[0.05] hover:bg-white/[0.1] transition-colors text-sm"
|
|
>
|
|
<span className="text-white/50">{i.nav.fullscreen}</span>
|
|
{isFullscreen ? (
|
|
<Minimize className="w-4 h-4 text-white/50" />
|
|
) : (
|
|
<Maximize className="w-4 h-4 text-white/50" />
|
|
)}
|
|
</button>
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
)
|
|
}
|