HeroDark Hero
Donkere hero met radiale gloed, grid achtergrond, gradient headline en optionele statistieken. Geen externe deps.
src/components/hero/HeroDark.astro
---
/**
* HeroDark
* Dark hero met groot stacked statement, grain texture overlay,
* animated accent orb, en code/terminal aesthetic optioneel.
* Puur CSS animaties.
*/
interface Props {
label?: string;
headline: string;
sub?: string;
ctaLabel?: string;
ctaHref?: string;
secondaryLabel?: string;
secondaryHref?: string;
stats?: { value: string; label: string }[];
}
const {
label,
headline,
sub,
ctaLabel = 'Aan de slag',
ctaHref = '#',
secondaryLabel,
secondaryHref = '#',
stats = [],
} = Astro.props;
---
<section class="hd" data-component="hero-dark">
<!-- Radial glow -->
<div class="hd__glow" aria-hidden="true"></div>
<!-- Grid lines -->
<div class="hd__grid" aria-hidden="true"></div>
<!-- Noise grain -->
<div class="hd__noise" aria-hidden="true"></div>
<div class="hd__content">
{label && (
<div class="hd__label-wrap">
<span class="hd__label">{label}</span>
</div>
)}
<h1 class="hd__headline" set:html={headline} />
{sub && <p class="hd__sub">{sub}</p>}
<div class="hd__actions">
<a href={ctaHref} class="hd__cta">{ctaLabel}</a>
{secondaryLabel && (
<a href={secondaryHref} class="hd__secondary">
{secondaryLabel}
<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"/>
</svg>
</a>
)}
</div>
{stats.length > 0 && (
<div class="hd__stats">
{stats.map((s, i) => (
<div class="hd__stat" style={`animation-delay:${0.6 + i * 0.1}s`}>
<span class="hd__stat-value">{s.value}</span>
<span class="hd__stat-label">{s.label}</span>
</div>
))}
</div>
)}
</div>
<!-- Decorative corner marks -->
<div class="hd__corner hd__corner--tl" aria-hidden="true">
<svg width="40" height="40" viewBox="0 0 40 40" fill="none">
<path d="M0 20H20V0" stroke="rgba(255,255,255,0.15)" stroke-width="1"/>
</svg>
</div>
<div class="hd__corner hd__corner--br" aria-hidden="true">
<svg width="40" height="40" viewBox="0 0 40 40" fill="none">
<path d="M40 20H20V40" stroke="rgba(255,255,255,0.15)" stroke-width="1"/>
</svg>
</div>
</section>
<style>
.hd {
min-height: 100vh;
position: relative;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
background: #050508;
}
/* === RADIAL GLOW === */
.hd__glow {
position: absolute;
width: 80vw;
height: 80vw;
max-width: 900px;
max-height: 900px;
background: radial-gradient(
ellipse at center,
rgba(99,102,241,0.22) 0%,
rgba(139,92,246,0.12) 35%,
transparent 65%
);
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
animation: hd-glow-pulse 8s ease-in-out infinite;
}
/* === GRID === */
.hd__grid {
position: absolute;
inset: 0;
background-image:
linear-gradient(rgba(255,255,255,0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,0.03) 1px, transparent 1px);
background-size: 64px 64px;
pointer-events: none;
}
/* === NOISE === */
.hd__noise {
position: absolute;
inset: 0;
opacity: 0.04;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
background-size: 256px;
pointer-events: none;
}
/* === CONTENT === */
.hd__content {
position: relative;
z-index: 1;
text-align: center;
max-width: 900px;
padding: 5rem 2rem;
display: flex;
flex-direction: column;
align-items: center;
}
/* Label */
.hd__label-wrap {
margin-bottom: 2rem;
animation: hd-fade-up 0.5s 0.1s cubic-bezier(0.22,1,0.36,1) both;
}
.hd__label {
display: inline-flex;
align-items: center;
gap: 0.5rem;
border: 1px solid rgba(99,102,241,0.4);
background: rgba(99,102,241,0.08);
padding: 0.375rem 1rem;
border-radius: 999px;
font-size: 0.8125rem;
font-weight: 600;
letter-spacing: 0.05em;
color: rgba(255,255,255,0.8);
}
.hd__label::before {
content: '';
width: 6px;
height: 6px;
background: var(--color-accent, #6366f1);
border-radius: 50%;
flex-shrink: 0;
animation: hd-pulse 2s ease-in-out infinite;
}
/* Headline */
.hd__headline {
font-size: clamp(3rem, 7.5vw, 7.5rem);
font-weight: 900;
line-height: 0.95;
letter-spacing: -0.05em;
color: #fff;
margin-bottom: 1.5rem;
animation: hd-fade-up 0.7s 0.2s cubic-bezier(0.22,1,0.36,1) both;
}
.hd__headline :global(em) {
font-style: normal;
background: linear-gradient(135deg, #818cf8, #a78bfa, #c084fc);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* Outline in dark context */
.hd__headline :global(strong) {
-webkit-text-stroke: 1.5px rgba(255,255,255,0.4);
-webkit-text-fill-color: transparent;
color: transparent;
}
/* Sub */
.hd__sub {
font-size: 1.0625rem;
line-height: 1.7;
color: rgba(255,255,255,0.45);
max-width: 48ch;
margin-bottom: 2.5rem;
animation: hd-fade-up 0.6s 0.35s cubic-bezier(0.22,1,0.36,1) both;
}
/* Actions */
.hd__actions {
display: flex;
align-items: center;
gap: 1.25rem;
flex-wrap: wrap;
justify-content: center;
margin-bottom: 3.5rem;
animation: hd-fade-up 0.6s 0.45s cubic-bezier(0.22,1,0.36,1) both;
}
.hd__cta {
display: inline-flex;
align-items: center;
background: var(--color-accent, #6366f1);
color: #fff;
padding: 1rem 2.25rem;
border-radius: var(--radius, 0.5rem);
font-weight: 700;
font-size: 0.9375rem;
text-decoration: none;
box-shadow: 0 0 40px rgba(99,102,241,0.3);
transition: transform 0.2s, box-shadow 0.2s;
}
.hd__cta:hover {
transform: translateY(-3px);
box-shadow: 0 0 60px rgba(99,102,241,0.45);
}
.hd__secondary {
display: inline-flex;
align-items: center;
gap: 0.4rem;
font-size: 0.9375rem;
font-weight: 600;
color: rgba(255,255,255,0.55);
text-decoration: none;
transition: color 0.2s, gap 0.2s;
}
.hd__secondary:hover { color: rgba(255,255,255,0.9); gap: 0.65rem; }
/* Stats */
.hd__stats {
display: flex;
gap: 3rem;
padding-top: 2.5rem;
border-top: 1px solid rgba(255,255,255,0.07);
flex-wrap: wrap;
justify-content: center;
}
.hd__stat {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.25rem;
animation: hd-fade-up 0.5s cubic-bezier(0.22,1,0.36,1) both;
}
.hd__stat-value {
font-size: 2.25rem;
font-weight: 800;
letter-spacing: -0.04em;
color: #fff;
line-height: 1;
}
.hd__stat-label {
font-size: 0.8125rem;
color: rgba(255,255,255,0.4);
letter-spacing: 0.04em;
}
/* === CORNERS === */
.hd__corner {
position: absolute;
pointer-events: none;
}
.hd__corner--tl { top: 1.5rem; left: 1.5rem; }
.hd__corner--br { bottom: 1.5rem; right: 1.5rem; }
/* === KEYFRAMES === */
@keyframes hd-fade-up {
from { opacity: 0; transform: translateY(24px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes hd-pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(1.4); }
}
@keyframes hd-glow-pulse {
0%, 100% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
50% { opacity: 0.8; transform: translate(-50%, -52%) scale(1.05); }
}
@media (prefers-reduced-motion: reduce) {
.hd__label-wrap, .hd__headline, .hd__sub, .hd__actions, .hd__stat { animation: none; }
.hd__label::before { animation: none; }
.hd__glow { animation: none; }
}
@media (max-width: 600px) {
.hd__content { padding: 4rem 1.25rem; }
.hd__actions { flex-direction: column; width: 100%; }
.hd__cta { width: 100%; justify-content: center; }
.hd__stats { gap: 2rem; }
}
</style>
Props
| Prop | Type | Default | Beschrijving |
|---|---|---|---|
headline * | string | — | H1 — gebruik <em> voor gradient kleur |
label | string | — | Pill label met pulserende dot |
sub | string | — | Ondertitel |
stats | { value: string; label: string }[] | — | Statistieken onderaan |
* = verplicht