ZENYNOZenith of Technology
Command Search
Search for a page
/ Components / Image Carousel Hero

Image Carousel Hero

Hero

Hero section with a 3D rotating image carousel, mouse parallax tilt, and a features grid. Theme-aware, fully animated.

herocarousel3danimationimages
Open preview
Live Preview
AI Generation Prompt

Create a hero section component with a 3D rotating image carousel. Layout (top to bottom, all vertically centered in min-h-screen): 1. Circular image carousel — 8 portrait photos (w-32 h-40 on mobile, w-40 h-48 on sm+) positioned in a circular orbit (radius 180px) using cos/sin math. Angles rotate 0.5° every 50ms via setInterval. Each card has an individual rotateZ tilt. The whole ring tilts toward the mouse cursor via rotateX/rotateY (±10° parallax from mouse position relative to container). Cards: rounded-2xl, overflow-hidden, shadow-2xl, hover:scale-110. Shimmer gradient overlay (from-white/20) appears on hover. Container: h-96 (sm:h-[500px]), perspective: 1000px, transform-style: preserve-3d. 2. Centered heading — font-serif, text-4xl/5xl/6xl, font-bold, text-balance, leading-tight. 3. Subtext — text-lg/xl, text-muted-foreground. 4. CTA button — rounded-full, px-8 py-3, bg-primary, with ArrowRight icon that slides right on hover (group-hover:translate-x-1). 5. Features grid — 1 col mobile → 3 cols sm+. Cards: text-center p-6 rounded-xl bg-card/50 backdrop-blur-sm border border-border/50, hover:bg-card/80, h3 group-hover:text-primary. Background: Two bg-primary/5 gradient blobs (top-right and bottom-left) with blur-3xl animate-pulse. All colors use CSS custom properties for dark mode support.

<script lang="ts">
	import ArrowRightIcon from '@lucide/svelte/icons/arrow-right';
	import { Button } from '$lib/components/ui/button';

	type Feature = { title: string; description: string };
	type ImageItem = { id: string; src: string; alt: string; rotation: number };

	type Props = {
		content: {
			heading: string;
			subtext: string;
			cta_label: string;
			images: ImageItem[];
			features: Feature[];
		};
	};

	let { content }: Props = $props();

	let angles: number[] = $state([]);
	let mouseX = $state(0.5);
	let mouseY = $state(0.5);

	$effect(() => {
		const count = content.images.length;
		angles = Array.from({ length: count }, (_, i) => i * (360 / count));
		const id = setInterval(() => {
			angles = angles.map((a) => (a + 0.5) % 360);
		}, 50);
		return () => clearInterval(id);
	});

	function handleMouseMove(e: MouseEvent) {
		const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
		mouseX = (e.clientX - rect.left) / rect.width;
		mouseY = (e.clientY - rect.top) / rect.height;
	}
</script>

<div class="relative w-full min-h-screen bg-linear-to-b from-background via-background to-background overflow-hidden">
	<!-- Ambient blobs -->
	<div class="absolute inset-0 overflow-hidden pointer-events-none">
		<div class="absolute top-0 right-0 w-96 h-96 bg-linear-to-br from-primary/5 to-transparent rounded-full blur-3xl animate-pulse"></div>
		<div class="absolute bottom-0 left-0 w-96 h-96 bg-linear-to-tr from-primary/5 to-transparent rounded-full blur-3xl animate-pulse"></div>
	</div>

	<div class="relative flex flex-col items-center justify-center min-h-screen px-4 sm:px-6 lg:px-8 py-12">
		<!-- Rotating image carousel -->
		<div
			class="relative w-full max-w-6xl h-96 sm:h-[500px] mb-12 sm:mb-16"
			onmousemove={handleMouseMove}
			onmouseleave={() => { mouseX = 0.5; mouseY = 0.5; }}
			role="img"
			aria-label="Rotating AI-generated image gallery"
		>
			<div class="absolute inset-0 flex items-center justify-center" style="perspective: 1000px">
				{#each content.images as img, i}
					{@const angle = (angles[i] ?? 0) * (Math.PI / 180)}
					{@const x = Math.cos(angle) * 180}
					{@const y = Math.sin(angle) * 180}
					{@const rx = (mouseY - 0.5) * 20}
					{@const ry = (mouseX - 0.5) * 20}
					<div
						class="absolute w-32 h-40 sm:w-40 sm:h-48 transition-all duration-300"
						style="transform: translate({x}px, {y}px) rotateX({rx}deg) rotateY({ry}deg) rotateZ({img.rotation}deg); transform-style: preserve-3d;"
					>
						<div
							class="relative w-full h-full rounded-2xl overflow-hidden shadow-2xl transition-all duration-300 hover:scale-110 cursor-pointer group"
							style="transform-style: preserve-3d;"
						>
							<img
								src={img.src}
								alt={img.alt}
								class="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
								loading={i < 3 ? 'eager' : 'lazy'}
							/>
							<div class="absolute inset-0 bg-linear-to-br from-white/20 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
						</div>
					</div>
				{/each}
			</div>
		</div>

		<!-- Heading + CTA -->
		<div class="text-center max-w-2xl mx-auto mb-8">
			<h1 class="text-4xl sm:text-5xl lg:text-6xl font-serif font-bold mb-4 sm:mb-6 text-balance leading-tight">
				{content.heading}
			</h1>
			<p class="text-lg sm:text-xl text-muted-foreground mb-8 text-balance">
				{content.subtext}
			</p>
			<Button class="inline-flex items-center gap-2 px-8 py-3 rounded-full font-medium hover:shadow-lg hover:scale-105 transition-all duration-300 active:scale-95 group">
				{content.cta_label}
				<ArrowRightIcon class="w-4 h-4 group-hover:translate-x-1 transition-transform" />
			</Button>
		</div>

		<!-- Features grid -->
		<div class="w-full max-w-4xl grid grid-cols-1 sm:grid-cols-3 gap-6 sm:gap-8 mt-8">
			{#each content.features as feature}
				<div class="text-center p-6 rounded-xl bg-card/50 backdrop-blur-sm border border-border/50 hover:bg-card/80 hover:border-border transition-all duration-300 group">
					<h3 class="text-lg sm:text-xl font-semibold mb-2 group-hover:text-primary transition-colors">
						{feature.title}
					</h3>
					<p class="text-sm sm:text-base text-muted-foreground">{feature.description}</p>
				</div>
			{/each}
		</div>
	</div>
</div>