Zoeken...  ⌘K GitHub

ImagePortfolio image

Portfoliogrid van case study kaarten met afbeelding, resultaatbadge, klantnaam en dienst.

/image-portfolio
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