Zoeken...  ⌘K GitHub

IconBento icon

Bento grid met icoon+tekst blokken. Wide en tall spans, accent en dark varianten. Mobiel: 1 kolom.

/icon-bento
src/components/icon/IconBento.astro
---
interface Props {
  eyebrow?: string;
  headline?: string;
  items: {
    icon: string;
    title: string;
    description: string;
    span?: 'normal' | 'wide' | 'tall';
    accent?: boolean;
    dark?: boolean;
  }[];
}

const {
  eyebrow,
  headline,
  items = [],
} = Astro.props;

function cardClass(item: Props['items'][0]) {
  const classes = ['ib__card'];
  if (item.span === 'wide') classes.push('ib__card--wide');
  if (item.span === 'tall') classes.push('ib__card--tall');
  if (item.accent) classes.push('ib__card--accent');
  if (item.dark) classes.push('ib__card--dark');
  return classes.join(' ');
}
---

<section class="ib__section">
  {(eyebrow || headline) && (
    <div class="ib__header">
      {eyebrow && <p class="ib__eyebrow">{eyebrow}</p>}
      {headline && <h2 class="ib__headline">{headline}</h2>}
    </div>
  )}

  <div class="ib__grid">
    {items.map((item, i) => (
      <div class={cardClass(item)} style={`--delay: ${i * 90}ms`}>
        <span class="ib__icon-wrap" aria-hidden="true" set:html={item.icon} />
        <p class="ib__title">{item.title}</p>
        <p class="ib__desc">{item.description}</p>
      </div>
    ))}
  </div>
</section>

<script>
  const grids = document.querySelectorAll<HTMLElement>('.ib__grid');

  grids.forEach((grid) => {
    const cards = grid.querySelectorAll<HTMLElement>('.ib__card');

    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            (entry.target as HTMLElement).classList.add('ib__card--visible');
            observer.unobserve(entry.target);
          }
        });
      },
      { threshold: 0.08, rootMargin: '0px 0px -40px 0px' }
    );

    cards.forEach((card) => observer.observe(card));
  });
</script>

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

  .ib__section {
    padding: 5rem 1.5rem;
    max-width: 72rem;
    margin-inline: auto;
  }

  /* Header */
  .ib__header {
    text-align: center;
    margin-bottom: 2.5rem;
  }

  .ib__eyebrow {
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--color-accent);
    margin: 0 0 0.75rem;
  }

  .ib__headline {
    font-size: clamp(1.75rem, 3vw, 2.5rem);
    font-weight: 800;
    color: var(--color-primary);
    margin: 0;
    line-height: 1.15;
  }

  /* Bento grid */
  .ib__grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1rem;
    align-items: start;
  }

  /* Cards */
  .ib__card {
    border-radius: 1rem;
    padding: 1.5rem;
    border: 1px solid rgba(0, 0, 0, 0.08);
    background: var(--color-bg);
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    opacity: 0;
    transform: translateY(16px);
    transition: box-shadow 0.22s ease;
  }

  .ib__card:hover {
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.07);
  }

  .ib__card--visible {
    animation: ib-fadein 0.5s ease forwards;
    animation-delay: var(--delay, 0ms);
  }

  @keyframes ib-fadein {
    to {
      opacity: 1;
      transform: translateY(0);
    }
  }

  /* Spans */
  .ib__card--wide { grid-column: span 2; }
  .ib__card--tall { grid-row: span 2; }

  /* Variants */
  .ib__card--accent {
    background: rgba(99, 102, 241, 0.06);
    border-color: rgba(99, 102, 241, 0.15);
  }

  .ib__card--dark {
    background: #0a0a0a;
    border-color: transparent;
  }

  .ib__card--dark .ib__title {
    color: #fff;
  }

  .ib__card--dark .ib__desc {
    color: rgba(255, 255, 255, 0.55);
  }

  .ib__card--dark .ib__icon-wrap {
    color: #fff;
  }

  /* Icon */
  .ib__icon-wrap {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 48px;
    height: 48px;
    color: var(--color-accent);
  }

  .ib__card--accent .ib__icon-wrap {
    color: var(--color-accent);
  }

  .ib__icon-wrap :global(svg) {
    width: 100%;
    height: 100%;
  }

  /* Text */
  .ib__title {
    font-size: 1.0625rem;
    font-weight: 700;
    color: var(--color-primary);
    margin: 0;
    line-height: 1.3;
  }

  .ib__desc {
    font-size: 0.9rem;
    color: var(--color-muted);
    margin: 0;
    line-height: 1.65;
  }

  /* Responsive */
  @media (max-width: 768px) {
    .ib__grid {
      grid-template-columns: 1fr;
    }
    .ib__card--wide,
    .ib__card--tall {
      grid-column: 1;
      grid-row: auto;
    }
  }

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

Props

Prop Type Default Beschrijving
items * { icon: string; title: string; description: string; span?: string; accent?: boolean; dark?: boolean }[] Bento items
eyebrow string Label boven sectie
headline string Sectie headline

* = verplicht