feat(pitch): add SDK demo slide with screenshot gallery + inline preview
- New annex slide 'annex-sdk-demo' with auto-scrolling screenshot gallery (22 real screenshots from Müller Maschinenbau demo project) - Browser chrome mockup, fullscreen view, thumbnail strip navigation - Inline SDK dashboard preview on Product slide - Seed script for creating demo data + taking Playwright screenshots - Presenter script for SDK demo narration Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,7 @@ import GTMSlide from './slides/GTMSlide'
|
||||
import RegulatorySlide from './slides/RegulatorySlide'
|
||||
import EngineeringSlide from './slides/EngineeringSlide'
|
||||
import AIPipelineSlide from './slides/AIPipelineSlide'
|
||||
import SDKDemoSlide from './slides/SDKDemoSlide'
|
||||
|
||||
interface PitchDeckProps {
|
||||
lang: Language
|
||||
@@ -154,6 +155,8 @@ export default function PitchDeck({ lang, onToggleLanguage }: PitchDeckProps) {
|
||||
return <EngineeringSlide lang={lang} />
|
||||
case 'annex-aipipeline':
|
||||
return <AIPipelineSlide lang={lang} />
|
||||
case 'annex-sdk-demo':
|
||||
return <SDKDemoSlide lang={lang} />
|
||||
default:
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import Image from 'next/image'
|
||||
import { Language, PitchProduct } from '@/lib/types'
|
||||
import { t } from '@/lib/i18n'
|
||||
import GradientText from '../ui/GradientText'
|
||||
@@ -13,21 +14,48 @@ interface ProductSlideProps {
|
||||
|
||||
export default function ProductSlide({ lang, products }: ProductSlideProps) {
|
||||
const i = t(lang)
|
||||
const de = lang === 'de'
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FadeInView className="text-center mb-12">
|
||||
<FadeInView className="text-center mb-8">
|
||||
<h2 className="text-4xl md:text-5xl font-bold mb-3">
|
||||
<GradientText>{i.product.title}</GradientText>
|
||||
</h2>
|
||||
<p className="text-lg text-white/50 max-w-2xl mx-auto">{i.product.subtitle}</p>
|
||||
</FadeInView>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
<div className="grid md:grid-cols-3 gap-6 mb-8">
|
||||
{products.map((product, idx) => (
|
||||
<PricingCard key={product.id} product={product} lang={lang} delay={0.2 + idx * 0.15} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* SDK Preview */}
|
||||
<FadeInView delay={0.6}>
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="rounded-xl overflow-hidden border border-white/10 bg-black/20 shadow-lg shadow-indigo-500/5">
|
||||
<div className="flex items-center gap-2 px-3 py-1.5 bg-white/[0.03] border-b border-white/10">
|
||||
<div className="flex gap-1">
|
||||
<div className="w-2 h-2 rounded-full bg-red-500/50" />
|
||||
<div className="w-2 h-2 rounded-full bg-yellow-500/50" />
|
||||
<div className="w-2 h-2 rounded-full bg-green-500/50" />
|
||||
</div>
|
||||
<span className="text-[10px] text-white/20 font-mono ml-2">admin.breakpilot.ai/sdk</span>
|
||||
</div>
|
||||
<Image
|
||||
src="/screenshots/01-dashboard.png"
|
||||
alt={de ? 'SDK Dashboard' : 'SDK Dashboard'}
|
||||
width={1920}
|
||||
height={1080}
|
||||
className="w-full h-auto opacity-80"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-center text-xs text-white/30 mt-2 font-mono">
|
||||
{de ? '65+ Module · Live-Plattform · Details im Anhang' : '65+ Modules · Live Platform · Details in Appendix'}
|
||||
</p>
|
||||
</div>
|
||||
</FadeInView>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
204
pitch-deck/components/slides/SDKDemoSlide.tsx
Normal file
204
pitch-deck/components/slides/SDKDemoSlide.tsx
Normal file
@@ -0,0 +1,204 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
import Image from 'next/image'
|
||||
import { Language } from '@/lib/types'
|
||||
import GradientText from '../ui/GradientText'
|
||||
import FadeInView from '../ui/FadeInView'
|
||||
import { ChevronLeft, ChevronRight, Maximize2 } from 'lucide-react'
|
||||
|
||||
interface SDKDemoSlideProps {
|
||||
lang: Language
|
||||
}
|
||||
|
||||
const SCREENSHOTS = [
|
||||
{ file: '01-dashboard.png', de: 'SDK Dashboard', en: 'SDK Dashboard', category: 'overview' },
|
||||
{ file: '21-sdk-flow.png', de: 'SDK Flow — 36 Steps', en: 'SDK Flow — 36 Steps', category: 'overview' },
|
||||
{ file: '02-company-profile.png', de: 'Unternehmensprofil', en: 'Company Profile', category: 'setup' },
|
||||
{ file: '03-compliance-scope.png', de: 'Compliance-Scope', en: 'Compliance Scope', category: 'setup' },
|
||||
{ file: '04-vvt.png', de: 'Verarbeitungsverzeichnis (Art. 30)', en: 'Processing Register (Art. 30)', category: 'docs' },
|
||||
{ file: '05-tom.png', de: 'TOM — Maßnahmen (Art. 32)', en: 'TOM — Measures (Art. 32)', category: 'docs' },
|
||||
{ file: '06-dsfa.png', de: 'Datenschutz-Folgenabschätzung', en: 'Data Protection Impact Assessment', category: 'docs' },
|
||||
{ file: '07-risks.png', de: 'Risikomatrix', en: 'Risk Matrix', category: 'analysis' },
|
||||
{ file: '08-obligations.png', de: 'Pflichtenübersicht', en: 'Obligations Overview', category: 'docs' },
|
||||
{ file: '09-loeschfristen.png', de: 'Löschfristen', en: 'Retention Policies', category: 'docs' },
|
||||
{ file: '10-controls.png', de: 'Controls', en: 'Controls', category: 'analysis' },
|
||||
{ file: '11-ai-act.png', de: 'AI Act Compliance', en: 'AI Act Compliance', category: 'analysis' },
|
||||
{ file: '12-requirements.png', de: 'Anforderungen', en: 'Requirements', category: 'analysis' },
|
||||
{ file: '13-evidence.png', de: 'Nachweise', en: 'Evidence', category: 'analysis' },
|
||||
{ file: '14-audit-report.png', de: 'Audit-Report', en: 'Audit Report', category: 'analysis' },
|
||||
{ file: '15-document-generator.png', de: 'Dokumenten-Generator', en: 'Document Generator', category: 'docs' },
|
||||
{ file: '16-einwilligungen.png', de: 'Einwilligungsmanagement', en: 'Consent Management', category: 'legal' },
|
||||
{ file: '22-iace.png', de: 'CE-Compliance (IACE)', en: 'CE Compliance (IACE)', category: 'analysis' },
|
||||
{ file: '20-rag.png', de: 'Legal RAG — 2.274 Rechtstexte', en: 'Legal RAG — 2,274 Legal Texts', category: 'ai' },
|
||||
{ file: '19-training.png', de: 'Compliance-Schulungen', en: 'Compliance Training', category: 'ops' },
|
||||
{ file: '23-incidents.png', de: 'Incident Response', en: 'Incident Response', category: 'ops' },
|
||||
{ file: '24-reporting.png', de: 'Reporting', en: 'Reporting', category: 'ops' },
|
||||
]
|
||||
|
||||
export default function SDKDemoSlide({ lang }: SDKDemoSlideProps) {
|
||||
const de = lang === 'de'
|
||||
const [current, setCurrent] = useState(0)
|
||||
const [fullscreen, setFullscreen] = useState(false)
|
||||
const [autoPlay, setAutoPlay] = useState(true)
|
||||
|
||||
const next = useCallback(() => {
|
||||
setCurrent(i => (i + 1) % SCREENSHOTS.length)
|
||||
}, [])
|
||||
|
||||
const prev = useCallback(() => {
|
||||
setCurrent(i => (i - 1 + SCREENSHOTS.length) % SCREENSHOTS.length)
|
||||
}, [])
|
||||
|
||||
// Auto-advance every 3s when not in fullscreen
|
||||
useEffect(() => {
|
||||
if (!autoPlay || fullscreen) return
|
||||
const timer = setInterval(next, 3000)
|
||||
return () => clearInterval(timer)
|
||||
}, [autoPlay, fullscreen, next])
|
||||
|
||||
const shot = SCREENSHOTS[current]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FadeInView className="text-center mb-6">
|
||||
<p className="text-xs font-mono text-indigo-400/60 uppercase tracking-widest mb-2">
|
||||
{de ? 'Anhang' : 'Appendix'}
|
||||
</p>
|
||||
<h2 className="text-4xl md:text-5xl font-bold mb-2">
|
||||
<GradientText>{de ? 'SDK Live-Demo' : 'SDK Live Demo'}</GradientText>
|
||||
</h2>
|
||||
<p className="text-base text-white/50 max-w-2xl mx-auto">
|
||||
{de
|
||||
? 'Echte Screenshots aus dem Compliance SDK — Kundenprojekt: Müller Maschinenbau GmbH'
|
||||
: 'Real screenshots from the Compliance SDK — Customer project: Müller Maschinenbau GmbH'}
|
||||
</p>
|
||||
</FadeInView>
|
||||
|
||||
<FadeInView delay={0.15}>
|
||||
<div className="max-w-5xl mx-auto">
|
||||
{/* Main screenshot viewer */}
|
||||
<div className="relative group">
|
||||
<div className="relative rounded-xl overflow-hidden border border-white/10 bg-black/30 shadow-2xl shadow-indigo-500/10">
|
||||
{/* Browser chrome mockup */}
|
||||
<div className="flex items-center gap-2 px-4 py-2 bg-white/[0.04] border-b border-white/10">
|
||||
<div className="flex gap-1.5">
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-red-500/60" />
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-yellow-500/60" />
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-green-500/60" />
|
||||
</div>
|
||||
<div className="flex-1 ml-3">
|
||||
<div className="bg-white/[0.06] rounded-md px-3 py-1 text-xs text-white/30 font-mono max-w-md">
|
||||
admin.breakpilot.ai/sdk/{shot.file.replace(/^\d+-/, '').replace('.png', '')}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setFullscreen(true)}
|
||||
className="text-white/30 hover:text-white/60 transition"
|
||||
>
|
||||
<Maximize2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Screenshot */}
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key={current}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<Image
|
||||
src={`/screenshots/${shot.file}`}
|
||||
alt={de ? shot.de : shot.en}
|
||||
width={1920}
|
||||
height={1080}
|
||||
className="w-full h-auto"
|
||||
priority={current < 3}
|
||||
/>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
{/* Navigation arrows */}
|
||||
<button
|
||||
onClick={() => { prev(); setAutoPlay(false) }}
|
||||
className="absolute left-3 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-black/60 border border-white/10 flex items-center justify-center text-white/60 hover:text-white hover:bg-black/80 transition opacity-0 group-hover:opacity-100"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { next(); setAutoPlay(false) }}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-black/60 border border-white/10 flex items-center justify-center text-white/60 hover:text-white hover:bg-black/80 transition opacity-0 group-hover:opacity-100"
|
||||
>
|
||||
<ChevronRight className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Caption + Progress */}
|
||||
<div className="flex items-center justify-between mt-4">
|
||||
<div>
|
||||
<p className="text-white font-medium">{de ? shot.de : shot.en}</p>
|
||||
<p className="text-xs text-white/30 font-mono">{current + 1} / {SCREENSHOTS.length}</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setAutoPlay(!autoPlay)}
|
||||
className="text-xs text-white/40 hover:text-white/60 transition font-mono"
|
||||
>
|
||||
{autoPlay ? (de ? '⏸ Pause' : '⏸ Pause') : (de ? '▶ Auto' : '▶ Auto')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Thumbnail strip */}
|
||||
<div className="flex gap-1.5 mt-3 overflow-x-auto pb-2 scrollbar-thin">
|
||||
{SCREENSHOTS.map((s, idx) => (
|
||||
<button
|
||||
key={idx}
|
||||
onClick={() => { setCurrent(idx); setAutoPlay(false) }}
|
||||
className={`shrink-0 w-16 h-9 rounded overflow-hidden border transition ${
|
||||
idx === current
|
||||
? 'border-indigo-500 ring-1 ring-indigo-500/50'
|
||||
: 'border-white/10 opacity-50 hover:opacity-80'
|
||||
}`}
|
||||
>
|
||||
<Image
|
||||
src={`/screenshots/${s.file}`}
|
||||
alt=""
|
||||
width={64}
|
||||
height={36}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</FadeInView>
|
||||
|
||||
{/* Fullscreen overlay */}
|
||||
<AnimatePresence>
|
||||
{fullscreen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 z-50 bg-black/95 flex items-center justify-center cursor-pointer"
|
||||
onClick={() => setFullscreen(false)}
|
||||
>
|
||||
<Image
|
||||
src={`/screenshots/${shot.file}`}
|
||||
alt={de ? shot.de : shot.en}
|
||||
width={1920}
|
||||
height={1080}
|
||||
className="max-w-[95vw] max-h-[90vh] object-contain"
|
||||
/>
|
||||
<p className="absolute bottom-6 text-white/50 text-sm">
|
||||
{de ? shot.de : shot.en} — {de ? 'Klicken zum Schließen' : 'Click to close'}
|
||||
</p>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user