121 lines
3.2 KiB
Svelte
121 lines
3.2 KiB
Svelte
|
|
<script>
|
||
|
|
import { onMount } from 'svelte';
|
||
|
|
import { browser } from '$app/environment';
|
||
|
|
import { screensaver, isScreensaverActive } from '$lib/stores/screensaver';
|
||
|
|
|
||
|
|
let snowflakes = [];
|
||
|
|
const SNOWFLAKE_COUNT = 100;
|
||
|
|
|
||
|
|
// Generate initial snowflakes
|
||
|
|
function initSnowflakes() {
|
||
|
|
snowflakes = Array.from({ length: SNOWFLAKE_COUNT }, (_, i) => ({
|
||
|
|
id: i,
|
||
|
|
x: Math.random() * 100, // % position
|
||
|
|
size: Math.random() * 4 + 2, // 2-6px
|
||
|
|
speed: Math.random() * 1 + 0.5, // Fall speed multiplier
|
||
|
|
drift: Math.random() * 2 - 1, // Horizontal drift
|
||
|
|
opacity: Math.random() * 0.5 + 0.5,
|
||
|
|
delay: Math.random() * 10 // Animation delay
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Dismiss on any interaction
|
||
|
|
function handleDismiss() {
|
||
|
|
screensaver.dismiss();
|
||
|
|
}
|
||
|
|
|
||
|
|
onMount(() => {
|
||
|
|
if (browser) {
|
||
|
|
initSnowflakes();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
</script>
|
||
|
|
|
||
|
|
{#if $isScreensaverActive}
|
||
|
|
<div
|
||
|
|
class="screensaver-overlay"
|
||
|
|
on:click={handleDismiss}
|
||
|
|
on:keydown={handleDismiss}
|
||
|
|
on:mousemove={handleDismiss}
|
||
|
|
role="button"
|
||
|
|
tabindex="0"
|
||
|
|
aria-label="Click or press any key to dismiss screensaver"
|
||
|
|
>
|
||
|
|
<div class="snowfall">
|
||
|
|
{#each snowflakes as flake (flake.id)}
|
||
|
|
<div
|
||
|
|
class="snowflake"
|
||
|
|
style="
|
||
|
|
--x: {flake.x}%;
|
||
|
|
--size: {flake.size}px;
|
||
|
|
--speed: {flake.speed};
|
||
|
|
--drift: {flake.drift};
|
||
|
|
--opacity: {flake.opacity};
|
||
|
|
--delay: {flake.delay}s;
|
||
|
|
"
|
||
|
|
/>
|
||
|
|
{/each}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="screensaver-hint">
|
||
|
|
Click or press any key to dismiss
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
{/if}
|
||
|
|
|
||
|
|
<style>
|
||
|
|
.screensaver-overlay {
|
||
|
|
position: fixed;
|
||
|
|
inset: 0;
|
||
|
|
background: rgba(0, 0, 0, 0.85);
|
||
|
|
z-index: 9997; /* Below other overlays (9998) but above content */
|
||
|
|
cursor: pointer;
|
||
|
|
overflow: hidden;
|
||
|
|
}
|
||
|
|
|
||
|
|
.snowfall {
|
||
|
|
position: absolute;
|
||
|
|
inset: 0;
|
||
|
|
pointer-events: none;
|
||
|
|
}
|
||
|
|
|
||
|
|
.snowflake {
|
||
|
|
position: absolute;
|
||
|
|
left: var(--x);
|
||
|
|
top: -10px;
|
||
|
|
width: var(--size);
|
||
|
|
height: var(--size);
|
||
|
|
background: white;
|
||
|
|
border-radius: 50%;
|
||
|
|
opacity: var(--opacity);
|
||
|
|
animation: fall linear infinite;
|
||
|
|
animation-duration: calc(10s / var(--speed));
|
||
|
|
animation-delay: var(--delay);
|
||
|
|
}
|
||
|
|
|
||
|
|
@keyframes fall {
|
||
|
|
0% {
|
||
|
|
transform: translateY(-10px) translateX(0);
|
||
|
|
}
|
||
|
|
100% {
|
||
|
|
transform: translateY(100vh) translateX(calc(var(--drift) * 100px));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
.screensaver-hint {
|
||
|
|
position: absolute;
|
||
|
|
bottom: 2rem;
|
||
|
|
left: 50%;
|
||
|
|
transform: translateX(-50%);
|
||
|
|
color: rgba(255, 255, 255, 0.5);
|
||
|
|
font-size: 0.875rem;
|
||
|
|
pointer-events: none;
|
||
|
|
animation: fadeInOut 3s ease-in-out infinite;
|
||
|
|
}
|
||
|
|
|
||
|
|
@keyframes fadeInOut {
|
||
|
|
0%, 100% { opacity: 0.3; }
|
||
|
|
50% { opacity: 0.7; }
|
||
|
|
}
|
||
|
|
</style>
|