122 lines
3.5 KiB
Svelte
122 lines
3.5 KiB
Svelte
<script lang="ts">
|
|
interface Props {
|
|
accentColor: string;
|
|
breakColor: string;
|
|
}
|
|
|
|
let { accentColor, breakColor }: Props = $props();
|
|
|
|
let reducedMotion = $state(window.matchMedia("(prefers-reduced-motion: reduce)").matches);
|
|
$effect(() => {
|
|
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
const handler = (e: MediaQueryListEvent) => { reducedMotion = e.matches; };
|
|
mq.addEventListener("change", handler);
|
|
return () => mq.removeEventListener("change", handler);
|
|
});
|
|
</script>
|
|
|
|
<div class="pointer-events-none absolute inset-0 overflow-hidden" aria-hidden="true">
|
|
<!-- Gradient blobs -->
|
|
<div
|
|
class="blob blob-1"
|
|
style="background: radial-gradient(circle, {accentColor} 0%, transparent 70%);"
|
|
></div>
|
|
<div
|
|
class="blob blob-2"
|
|
style="background: radial-gradient(circle, {breakColor} 0%, transparent 70%);"
|
|
></div>
|
|
<div
|
|
class="blob blob-3"
|
|
style="background: radial-gradient(circle, {accentColor} 0%, transparent 70%);"
|
|
></div>
|
|
<div
|
|
class="blob blob-4"
|
|
style="background: radial-gradient(circle, {breakColor} 0%, transparent 70%);"
|
|
></div>
|
|
|
|
<!-- Film grain overlay -->
|
|
<svg class="absolute inset-0 h-full w-full" style="opacity: 0.08; mix-blend-mode: overlay;">
|
|
<filter id="grain-filter">
|
|
<feTurbulence type="fractalNoise" baseFrequency="0.45" numOctaves="3" stitchTiles="stitch">
|
|
{#if !reducedMotion}
|
|
<animate
|
|
attributeName="seed"
|
|
from="0"
|
|
to="100"
|
|
dur="2s"
|
|
repeatCount="indefinite"
|
|
/>
|
|
{/if}
|
|
</feTurbulence>
|
|
</filter>
|
|
<rect width="100%" height="100%" filter="url(#grain-filter)" />
|
|
</svg>
|
|
</div>
|
|
|
|
<style>
|
|
.blob {
|
|
position: absolute;
|
|
width: 300px;
|
|
height: 300px;
|
|
border-radius: 50%;
|
|
filter: blur(80px);
|
|
opacity: 0.4;
|
|
will-change: transform;
|
|
}
|
|
|
|
.blob-1 {
|
|
top: -15%;
|
|
left: -10%;
|
|
animation: drift-1 30s ease-in-out infinite;
|
|
}
|
|
.blob-2 {
|
|
bottom: -15%;
|
|
right: -10%;
|
|
animation: drift-2 35s ease-in-out infinite;
|
|
}
|
|
.blob-3 {
|
|
top: 40%;
|
|
right: -15%;
|
|
width: 250px;
|
|
height: 250px;
|
|
animation: drift-3 28s ease-in-out infinite;
|
|
}
|
|
.blob-4 {
|
|
bottom: 20%;
|
|
left: -15%;
|
|
width: 200px;
|
|
height: 200px;
|
|
animation: drift-4 32s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes drift-1 {
|
|
0%, 100% { transform: translate(0, 0) scale(1); }
|
|
20% { transform: translate(300px, 200px) scale(1.15); }
|
|
40% { transform: translate(150px, 450px) scale(0.9); }
|
|
60% { transform: translate(350px, 350px) scale(1.1); }
|
|
80% { transform: translate(100px, 100px) scale(0.95); }
|
|
}
|
|
|
|
@keyframes drift-2 {
|
|
0%, 100% { transform: translate(0, 0) scale(1); }
|
|
20% { transform: translate(-250px, -150px) scale(1.1); }
|
|
40% { transform: translate(-100px, -400px) scale(0.95); }
|
|
60% { transform: translate(-300px, -200px) scale(1.15); }
|
|
80% { transform: translate(-50px, -300px) scale(0.9); }
|
|
}
|
|
|
|
@keyframes drift-3 {
|
|
0%, 100% { transform: translate(0, 0) scale(1); }
|
|
25% { transform: translate(-300px, -200px) scale(1.1); }
|
|
50% { transform: translate(-150px, 150px) scale(0.9); }
|
|
75% { transform: translate(-350px, -50px) scale(1.15); }
|
|
}
|
|
|
|
@keyframes drift-4 {
|
|
0%, 100% { transform: translate(0, 0) scale(1); }
|
|
25% { transform: translate(280px, -180px) scale(1.15); }
|
|
50% { transform: translate(100px, 200px) scale(0.9); }
|
|
75% { transform: translate(320px, 50px) scale(1.1); }
|
|
}
|
|
</style>
|