HeroFloatingTestimonial Hero
Hero met zwevende testimonial card over de afbeelding. Split-layout, badge, accent-kleur.
src/components/hero/HeroFloatingTestimonial.astro
---
/**
* HeroFloatingTestimonial
* Hero met zwevende testimonial card, badge, headline + CTA.
* Modern Retail archetype — clean wit, accent kleur, subtiele animaties.
*/
interface Props {
badge?: string;
headline: string;
sub: string;
ctaLabel?: string;
ctaHref?: string;
ctaSecondary?: string;
ctaSecondaryHref?: string;
testimonialText: string;
testimonialAuthor: string;
testimonialRole?: string;
testimonialAvatar?: string;
imageSrc: string;
imageAlt?: string;
}
const {
badge,
headline,
sub,
ctaLabel = 'Aan de slag',
ctaHref = '#',
ctaSecondary,
ctaSecondaryHref = '#',
testimonialText,
testimonialAuthor,
testimonialRole,
testimonialAvatar,
imageSrc,
imageAlt = '',
} = Astro.props;
---
<section class="hft" data-component="hero-floating-testimonial">
<div class="hft__inner">
<!-- Left: text content -->
<div class="hft__content">
{badge && <span class="hft__badge">{badge}</span>}
<h1 class="hft__headline" set:html={headline} />
<p class="hft__sub">{sub}</p>
<div class="hft__ctas">
<a href={ctaHref} class="hft__cta hft__cta--primary">{ctaLabel}</a>
{ctaSecondary && (
<a href={ctaSecondaryHref} class="hft__cta hft__cta--ghost">{ctaSecondary}</a>
)}
</div>
</div>
<!-- Right: image + floating testimonial -->
<div class="hft__visual">
<div class="hft__img-wrap">
<img src={imageSrc} alt={imageAlt} class="hft__img" loading="eager" />
<div class="hft__testimonial">
<svg class="hft__quote-icon" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
<path d="M14.017 21v-7.391c0-5.704 3.731-9.57 8.983-10.609l.995 2.151c-2.432.917-3.995 3.638-3.995 5.849h4v10h-9.983zm-14.017 0v-7.391c0-5.704 3.748-9.57 9-10.609l.996 2.151c-2.433.917-3.996 3.638-3.996 5.849h3.983v10h-9.983z"/>
</svg>
<p class="hft__testimonial-text">{testimonialText}</p>
<div class="hft__testimonial-author">
{testimonialAvatar && (
<img src={testimonialAvatar} alt={testimonialAuthor} class="hft__avatar" />
)}
<div>
<div class="hft__author-name">{testimonialAuthor}</div>
{testimonialRole && <div class="hft__author-role">{testimonialRole}</div>}
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<style>
.hft {
background: var(--color-bg, #fff);
padding: 5rem 1.5rem 4rem;
overflow: hidden;
}
.hft__inner {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4rem;
align-items: center;
}
.hft__badge {
display: inline-block;
background: color-mix(in srgb, var(--color-accent, #6366f1) 12%, transparent);
color: var(--color-accent, #6366f1);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
padding: 0.35rem 0.85rem;
border-radius: 100px;
margin-bottom: 1.25rem;
}
.hft__headline {
font-size: clamp(2.25rem, 4vw, 3.5rem);
font-weight: 800;
line-height: 1.1;
letter-spacing: -0.03em;
color: var(--color-primary, #0a0a0a);
margin-bottom: 1.25rem;
}
.hft__headline :global(em) {
font-style: normal;
color: var(--color-accent, #6366f1);
}
.hft__sub {
font-size: 1.125rem;
color: var(--color-muted, #6b7280);
line-height: 1.65;
margin-bottom: 2rem;
max-width: 42ch;
}
.hft__ctas {
display: flex;
gap: 0.75rem;
flex-wrap: wrap;
}
.hft__cta {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.8125rem 1.75rem;
border-radius: var(--radius, 0.5rem);
font-size: 0.9375rem;
font-weight: 700;
text-decoration: none;
transition: all 0.2s;
}
.hft__cta--primary {
background: var(--color-accent, #6366f1);
color: #fff;
}
.hft__cta--primary:hover {
filter: brightness(1.1);
transform: translateY(-1px);
}
.hft__cta--ghost {
color: var(--color-primary, #0a0a0a);
border: 1.5px solid currentColor;
opacity: 0.7;
}
.hft__cta--ghost:hover {
opacity: 1;
}
/* Visual */
.hft__visual {
position: relative;
}
.hft__img-wrap {
position: relative;
border-radius: calc(var(--radius, 0.5rem) * 2);
overflow: visible;
}
.hft__img {
width: 100%;
height: 480px;
object-fit: cover;
border-radius: calc(var(--radius, 0.5rem) * 2);
display: block;
}
/* Floating testimonial card */
.hft__testimonial {
position: absolute;
bottom: -2rem;
left: -2.5rem;
background: #fff;
border: 1px solid rgba(0,0,0,0.07);
border-radius: calc(var(--radius, 0.5rem) * 1.5);
padding: 1.25rem 1.5rem;
max-width: 300px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
}
.hft__quote-icon {
color: var(--color-accent, #6366f1);
margin-bottom: 0.625rem;
opacity: 0.7;
}
.hft__testimonial-text {
font-size: 0.875rem;
line-height: 1.6;
color: var(--color-primary, #0a0a0a);
margin-bottom: 0.875rem;
}
.hft__testimonial-author {
display: flex;
align-items: center;
gap: 0.625rem;
}
.hft__avatar {
width: 32px;
height: 32px;
border-radius: 50%;
object-fit: cover;
}
.hft__author-name {
font-size: 0.8125rem;
font-weight: 700;
color: var(--color-primary, #0a0a0a);
}
.hft__author-role {
font-size: 0.75rem;
color: var(--color-muted, #6b7280);
}
@media (max-width: 768px) {
.hft__inner {
grid-template-columns: 1fr;
gap: 2rem;
}
.hft__testimonial {
position: static;
margin-top: 1rem;
max-width: 100%;
}
.hft__img {
height: 300px;
}
}
</style>
<script>
import gsap from 'https://cdn.skypack.dev/gsap';
import ScrollTrigger from 'https://cdn.skypack.dev/gsap/ScrollTrigger';
if (typeof gsap !== 'undefined') {
gsap.registerPlugin(ScrollTrigger);
const section = document.querySelector('[data-component="hero-floating-testimonial"]');
if (section) {
gsap.from('[data-component="hero-floating-testimonial"] .hft__badge', {
opacity: 0, y: 20, duration: 0.5, delay: 0.1
});
gsap.from('[data-component="hero-floating-testimonial"] .hft__headline', {
opacity: 0, y: 30, duration: 0.6, delay: 0.2
});
gsap.from('[data-component="hero-floating-testimonial"] .hft__sub', {
opacity: 0, y: 20, duration: 0.5, delay: 0.35
});
gsap.from('[data-component="hero-floating-testimonial"] .hft__ctas', {
opacity: 0, y: 20, duration: 0.5, delay: 0.45
});
gsap.from('[data-component="hero-floating-testimonial"] .hft__img', {
opacity: 0, x: 40, duration: 0.8, delay: 0.2, ease: 'power2.out'
});
gsap.from('[data-component="hero-floating-testimonial"] .hft__testimonial', {
opacity: 0, y: 30, duration: 0.6, delay: 0.7, ease: 'back.out(1.5)'
});
}
}
</script>
Props
| Prop | Type | Default | Beschrijving |
|---|---|---|---|
headline * | string | — | H1 tekst. Gebruik <em> voor accent. |
sub * | string | — | Ondertitel |
imageSrc * | string | — | Hero afbeelding |
testimonialText * | string | — | Quote tekst |
testimonialAuthor * | string | — | Naam van auteur |
badge | string | — | Kleine badge boven headline |
ctaLabel | string | 'Aan de slag' | Primaire CTA tekst |
* = verplicht