HeroGradient Hero
Lichte gradient-hero met zwevende blobs: badge-pill, kop met gradient-accent, sub, CTA + tekstlink en social-proof.
src/components/hero/HeroGradient.astro
---
/**
* HeroGradient
* Lichte gradient-hero met zwevende blobs: badge-pill, kop met gradient-accent,
* sub, primaire CTA + tekstlink, en social-proof (avatars + tekst). Centreert
* binnen de pagina-rail (.bl-inner).
*
* Props: zie interface. Alle copy is prop-driven; gradient instelbaar via tokens.
*/
interface Props {
badge?: string;
headline: string;
accent?: string;
sub?: string;
primaryCta?: { label: string; href: string };
secondaryCta?: { label: string; href: string };
proofText?: string;
proofCount?: number;
from?: string;
to?: string;
blob?: string;
}
const {
badge,
headline,
accent,
sub,
primaryCta,
secondaryCta,
proofText,
proofCount = 4,
from = '#f5f3ff',
to = '#ede9fe',
blob = '#818cf8',
} = Astro.props;
const avatarHues = [220, 290, 360, 430];
---
<section class:list={['bl-section', 'hg']} data-component="hero-gradient" style={`--hg-from:${from};--hg-to:${to};--hg-blob:${blob};`}>
<div class="hg__blobs" aria-hidden="true">
<div class="hg__blob hg__blob--1"></div>
<div class="hg__blob hg__blob--2"></div>
<div class="hg__blob hg__blob--3"></div>
</div>
<div class="hg__noise" aria-hidden="true"></div>
<div class="bl-inner bl-inner--narrow hg__content">
{badge && (
<div class="hg__badge-wrap">
<span class="hg__badge">
<span class="hg__badge-dot"></span>
{badge}
</span>
</div>
)}
<h1 class="hg__headline">{headline}{accent && <em> {accent}</em>}</h1>
{sub && <p class="hg__sub">{sub}</p>}
{(primaryCta || secondaryCta) && (
<div class="hg__actions">
{primaryCta && <a href={primaryCta.href} class="hg__cta">{primaryCta.label}</a>}
{secondaryCta && (
<a href={secondaryCta.href} class="hg__secondary">
{secondaryCta.label}
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 7h10M8 3l4 4-4 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>
</a>
)}
</div>
)}
{proofText && (
<div class="hg__proof">
<div class="hg__avatars">
{avatarHues.slice(0, Math.min(proofCount, 4)).map((hue) => (
<div class="hg__avatar" style={`background:hsl(${hue},60%,65%)`}></div>
))}
</div>
<p class="hg__proof-text" set:html={proofText}></p>
</div>
)}
</div>
</section>
<style>
.hg{min-height:100vh;position:relative;display:flex;align-items:center;justify-content:center;overflow:hidden;background:linear-gradient(135deg,var(--hg-from, #f5f3ff) 0%,var(--hg-to, #ede9fe) 100%)}
.hg__blobs{position:absolute;inset:0;pointer-events:none}
.hg__blob{position:absolute;border-radius:50%;filter:blur(72px);opacity:.45}
.hg__blob--1{width:55vw;height:55vw;background:var(--hg-blob, #818cf8);top:-20%;left:-15%;animation:hg-float-1 18s ease-in-out infinite}
.hg__blob--2{width:40vw;height:40vw;background:#a78bfa;bottom:-10%;right:-10%;animation:hg-float-2 22s ease-in-out infinite}
.hg__blob--3{width:30vw;height:30vw;background:#f9a8d4;top:40%;right:20%;animation:hg-float-3 16s ease-in-out infinite}
.hg__noise{position:absolute;inset:0;opacity:.035;background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");background-size:256px;pointer-events:none}
.hg__content{position:relative;z-index:1;text-align:center;display:flex;flex-direction:column;align-items:center;gap:0}
.hg__badge-wrap{margin-bottom:1.75rem;animation:hg-fade-up .5s .1s cubic-bezier(.22,1,.36,1) both}
.hg__badge{display:inline-flex;align-items:center;gap:.5rem;background:#ffffffa6;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,.8);padding:.4375rem 1rem;border-radius:999px;font-size:.8125rem;font-weight:600;color:var(--color-primary, #0a0a0a);box-shadow:0 2px 8px #0000000f}
.hg__badge-dot{width:6px;height:6px;background:var(--color-accent, #6366f1);border-radius:50%;flex-shrink:0;animation:hg-pulse 2s ease-in-out infinite}
.hg__headline{font-size:clamp(3rem, 5vw, 5rem);font-weight:900;line-height:1;letter-spacing:-.05em;color:var(--color-primary, #0a0a0a);margin-bottom:1.5rem;animation:hg-fade-up .7s .2s cubic-bezier(.22,1,.36,1) both}
.hg__headline em{font-style:normal;background:linear-gradient(135deg,var(--color-accent, #6366f1),#a855f7);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
.hg__sub{font-size:1.125rem;line-height:1.65;color:#0a0a0acc;max-width:44ch;margin-bottom:2.5rem;animation:hg-fade-up .6s .35s cubic-bezier(.22,1,.36,1) both}
.hg__actions{display:flex;align-items:center;gap:1.25rem;flex-wrap:wrap;justify-content:center;margin-bottom:2.5rem;animation:hg-fade-up .6s .45s cubic-bezier(.22,1,.36,1) both}
.hg__cta{display:inline-flex;align-items:center;background:var(--color-primary, #0a0a0a);color:#fff;padding:1rem 2.25rem;border-radius:var(--radius, .5rem);font-weight:700;font-size:.9375rem;text-decoration:none;box-shadow:0 4px 24px #0000002e;transition:transform .2s,box-shadow .2s}
.hg__cta:hover{transform:translateY(-3px);box-shadow:0 8px 32px #00000038}
.hg__secondary{display:inline-flex;align-items:center;gap:.4rem;font-size:.9375rem;font-weight:600;color:var(--color-primary, #0a0a0a);text-decoration:none;opacity:.7;transition:opacity .2s,gap .2s}
.hg__secondary:hover{opacity:1;gap:.65rem}
.hg__proof{display:flex;align-items:center;gap:.875rem;animation:hg-fade-up .5s .6s cubic-bezier(.22,1,.36,1) both}
.hg__avatars{display:flex}
.hg__avatar{width:32px;height:32px;border-radius:50%;border:2px solid rgba(255,255,255,.9);margin-left:-8px;flex-shrink:0}
.hg__avatars .hg__avatar:first-child{margin-left:0}
.hg__proof-text{font-size:.9375rem;color:#0a0a0acc}
.hg__proof-text strong{color:var(--color-primary, #0a0a0a);font-weight:700}
@media (prefers-reduced-motion: reduce){
.hg__blob--1,.hg__blob--2,.hg__blob--3,.hg__badge-dot,.hg__badge-wrap,.hg__headline,.hg__sub,.hg__actions,.hg__proof{animation:none}
}
@media (max-width:640px){
.hg__actions{flex-direction:column;width:100%}
.hg__cta{width:100%;justify-content:center}
}
</style>
Props
| Prop | Type | Default | Beschrijving |
|---|---|---|---|
badge | string | - | Badge-pill boven de kop |
headline * | string | - | H1-kop |
accent | string | - | Gradient-accent direct na de kop |
sub | string | - | Ondertitel |
primaryCta | { label: string; href: string } | - | Primaire knop |
secondaryCta | { label: string; href: string } | - | Secundaire tekstlink |
proofText | string | - | Social-proof tekst (HTML toegestaan voor <strong>) |
proofCount | number | 4 | Aantal avatars (max 4) |
from | string | '#f5f3ff' | Gradient startkleur |
to | string | '#ede9fe' | Gradient eindkleur |
blob | string | '#818cf8' | Blob-kleur |
* = verplicht