fix(andreknie.de): close accessibility and performance review gaps

This commit is contained in:
2026-07-29 00:05:18 +02:00
parent 88bd952a60
commit c4b5aa40ab
19 changed files with 198 additions and 58 deletions
@@ -1,4 +1,4 @@
import React, { useLayoutEffect, useRef, useState } from 'react'
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'
import { ChevronLeft, ChevronRight } from 'lucide-react'
import './TestimonialSlider.css'
@@ -12,7 +12,9 @@ const MIDDLE_SET = Math.floor(LOOP_SETS / 2)
export default function TestimonialSlider({ testimonials = [] }) {
const scrollerRef = useRef(null)
const dragStateRef = useRef({ isDragging: false, startX: 0, scrollLeft: 0 })
const recenterTimerRef = useRef(null)
const [isDragging, setIsDragging] = useState(false)
const [activeIndex, setActiveIndex] = useState(0)
const canLoop = testimonials.length > 1
const visibleTestimonials = canLoop
? Array.from({ length: LOOP_SETS }, () => testimonials).flat()
@@ -46,10 +48,12 @@ export default function TestimonialSlider({ testimonials = [] }) {
const centerCard = (cardIndex, behavior = 'auto') => {
const scroller = scrollerRef.current
if (!scroller) return
const normalizedIndex = ((cardIndex % testimonials.length) + testimonials.length) % testimonials.length
setActiveIndex(normalizedIndex)
scroller.scrollTo({
left: getCenteredScrollLeft(cardIndex),
behavior,
behavior: window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : behavior,
})
}
@@ -67,6 +71,8 @@ export default function TestimonialSlider({ testimonials = [] }) {
return () => window.cancelAnimationFrame(frameId)
}, [canLoop, testimonials.length])
useEffect(() => () => window.clearTimeout(recenterTimerRef.current), [])
const scrollByCard = (direction) => {
const scroller = scrollerRef.current
if (!scroller) return
@@ -74,7 +80,8 @@ export default function TestimonialSlider({ testimonials = [] }) {
const targetIndex = getClosestCardIndex() + direction
centerCard(targetIndex, 'smooth')
window.setTimeout(recenterIfNeeded, 450)
window.clearTimeout(recenterTimerRef.current)
recenterTimerRef.current = window.setTimeout(recenterIfNeeded, 450)
}
const recenterIfNeeded = () => {
@@ -82,11 +89,12 @@ export default function TestimonialSlider({ testimonials = [] }) {
if (!canLoop || !scroller) return
const currentIndex = getClosestCardIndex()
const normalizedIndex = ((currentIndex % testimonials.length) + testimonials.length) % testimonials.length
setActiveIndex(normalizedIndex)
const lowBoundary = testimonials.length
const highBoundary = testimonials.length * (LOOP_SETS - 1)
if (currentIndex >= lowBoundary && currentIndex < highBoundary) return
const normalizedIndex = ((currentIndex % testimonials.length) + testimonials.length) % testimonials.length
centerCard(testimonials.length * MIDDLE_SET + normalizedIndex)
}
@@ -146,6 +154,9 @@ export default function TestimonialSlider({ testimonials = [] }) {
<ChevronRight size={22} aria-hidden="true" />
</button>
</div>
<p className="sr-only" role="status" aria-live="polite">
Testimonial {activeIndex + 1} von {testimonials.length}
</p>
<div
className={`testimonial-scroll-container${isDragging ? ' dragging' : ''}`}
ref={scrollerRef}