- Create HeroSection with animated search bar and stat counters - Add LiveMatchTicker with auto-scrolling recent matches - Build FlashLeaderboard "Wall of Shame" with podium display - Implement FeatureShowcase with scroll-triggered animations - Add NeonCTA call-to-action section with trust badges - Create reusable NeonButton component with glow effects Accessibility improvements: - Add aria-labels, aria-hidden for decorative elements - Implement focus-visible ring styles for keyboard navigation - Support prefers-reduced-motion across all animations - Use semantic HTML (article, nav, dl) for screen readers - Improve color contrast ratios for WCAG compliance 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
64 lines
1.2 KiB
Svelte
64 lines
1.2 KiB
Svelte
<script lang="ts">
|
|
import { tweened } from 'svelte/motion';
|
|
import { cubicOut } from 'svelte/easing';
|
|
import { onMount } from 'svelte';
|
|
|
|
interface Props {
|
|
value: number;
|
|
duration?: number;
|
|
prefix?: string;
|
|
suffix?: string;
|
|
format?: (value: number) => string;
|
|
}
|
|
|
|
let {
|
|
value,
|
|
duration = 2000,
|
|
prefix = '',
|
|
suffix = '',
|
|
format = (val: number) => Math.floor(val).toLocaleString()
|
|
}: Props = $props();
|
|
|
|
const displayValue = tweened(0, {
|
|
duration,
|
|
easing: cubicOut
|
|
});
|
|
|
|
let hasAnimated = false;
|
|
let containerElement: HTMLElement;
|
|
|
|
onMount(() => {
|
|
// Use Intersection Observer to start animation when visible
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
for (const entry of entries) {
|
|
if (entry.isIntersecting && !hasAnimated) {
|
|
hasAnimated = true;
|
|
displayValue.set(value);
|
|
}
|
|
}
|
|
},
|
|
{ threshold: 0.1 }
|
|
);
|
|
|
|
if (containerElement) {
|
|
observer.observe(containerElement);
|
|
}
|
|
|
|
return () => {
|
|
observer.disconnect();
|
|
};
|
|
});
|
|
|
|
// Update the target value if it changes after initial animation
|
|
$effect(() => {
|
|
if (hasAnimated) {
|
|
displayValue.set(value);
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<span bind:this={containerElement} class="tabular-nums">
|
|
{prefix}{format($displayValue)}{suffix}
|
|
</span>
|