ImageTimeline image
Tijdlijn met afwisselend beeld en tekst langs een centrale lijn. Geschikt voor bedrijfsgeschiedenis, procesoverzicht of case-milestones.
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