feat: Add neon esports landing page with WCAG accessibility
- 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>
This commit is contained in:
124
src/lib/components/landing/LeaderboardPodium.svelte
Normal file
124
src/lib/components/landing/LeaderboardPodium.svelte
Normal file
@@ -0,0 +1,124 @@
|
||||
<script lang="ts">
|
||||
import { Trophy, Skull, Zap } from 'lucide-svelte';
|
||||
|
||||
interface Player {
|
||||
rank: number;
|
||||
name: string;
|
||||
steamId: string;
|
||||
avatarUrl?: string;
|
||||
teammatesBlinded: number;
|
||||
selfFlashes: number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
player: Player;
|
||||
}
|
||||
|
||||
let { player }: Props = $props();
|
||||
|
||||
const podiumConfig = {
|
||||
1: {
|
||||
height: 'h-32',
|
||||
bgGradient: 'from-yellow-500/20 to-yellow-600/5',
|
||||
borderColor: 'border-yellow-500/50',
|
||||
glowColor: 'shadow-yellow-500/30',
|
||||
textColor: 'text-yellow-400',
|
||||
title: 'Flash Criminal of the Week',
|
||||
icon: Trophy
|
||||
},
|
||||
2: {
|
||||
height: 'h-24',
|
||||
bgGradient: 'from-gray-400/20 to-gray-500/5',
|
||||
borderColor: 'border-gray-400/50',
|
||||
glowColor: 'shadow-gray-400/30',
|
||||
textColor: 'text-gray-300',
|
||||
title: 'Serial Team Flasher',
|
||||
icon: Skull
|
||||
},
|
||||
3: {
|
||||
height: 'h-20',
|
||||
bgGradient: 'from-amber-600/20 to-amber-700/5',
|
||||
borderColor: 'border-amber-600/50',
|
||||
glowColor: 'shadow-amber-600/30',
|
||||
textColor: 'text-amber-500',
|
||||
title: 'Flash Menace',
|
||||
icon: Zap
|
||||
}
|
||||
};
|
||||
|
||||
const config = podiumConfig[player.rank as 1 | 2 | 3] || podiumConfig[3];
|
||||
const IconComponent = config.icon;
|
||||
|
||||
// Shadow colors for each rank
|
||||
const shadowColors: Record<number, string> = {
|
||||
1: 'rgba(234, 179, 8, 0.2)',
|
||||
2: 'rgba(156, 163, 175, 0.2)',
|
||||
3: 'rgba(217, 119, 6, 0.2)'
|
||||
};
|
||||
const shadowColor = shadowColors[player.rank] || shadowColors[3];
|
||||
</script>
|
||||
|
||||
<article
|
||||
class="flex flex-col items-center"
|
||||
aria-label="Rank {player.rank}: {player.name} - {player.teammatesBlinded} teammates blinded"
|
||||
>
|
||||
<!-- Player Card -->
|
||||
<div
|
||||
class="group relative mb-4 w-full max-w-[200px] overflow-hidden rounded-xl border bg-gradient-to-b p-4 transition-all hover:scale-105 motion-reduce:hover:scale-100 {config.borderColor} {config.bgGradient}"
|
||||
style="box-shadow: 0 0 20px {shadowColor};"
|
||||
>
|
||||
<!-- Rank Badge -->
|
||||
<div
|
||||
class="absolute -right-2 -top-2 flex h-8 w-8 items-center justify-center rounded-full border-2 bg-void font-bold {config.borderColor} {config.textColor}"
|
||||
aria-hidden="true"
|
||||
>
|
||||
#{player.rank}
|
||||
</div>
|
||||
|
||||
<!-- Avatar -->
|
||||
<div class="mx-auto mb-3 h-16 w-16 overflow-hidden rounded-full border-2 {config.borderColor}">
|
||||
{#if player.avatarUrl}
|
||||
<img
|
||||
src={player.avatarUrl}
|
||||
alt="Avatar for {player.name}"
|
||||
class="h-full w-full object-cover"
|
||||
/>
|
||||
{:else}
|
||||
<div
|
||||
class="flex h-full w-full items-center justify-center bg-void-light"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<IconComponent class="h-8 w-8 {config.textColor}" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Player Name -->
|
||||
<h3 class="mb-1 truncate text-center font-semibold text-white">{player.name}</h3>
|
||||
|
||||
<!-- Title -->
|
||||
<p class="mb-3 text-center text-xs {config.textColor}">{config.title}</p>
|
||||
|
||||
<!-- Stats -->
|
||||
<dl class="space-y-1 text-center text-xs">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<dt class="text-white/60">Teammates Blinded</dt>
|
||||
<dd class="font-mono font-bold text-neon-red">{player.teammatesBlinded}</dd>
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<dt class="text-white/60">Self-Flashes</dt>
|
||||
<dd class="font-mono font-bold text-white/80">{player.selfFlashes}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<!-- Podium Stand -->
|
||||
<div
|
||||
class="w-24 rounded-t-lg border-t-2 bg-gradient-to-b {config.height} {config.borderColor} {config.bgGradient}"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="flex h-full items-center justify-center">
|
||||
<span class="text-4xl font-bold {config.textColor}">{player.rank}</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
Reference in New Issue
Block a user