Split Hero with Video
HeroTwo-column hero: badge + shimmering heading + CTAs on the left, a playable video thumbnail with duration overlay on the right.
Create a Svelte 5 (runes) hero section with a 2-column layout on large screens (stacked on mobile). Left column: a small outlined badge with a medal icon, a large shimmering-text heading, subtext, and two CTA buttons (solid primary, and outline) each wrapped in a subtle looping shine/sheen hover effect. Right column: a video thumbnail with rounded corners and shadow; clicking the centered play button (a circle badge with a play icon) swaps the thumbnail for a lazy-loaded YouTube iframe using local `$state`. Overlay a gradient fade at the bottom of the thumbnail with a duration label + duration text in the corner. Use a scroll-reveal action sliding the left column in from the left and the right column in from the right with a stagger delay. Data-driven via `content: { badge, heading, subtext, primary_cta:{label,href}, secondary_cta:{label,href}, media:{thumbnail_url,thumbnail_alt,video_url,video_title,duration_label,duration} }`.
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import { Badge } from '$lib/components/ui/badge';
import ShimmeringText from '$lib/components/animate-ui/ShimmeringText.svelte';
import Shine from '$lib/components/animate-ui/Shine.svelte';
import PlayIcon from '@lucide/svelte/icons/play';
import MedalIcon from '@lucide/svelte/icons/medal';
import { reveal } from '$lib/actions/reveal';
type Props = {
content: {
badge: string;
heading: string;
subtext: string;
primary_cta: { label: string; href: string };
secondary_cta: { label: string; href: string };
media: {
thumbnail_url: string;
thumbnail_alt: string;
video_url: string;
video_title: string;
duration_label: string;
duration: string;
};
};
};
let { content }: Props = $props();
let isPlaying = $state(false);
</script>
<section class="relative z-0 w-full py-12 md:py-16 lg:py-20">
<div class="mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8">
<div class="grid items-center gap-8 lg:grid-cols-2 lg:gap-12">
<div class="flex flex-col items-center gap-6 lg:items-start" use:reveal={{ animation: 'left' }}>
<Badge variant="outline" class="h-auto font-semibold rounded-md text-muted-foreground gap-2 px-4 py-2">
<MedalIcon class="size-4" aria-hidden="true" />
{content.badge}
</Badge>
<div class="flex flex-col gap-4 text-center lg:text-start">
<h1 class="text-4xl font-bold tracking-tight text-balance md:text-5xl">
<ShimmeringText text={content.heading} />
</h1>
<p class="text-muted-foreground text-balance md:text-lg">{content.subtext}</p>
</div>
<div class="flex flex-wrap items-center justify-center gap-4 lg:justify-start">
<Shine loop loopDelay={3000} delay={0} color="#ffffff" opacity={0.4} class="rounded-lg">
<Button size="lg" class="h-10 px-8" href={content.primary_cta.href}>
{content.primary_cta.label}
</Button>
</Shine>
<Shine loop loopDelay={3000} delay={400} color="#0078B7" opacity={0.3} class="rounded-lg">
<Button variant="outline" size="lg" class="h-10 px-8 bg-background" href={content.secondary_cta.href}>
{content.secondary_cta.label}
</Button>
</Shine>
</div>
</div>
<div class="group relative isolate overflow-hidden rounded-xl" use:reveal={{ animation: 'right', delay: 150 }}>
<div class="overflow-hidden border-0 shadow-lg rounded-xl">
<div class="relative w-full aspect-video">
{#if isPlaying}
<iframe
src={content.media.video_url}
title={content.media.video_title}
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
class="absolute inset-0 w-full h-full rounded-lg"
loading="lazy"
></iframe>
{:else}
<div class="relative w-full h-full">
<img
src={content.media.thumbnail_url}
alt={content.media.thumbnail_alt}
class="w-full h-full rounded-lg object-cover dark:brightness-95 dark:invert"
fetchpriority="high"
decoding="sync"
/>
<div class="absolute inset-0 bg-linear-to-t from-background/40 via-background/20 to-transparent"></div>
<button
onclick={() => (isPlaying = true)}
class="absolute inset-0 flex w-full h-full items-center justify-center hover:opacity-90 transition-opacity"
aria-label="Play demo video"
>
<div class="bg-background/90 flex size-14 items-center justify-center rounded-full shadow-lg hover:scale-105 transition-transform">
<PlayIcon size={20} class="ms-1" />
</div>
</button>
<div class="absolute start-4 end-4 bottom-4">
<p class="text-foreground text-sm font-medium">{content.media.duration_label}</p>
<p class="text-muted-foreground text-xs">{content.media.duration}</p>
</div>
</div>
{/if}
</div>
</div>
</div>
</div>
</div>
</section>