e6b50fb4f1
Fix all build/type/lint errors (zod 3.25 compat wrapper, Svelte 5 fixes), write 115 unit tests across 10 test files, expand seed script with demo data, update Docker config with migration on startup.
72 lines
1.4 KiB
Svelte
72 lines
1.4 KiB
Svelte
<script lang="ts">
|
|
import { theme } from '$lib/stores/theme.svelte.js';
|
|
|
|
interface Blob {
|
|
x: number;
|
|
y: number;
|
|
vx: number;
|
|
vy: number;
|
|
hueOffset: number;
|
|
size: number;
|
|
}
|
|
|
|
const blobCount = 4;
|
|
let blobs = $state<Blob[]>([]);
|
|
let animFrame: number;
|
|
|
|
function initBlobs(): Blob[] {
|
|
return Array.from({ length: blobCount }, (_, i) => ({
|
|
x: 20 + Math.random() * 60,
|
|
y: 20 + Math.random() * 60,
|
|
vx: (Math.random() - 0.5) * 0.02,
|
|
vy: (Math.random() - 0.5) * 0.02,
|
|
hueOffset: i * 40,
|
|
size: 35 + Math.random() * 20
|
|
}));
|
|
}
|
|
|
|
function animate() {
|
|
blobs = blobs.map((blob) => {
|
|
let { x, y, vx, vy } = blob;
|
|
x += vx;
|
|
y += vy;
|
|
|
|
if (x < 5 || x > 95) vx = -vx;
|
|
if (y < 5 || y > 95) vy = -vy;
|
|
|
|
return { ...blob, x, y, vx, vy };
|
|
});
|
|
|
|
animFrame = requestAnimationFrame(animate);
|
|
}
|
|
|
|
$effect(() => {
|
|
blobs = initBlobs();
|
|
animFrame = requestAnimationFrame(animate);
|
|
|
|
return () => {
|
|
cancelAnimationFrame(animFrame);
|
|
};
|
|
});
|
|
</script>
|
|
|
|
<div class="absolute inset-0">
|
|
<svg class="h-full w-full" xmlns="http://www.w3.org/2000/svg">
|
|
<defs>
|
|
<filter id="mesh-blur">
|
|
<feGaussianBlur stdDeviation="60" />
|
|
</filter>
|
|
</defs>
|
|
|
|
{#each blobs as blob (blob.hueOffset)}
|
|
<circle
|
|
cx="{blob.x}%"
|
|
cy="{blob.y}%"
|
|
r="{blob.size}%"
|
|
fill="hsla({theme.primaryHue + blob.hueOffset}, {theme.primarySaturation}%, {theme.isDark ? 40 : 60}%, 0.12)"
|
|
filter="url(#mesh-blur)"
|
|
/>
|
|
{/each}
|
|
</svg>
|
|
</div>
|