Zoeken...  ⌘K GitHub

ImageTimeline image

Tijdlijn met afwisselend beeld en tekst langs een centrale lijn. Geschikt voor bedrijfsgeschiedenis, procesoverzicht of case-milestones.

/image-timeline
src/components/image/ImageTimeline.astro
---
/**
 * ImageTimeline
 * Tijdlijn met afwisselend beeld + tekst links/rechts van een centrale lijn.
 * Geschikt voor bedrijfsgeschiedenis, procesoverzicht of case-milestones.
 *
 * Props:
 * - heading?: string
 * - items: { year?: string; title: string; body?: string; image?: { src: string; alt?: string } }[]
 * - theme?: 'light' | 'dark'    default 'light'
 */
interface TimelineItem {
  year?: string;
  title: string;
  body?: string;
  image?: { src: string; alt?: string };
}

interface Props {
  heading?: string;
  items: TimelineItem[];
  theme?: 'light' | 'dark';
}

const {
  heading,
  items,
  theme = 'light',
} = Astro.props;
---

<section class:list={['bl-section', 'itl', `itl--${theme}`]}>
  <div class="bl-inner itl-inner">
    {heading && <h2 class="itl-heading">{heading}</h2>}
    <div class="itl-list">
      {items.map((item, i) => (
        <div class:list={['itl-item', i % 2 !== 0 && 'itl-item--right']}>
          <div class="itl-media">
            {item.image
              ? <img src={item.image.src} alt={item.image.alt ?? ''} class="itl-img" loading="lazy" decoding="async" />
              : <div class="itl-placeholder" aria-hidden="true"></div>
            }
          </div>
          <div class="itl-dot" aria-hidden="true"></div>
          <div class="itl-content">
            {item.year && <span class="itl-year">{item.year}</span>}
            <h3 class="itl-name">{item.title}</h3>
            {item.body && <p class="itl-body">{item.body}</p>}
          </div>
        </div>
      ))}
      <div class="itl-line" aria-hidden="true"></div>
    </div>
  </div>
</section>

<style>
  .itl--light { background: var(--color-bg, #fff); color: var(--color-text, #0a0a0a); }
  .itl--dark  { background: #0a0a0a; color: #e8e8e8; }

  .itl-heading {
    font-size: clamp(1.75rem, 3vw, 2.5rem);
    font-weight: 700;
    text-align: center;
    margin: 0 0 3rem;
  }

  .itl-list {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 3rem;
  }

  .itl-line {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    transform: translateX(-50%);
    width: 2px;
    background: #e0e0e0;
    z-index: 0;
  }

  .itl--dark .itl-line { background: #333; }

  .itl-item {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 2rem;
    position: relative;
    z-index: 1;
  }

  .itl-item--right .itl-media  { order: 3; }
  .itl-item--right .itl-content { order: 1; text-align: right; }

  .itl-media {
    aspect-ratio: 4 / 3;
    border-radius: 8px;
    overflow: hidden;
  }

  .itl-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
  }

  .itl-placeholder {
    width: 100%;
    height: 100%;
    background: linear-gradient(135deg, #e0e0f0, #c8c8e8);
  }

  .itl--dark .itl-placeholder {
    background: linear-gradient(135deg, #1e1e38, #2a2a48);
  }

  .itl-dot {
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--color-accent, #6366f1);
    border: 3px solid #fff;
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent, #6366f1) 20%, transparent);
    flex-shrink: 0;
  }

  .itl--dark .itl-dot { border-color: #0a0a0a; }

  .itl-content { padding: .5rem; }

  .itl-year {
    font-size: .75rem;
    font-weight: 700;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--color-accent, #6366f1);
    display: block;
    margin-bottom: .35rem;
  }

  .itl-name {
    font-size: 1.1rem;
    font-weight: 700;
    margin: 0 0 .5rem;
    color: inherit;
  }

  .itl-body {
    font-size: 1rem;
    color: var(--color-muted, #6b7280);
    line-height: 1.6;
    margin: 0;
  }

  .itl--dark .itl-body { color: #aaa; }

  /* Mobile: single-column stacked */
  @media (max-width: 700px) {
    .itl-line { left: 1.5rem; transform: none; }

    .itl-item,
    .itl-item--right {
      grid-template-columns: 1fr;
      padding-left: 3.5rem;
    }

    .itl-item--right .itl-media  { order: 0; }
    .itl-item--right .itl-content { order: 2; text-align: left; }

    .itl-dot {
      position: absolute;
      left: 1rem;
      top: .25rem;
    }
  }
</style>

Props

Prop Type Default Beschrijving
heading string - Sectie-kop
items * { year?: string; title: string; body?: string; image?: { src: string; alt?: string } }[] - Tijdlijn-items
theme 'light' | 'dark' 'light' Achtergrond-mode

* = verplicht