src/components/content/ContentTestimonial.astro
---
interface Testimonial {
quote: string;
name: string;
role?: string;
company?: string;
avatar?: string;
rating?: number;
}
interface Props {
heading?: string;
testimonials?: Testimonial[];
layout?: "grid" | "single";
}
const {
heading = "Wat onze klanten zeggen",
layout = "grid",
testimonials = [
{
quote: "BLURR heeft onze online omzet in 5 maanden verdubbeld. Ze denken mee, communiceren snel en leveren elke maand betere resultaten.",
name: "Miriam van den Berg",
role: "Marketing Director",
company: "Dutchstore.nl",
rating: 5,
},
{
quote: "Eindelijk een bureau dat begrijpt wat B2B marketing inhoudt. Geen bullshit, gewoon leads die converteren.",
name: "Thomas Kooij",
role: "CEO",
company: "Infra Solutions BV",
rating: 5,
},
{
quote: "We hebben drie bureaus geprobeerd voor BLURR. Het verschil zit in de proactiviteit — ze bellen jou als er iets beter kan.",
name: "Sara de Vries",
role: "E-commerce Manager",
company: "Modehuis Sara",
rating: 5,
},
],
} = Astro.props;
---
<section class="ctes-wrap">
{heading && <h2 class="ctes-heading">{heading}</h2>}
<div class={`ctes-grid ctes-grid--${layout}`}>
{testimonials.map(t => (
<blockquote class="ctes-card">
{t.rating && (
<div class="ctes-stars" aria-label={`${t.rating} sterren`}>
{"★".repeat(t.rating)}{"☆".repeat(5 - t.rating)}
</div>
)}
<p class="ctes-quote">"{t.quote}"</p>
<footer class="ctes-footer">
{t.avatar
? <img class="ctes-avatar" src={t.avatar} alt={t.name} loading="lazy" />
: <div class="ctes-avatar-placeholder">{t.name[0]}</div>
}
<div>
<cite class="ctes-name">{t.name}</cite>
{(t.role || t.company) && (
<span class="ctes-meta">
{[t.role, t.company].filter(Boolean).join(", ")}
</span>
)}
</div>
</footer>
</blockquote>
))}
</div>
</section>
<style>
.ctes-wrap { padding: 3rem 0; }
.ctes-heading {
font-size: 2rem;
font-weight: 800;
color: var(--color-primary, #0a0a0a);
margin: 0 0 2rem;
}
.ctes-grid--grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
}
.ctes-grid--single { max-width: 640px; }
.ctes-card {
background: #fff;
border: 1px solid #ebebeb;
border-radius: 12px;
padding: 1.75rem;
margin: 0;
transition: box-shadow 0.2s;
}
.ctes-card:hover {
box-shadow: 0 8px 30px rgba(0,0,0,0.07);
}
.ctes-stars {
font-size: 1rem;
color: #f59e0b;
letter-spacing: 0.05em;
margin-bottom: 0.75rem;
}
.ctes-quote {
font-size: 0.975rem;
line-height: 1.7;
color: #333;
margin: 0 0 1.5rem;
font-style: italic;
}
.ctes-footer {
display: flex;
align-items: center;
gap: 0.875rem;
}
.ctes-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
flex-shrink: 0;
}
.ctes-avatar-placeholder {
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--color-accent, #6366f1);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 1rem;
flex-shrink: 0;
}
.ctes-name {
display: block;
font-size: 0.9rem;
font-weight: 700;
color: var(--color-primary, #0a0a0a);
font-style: normal;
}
.ctes-meta {
font-size: 0.8rem;
color: #888;
}
</style>
Props
| Prop | Type | Default | Beschrijving |
|---|---|---|---|
heading | string | — | Sectietitel |
testimonials | { quote: string; name: string; role?: string; company?: string; avatar?: string; rating?: number }[] | — | Testimonial items |
layout | 'grid' | 'single' | — | Grid of enkel testimonial |
* = verplicht