Zoeken...  ⌘K GitHub

ImagePortfolio image

Portfolio-grid met klant, dienst en resultaat per item.

/image-portfolio
src/components/image/ImagePortfolio.astro
---
interface PortfolioItem {
  src?: string;
  alt: string;
  client: string;
  service: string;
  result?: string;
}

interface Props {
  items?: PortfolioItem[];
  title?: string;
  subtitle?: string;
}

const {
  items = [
    { alt: 'Klant 1', client: 'TechFlow B.V.', service: 'Google Ads', result: '+280% leads' },
    { alt: 'Klant 2', client: 'Mode & Meer', service: 'Meta Ads', result: '5.1x ROAS' },
    { alt: 'Klant 3', client: 'Bouw & Co', service: 'Branding', result: 'Nieuw merk live' },
    { alt: 'Klant 4', client: 'Vitaal Leven', service: 'SEO', result: '+420% traffic' },
    { alt: 'Klant 5', client: 'Horeca Plus', service: 'Social media', result: '12k volgers' },
    { alt: 'Klant 6', client: 'FinTech NL', service: 'Website', result: 'Conversie +60%' },
  ],
  title = 'Cases',
  subtitle = 'Resultaten waar wij trots op zijn',
} = Astro.props;
---

<section class="ipt">
  <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 class="ipt__img" src={item.src} alt={item.alt} />
          ) : (
            <div class="ipt__placeholder" aria-label={item.alt}></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>
</section>

<style>
  :root {
    --color-accent: #6366f1;
    --color-primary: #0a0a0a;
  }
  .ipt { padding: 3rem 0; }
  .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: #666;
    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 0%, #c8d8f0 100%);
  }
  .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: #888;
    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
items { src?: string; alt: string; client: string; service: string; result?: string }[] Portfolio items
title string Sectietitel
subtitle string Subtitel

* = verplicht