HeroPortfolio Hero
Donkere portfolio-hero: tekstkolom links (eyebrow-pill, kop met accent, sub, CTA) en rechts een stapel case-cards (beeld + categorie + titel).
src/components/hero/HeroPortfolio.astro
---
/**
* HeroPortfolio
* Donkere portfolio-hero: tekstkolom links (eyebrow-pill, kop met cursief
* accent, sub, CTA) en rechts een stapel case-cards (beeld + categorie + titel).
* Content ligt op de pagina-rail (.bl-inner).
*
* Props: zie interface. Alle copy is prop-driven; cases is een array.
*/
interface Case {
image: string;
alt?: string;
category: string;
title: string;
href?: string;
}
interface Props {
eyebrow?: string;
headline: string;
accent?: string;
sub?: string;
cta?: { label: string; href: string };
cases?: Case[];
}
const {
eyebrow,
headline,
accent,
sub,
cta,
cases = [],
} = Astro.props;
---
<section class:list={['bl-section', 'hpf__root']} aria-label="Hero">
<div class="bl-inner hpf__container">
<div class="hpf__left">
{eyebrow && <span class="hpf__eyebrow">{eyebrow}</span>}
<h1 class="hpf__headline">{headline}{accent && <em> {accent}</em>}</h1>
{sub && <p class="hpf__sub">{sub}</p>}
{cta && (
<a href={cta.href} class="hpf__cta">
<span>{cta.label}</span>
<svg class="hpf__cta-arrow" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M3 8h10M9 4l4 4-4 4" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path></svg>
</a>
)}
</div>
{cases.length > 0 && (
<div class="hpf__right" aria-label="Portfolio cases">
{cases.map((c, i) => (
<article class="hpf__card" data-index={i}>
<a href={c.href ?? '#'} class="hpf__card-link">
<div class="hpf__card-img-wrap">
<img data-allow-img src={c.image} alt={c.alt ?? c.title} class="hpf__card-img" loading={i === 0 ? 'eager' : 'lazy'} fetchpriority={i === 0 ? 'high' : 'auto'} decoding="async" />
</div>
<div class="hpf__card-body">
<span class="hpf__card-cat">{c.category}</span>
<h2 class="hpf__card-title">{c.title}</h2>
</div>
</a>
</article>
))}
</div>
)}
</div>
</section>
<style>
.hpf__root{background:var(--color-primary, #0a0a0a);color:#fff;min-height:100svh;display:flex;align-items:center;overflow:hidden}
.hpf__container{display:grid;grid-template-columns:1fr 1fr;gap:clamp(3rem, 6vw, 5rem);align-items:center}
.hpf__left{display:flex;flex-direction:column;gap:1.5rem}
.hpf__eyebrow{display:inline-block;font-size:.8125rem;font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--color-accent, #6366f1);padding:.3rem .875rem;border:1px solid var(--color-accent, #6366f1);border-radius:999px;width:fit-content}
.hpf__headline{font-size:clamp(3.5rem, 5.5vw, 5rem);font-weight:900;line-height:1;letter-spacing:-.035em;color:#fff;margin:0}
.hpf__headline em{font-style:normal;color:var(--color-accent, #6366f1)}
.hpf__sub{font-size:clamp(1rem,1.4vw,1.1875rem);color:#cfcfcf;line-height:1.7;margin:0;max-width:44ch}
.hpf__cta{display:inline-flex;align-items:center;gap:.5rem;background:var(--color-accent, #6366f1);color:#fff;text-decoration:none;font-weight:600;font-size:.9375rem;padding:.875rem 1.875rem;border-radius:var(--radius, .5rem);width:fit-content;transition:opacity .2s,transform .2s}
.hpf__cta:hover{opacity:.88;transform:translateY(-1px)}
.hpf__cta-arrow{flex-shrink:0;transition:transform .2s}
.hpf__cta:hover .hpf__cta-arrow{transform:translate(3px)}
.hpf__right{display:flex;flex-direction:column;gap:1.25rem;position:relative}
.hpf__card{border-radius:calc(var(--radius, .5rem) * 1.5);overflow:hidden;background:#161616;border:1px solid rgba(255,255,255,.07);transition:transform .28s cubic-bezier(.16,1,.3,1);will-change:transform}
.hpf__card:hover{transform:translateY(-4px)}
.hpf__card[data-index="0"]{flex:0 0 auto}
.hpf__card-link{display:block;text-decoration:none;color:inherit}
.hpf__card-img-wrap{width:100%;overflow:hidden;aspect-ratio:16 / 7;background:#222}
.hpf__card[data-index="0"] .hpf__card-img-wrap{aspect-ratio:16 / 8}
.hpf__card-img{width:100%;height:100%;object-fit:cover;display:block;transition:transform .5s cubic-bezier(.16,1,.3,1)}
.hpf__card:hover .hpf__card-img{transform:scale(1.04)}
.hpf__card-body{padding:1.125rem 1.375rem 1.25rem;display:flex;flex-direction:column;gap:.375rem}
.hpf__card-cat{font-size:.75rem;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--color-accent, #6366f1)}
.hpf__card-title{font-size:clamp(1rem,1.5vw,1.1875rem);font-weight:700;color:#fff;margin:0;line-height:1.3}
@media (max-width:768px){
.hpf__container{grid-template-columns:1fr}
.hpf__right{flex-direction:row;overflow-x:auto;gap:1rem;padding-bottom:.5rem;scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch}
.hpf__card{min-width:min(280px,78vw);scroll-snap-align:start}
.hpf__card-img-wrap,.hpf__card[data-index="0"] .hpf__card-img-wrap{aspect-ratio:4 / 3}
}
</style>
Props
| Prop | Type | Default | Beschrijving |
|---|---|---|---|
eyebrow | string | - | Eyebrow-pill boven de kop |
headline * | string | - | H1-kop |
accent | string | - | Cursief accent direct na de kop |
sub | string | - | Ondertitel |
cta | { label: string; href: string } | - | CTA-knop |
cases | { image: string; alt?: string; category: string; title: string; href?: string }[] | - | Case-cards rechts |
* = verplicht