feat: Add Academy, Whistleblower, Incidents SDK modules, pitch-deck, blog and CI/CD config
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
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
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 / Go Tests (push) Has been cancelled
Tests / Python Tests (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
Tests / All Checks Passed (push) Has been cancelled

- Academy, Whistleblower, Incidents frontend pages with API proxies and types
- Vendor compliance API proxy route
- Go backend handlers and models for all new SDK modules
- Investor pitch-deck app with interactive slides
- Blog section with DSGVO, AI Act, NIS2, glossary articles
- MkDocs documentation site
- CI/CD pipelines (Woodpecker, GitHub Actions), security scanning config
- Planning and implementation documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
BreakPilot Dev
2026-02-13 21:12:16 +01:00
parent d7ba705562
commit 557305db5d
208 changed files with 141969 additions and 5680 deletions

View File

@@ -0,0 +1,98 @@
'use client'
import { useEffect, useCallback } from 'react'
interface UseKeyboardProps {
onNext: () => void
onPrev: () => void
onFirst: () => void
onLast: () => void
onOverview: () => void
onFullscreen: () => void
onLanguageToggle: () => void
onMenuToggle: () => void
onGoToSlide: (index: number) => void
enabled?: boolean
}
export function useKeyboard({
onNext,
onPrev,
onFirst,
onLast,
onOverview,
onFullscreen,
onLanguageToggle,
onMenuToggle,
onGoToSlide,
enabled = true,
}: UseKeyboardProps) {
const handleKeyDown = useCallback(
(e: KeyboardEvent) => {
if (!enabled) return
const target = e.target as HTMLElement
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
return
}
switch (e.key) {
case 'ArrowRight':
case ' ':
case 'Enter':
e.preventDefault()
onNext()
break
case 'ArrowLeft':
e.preventDefault()
onPrev()
break
case 'Escape':
e.preventDefault()
onOverview()
break
case 'f':
case 'F':
e.preventDefault()
onFullscreen()
break
case 'Home':
e.preventDefault()
onFirst()
break
case 'End':
e.preventDefault()
onLast()
break
case 'l':
case 'L':
e.preventDefault()
onLanguageToggle()
break
case 'm':
case 'M':
e.preventDefault()
onMenuToggle()
break
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
e.preventDefault()
onGoToSlide(parseInt(e.key) - 1)
break
}
},
[enabled, onNext, onPrev, onFirst, onLast, onOverview, onFullscreen, onLanguageToggle, onMenuToggle, onGoToSlide]
)
useEffect(() => {
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, [handleKeyDown])
}

View File

@@ -0,0 +1,39 @@
'use client'
import { createContext, useContext, useState, useCallback, ReactNode } from 'react'
import { Language } from '../types'
import React from 'react'
interface LanguageContextType {
lang: Language
toggleLanguage: () => void
setLanguage: (lang: Language) => void
}
const LanguageContext = createContext<LanguageContextType>({
lang: 'de',
toggleLanguage: () => {},
setLanguage: () => {},
})
export function LanguageProvider({ children }: { children: ReactNode }) {
const [lang, setLang] = useState<Language>('de')
const toggleLanguage = useCallback(() => {
setLang(prev => prev === 'de' ? 'en' : 'de')
}, [])
const setLanguage = useCallback((newLang: Language) => {
setLang(newLang)
}, [])
return React.createElement(
LanguageContext.Provider,
{ value: { lang, toggleLanguage, setLanguage } },
children
)
}
export function useLanguage() {
return useContext(LanguageContext)
}

View File

@@ -0,0 +1,29 @@
'use client'
import { useState, useEffect } from 'react'
import { PitchData } from '../types'
export function usePitchData() {
const [data, setData] = useState<PitchData | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
async function fetchData() {
try {
const res = await fetch('/api/data')
if (!res.ok) throw new Error(`HTTP ${res.status}`)
const json = await res.json()
setData(json)
} catch (err) {
console.error('Failed to load pitch data:', err)
setError(err instanceof Error ? err.message : 'Unknown error')
} finally {
setLoading(false)
}
}
fetchData()
}, [])
return { data, loading, error }
}

View File

@@ -0,0 +1,77 @@
'use client'
import { useState, useCallback } from 'react'
import { SlideId } from '../types'
const SLIDE_ORDER: SlideId[] = [
'cover',
'problem',
'solution',
'product',
'how-it-works',
'market',
'business-model',
'traction',
'competition',
'team',
'financials',
'the-ask',
'ai-qa',
]
export const TOTAL_SLIDES = SLIDE_ORDER.length
export function useSlideNavigation() {
const [currentIndex, setCurrentIndex] = useState(0)
const [direction, setDirection] = useState(0)
const [visitedSlides, setVisitedSlides] = useState<Set<number>>(new Set([0]))
const [showOverview, setShowOverview] = useState(false)
const currentSlide = SLIDE_ORDER[currentIndex]
const goToSlide = useCallback((index: number) => {
if (index < 0 || index >= TOTAL_SLIDES) return
setDirection(index > currentIndex ? 1 : -1)
setCurrentIndex(index)
setVisitedSlides(prev => new Set([...prev, index]))
setShowOverview(false)
}, [currentIndex])
const nextSlide = useCallback(() => {
if (currentIndex < TOTAL_SLIDES - 1) {
goToSlide(currentIndex + 1)
}
}, [currentIndex, goToSlide])
const prevSlide = useCallback(() => {
if (currentIndex > 0) {
goToSlide(currentIndex - 1)
}
}, [currentIndex, goToSlide])
const goToFirst = useCallback(() => goToSlide(0), [goToSlide])
const goToLast = useCallback(() => goToSlide(TOTAL_SLIDES - 1), [goToSlide])
const toggleOverview = useCallback(() => {
setShowOverview(prev => !prev)
}, [])
return {
currentIndex,
currentSlide,
direction,
visitedSlides,
showOverview,
totalSlides: TOTAL_SLIDES,
slideOrder: SLIDE_ORDER,
goToSlide,
nextSlide,
prevSlide,
goToFirst,
goToLast,
toggleOverview,
setShowOverview,
isFirst: currentIndex === 0,
isLast: currentIndex === TOTAL_SLIDES - 1,
}
}