feat(privat/CV): sync to latest upstream (cv-upstream/main, 30f9608b)

This commit is contained in:
2026-07-07 15:20:55 +02:00
parent 8ac664d33d
commit a4efabbc60
417 changed files with 48564 additions and 712 deletions
@@ -0,0 +1,52 @@
import React, { useState, useEffect } from 'react'
import './TestimonialSlider.css'
/**
* Auto-rotating testimonial slider with quotes.
* Testimonials are passed as props (from content/meta/testimonials.yaml later).
*/
export default function TestimonialSlider({ testimonials = [] }) {
const [activeIndex, setActiveIndex] = useState(0)
useEffect(() => {
if (testimonials.length <= 1) return
const interval = setInterval(() => {
setActiveIndex(prev => (prev + 1) % testimonials.length)
}, 6000)
return () => clearInterval(interval)
}, [testimonials.length])
if (testimonials.length === 0) return null
const current = testimonials[activeIndex]
return (
<section className="testimonials-section app-container">
<div className="testimonial-card glass-panel">
<div className="testimonial-quote">
<span className="quote-mark">"</span>
<p>{current.quote}</p>
</div>
<div className="testimonial-author">
{current.photo && <img src={current.photo} alt={current.name} className="author-photo" />}
<div>
<strong>{current.name}</strong>
<span>{current.role}</span>
</div>
</div>
{testimonials.length > 1 && (
<div className="testimonial-dots">
{testimonials.map((_, i) => (
<button
key={i}
className={`dot ${i === activeIndex ? 'active' : ''}`}
onClick={() => setActiveIndex(i)}
aria-label={`Testimonial ${i + 1}`}
/>
))}
</div>
)}
</div>
</section>
)
}