ImagePortfolio image
Portfoliogrid van case study kaarten met afbeelding, resultaatbadge, klantnaam en dienst.
src/components/image/ImagePortfolio.astro
---
interface PortfolioItem {
src?: string;
alt?: string;
result?: string;
client: string;
service: string;
}
interface Props {
title?: string;
subtitle?: string;
items?: PortfolioItem[];
}
const {
title = 'Cases',
subtitle = 'Resultaten waar wij trots op zijn',
items = [
{ alt: 'Klant 1', result: '+280% leads', client: 'TechFlow B.V.', service: 'Google Ads' },
{ alt: 'Klant 2', result: '5.1x ROAS', client: 'Mode & Meer', service: 'Meta Ads' },
{ alt: 'Klant 3', result: 'Nieuw merk live', client: 'Bouw & Co', service: 'Branding' },
],
} = Astro.props;
---
<section class="bl-section ipt">
<div class="bl-inner ipt__inner">
{(title || subtitle) && (
<div class="ipt__header">
{title && <h2 class="ipt__title">{title}</h2>}
{subtitle && <p class="ipt__subtitle">{subtitle}</p>}
</div>
)}
<div class="ipt__grid">
{items.map((item) => (
<div class="ipt__item">
<div class="ipt__media">
{item.src
? <img data-allow-img class="ipt__img" src={item.src} alt={item.alt ?? item.client} loading="lazy" decoding="async" />
: <div class="ipt__placeholder" aria-label={item.alt ?? item.client}></div>
}
{item.result && <span class="ipt__result">{item.result}</span>}
</div>
<div class="ipt__meta">
<p class="ipt__client">{item.client}</p>
<p class="ipt__service">{item.service}</p>
</div>
</div>
))}
</div>
</div>
</section>
<style>
.ipt__header {
text-align: center;
margin-bottom: 3rem;
}
.ipt__title {
font-size: clamp(1.75rem, 3vw, 2.5rem);
font-weight: 700;
color: var(--color-primary, #0a0a0a);
margin: 0 0 0.5rem;
}
.ipt__subtitle {
font-size: 1.05rem;
color: #555;
margin: 0;
}
.ipt__grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.5rem;
}
.ipt__item {
border-radius: 10px;
overflow: hidden;
border: 1px solid #eee;
transition: transform 0.2s, box-shadow 0.2s;
}
.ipt__item:hover {
transform: translateY(-4px);
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.1);
}
.ipt__media {
position: relative;
aspect-ratio: 3/2;
}
.ipt__img {
width: 100%;
height: 100%;
object-fit: cover;
}
.ipt__placeholder {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #e0e8f8, #c8d8f0);
}
.ipt__result {
position: absolute;
top: 0.75rem;
right: 0.75rem;
background: var(--color-accent, #6366f1);
color: #fff;
font-size: 0.75rem;
font-weight: 700;
padding: 0.25rem 0.6rem;
border-radius: 4px;
}
.ipt__meta {
padding: 1rem;
}
.ipt__client {
font-size: 0.95rem;
font-weight: 700;
color: var(--color-primary, #0a0a0a);
margin: 0 0 0.2rem;
}
.ipt__service {
font-size: 0.8rem;
color: #777;
margin: 0;
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 600;
}
@media (max-width: 768px) {
.ipt__grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 480px) {
.ipt__grid {
grid-template-columns: 1fr;
}
}
</style>
Props
| Prop | Type | Default | Beschrijving |
|---|---|---|---|
title | string | - | Kop boven het portfoliogrid |
subtitle | string | - | Subtekst onder de kop |
items | { src?: string; alt?: string; result?: string; client: string; service: string }[] | - | Portfolio-items met optionele afbeelding en resultaatbadge |
* = verplicht