feat(video): add YouTube lightbox to benefits section
Replaces static video link with a self-contained VideoCard component that opens a native <dialog> lightbox with autoplay on open and iframe src teardown on close. Includes pulsing ring animation on the play button and a rounded-triangle SVG icon. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,8 @@ interface Props { lang?: string }
|
||||
const { lang = 'en' } = Astro.props;
|
||||
const es = lang === 'es';
|
||||
const entry = await getEntry('pages', es ? 'es/index' : 'en/index');
|
||||
const { title, description, canonical, sections = [] } = entry!.data;
|
||||
if (!entry) throw new Error(`[HomePage] Content entry not found for lang="${lang}". If running in dev, restart the dev server to rebuild the content layer cache.`);
|
||||
const { title, description, canonical, sections = [] } = entry.data;
|
||||
---
|
||||
<BaseLayout {title} {description} {canonical} {lang}>
|
||||
{(sections as HomeSection[]).map((section) => {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
interface Props {
|
||||
image: string;
|
||||
alt: string;
|
||||
videoUrl: string;
|
||||
videoTitle: string;
|
||||
}
|
||||
const { image, alt, videoUrl, videoTitle } = Astro.props;
|
||||
---
|
||||
<button class="video-card" data-video-url={videoUrl} aria-haspopup="dialog" aria-label={`Watch video: ${videoTitle}`}>
|
||||
<img src={`/assets/images/${image}`} alt={alt} />
|
||||
<span aria-hidden="true">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="50" height="50" fill="currentColor" aria-hidden="true">
|
||||
<path d="M21 12.88L37.5 22.4A3 3 0 0 1 37.5 27.6L21 37.12A3 3 0 0 1 16.5 34.52L16.5 15.48A3 3 0 0 1 21 12.88Z" />
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
<dialog class="video-dialog">
|
||||
<div class="video-dialog-inner">
|
||||
<button class="video-dialog-close" aria-label="Close video" autofocus>✕</button>
|
||||
<div class="video-dialog-frame">
|
||||
<iframe title={videoTitle} allowfullscreen allow="autoplay; encrypted-media"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<script>
|
||||
document.querySelectorAll('button.video-card[data-video-url]').forEach((card) => {
|
||||
const dialog = card.nextElementSibling as HTMLDialogElement;
|
||||
const iframe = dialog.querySelector('iframe') as HTMLIFrameElement;
|
||||
const base = card.getAttribute('data-video-url')!;
|
||||
const sep = base.includes('?') ? '&' : '?';
|
||||
|
||||
const closeBtn = dialog.querySelector('.video-dialog-close') as HTMLButtonElement;
|
||||
closeBtn.addEventListener('click', () => dialog.close());
|
||||
|
||||
card.addEventListener('click', () => {
|
||||
iframe.src = `${base}${sep}autoplay=1`;
|
||||
dialog.showModal();
|
||||
});
|
||||
|
||||
dialog.addEventListener('close', () => {
|
||||
iframe.src = '';
|
||||
});
|
||||
|
||||
// Close on backdrop click
|
||||
dialog.addEventListener('click', (e) => {
|
||||
if (e.target === dialog) dialog.close();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
import type { BenefitsSection } from '../../types/home-sections';
|
||||
import VideoCard from '../VideoCard.astro';
|
||||
interface Props { section: BenefitsSection }
|
||||
const { section } = Astro.props;
|
||||
---
|
||||
@@ -10,9 +11,11 @@ const { section } = Astro.props;
|
||||
<h3>{section.subheading}</h3>
|
||||
<ul class="source-list">{section.items.map((item) => <li>{item}</li>)}</ul>
|
||||
</div>
|
||||
<a class="video-card" href={section.videoHref}>
|
||||
<img src={`/assets/images/${section.videoImage}`} alt={section.videoImageAlt} />
|
||||
<span>▶</span>
|
||||
</a>
|
||||
<VideoCard
|
||||
image={section.videoImage}
|
||||
alt={section.videoImageAlt}
|
||||
videoUrl={section.videoUrl}
|
||||
videoTitle={section.videoTitle}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user