frontend.blogwoman.de/src/components/blocks/VideoBlock.tsx
CCS Admin 75f31b1cb8 Fix design errors, UX issues, and improve accessibility
Critical fixes:
- Add group class to Card component for image zoom on hover
- Create Skeleton, EmptyState, and Pagination UI components
- Add proper empty state to PostsListBlock instead of returning null

Visual consistency:
- Fix Button hover states (subtler secondary/tertiary transitions)
- Add badge variants for FavoritesBlock with German labels
- Increase overlay opacity in HeroBlock/VideoBlock for better contrast

Accessibility improvements:
- Add skip-to-content link in layout for keyboard navigation
- Add focus-visible states to FAQ accordion and Testimonials carousel
- Implement focus trap in MobileMenu with proper ARIA attributes
- Enhance 404 page with helpful navigation links

Polish:
- Fix DividerBlock text contrast
- Fix lint errors (Link component, const declaration)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 15:16:17 +00:00

97 lines
3.2 KiB
TypeScript

'use client'
import { useState } from 'react'
import Image from 'next/image'
import { cn, extractYouTubeId, getPrivacyYouTubeUrl, getYouTubeThumbnail } from '@/lib/utils'
import type { VideoBlock as VideoBlockType } from '@/lib/types'
type VideoBlockProps = Omit<VideoBlockType, 'blockType'>
export function VideoBlock({
title,
videoUrl,
thumbnailImage,
aspectRatio = '16:9',
}: VideoBlockProps) {
const [isPlaying, setIsPlaying] = useState(false)
const videoId = extractYouTubeId(videoUrl)
const thumbnailUrl = thumbnailImage?.url || (videoId ? getYouTubeThumbnail(videoId) : null)
const embedUrl = videoId ? getPrivacyYouTubeUrl(videoId) : null
const aspectClasses = {
'16:9': 'aspect-video',
'4:3': 'aspect-[4/3]',
'1:1': 'aspect-square',
}
if (!embedUrl) {
return null
}
return (
<section className="py-12 md:py-16">
<div className="container">
{title && (
<h2 className="text-center mb-8">{title}</h2>
)}
<div className="max-w-4xl mx-auto">
<div
className={cn(
'relative rounded-2xl overflow-hidden bg-espresso',
aspectClasses[aspectRatio]
)}
>
{isPlaying ? (
<iframe
src={`${embedUrl}?autoplay=1&rel=0`}
title={title || 'Video'}
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
className="absolute inset-0 w-full h-full"
/>
) : (
<button
type="button"
onClick={() => setIsPlaying(true)}
className="absolute inset-0 w-full h-full group"
aria-label="Video abspielen"
>
{thumbnailUrl && (
<Image
src={thumbnailUrl}
alt={title || 'Video Thumbnail'}
fill
className="object-cover"
/>
)}
{/* Overlay */}
<div className="absolute inset-0 bg-espresso/50 group-hover:bg-espresso/40 transition-colors" />
{/* Play Button */}
<div className="absolute inset-0 flex items-center justify-center">
<div className="w-16 h-16 md:w-20 md:h-20 rounded-full bg-soft-white/90 flex items-center justify-center shadow-lg group-hover:scale-110 transition-transform">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className="w-8 h-8 md:w-10 md:h-10 text-brass ml-1"
>
<path
fillRule="evenodd"
d="M4.5 5.653c0-1.426 1.529-2.33 2.779-1.643l11.54 6.348c1.295.712 1.295 2.573 0 3.285L7.28 19.991c-1.25.687-2.779-.217-2.779-1.643V5.653z"
clipRule="evenodd"
/>
</svg>
</div>
</div>
</button>
)}
</div>
</div>
</div>
</section>
)
}