Command Search
Search for a page
/ Components / Smoke Background Hero

Smoke Background Hero

Hero

Full-viewport hero with an animated smoke/fluid canvas background, an alert pill, shimmering heading, dual CTAs, and a tech-stack logo row.

herocanvassmokeanimationtech-stack
Open preview
Live Preview
AI Generation Prompt

Create a Svelte 5 (runes) full-viewport hero (`min-h-[calc(100dvh-header)]`) with an animated smoke/fluid simulation running on a canvas absolutely filling the background, tinted with a theme-aware color that swaps between light/dark mode values via `mode-watcher`. Centered content: a pill-shaped 'alert' link (dark rounded-full background, a small primary-colored label chip on the left, message text, and a trailing chevron icon), a large shimmering-text heading (white, with the shimmer sweep colored `var(--primary)`), a subtext paragraph, two CTA buttons full-width on mobile (solid with white shine + outline with primary-tinted shine, both using a looping shine/sheen hover effect), and a 'tech stack' row of small logo+label pairs that fade in with a staggered entrance. Add manual CSS keyframe entrance animations (staggered slide-down for the alert, slide-up for the heading/CTAs, fade for subtext/tech-row) each with their own delay, and respect `prefers-reduced-motion` by disabling them. Data-driven via `content: { alert:{label,text,href}, heading, subtext, primary_cta, secondary_cta, tech_stack_label, tech_stack: string[] }`.

<script lang="ts">
	import { mode } from 'mode-watcher';
	import { Button } from '$lib/components/ui/button';
	import ShimmeringText from '$lib/components/animate-ui/ShimmeringText.svelte';
	import Shine from '$lib/components/animate-ui/Shine.svelte';
	import SmokeBackground from '$lib/components/backgrounds/SmokeBackground.svelte';

	type AlertContent = { label: string; text: string; href: string };
	type CtaContent = { label: string; href: string };
	type Props = {
		content: {
			alert: AlertContent;
			heading: string;
			subtext: string;
			primary_cta: CtaContent;
			secondary_cta: CtaContent;
			tech_stack_label: string;
			tech_stack: string[];
		};
	};
	let { content }: Props = $props();

	const techStackFilename: Record<string, string> = {
		Golang: 'Go',
		PostgreSQL: 'PostgresSQL',
		'C#': 'C# (CSharp)',
		'C++': 'C++ (CPlusPlus)',
		'Next.js': 'Next.js',
		'Node.js': 'Node.js',
		'Vue.js': 'Vue.js',
		'Nuxt JS': 'Nuxt JS',
		'Nest.js': 'Nest.js',
		'Tailwind CSS': 'Tailwind CSS',
		'NET core': 'NET core',
		'.NET': 'NET',
	};

	function techSrc(name: string) {
		return `/techstacks/${techStackFilename[name] ?? name}.svg`;
	}

	const smokeColor = $derived(mode.current === 'dark' ? '#024C77' : '#0078B7');
</script>

<section class="relative z-0 overflow-hidden min-h-[calc(100dvh-var(--header-height))] flex items-center">
	<div class="absolute inset-0 w-full h-full">
		<SmokeBackground {smokeColor} />
	</div>
	<div class="relative z-10 py-8 px-4 mx-auto max-w-screen-xl text-center lg:py-16 lg:px-12 w-full">
		<a
			href={content.alert.href}
			class="inline-flex justify-between items-center py-1 px-1 pr-4 mb-7 text-sm text-white bg-gray-800 rounded-full hover:bg-gray-700 hero-anim-1"
		>
			<span class="text-xs bg-primary rounded-full text-primary-foreground px-4 py-1.5 mr-3">
				{content.alert.label}
			</span>
			<span class="text-sm font-medium">{content.alert.text}</span>
			<svg class="ml-2 w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
				<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
			</svg>
		</a>

		<h1 class="mb-4 text-4xl font-extrabold tracking-tight leading-none text-white md:text-5xl lg:text-6xl hero-anim-2">
			<ShimmeringText text={content.heading} color="#ffffff" shimmeringColor="var(--primary)" />
		</h1>
		<p class="mb-8 text-lg font-normal text-gray-400 lg:text-xl sm:px-16 xl:px-48 hero-anim-3">
			{content.subtext}
		</p>

		<div class="flex flex-col mb-8 lg:mb-16 space-y-4 sm:flex-row sm:justify-center sm:space-y-0 sm:space-x-4 hero-anim-4">
			<Shine enableOnHover color="#ffffff" opacity={0.4} class="rounded-lg">
				<Button size="lg" class="h-16 px-8 w-full" href={content.primary_cta.href}>
					{content.primary_cta.label}
					<svg class="ml-2 -mr-1 w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
						<path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd" />
					</svg>
				</Button>
			</Shine>
			<Shine enableOnHover color="#0078B7" opacity={0.3} class="rounded-lg">
				<Button variant="outline" size="lg" class="h-16 px-8 w-full text-white" href={content.secondary_cta.href}>
					{content.secondary_cta.label}
				</Button>
			</Shine>
		</div>

		<div class="px-4 mx-auto text-center md:max-w-screen-md lg:max-w-screen-lg lg:px-36 hero-anim-5">
			<span class="font-semibold text-gray-400 uppercase">{content.tech_stack_label}</span>
			<div class="flex flex-wrap justify-center items-center mt-6 gap-x-10 gap-y-6">
				{#each content.tech_stack as name}
					<div class="flex flex-col items-center gap-2 opacity-80 hover:opacity-100 transition-opacity">
						<img src={techSrc(name)} alt={name} class="h-10 w-10 object-contain" loading="lazy" decoding="async" />
						<span class="text-xs font-semibold text-gray-400 tracking-wide uppercase">{name}</span>
					</div>
				{/each}
			</div>
		</div>
	</div>
</section>

<style>
	@keyframes hero-down {
		from { opacity: 0; transform: translateY(-16px); }
		to { opacity: 1; transform: none; }
	}
	@keyframes hero-up {
		from { opacity: 0; transform: translateY(28px); }
		to { opacity: 1; transform: none; }
	}
	@keyframes hero-fade {
		from { opacity: 0; }
		to { opacity: 1; }
	}
	.hero-anim-1 { animation: hero-down 0.6s cubic-bezier(0.22, 1, 0.36, 1) both 0ms; }
	.hero-anim-2 { animation: hero-up  0.7s cubic-bezier(0.22, 1, 0.36, 1) both 150ms; }
	.hero-anim-3 { animation: hero-fade 0.7s ease-out both 300ms; }
	.hero-anim-4 { animation: hero-up  0.7s cubic-bezier(0.22, 1, 0.36, 1) both 450ms; }
	.hero-anim-5 { animation: hero-fade 0.8s ease-out both 620ms; }
	@media (prefers-reduced-motion: reduce) {
		.hero-anim-1, .hero-anim-2, .hero-anim-3, .hero-anim-4, .hero-anim-5 { animation: none; }
	}
</style>