src/components/video/VideoPlaylist.astro
---
interface PlaylistItem {
poster?: string;
alt: string;
title: string;
duration?: string;
active?: boolean;
}
interface Props {
items?: PlaylistItem[];
heading?: string;
}
const {
items = [
{ alt: 'Video 1', title: 'Introductie BLURR methode', duration: '3:28', active: true },
{ alt: 'Video 2', title: 'Stap 1: Strategie & positionering', duration: '8:14' },
{ alt: 'Video 3', title: 'Stap 2: Brand identity bouwen', duration: '6:55' },
{ alt: 'Video 4', title: 'Stap 3: Campagnes opzetten', duration: '10:02' },
{ alt: 'Video 5', title: 'Stap 4: Meten en optimaliseren', duration: '7:40' },
],
heading = 'Videoserie: de BLURR methode',
} = Astro.props;
const activeItem = items.find(i => i.active) ?? items[0];
---
<section class="vpl">
{heading && <h2 class="vpl__heading">{heading}</h2>}
<div class="vpl__layout">
<div class="vpl__main">
<div class="vpl__player">
{activeItem?.poster ? (
<img class="vpl__active-poster" src={activeItem.poster} alt={activeItem.alt} />
) : (
<div class="vpl__active-placeholder" aria-label={activeItem?.alt}>
<svg width="60" height="60" viewBox="0 0 60 60">
<circle cx="30" cy="30" r="30" fill="rgba(99,102,241,0.85)"/>
<polygon points="24,16 50,30 24,44" fill="#fff"/>
</svg>
</div>
)}
</div>
<p class="vpl__active-title">{activeItem?.title}</p>
</div>
<div class="vpl__sidebar">
{items.map((item) => (
<button
class:list={['vpl__item', item.active && 'vpl__item--active']}
type="button"
>
<div class="vpl__thumb">
{item.poster ? (
<img class="vpl__thumb-img" src={item.poster} alt={item.alt} />
) : (
<div class="vpl__thumb-placeholder" aria-label={item.alt}></div>
)}
{item.active && <span class="vpl__playing-dot" aria-hidden="true"></span>}
</div>
<div class="vpl__item-info">
<p class="vpl__item-title">{item.title}</p>
{item.duration && <p class="vpl__item-duration">{item.duration}</p>}
</div>
</button>
))}
</div>
</div>
</section>
<style>
:root {
--color-accent: #6366f1;
--color-primary: #0a0a0a;
}
.vpl { padding: 2rem 0; }
.vpl__heading {
font-size: 1.75rem;
font-weight: 700;
color: var(--color-primary, #0a0a0a);
margin: 0 0 2rem;
}
.vpl__layout {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 1.5rem;
align-items: start;
}
.vpl__player {
aspect-ratio: 16/9;
border-radius: 10px;
overflow: hidden;
background: #111;
}
.vpl__active-poster { width: 100%; height: 100%; object-fit: cover; }
.vpl__active-placeholder {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #0a0a1a 0%, #1a1a3a 100%);
display: flex;
align-items: center;
justify-content: center;
}
.vpl__active-title {
font-size: 1rem;
font-weight: 600;
color: var(--color-primary, #0a0a0a);
margin: 0.75rem 0 0;
}
.vpl__sidebar {
display: flex;
flex-direction: column;
gap: 0.5rem;
max-height: 400px;
overflow-y: auto;
}
.vpl__item {
display: flex;
align-items: flex-start;
gap: 0.75rem;
padding: 0.6rem;
border: none;
background: transparent;
border-radius: 8px;
cursor: pointer;
text-align: left;
transition: background 0.15s;
}
.vpl__item:hover { background: #f5f5f5; }
.vpl__item--active { background: #eeeef8; }
.vpl__thumb {
position: relative;
width: 80px;
aspect-ratio: 16/9;
border-radius: 4px;
overflow: hidden;
flex-shrink: 0;
}
.vpl__thumb-img { width: 100%; height: 100%; object-fit: cover; }
.vpl__thumb-placeholder {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #1a1a2e 0%, #2a2a4e 100%);
}
.vpl__playing-dot {
position: absolute;
inset: 0;
background: rgba(99,102,241,0.5);
display: flex;
align-items: center;
justify-content: center;
}
.vpl__playing-dot::after {
content: '▶';
color: #fff;
font-size: 0.8rem;
}
.vpl__item-info { flex: 1; min-width: 0; }
.vpl__item-title {
font-size: 0.8rem;
font-weight: 600;
color: var(--color-primary, #0a0a0a);
margin: 0 0 0.2rem;
line-height: 1.3;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.vpl__item-duration {
font-size: 0.72rem;
color: #999;
margin: 0;
font-weight: 500;
}
@media (max-width: 768px) {
.vpl__layout { grid-template-columns: 1fr; }
.vpl__sidebar { max-height: none; }
}
</style>
Props
| Prop | Type | Default | Beschrijving |
|---|---|---|---|
items | { poster?: string; alt: string; title: string; duration?: string; active?: boolean }[] | — | Playlist items |
heading | string | — | Sectietitel |
* = verplicht