Files
core-cooldown/src/lib/components/BackgroundBlobs.svelte
2026-02-07 12:13:03 +02:00

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>