- 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>
82 lines
2.5 KiB
Svelte
82 lines
2.5 KiB
Svelte
<script lang="ts">
|
|
import LiveMatchTickerCard from './LiveMatchTickerCard.svelte';
|
|
import { Activity } from 'lucide-svelte';
|
|
|
|
interface Match {
|
|
id: string;
|
|
map: string;
|
|
scoreT: number;
|
|
scoreCT: number;
|
|
isProcessing?: boolean;
|
|
timestamp?: string;
|
|
}
|
|
|
|
interface Props {
|
|
matches?: Match[];
|
|
}
|
|
|
|
// Sample matches for demo - in production, this would come from the API
|
|
let {
|
|
matches = [
|
|
{ id: '1', map: 'de_dust2', scoreT: 16, scoreCT: 14, isProcessing: true },
|
|
{ id: '2', map: 'de_mirage', scoreT: 13, scoreCT: 16 },
|
|
{ id: '3', map: 'de_inferno', scoreT: 16, scoreCT: 9 },
|
|
{ id: '4', map: 'de_ancient', scoreT: 11, scoreCT: 16 },
|
|
{ id: '5', map: 'de_anubis', scoreT: 16, scoreCT: 12, isProcessing: true },
|
|
{ id: '6', map: 'de_nuke', scoreT: 8, scoreCT: 16 },
|
|
{ id: '7', map: 'de_overpass', scoreT: 16, scoreCT: 14 },
|
|
{ id: '8', map: 'de_vertigo', scoreT: 14, scoreCT: 16 }
|
|
]
|
|
}: Props = $props();
|
|
|
|
// Duplicate matches for seamless loop with unique keys
|
|
const duplicatedMatches = $derived([
|
|
...matches.map((m, i) => ({ ...m, uniqueKey: `first-${i}-${m.id}` })),
|
|
...matches.map((m, i) => ({ ...m, uniqueKey: `second-${i}-${m.id}` }))
|
|
]);
|
|
</script>
|
|
|
|
<section class="relative overflow-hidden bg-void py-8" aria-labelledby="recent-matches-heading">
|
|
<!-- Section Header -->
|
|
<div class="container mx-auto mb-6 flex items-center justify-between px-4">
|
|
<div class="flex items-center gap-3">
|
|
<Activity
|
|
class="h-5 w-5 animate-pulse text-neon-green motion-reduce:animate-none"
|
|
aria-hidden="true"
|
|
/>
|
|
<h2 id="recent-matches-heading" class="text-lg font-semibold text-white">Recent Matches</h2>
|
|
</div>
|
|
<a
|
|
href="/matches"
|
|
class="rounded text-sm text-neon-blue transition-colors hover:text-neon-blue/80 focus:outline-none focus:ring-2 focus:ring-neon-blue focus:ring-offset-2 focus:ring-offset-void"
|
|
>
|
|
View all →
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Ticker Container -->
|
|
<div class="relative">
|
|
<!-- Left Fade -->
|
|
<div
|
|
class="pointer-events-none absolute left-0 top-0 z-10 h-full w-24 bg-gradient-to-r from-void to-transparent"
|
|
aria-hidden="true"
|
|
></div>
|
|
|
|
<!-- Right Fade -->
|
|
<div
|
|
class="pointer-events-none absolute right-0 top-0 z-10 h-full w-24 bg-gradient-to-l from-void to-transparent"
|
|
aria-hidden="true"
|
|
></div>
|
|
|
|
<!-- Scrolling Ticker -->
|
|
<nav
|
|
class="hover:pause-animation flex animate-ticker gap-4 motion-reduce:animate-none"
|
|
aria-label="Recent match scores"
|
|
>
|
|
{#each duplicatedMatches as match (match.uniqueKey)}
|
|
<LiveMatchTickerCard {match} />
|
|
{/each}
|
|
</nav>
|
|
</div>
|
|
</section>
|