style: Redesign match detail pages with neon esports aesthetic

Complete overhaul of all 7 match sub-pages (Overview, Flashes, Economy,
Details, Weapons, Damage, Chat) with consistent neon design system.

Key changes:
- Update Card/Tabs components with void backgrounds and neon accents
- Add decorative blur orbs and grid pattern to match layout hero
- Convert DaisyUI classes to custom Tailwind with neon colors
- Update chart components with neon-themed tooltips and grid styling
- Add RoundTimeline neon glow on selection with void-themed tooltips

Puns added throughout:
- "Hall of Shame" for players who flash teammates more than enemies
- "Needs Therapy Award" for high team damage
- "MVP (Most Violent Player)" badge
- "The Poverty Round", "YOLO Buy" economy labels
- "Multi-Threat Level", "Can't Touch This", "Molotov Mixologist"

🤖 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:54:29 +01:00
parent 51112df979
commit ee233bb6fb
14 changed files with 1174 additions and 565 deletions

View File

@@ -72,9 +72,9 @@
<Card padding="lg">
<div class="mb-6">
<h2 class="text-2xl font-bold text-base-content">Round Timeline</h2>
<p class="mt-2 text-sm text-base-content/60">
Click on a round to see detailed information. T = Terrorists, CT = Counter-Terrorists
<h2 class="text-2xl font-bold text-white">Round Timeline</h2>
<p class="mt-2 text-sm text-white/60">
Click on a round to see the battle details. T = Terrorists, CT = Counter-Terrorists
</p>
</div>
@@ -100,25 +100,20 @@
>
<!-- Round number -->
<div
class="mb-2 text-xs font-semibold transition-colors"
class:text-primary={isSelected}
class:opacity-60={!isSelected}
class="mb-2 text-xs font-semibold transition-colors {isSelected
? 'text-neon-blue'
: 'text-white/60'}"
>
{round.round}
</div>
<!-- Round indicator circle -->
<div
class="relative flex h-12 w-12 items-center justify-center rounded-full border-2 transition-all"
class:border-terrorist={isWinner2}
class:bg-terrorist={isWinner2}
class:bg-opacity-20={isWinner2 || isWinner3}
class:border-ct={isWinner3}
class:bg-ct={isWinner3}
class:ring-4={isSelected}
class:ring-primary={isSelected}
class:ring-opacity-30={isSelected}
class:scale-110={isSelected}
class="relative flex h-12 w-12 items-center justify-center rounded-full border-2 transition-all {isWinner2
? 'border-terrorist bg-terrorist/20'
: ''} {isWinner3 ? 'border-ct bg-ct/20' : ''} {isSelected
? 'scale-110 shadow-[0_0_15px_rgba(0,212,255,0.4)] ring-2 ring-neon-blue'
: ''}"
>
<!-- Win reason icon or T/CT badge -->
{#if Icon}
@@ -147,18 +142,18 @@
<!-- Connecting line to next round -->
{#if round.round < rounds.length}
<div
class="absolute left-[60px] top-[34px] h-0.5 w-[calc(100%-60px)] bg-base-300"
class="absolute left-[60px] top-[34px] h-0.5 w-[calc(100%-60px)] bg-white/10"
></div>
{/if}
<!-- Hover tooltip -->
<div
class="pointer-events-none absolute top-full z-10 mt-2 hidden w-48 rounded-lg bg-base-100 p-3 text-left shadow-xl ring-1 ring-base-300 group-hover:block"
class="pointer-events-none absolute top-full z-10 mt-2 hidden w-48 rounded-lg border border-white/10 bg-void-light p-3 text-left shadow-xl backdrop-blur-sm group-hover:block"
>
<div class="text-xs font-semibold text-base-content">
<div class="text-xs font-semibold text-white">
Round {round.round}
</div>
<div class="mt-1 text-xs text-base-content/80">
<div class="mt-1 text-xs text-white/80">
Winner:
<span
class="font-bold"
@@ -168,10 +163,10 @@
{isWinner2 ? 'Terrorists' : 'Counter-Terrorists'}
</span>
</div>
<div class="mt-1 text-xs text-base-content/60">
<div class="mt-1 text-xs text-white/60">
{getWinReasonText(round.win_reason)}
</div>
<div class="mt-2 text-xs text-base-content/60">
<div class="mt-2 text-xs text-white/60">
Score: {scoreAtRound.teamA} - {scoreAtRound.teamB}
</div>
</div>
@@ -196,13 +191,13 @@
<!-- Selected Round Details -->
{#if selectedRoundData}
<div class="mt-6 border-t border-base-300 pt-6">
<div class="mt-6 border-t border-white/10 pt-6">
<div class="mb-4 flex items-center justify-between">
<h3 class="text-xl font-bold text-base-content">
<h3 class="text-xl font-bold text-white">
Round {selectedRoundData.round} Details
</h3>
<button
class="btn btn-ghost btn-sm"
class="rounded-lg px-3 py-1.5 text-sm text-white/60 transition-colors hover:bg-white/5 hover:text-white"
onclick={() => (selectedRound = null)}
aria-label="Close details"
>
@@ -212,7 +207,7 @@
<div class="grid gap-4 md:grid-cols-2">
<div>
<div class="text-sm text-base-content/60">Winner</div>
<div class="text-sm text-white/50">Winner</div>
<div
class="text-lg font-bold"
class:text-terrorist={selectedRoundData.winner === 2}
@@ -222,8 +217,8 @@
</div>
</div>
<div>
<div class="text-sm text-base-content/60">Win Reason</div>
<div class="text-lg font-semibold text-base-content">
<div class="text-sm text-white/50">Win Reason</div>
<div class="text-lg font-semibold text-white">
{getWinReasonText(selectedRoundData.win_reason)}
</div>
</div>
@@ -232,37 +227,46 @@
<!-- Player stats for the round if available -->
{#if selectedRoundData.players && selectedRoundData.players.length > 0}
<div class="mt-4">
<h4 class="mb-2 text-sm font-semibold text-base-content">Round Economy</h4>
<h4 class="mb-2 text-sm font-semibold text-white">Round Economy</h4>
<div class="overflow-x-auto">
<table class="table table-sm">
<table class="w-full text-sm">
<thead>
<tr class="border-base-300">
<th>Player</th>
<th>Bank</th>
<th>Equipment</th>
<th>Spent</th>
<tr class="border-b border-white/10 text-left text-white/50">
<th class="px-3 py-2">Player</th>
<th class="px-3 py-2">Bank</th>
<th class="px-3 py-2">Equipment</th>
<th class="px-3 py-2">Spent</th>
{#if selectedRoundData.players.some((p) => p.kills_in_round !== undefined)}
<th>Kills</th>
<th class="px-3 py-2">Kills</th>
{/if}
{#if selectedRoundData.players.some((p) => p.damage_in_round !== undefined)}
<th>Damage</th>
<th class="px-3 py-2">Damage</th>
{/if}
</tr>
</thead>
<tbody>
{#each selectedRoundData.players as player}
<tr class="border-base-300">
<td class="font-medium"
<tr class="border-b border-white/5 transition-colors hover:bg-white/5">
<td class="px-3 py-2 font-medium text-white"
>Player {player.player_id || player.match_player_id || '?'}</td
>
<td class="font-mono text-success">${player.bank.toLocaleString()}</td>
<td class="font-mono">${player.equipment.toLocaleString()}</td>
<td class="font-mono text-error">${player.spent.toLocaleString()}</td>
<td class="px-3 py-2 font-mono text-neon-green"
>${player.bank.toLocaleString()}</td
>
<td class="px-3 py-2 font-mono text-white/80"
>${player.equipment.toLocaleString()}</td
>
<td class="px-3 py-2 font-mono text-neon-red"
>${player.spent.toLocaleString()}</td
>
{#if selectedRoundData.players.some((p) => p.kills_in_round !== undefined)}
<td class="font-mono">{player.kills_in_round || 0}</td>
<td class="px-3 py-2 font-mono text-white/80">{player.kills_in_round || 0}</td
>
{/if}
{#if selectedRoundData.players.some((p) => p.damage_in_round !== undefined)}
<td class="font-mono">{player.damage_in_round || 0}</td>
<td class="px-3 py-2 font-mono text-white/80"
>{player.damage_in_round || 0}</td
>
{/if}
</tr>
{/each}

View File

@@ -56,7 +56,7 @@
display: true,
position: 'top',
labels: {
color: 'rgb(156, 163, 175)',
color: 'rgba(255, 255, 255, 0.7)',
font: {
family: 'Inter, system-ui, sans-serif',
size: 12
@@ -64,21 +64,21 @@
}
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
backgroundColor: 'rgba(18, 18, 26, 0.95)',
padding: 12,
titleColor: '#fff',
bodyColor: '#fff',
borderColor: 'rgba(255, 255, 255, 0.1)',
bodyColor: 'rgba(255, 255, 255, 0.8)',
borderColor: 'rgba(0, 212, 255, 0.3)',
borderWidth: 1
}
},
scales: {
x: {
grid: {
color: 'rgba(156, 163, 175, 0.1)'
color: 'rgba(255, 255, 255, 0.05)'
},
ticks: {
color: 'rgb(156, 163, 175)',
color: 'rgba(255, 255, 255, 0.5)',
font: {
size: 11
}
@@ -86,10 +86,10 @@
},
y: {
grid: {
color: 'rgba(156, 163, 175, 0.1)'
color: 'rgba(255, 255, 255, 0.05)'
},
ticks: {
color: 'rgb(156, 163, 175)',
color: 'rgba(255, 255, 255, 0.5)',
font: {
size: 11
}

View File

@@ -65,7 +65,7 @@
display: true,
position: 'top',
labels: {
color: 'rgb(156, 163, 175)',
color: 'rgba(255, 255, 255, 0.7)',
font: {
family: 'Inter, system-ui, sans-serif',
size: 12
@@ -73,21 +73,21 @@
}
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
backgroundColor: 'rgba(18, 18, 26, 0.95)',
padding: 12,
titleColor: '#fff',
bodyColor: '#fff',
borderColor: 'rgba(255, 255, 255, 0.1)',
bodyColor: 'rgba(255, 255, 255, 0.8)',
borderColor: 'rgba(0, 212, 255, 0.3)',
borderWidth: 1
}
},
scales: {
x: {
grid: {
color: 'rgba(156, 163, 175, 0.1)'
color: 'rgba(255, 255, 255, 0.05)'
},
ticks: {
color: 'rgb(156, 163, 175)',
color: 'rgba(255, 255, 255, 0.5)',
font: {
size: 11
}
@@ -95,10 +95,10 @@
},
y: {
grid: {
color: 'rgba(156, 163, 175, 0.1)'
color: 'rgba(255, 255, 255, 0.05)'
},
ticks: {
color: 'rgb(156, 163, 175)',
color: 'rgba(255, 255, 255, 0.5)',
font: {
size: 11
}

View File

@@ -54,7 +54,7 @@
display: true,
position: 'bottom',
labels: {
color: 'rgb(156, 163, 175)',
color: 'rgba(255, 255, 255, 0.7)',
font: {
family: 'Inter, system-ui, sans-serif',
size: 12
@@ -63,11 +63,11 @@
}
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
backgroundColor: 'rgba(18, 18, 26, 0.95)',
padding: 12,
titleColor: '#fff',
bodyColor: '#fff',
borderColor: 'rgba(255, 255, 255, 0.1)',
bodyColor: 'rgba(255, 255, 255, 0.8)',
borderColor: 'rgba(0, 212, 255, 0.3)',
borderWidth: 1
}
}

View File

@@ -17,13 +17,13 @@
children
}: Props = $props();
const baseClasses = 'bg-base-200 border border-base-300 rounded-md transition-all duration-200';
const baseClasses = 'bg-void-light border border-white/10 rounded-xl transition-all duration-300';
const variantClasses = {
default: 'shadow-sm',
elevated: 'shadow-lg shadow-black/10',
default: 'shadow-sm hover:shadow-[0_0_20px_rgba(0,212,255,0.05)]',
elevated: 'shadow-lg shadow-black/20 hover:shadow-[0_0_30px_rgba(0,212,255,0.1)]',
interactive:
'cursor-pointer hover:border-primary hover:shadow-lg hover:shadow-primary/20 hover:-translate-y-0.5'
'cursor-pointer hover:border-neon-blue/50 hover:shadow-[0_0_20px_rgba(0,212,255,0.15)] hover:-translate-y-0.5'
};
const paddingClasses = {

View File

@@ -12,8 +12,7 @@
tabs: Tab[];
activeTab?: string;
onTabChange?: (value: string) => void;
variant?: 'boxed' | 'bordered' | 'lifted';
size?: 'xs' | 'sm' | 'md' | 'lg';
size?: 'sm' | 'md' | 'lg';
class?: string;
}
@@ -21,7 +20,6 @@
tabs,
activeTab = $bindable(),
onTabChange,
variant = 'bordered',
size = 'md',
class: className = ''
}: Props = $props();
@@ -43,21 +41,36 @@
}
};
const variantClass =
variant === 'boxed' ? 'tabs-boxed' : variant === 'lifted' ? 'tabs-lifted' : '';
const sizeClass =
size === 'xs' ? 'tabs-xs' : size === 'sm' ? 'tabs-sm' : size === 'lg' ? 'tabs-lg' : '';
const sizeClasses = {
sm: 'text-xs px-3 py-1.5',
md: 'text-sm px-4 py-2',
lg: 'text-base px-5 py-2.5'
};
const baseTabClasses = 'rounded-md font-medium transition-all duration-200 whitespace-nowrap';
const inactiveClasses = 'text-white/60 hover:text-white hover:bg-white/5';
const activeClasses =
'text-neon-blue bg-neon-blue/10 border border-neon-blue/50 shadow-[0_0_10px_rgba(0,212,255,0.15)]';
const disabledClasses = 'opacity-40 cursor-not-allowed pointer-events-none';
</script>
<div role="tablist" class="tabs {variantClass} {sizeClass} {className}">
<div
role="tablist"
class="inline-flex gap-1 rounded-lg bg-void/50 p-1 backdrop-blur-sm {className}"
>
{#each tabs as tab}
{@const active = isActive(tab)}
{@const classes = `${baseTabClasses} ${sizeClasses[size]} ${active ? activeClasses : inactiveClasses} ${tab.disabled ? disabledClasses : ''}`}
{#if tab.href}
<a
href={tab.href}
role="tab"
class="tab"
class:tab-active={isActive(tab)}
class:tab-disabled={tab.disabled}
class={classes}
aria-selected={active}
aria-disabled={tab.disabled}
>
{tab.label}
@@ -65,9 +78,8 @@
{:else}
<button
role="tab"
class="tab"
class:tab-active={isActive(tab)}
class:tab-disabled={tab.disabled}
class={classes}
aria-selected={active}
disabled={tab.disabled}
onclick={() => handleTabClick(tab)}
>