style: Redesign players page with neon esports aesthetic

Convert the players page and related components from DaisyUI to the
custom neon design system, matching the landing page and matches page
visual style. Adds decorative blur orbs, neon glow effects, and
consistent color semantics across PlayerCard, RecentPlayers, and
TrackPlayerModal components.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-07 17:18:01 +01:00
parent 6dc12f0c35
commit 8de8f1696f
4 changed files with 146 additions and 108 deletions

View File

@@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import { User, TrendingUp, Target } from 'lucide-svelte'; import { User, TrendingUp, Target } from 'lucide-svelte';
import Badge from '$lib/components/ui/Badge.svelte';
import type { PlayerMeta } from '$lib/types'; import type { PlayerMeta } from '$lib/types';
interface Props { interface Props {
@@ -10,26 +9,28 @@
let { player, showStats = true }: Props = $props(); let { player, showStats = true }: Props = $props();
const kd = const killDeathRatio =
player.avg_deaths > 0 player.avg_deaths > 0
? (player.avg_kills / player.avg_deaths).toFixed(2) ? (player.avg_kills / player.avg_deaths).toFixed(2)
: player.avg_kills.toFixed(2); : player.avg_kills.toFixed(2);
const winRate = (player.win_rate * 100).toFixed(1); const winRatePercentage = (player.win_rate * 100).toFixed(1);
</script> </script>
<a <a
href={`/player/${player.id}`} href={`/player/${player.id}`}
class="block overflow-hidden rounded-lg border border-base-300 bg-base-100 shadow-md transition-all hover:scale-[1.02] hover:shadow-xl" class="block overflow-hidden rounded-lg border border-l-4 border-white/10 border-l-neon-blue bg-void-light transition-all duration-300 hover:scale-[1.02] hover:border-neon-blue/50 hover:shadow-[0_0_20px_rgba(0,212,255,0.1)]"
> >
<!-- Header --> <!-- Header -->
<div class="bg-gradient-to-r from-primary/20 to-secondary/20 p-4"> <div class="bg-gradient-to-r from-neon-blue/20 to-neon-purple/20 p-4">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-base-100"> <div
<User class="h-6 w-6 text-primary" /> class="flex h-12 w-12 items-center justify-center rounded-full border border-neon-blue/30 bg-void"
>
<User class="h-6 w-6 text-neon-blue" />
</div> </div>
<div class="min-w-0 flex-1"> <div class="min-w-0 flex-1">
<h3 class="truncate text-lg font-bold text-base-content">{player.name}</h3> <h3 class="truncate text-lg font-bold text-white">{player.name}</h3>
<p class="text-sm text-base-content/60">ID: {player.id}</p> <p class="text-sm text-white/50">ID: {player.id}</p>
</div> </div>
</div> </div>
</div> </div>
@@ -39,34 +40,38 @@
<div class="grid grid-cols-3 gap-4 p-4"> <div class="grid grid-cols-3 gap-4 p-4">
<div class="text-center"> <div class="text-center">
<div class="mb-1 flex items-center justify-center"> <div class="mb-1 flex items-center justify-center">
<Target class="mr-1 h-4 w-4 text-primary" /> <Target class="mr-1 h-4 w-4 text-neon-gold" />
</div> </div>
<div class="text-xl font-bold text-base-content">{kd}</div> <div class="font-mono text-xl font-bold text-white">{killDeathRatio}</div>
<div class="text-xs text-base-content/60">K/D</div> <div class="text-xs text-white/50">K/D</div>
</div> </div>
<div class="text-center"> <div class="text-center">
<div class="mb-1 flex items-center justify-center"> <div class="mb-1 flex items-center justify-center">
<TrendingUp class="mr-1 h-4 w-4 text-success" /> <TrendingUp class="mr-1 h-4 w-4 text-neon-green" />
</div> </div>
<div class="text-xl font-bold text-base-content">{winRate}%</div> <div class="font-mono text-xl font-bold text-white">{winRatePercentage}%</div>
<div class="text-xs text-base-content/60">Win Rate</div> <div class="text-xs text-white/50">Win Rate</div>
</div> </div>
<div class="text-center"> <div class="text-center">
<div class="mb-1 flex items-center justify-center"> <div class="mb-1 flex items-center justify-center">
<User class="mr-1 h-4 w-4 text-info" /> <User class="mr-1 h-4 w-4 text-neon-blue" />
</div> </div>
<div class="text-xl font-bold text-base-content">{player.recent_matches}</div> <div class="font-mono text-xl font-bold text-white">{player.recent_matches}</div>
<div class="text-xs text-base-content/60">Matches</div> <div class="text-xs text-white/50">Matches</div>
</div> </div>
</div> </div>
<!-- Footer --> <!-- Footer -->
<div class="border-t border-base-300 bg-base-200 px-4 py-3"> <div class="border-t border-white/10 bg-void px-4 py-3">
<div class="flex items-center justify-between text-sm"> <div class="flex items-center justify-between text-sm">
<span class="text-base-content/60">Avg KAST:</span> <span class="text-white/50">Avg KAST:</span>
<Badge variant="info" size="sm">{player.avg_kast.toFixed(1)}%</Badge> <span
class="rounded-full border border-neon-blue/30 bg-neon-blue/10 px-2.5 py-0.5 text-xs font-medium text-neon-blue"
>
{player.avg_kast.toFixed(1)}%
</span>
</div> </div>
</div> </div>
{/if} {/if}

View File

@@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import { Clock, X } from 'lucide-svelte'; import { Clock, X } from 'lucide-svelte';
import Card from '$lib/components/ui/Card.svelte';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { import {
getRecentPlayers, getRecentPlayers,
@@ -34,32 +33,32 @@
</script> </script>
{#if recentPlayers.length > 0} {#if recentPlayers.length > 0}
<Card padding="lg"> <div class="rounded-xl border border-white/10 bg-void-light p-6">
<div class="mb-4 flex items-center gap-2"> <div class="mb-4 flex items-center gap-2">
<Clock class="h-5 w-5 text-primary" /> <Clock class="h-5 w-5 text-neon-blue" />
<h2 class="text-xl font-bold text-base-content">Recently Visited Players</h2> <h2 class="text-xl font-bold text-white">Recently Visited Players</h2>
</div> </div>
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"> <div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{#each recentPlayers as player (player.id)} {#each recentPlayers as player (player.id)}
<div <div
class="group relative rounded-lg border border-base-300 bg-base-200 p-3 transition-all hover:border-primary hover:shadow-lg" class="group relative rounded-lg border border-white/10 bg-void p-3 transition-all duration-300 hover:border-neon-blue/50 hover:shadow-[0_0_15px_rgba(0,212,255,0.1)]"
> >
<a href="/player/{player.id}" class="flex items-center gap-3"> <a href="/player/{player.id}" class="flex items-center gap-3">
<img <img
src={player.avatar} src={player.avatar}
alt={player.name} alt={player.name}
class="h-12 w-12 rounded-full border-2 border-base-300" class="h-12 w-12 rounded-full border-2 border-neon-blue/30"
/> />
<div class="flex-1 overflow-hidden"> <div class="flex-1 overflow-hidden">
<div class="truncate font-medium text-base-content">{player.name}</div> <div class="truncate font-medium text-white">{player.name}</div>
<div class="text-xs text-base-content/60">{formatTimeAgo(player.visitedAt)}</div> <div class="text-xs text-white/50">{formatTimeAgo(player.visitedAt)}</div>
</div> </div>
</a> </a>
<!-- Remove button --> <!-- Remove button -->
<button <button
class="btn btn-circle btn-ghost btn-xs absolute right-1 top-1 opacity-0 transition-opacity group-hover:opacity-100" class="absolute right-1 top-1 flex h-6 w-6 items-center justify-center rounded-full text-white/40 opacity-0 transition-all duration-200 hover:bg-neon-red/20 hover:text-neon-red group-hover:opacity-100"
onclick={(e) => { onclick={(e) => {
e.preventDefault(); e.preventDefault();
handleRemove(player.id); handleRemove(player.id);
@@ -72,10 +71,10 @@
{/each} {/each}
</div> </div>
<div class="mt-4 text-center text-xs text-base-content/60"> <div class="mt-4 text-center text-xs text-white/40">
Showing up to {recentPlayers.length} recently visited player{recentPlayers.length !== 1 Showing up to {recentPlayers.length} recently visited player{recentPlayers.length !== 1
? 's' ? 's'
: ''} : ''}
</div> </div>
</Card> </div>
{/if} {/if}

View File

@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import { Info, AlertCircle, Loader2 } from 'lucide-svelte';
import Modal from '$lib/components/ui/Modal.svelte'; import Modal from '$lib/components/ui/Modal.svelte';
import { playersAPI } from '$lib/api/players'; import { playersAPI } from '$lib/api/players';
import { toast } from '$lib/stores/toast'; import { toast } from '$lib/stores/toast';
@@ -78,26 +79,18 @@
<Modal bind:open onClose={handleClose} title={isTracked ? 'Untrack Player' : 'Track Player'}> <Modal bind:open onClose={handleClose} title={isTracked ? 'Untrack Player' : 'Track Player'}>
<div class="space-y-4"> <div class="space-y-4">
<div class="alert alert-info"> <!-- Info Alert -->
<svg <div class="flex items-start gap-3 rounded-lg border border-neon-blue/30 bg-neon-blue/10 p-4">
xmlns="http://www.w3.org/2000/svg" <Info class="h-5 w-5 shrink-0 text-neon-blue" />
fill="none" <div class="text-sm text-neon-blue">
viewBox="0 0 24 24"
class="h-6 w-6 shrink-0 stroke-current"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
></path>
</svg>
<div class="text-sm">
{#if isTracked} {#if isTracked}
<p>Remove <strong>{playerName}</strong> from automatic match tracking.</p> <p>
Remove <strong class="font-semibold">{playerName}</strong> from automatic match tracking.
</p>
{:else} {:else}
<p> <p>
Add <strong>{playerName}</strong> to the tracking system to automatically fetch new matches. Add <strong class="font-semibold">{playerName}</strong> to the tracking system to automatically
fetch new matches.
</p> </p>
{/if} {/if}
</div> </div>
@@ -105,73 +98,77 @@
<!-- Auth Code Input (only for tracking, untrack doesn't need auth) --> <!-- Auth Code Input (only for tracking, untrack doesn't need auth) -->
{#if !isTracked} {#if !isTracked}
<div class="form-control"> <div class="space-y-2">
<label class="label" for="authCode"> <label class="block text-sm font-medium text-white" for="authCode">
<span class="label-text font-medium">Authentication Code *</span> Authentication Code *
</label> </label>
<input <input
id="authCode" id="authCode"
type="text" type="text"
placeholder="Enter your auth code" placeholder="Enter your auth code"
class="input input-bordered w-full" class="w-full rounded-lg border border-neon-blue/30 bg-void px-4 py-2.5 text-white placeholder-white/40 transition-all duration-300 focus:border-neon-blue focus:outline-none focus:ring-1 focus:ring-neon-blue disabled:opacity-50"
bind:value={authCode} bind:value={authCode}
disabled={isLoading} disabled={isLoading}
required required
/> />
<div class="label"> <p class="text-xs text-white/50">Required to verify ownership of this Steam account</p>
<span class="label-text-alt text-base-content/60">
Required to verify ownership of this Steam account
</span>
</div>
</div> </div>
{/if} {/if}
<!-- Error Message --> <!-- Error Message -->
{#if error} {#if error}
<div class="alert alert-error"> <div class="flex items-start gap-3 rounded-lg border border-neon-red/30 bg-neon-red/10 p-4">
<svg <AlertCircle class="h-5 w-5 shrink-0 text-neon-red" />
xmlns="http://www.w3.org/2000/svg" <span class="text-sm text-neon-red">{error}</span>
class="h-6 w-6 shrink-0 stroke-current"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>{error}</span>
</div> </div>
{/if} {/if}
<!-- Help Text --> <!-- Help Text -->
<div class="text-sm text-base-content/70"> <div class="text-sm text-white/60">
<p class="mb-2 font-medium">How to get your authentication code:</p> <p class="mb-2 font-medium text-white/70">How to get your authentication code:</p>
<ol class="list-inside list-decimal space-y-1"> <ol class="list-inside list-decimal space-y-1">
<li>Open CS2 and go to Settings → Game</li> <li>Open CS2 and go to Settings → Game</li>
<li>Enable the Developer Console</li> <li>Enable the Developer Console</li>
<li>Press <kbd class="kbd kbd-sm">~</kbd> to open the console</li> <li>
<li>Type: <code class="rounded bg-base-300 px-1">status</code></li> Press <kbd class="rounded border border-white/20 bg-void px-2 py-0.5 font-mono text-xs"
>~</kbd
> to open the console
</li>
<li>
Type: <code class="rounded bg-void px-1.5 py-0.5 font-mono text-neon-blue">status</code>
</li>
<li>Copy the code shown next to "Account:"</li> <li>Copy the code shown next to "Account:"</li>
</ol> </ol>
</div> </div>
</div> </div>
{#snippet actions()} {#snippet actions()}
<button class="btn" onclick={handleClose} disabled={isLoading}>Cancel</button> <button
class="rounded-lg border border-white/20 px-4 py-2 text-sm font-medium text-white/70 transition-all duration-300 hover:bg-white/10 hover:text-white disabled:opacity-50"
onclick={handleClose}
disabled={isLoading}
>
Cancel
</button>
{#if isTracked} {#if isTracked}
<button class="btn btn-error" onclick={handleUntrack} disabled={isLoading}> <button
class="flex items-center gap-2 rounded-lg bg-neon-red px-4 py-2 text-sm font-medium text-white transition-all duration-300 hover:shadow-[0_0_20px_rgba(255,51,102,0.4)] disabled:opacity-50"
onclick={handleUntrack}
disabled={isLoading}
>
{#if isLoading} {#if isLoading}
<span class="loading loading-spinner loading-sm"></span> <Loader2 class="h-4 w-4 animate-spin" />
{/if} {/if}
Untrack Player Untrack Player
</button> </button>
{:else} {:else}
<button class="btn btn-primary" onclick={handleTrack} disabled={isLoading}> <button
class="flex items-center gap-2 rounded-lg bg-neon-blue px-4 py-2 text-sm font-medium text-void transition-all duration-300 hover:shadow-[0_0_20px_rgba(0,212,255,0.4)] disabled:opacity-50"
onclick={handleTrack}
disabled={isLoading}
>
{#if isLoading} {#if isLoading}
<span class="loading loading-spinner loading-sm"></span> <Loader2 class="h-4 w-4 animate-spin" />
{/if} {/if}
Track Player Track Player
</button> </button>

View File

@@ -1,41 +1,78 @@
<script lang="ts"> <script lang="ts">
import { Search, TrendingUp } from 'lucide-svelte'; import { Search, TrendingUp } from 'lucide-svelte';
import Card from '$lib/components/ui/Card.svelte';
import Badge from '$lib/components/ui/Badge.svelte';
</script> </script>
<svelte:head> <svelte:head>
<title>Players - teamflash.rip</title> <title>Players - teamflash.rip</title>
</svelte:head> </svelte:head>
<div class="container mx-auto px-4 py-8"> <div class="relative min-h-screen bg-void">
<div class="mb-8"> <!-- Decorative Background -->
<h1 class="mb-2 text-4xl font-bold">Players</h1> <div class="pointer-events-none absolute inset-0 overflow-hidden">
<p class="text-base-content/60">Search and browse player profiles</p> <!-- Blur orbs -->
<div class="absolute -left-40 top-20 h-80 w-80 rounded-full bg-neon-blue/10 blur-[100px]"></div>
<div
class="absolute -right-40 top-60 h-80 w-80 rounded-full bg-neon-purple/10 blur-[100px]"
></div>
<div
class="absolute bottom-40 left-1/3 h-60 w-60 rounded-full bg-neon-gold/5 blur-[80px]"
></div>
<!-- Grid pattern -->
<div
class="absolute inset-0 opacity-20"
style="background-image: linear-gradient(rgba(0, 212, 255, 0.03) 1px, transparent 1px), linear-gradient(90deg, rgba(0, 212, 255, 0.03) 1px, transparent 1px); background-size: 60px 60px;"
></div>
</div> </div>
<!-- Search --> <!-- Content -->
<Card padding="lg" class="mb-8"> <div class="container relative z-10 mx-auto px-4 py-8">
<div class="relative"> <!-- Page Header -->
<Search class="absolute left-3 top-1/2 h-5 w-5 -translate-y-1/2 text-base-content/40" /> <div class="mb-8">
<input <h1
type="text" class="mb-2 text-4xl font-bold text-white"
placeholder="Search by Steam ID or player name..." style="text-shadow: 0 0 30px rgba(0, 212, 255, 0.5);"
class="input input-bordered w-full pl-10" >
/> Players
</h1>
<p class="text-white/60">Search and browse player profiles</p>
</div> </div>
</Card>
<!-- Coming Soon --> <!-- Search Section -->
<div <div class="mb-8 rounded-xl border border-white/10 bg-void-light p-6">
class="flex min-h-[400px] items-center justify-center rounded-lg border-2 border-dashed border-base-300 bg-base-200/50" <div class="relative">
> <Search class="absolute left-4 top-1/2 h-5 w-5 -translate-y-1/2 text-white/40" />
<div class="text-center"> <input
<TrendingUp class="mx-auto mb-4 h-16 w-16 text-base-content/20" /> type="text"
<h2 class="mb-2 text-2xl font-bold text-base-content">Coming Soon</h2> placeholder="Search by Steam ID or player name..."
<p class="text-base-content/60">Player search and profiles will be available in Phase 3</p> class="w-full rounded-lg border border-neon-blue/30 bg-void px-4 py-3 pl-12 text-white placeholder-white/40 transition-all duration-300 focus:border-neon-blue focus:outline-none focus:ring-1 focus:ring-neon-blue"
<div class="mt-6"> />
<Badge variant="info">Phase 3 - In Development</Badge> </div>
</div>
<!-- Coming Soon Section -->
<div
class="flex min-h-[400px] items-center justify-center rounded-xl border-2 border-dashed border-neon-blue/20 bg-void-light/50"
>
<div class="text-center">
<TrendingUp
class="mx-auto mb-4 h-16 w-16 text-neon-blue/30"
style="filter: drop-shadow(0 0 15px rgba(0, 212, 255, 0.3));"
/>
<h2
class="mb-2 text-2xl font-bold text-white"
style="text-shadow: 0 0 20px rgba(0, 212, 255, 0.3);"
>
Coming Soon
</h2>
<p class="text-white/60">Player search and profiles will be available in Phase 3</p>
<div class="mt-6">
<span
class="inline-flex items-center gap-2 rounded-full border border-neon-gold/30 bg-neon-gold/10 px-4 py-1.5 text-sm text-neon-gold"
>
<span class="h-1.5 w-1.5 animate-pulse rounded-full bg-neon-gold"></span>
Phase 3 - In Development
</span>
</div>
</div> </div>
</div> </div>
</div> </div>