feat(marketing-website): add BreakPilot marketing website with CMP integration
Multi-page marketing website positioned as "Deterministic Regulatory Engineering Platform": - 7 pages: Home, Plattform, CE-Prozess, Product Compliance, Architektur, Team, Preise - Platform Bridge animation (adapted from pitch-deck USP slide) - Cookie-Banner with consent-service integration (breakpilot-marketing site) - DE/EN language toggle + Dark/Light theme - Docker service on port 3014 [guardrail-change] PlatformBridgeSection.tsx added to loc-exceptions (816 LOC, SVG animation) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
interface AnimatedCounterProps {
|
||||
value: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function AnimatedCounter({ value, className = '' }: AnimatedCounterProps) {
|
||||
const [display, setDisplay] = useState('0')
|
||||
const ref = useRef<HTMLSpanElement>(null)
|
||||
const hasAnimated = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (hasAnimated.current) return
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting && !hasAnimated.current) {
|
||||
hasAnimated.current = true
|
||||
const numericMatch = value.match(/^([\d.]+)/)
|
||||
if (!numericMatch) {
|
||||
setDisplay(value)
|
||||
return
|
||||
}
|
||||
const target = parseFloat(numericMatch[1])
|
||||
const suffix = value.slice(numericMatch[1].length)
|
||||
const isFloat = value.includes('.')
|
||||
const duration = 1500
|
||||
const start = performance.now()
|
||||
|
||||
const animate = (now: number) => {
|
||||
const progress = Math.min((now - start) / duration, 1)
|
||||
const eased = 1 - Math.pow(1 - progress, 3)
|
||||
const current = target * eased
|
||||
setDisplay(
|
||||
(isFloat ? current.toFixed(1) : Math.floor(current).toLocaleString('de-DE')) + suffix
|
||||
)
|
||||
if (progress < 1) requestAnimationFrame(animate)
|
||||
}
|
||||
requestAnimationFrame(animate)
|
||||
}
|
||||
},
|
||||
{ threshold: 0.5 }
|
||||
)
|
||||
|
||||
if (ref.current) observer.observe(ref.current)
|
||||
return () => observer.disconnect()
|
||||
}, [value])
|
||||
|
||||
return <span ref={ref} className={className}>{display}</span>
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from 'framer-motion'
|
||||
|
||||
interface CTAButtonProps {
|
||||
children: React.ReactNode
|
||||
variant?: 'primary' | 'ghost'
|
||||
href?: string
|
||||
className?: string
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
export default function CTAButton({ children, variant = 'primary', href, className = '', onClick }: CTAButtonProps) {
|
||||
const baseClass = 'inline-flex items-center gap-2 px-6 py-3 rounded-xl font-semibold text-sm transition-all duration-200'
|
||||
|
||||
const variantClass = variant === 'primary'
|
||||
? 'bg-accent-electric text-white glow-blue hover:bg-blue-500'
|
||||
: 'border border-white/[0.12] text-white/80 hover:bg-white/[0.06] hover:text-white'
|
||||
|
||||
const Component = href ? motion.a : motion.button
|
||||
|
||||
return (
|
||||
<Component
|
||||
href={href}
|
||||
onClick={onClick}
|
||||
whileHover={{ scale: 1.03 }}
|
||||
whileTap={{ scale: 0.97 }}
|
||||
className={`${baseClass} ${variantClass} ${className}`}
|
||||
>
|
||||
{children}
|
||||
</Component>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Check, X, Minus } from 'lucide-react'
|
||||
|
||||
interface ComparisonCellProps {
|
||||
value: boolean | 'partial'
|
||||
}
|
||||
|
||||
export default function ComparisonCell({ value }: ComparisonCellProps) {
|
||||
if (value === true) {
|
||||
return <Check className="w-4 h-4 text-green-400 mx-auto" />
|
||||
}
|
||||
if (value === 'partial') {
|
||||
return <Minus className="w-4 h-4 text-amber-400 mx-auto" />
|
||||
}
|
||||
return <X className="w-4 h-4 text-white/20 mx-auto" />
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from 'framer-motion'
|
||||
import { ReactNode } from 'react'
|
||||
import { ANIMATION } from '@/lib/constants'
|
||||
|
||||
interface FadeInViewProps {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
delay?: number
|
||||
direction?: 'up' | 'down' | 'left' | 'right' | 'none'
|
||||
duration?: number
|
||||
}
|
||||
|
||||
const directionMap = {
|
||||
up: { y: 30 },
|
||||
down: { y: -30 },
|
||||
left: { x: 30 },
|
||||
right: { x: -30 },
|
||||
none: {},
|
||||
}
|
||||
|
||||
export default function FadeInView({
|
||||
children,
|
||||
className = '',
|
||||
delay = 0,
|
||||
direction = 'up',
|
||||
duration = ANIMATION.duration,
|
||||
}: FadeInViewProps) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, ...directionMap[direction] }}
|
||||
whileInView={{ opacity: 1, x: 0, y: 0 }}
|
||||
viewport={{ once: true, margin: '100px 0px -60px 0px' }}
|
||||
transition={{ duration, delay, ease: ANIMATION.ease }}
|
||||
className={className}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from 'framer-motion'
|
||||
import { ReactNode } from 'react'
|
||||
import { ANIMATION } from '@/lib/constants'
|
||||
|
||||
interface GlassCardProps {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
delay?: number
|
||||
hover?: boolean
|
||||
}
|
||||
|
||||
export default function GlassCard({ children, className = '', delay = 0, hover = true }: GlassCardProps) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '100px 0px -60px 0px' }}
|
||||
transition={{ duration: ANIMATION.duration, delay }}
|
||||
whileHover={hover ? { scale: 1.02, backgroundColor: 'rgba(255, 255, 255, 0.10)' } : undefined}
|
||||
className={`
|
||||
bg-white/[0.06] backdrop-blur-xl
|
||||
border border-white/[0.08] rounded-2xl
|
||||
p-6 transition-colors duration-200
|
||||
${className}
|
||||
`}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
'use client'
|
||||
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
interface GradientTextProps {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
variant?: 'default' | 'signal'
|
||||
}
|
||||
|
||||
export default function GradientText({ children, className = '', variant = 'default' }: GradientTextProps) {
|
||||
const gradientClass = variant === 'signal' ? 'gradient-text-signal' : 'gradient-text'
|
||||
return (
|
||||
<span className={`${gradientClass} ${className}`}>
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from 'framer-motion'
|
||||
import GradientText from './GradientText'
|
||||
|
||||
const ease = [0.22, 1, 0.36, 1] as const
|
||||
|
||||
interface PageHeaderProps {
|
||||
tag: string
|
||||
title: string
|
||||
titleHighlight: string
|
||||
subtitle: string
|
||||
}
|
||||
|
||||
export default function PageHeader({ tag, title, titleHighlight, subtitle }: PageHeaderProps) {
|
||||
return (
|
||||
<div className="pt-32 pb-16 text-center">
|
||||
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5, ease }}>
|
||||
<p className="mono-label mb-4">{tag}</p>
|
||||
</motion.div>
|
||||
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5, delay: 0.1, ease }}>
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold mb-6">
|
||||
{title}{' '}
|
||||
<GradientText>{titleHighlight}</GradientText>
|
||||
</h1>
|
||||
</motion.div>
|
||||
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5, delay: 0.2, ease }}>
|
||||
<p className="text-white/50 text-lg max-w-3xl mx-auto">
|
||||
{subtitle}
|
||||
</p>
|
||||
</motion.div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
'use client'
|
||||
|
||||
import FadeInView from './FadeInView'
|
||||
import GradientText from './GradientText'
|
||||
|
||||
interface SectionHeadingProps {
|
||||
tag: string
|
||||
title: string
|
||||
titleHighlight: string
|
||||
subtitle: string
|
||||
center?: boolean
|
||||
}
|
||||
|
||||
export default function SectionHeading({ tag, title, titleHighlight, subtitle, center = true }: SectionHeadingProps) {
|
||||
return (
|
||||
<div className={`mb-16 ${center ? 'text-center' : ''}`}>
|
||||
<FadeInView>
|
||||
<p className="mono-label mb-4">{tag}</p>
|
||||
</FadeInView>
|
||||
<FadeInView delay={0.1}>
|
||||
<h2 className="text-4xl md:text-5xl font-bold mb-6">
|
||||
{title}{' '}
|
||||
<GradientText>{titleHighlight}</GradientText>
|
||||
</h2>
|
||||
</FadeInView>
|
||||
<FadeInView delay={0.2}>
|
||||
<p className={`text-white/50 text-lg ${center ? 'max-w-3xl mx-auto' : 'max-w-3xl'}`}>
|
||||
{subtitle}
|
||||
</p>
|
||||
</FadeInView>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
interface StatusIndicatorProps {
|
||||
label: string
|
||||
status?: 'active' | 'warning' | 'error'
|
||||
className?: string
|
||||
}
|
||||
|
||||
const statusColors = {
|
||||
active: 'bg-green-500',
|
||||
warning: 'bg-amber-500',
|
||||
error: 'bg-red-500',
|
||||
}
|
||||
|
||||
export default function StatusIndicator({ label, status = 'active', className = '' }: StatusIndicatorProps) {
|
||||
return (
|
||||
<div className={`inline-flex items-center gap-2 ${className}`}>
|
||||
<span className="relative flex h-2.5 w-2.5">
|
||||
<span className={`animate-ping absolute inline-flex h-full w-full rounded-full ${statusColors[status]} opacity-75`} />
|
||||
<span className={`relative inline-flex rounded-full h-2.5 w-2.5 ${statusColors[status]}`} />
|
||||
</span>
|
||||
<span className="font-mono text-xs text-white/50">{label}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
interface TechBadgeProps {
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function TechBadge({ children, className = '' }: TechBadgeProps) {
|
||||
return (
|
||||
<span className={`
|
||||
inline-block px-3 py-1 rounded-md
|
||||
font-mono text-xs
|
||||
bg-white/[0.06] border border-white/[0.08]
|
||||
text-white/60
|
||||
${className}
|
||||
`}>
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user