HeroSaas Hero
SaaS-hero: tekstkolom links (eyebrow-pill, kop, sub, twee CTA's, metric-rij) en rechts een dashboard-mockup met stat-tegels en bar-chart.
src/components/hero/HeroSaas.astro
---
/**
* HeroSaas
* SaaS-hero: tekstkolom links (eyebrow-pill, kop, sub, twee CTA's, metric-rij)
* en rechts een dashboard-mockup (stat-tegels + bar-chart). Content ligt op de
* pagina-rail (.bl-inner).
*
* Props: zie interface. Alle copy is prop-driven; metrics is een array.
*/
interface Metric {
value: string;
label: string;
}
interface MockStat {
value: string;
label: string;
}
interface Props {
eyebrow?: string;
headline: string;
sub?: string;
primaryCta?: { label: string; href: string };
ghostCta?: { label: string; href: string };
metrics?: Metric[];
mockStats?: MockStat[];
}
const {
eyebrow,
headline,
sub,
primaryCta,
ghostCta,
metrics = [],
mockStats = [],
} = Astro.props;
const barHeights = [40, 65, 50, 80, 70, 90];
---
<section class:list={['bl-section', 'hs']}>
<div class="bl-inner hs-inner">
<div class="hs-text">
{eyebrow && <span class="hs-eyebrow">{eyebrow}</span>}
<h1 class="hs-headline">{headline}</h1>
{sub && <p class="hs-sub">{sub}</p>}
{(primaryCta || ghostCta) && (
<div class="hs-actions">
{primaryCta && <a href={primaryCta.href} class="hs-btn hs-btn--primary">{primaryCta.label}</a>}
{ghostCta && <a href={ghostCta.href} class="hs-btn hs-btn--ghost">{ghostCta.label}</a>}
</div>
)}
{metrics.length > 0 && (
<div class="hs-metrics">
{metrics.map((metric) => (
<div class="hs-metric">
<span class="hs-metric-value">{metric.value}</span>
<span class="hs-metric-label">{metric.label}</span>
</div>
))}
</div>
)}
</div>
<div class="hs-preview">
<div class="hs-mockup">
<div class="hs-mock-bar"><span></span><span></span><span></span></div>
<div class="hs-mock-grid">
{(mockStats.length ? mockStats : metrics).slice(0, 3).map((stat) => (
<div class="hs-mock-stat"><div class="hs-mock-val">{stat.value}</div><div class="hs-mock-lbl">{stat.label}</div></div>
))}
<div class="hs-mock-chart">
{barHeights.map((h) => <div class="hs-bar" style={`height:${h}%`}></div>)}
</div>
</div>
</div>
</div>
</div>
</section>
<style>
.hs{background:#f8fafc}
.hs-inner{display:grid;grid-template-columns:1fr 1fr;gap:4rem;align-items:center}
.hs-eyebrow{display:inline-block;font-size:.8125rem;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--color-accent,#6366f1);background:#6366f11a;padding:.25rem .75rem;border-radius:100px;margin-bottom:1.25rem}
.hs-headline{font-size:clamp(2rem,3.5vw,3.5rem);font-weight:800;letter-spacing:-.03em;line-height:1.1;color:#0f172a;margin:0 0 1rem}
.hs-sub{font-size:1.0625rem;color:#475569;line-height:1.65;margin:0 0 2rem}
.hs-actions{display:flex;gap:.75rem;flex-wrap:wrap;margin-bottom:2rem}
.hs-btn{display:inline-flex;align-items:center;padding:.75rem 1.5rem;border-radius:.5rem;font-size:.9375rem;font-weight:600;text-decoration:none;transition: opacity, transform, background-color, color, border-color, box-shadow, filter .2s}
.hs-btn--primary{background:var(--color-accent,#6366f1);color:#fff}
.hs-btn--primary:hover{opacity:.9;transform:translateY(-1px)}
.hs-btn--ghost{border:1.5px solid #cbd5e1;color:#334155}
.hs-btn--ghost:hover{border-color:#94a3b8}
.hs-metrics{display:flex;gap:2rem;padding-top:1.5rem;border-top:1px solid #e2e8f0;flex-wrap:wrap}
.hs-metric{display:flex;flex-direction:column;gap:.2rem}
.hs-metric-value{font-size:1.5rem;font-weight:800;color:#0f172a}
.hs-metric-label{font-size:.8125rem;color:#64748b}
.hs-preview{border-radius:1rem;overflow:hidden;box-shadow:0 20px 60px #0000001a}
.hs-mockup{background:#1e293b;padding:1.25rem;border-radius:1rem}
.hs-mock-bar{display:flex;gap:.4rem;margin-bottom:1rem}
.hs-mock-bar span{width:10px;height:10px;border-radius:50%;background:#ffffff26}
.hs-mock-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:.75rem}
.hs-mock-stat{background:#ffffff0f;border-radius:.5rem;padding:1rem}
.hs-mock-val{font-size:1.25rem;font-weight:800;color:#fff}
.hs-mock-lbl{font-size:.75rem;color:#cbd5e1;margin-top:.25rem}
.hs-mock-chart{grid-column:span 3;background:#ffffff0a;border-radius:.5rem;padding:1rem;display:flex;align-items:flex-end;gap:.5rem;height:80px}
.hs-bar{flex:1;background:var(--color-accent,#6366f1);border-radius:3px 3px 0 0;opacity:.8}
@media (max-width:768px){
.hs-inner{grid-template-columns:1fr}
.hs-preview{order:-1}
}
</style>
Props
| Prop | Type | Default | Beschrijving |
|---|---|---|---|
eyebrow | string | - | Eyebrow-pill boven de kop |
headline * | string | - | H1-kop |
sub | string | - | Ondertitel |
primaryCta | { label: string; href: string } | - | Primaire knop |
ghostCta | { label: string; href: string } | - | Ghost-knop |
metrics | { value: string; label: string }[] | - | Metric-rij onder de CTA's |
mockStats | { value: string; label: string }[] | - | Stat-tegels in de mockup (default: metrics) |
* = verplicht