Zoeken...  ⌘K GitHub

ContentWithStats content

Tekst + grote statistieken naast elkaar. Cijfers in accent kleur, dunne scheidingslijn. Sterk voor social proof in context.

/content-with-stats
src/components/content/ContentWithStats.astro
---
interface Stat {
  value: string;
  label: string;
  description?: string;
}

interface Props {
  eyebrow?: string;
  headline: string;
  body: string;
  image?: string;
  stats: Stat[];
  ctaLabel?: string;
  ctaHref?: string;
}

const {
  eyebrow,
  headline,
  body,
  image,
  stats = [],
  ctaLabel,
  ctaHref = '#',
} = Astro.props;
---

<section class="cws__section">
  <div class="cws__inner">
    <!-- Left: text -->
    <div class="cws__text cws-reveal cws-reveal--left">
      {eyebrow && <p class="cws__eyebrow">{eyebrow}</p>}
      <h2 class="cws__headline" set:html={headline} />
      <p class="cws__body">{body}</p>
      {ctaLabel && (
        <a href={ctaHref} class="cws__cta">{ctaLabel}</a>
      )}
    </div>

    <!-- Right: stats -->
    <div class="cws__stats-col cws-reveal cws-reveal--right">
      {image && (
        <div class="cws__thumb">
          <img src={image} alt="" class="cws__thumb-img" loading="lazy" decoding="async" />
        </div>
      )}

      <div class="cws__stats">
        {stats.map((stat, i) => (
          <div class="cws__stat">
            <span class="cws__stat-value">{stat.value}</span>
            <span class="cws__stat-label">{stat.label}</span>
            {stat.description && (
              <span class="cws__stat-desc">{stat.description}</span>
            )}
            {i < stats.length - 1 && <hr class="cws__stat-rule" />}
          </div>
        ))}
      </div>
    </div>
  </div>
</section>

<script>
  const reveals = document.querySelectorAll<HTMLElement>('.cws-reveal');

  const observer = new IntersectionObserver(
    (entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          (entry.target as HTMLElement).classList.add('cws-reveal--visible');
          observer.unobserve(entry.target);
        }
      });
    },
    { threshold: 0.2 }
  );

  reveals.forEach((el) => observer.observe(el));
</script>

<style>
  :root {
    --color-primary: #0a0a0a;
    --color-accent: #6366f1;
    --color-bg: #fff;
    --color-muted: #6b7280;
    --radius: 0.5rem;
  }

  .cws__section {
    background: var(--color-bg);
    padding: clamp(3rem, 8vw, 7rem) clamp(1rem, 5vw, 2rem);
  }

  .cws__inner {
    display: flex;
    align-items: flex-start;
    gap: clamp(2.5rem, 6vw, 6rem);
    max-width: 1200px;
    margin: 0 auto;
  }

  /* Left column */
  .cws__text {
    flex: 1 1 45%;
    min-width: 0;
  }

  .cws__eyebrow {
    font-size: clamp(0.65rem, 1.2vw, 0.75rem);
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--color-accent);
    margin: 0 0 0.75rem;
  }

  .cws__headline {
    font-size: clamp(2rem, 3.5vw, 3rem);
    font-weight: 800;
    line-height: 1.15;
    color: var(--color-primary);
    margin: 0 0 1.25rem;
  }

  .cws__headline :global(em) {
    font-style: italic;
    color: var(--color-accent);
  }

  .cws__body {
    font-size: clamp(1rem, 1.5vw, 1.125rem);
    line-height: 1.7;
    color: var(--color-muted);
    margin: 0 0 2rem;
  }

  .cws__cta {
    display: inline-flex;
    align-items: center;
    padding: 0.75rem 1.5rem;
    border-radius: var(--radius);
    font-size: 0.9375rem;
    font-weight: 600;
    text-decoration: none;
    background: var(--color-accent);
    color: #fff;
    transition: opacity 0.2s ease, transform 0.2s ease;
  }

  .cws__cta:hover {
    opacity: 0.85;
    transform: translateY(-1px);
  }

  /* Right column */
  .cws__stats-col {
    flex: 1 1 45%;
    min-width: 0;
  }

  .cws__thumb {
    margin-bottom: 1.5rem;
    border-radius: var(--radius);
    overflow: hidden;
  }

  .cws__thumb-img {
    display: block;
    width: 100%;
    height: 180px;
    object-fit: cover;
    border-radius: var(--radius);
  }

  .cws__stats {
    display: flex;
    flex-direction: column;
    gap: 0;
  }

  .cws__stat {
    display: flex;
    flex-direction: column;
    padding: 1.25rem 0;
  }

  .cws__stat-value {
    font-size: clamp(2.5rem, 4vw, 4rem);
    font-weight: 900;
    line-height: 1;
    color: var(--color-accent);
    letter-spacing: -0.02em;
  }

  .cws__stat-label {
    font-size: clamp(0.9rem, 1.2vw, 1rem);
    font-weight: 700;
    color: var(--color-primary);
    margin-top: 0.25rem;
  }

  .cws__stat-desc {
    font-size: 0.875rem;
    color: var(--color-muted);
    margin-top: 0.25rem;
    line-height: 1.5;
  }

  .cws__stat-rule {
    border: none;
    border-top: 1px solid rgba(0, 0, 0, 0.08);
    margin: 0;
  }

  /* Scroll reveal */
  .cws-reveal {
    opacity: 0;
    transition: opacity 0.7s ease, transform 0.7s ease;
  }

  .cws-reveal--left {
    transform: translateX(-2rem);
  }

  .cws-reveal--right {
    transform: translateX(2rem);
    transition-delay: 0.12s;
  }

  .cws-reveal--visible {
    opacity: 1;
    transform: translateX(0);
  }

  /* Responsive */
  @media (max-width: 768px) {
    .cws__inner {
      flex-direction: column;
    }

    .cws__text,
    .cws__stats-col {
      flex: 1 1 100%;
      width: 100%;
    }

    .cws__stats {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 1.25rem;
    }

    .cws__stat {
      padding: 0;
    }

    .cws__stat-rule {
      display: none;
    }

    .cws-reveal--left,
    .cws-reveal--right {
      transform: translateY(1.5rem);
    }

    .cws-reveal--visible {
      transform: translateY(0);
    }
  }

  @media (max-width: 480px) {
    .cws__stats {
      grid-template-columns: 1fr;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    * {
      animation: none !important;
      transition: none !important;
    }
  }
</style>

Props

Prop Type Default Beschrijving
headline * string Sectie headline
body * string Tekst blok
stats * { value: string; label: string; description?: string }[] Statistieken rechts
eyebrow string Label boven headline
ctaLabel string CTA tekst

* = verplicht