website refactor

This commit is contained in:
2026-01-18 22:55:55 +01:00
parent b43a23a48c
commit aeaa43f4d3
179 changed files with 4736 additions and 6832 deletions

View File

@@ -1,7 +1,8 @@
import { Icon } from '@/ui/Icon';
import { Box } from '@/ui/primitives/Box';
import { Text } from '@/ui/Text';
import { Badge } from '@/ui/Badge';
import { ChevronDown, ChevronUp, Minus } from 'lucide-react';
import React from 'react';
interface DeltaChipProps {
value: number;
@@ -11,40 +12,26 @@ interface DeltaChipProps {
export function DeltaChip({ value, type = 'rank' }: DeltaChipProps) {
if (value === 0) {
return (
<Box display="flex" alignItems="center" gap={1} color="text-gray-600">
<Icon icon={Minus} size={3} />
<Text size="xs" font="mono">0</Text>
</Box>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.25rem' }}>
<Icon icon={Minus} size={3} intent="low" />
<Text size="xs" font="mono" variant="low">0</Text>
</div>
);
}
const isPositive = value > 0;
const color = isPositive
? (type === 'rank' ? 'text-performance-green' : 'text-performance-green')
: (type === 'rank' ? 'text-error-red' : 'text-error-red');
// For rank, positive delta usually means dropping positions (e.g. +1 rank means 1st -> 2nd)
// But usually "Delta" in leaderboards means "positions gained/lost"
// Let's assume value is "positions gained" (positive = up, negative = down)
const variant = isPositive ? 'success' : 'critical';
const IconComponent = isPositive ? ChevronUp : ChevronDown;
const absoluteValue = Math.abs(value);
return (
<Box
display="flex"
alignItems="center"
gap={0.5}
color={color}
bg={`${color.replace('text-', 'bg-')}/10`}
px={1.5}
py={0.5}
rounded="full"
>
<Icon icon={IconComponent} size={3} />
<Text size="xs" font="mono" weight="bold">
{absoluteValue}
</Text>
</Box>
<Badge variant={variant} size="sm">
<div style={{ display: 'flex', alignItems: 'center', gap: '0.125rem' }}>
<Icon icon={IconComponent} size={3} />
<Text size="xs" font="mono" weight="bold">
{absoluteValue}
</Text>
</div>
</Badge>
);
}

View File

@@ -1,6 +1,7 @@
import { Icon } from '@/ui/Icon';
import { Stack } from '@/ui/primitives/Stack';
import { Text } from '@/ui/Text';
import { Input } from '@/ui/Input';
import { ControlBar } from '@/ui/ControlBar';
import { Button } from '@/ui/Button';
import { Filter, Search } from 'lucide-react';
import React from 'react';
@@ -18,70 +19,32 @@ export function LeaderboardFiltersBar({
children,
}: LeaderboardFiltersBarProps) {
return (
<Stack
mb={6}
p={3}
bg="bg-deep-charcoal/40"
border
borderColor="border-charcoal-outline/50"
rounded="lg"
blur="sm"
>
<Stack direction="row" align="center" justify="between" gap={4}>
<Stack position="relative" flexGrow={1} maxWidth="md">
<Stack
position="absolute"
left="3"
top="1/2"
transform="translateY(-50%)"
pointerEvents="none"
zIndex={10}
>
<Icon icon={Search} size={4} color="text-gray-500" />
</Stack>
<Stack
as="input"
type="text"
value={searchQuery}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onSearchChange?.(e.target.value)}
placeholder={placeholder}
w="full"
bg="bg-graphite-black/50"
border
borderColor="border-charcoal-outline"
rounded="md"
py={2}
pl={10}
pr={4}
fontSize="0.875rem"
color="text-white"
transition
hoverBorderColor="border-primary-blue/50"
/>
</Stack>
<Stack direction="row" align="center" gap={4}>
<div style={{ marginBottom: '1.5rem' }}>
<ControlBar
leftContent={
<div style={{ maxWidth: '32rem', width: '100%' }}>
<Input
type="text"
value={searchQuery}
onChange={(e) => onSearchChange?.(e.target.value)}
placeholder={placeholder}
icon={<Icon icon={Search} size={4} intent="low" />}
fullWidth
/>
</div>
}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
{children}
<Stack
display="flex"
alignItems="center"
gap={2}
px={3}
py={2}
bg="bg-graphite-black/30"
border
borderColor="border-charcoal-outline"
rounded="md"
cursor="pointer"
transition
hoverBg="bg-graphite-black/50"
hoverBorderColor="border-gray-600"
<Button
variant="secondary"
size="sm"
icon={<Icon icon={Filter} size={3.5} intent="low" />}
>
<Icon icon={Filter} size={3.5} color="text-gray-400" />
<Text size="xs" weight="bold" color="text-gray-400" uppercase letterSpacing="wider">Filters</Text>
</Stack>
</Stack>
</Stack>
</Stack>
Filters
</Button>
</div>
</ControlBar>
</div>
);
}

View File

@@ -1,15 +1,20 @@
import { Box } from '@/ui/primitives/Box';
import { Badge } from '@/ui/Badge';
import { Text } from '@/ui/Text';
import React from 'react';
interface RankBadgeProps {
rank: number;
size?: 'sm' | 'md' | 'lg';
showLabel?: boolean;
size?: 'sm' | 'md';
}
export function RankBadge({ rank, size = 'md', showLabel = true }: RankBadgeProps) {
export function RankBadge({ rank, size = 'md' }: RankBadgeProps) {
const getVariant = (rank: number): 'warning' | 'primary' | 'info' | 'default' => {
if (rank <= 3) return 'warning';
if (rank <= 10) return 'primary';
if (rank <= 50) return 'info';
return 'default';
};
const getMedalEmoji = (rank: number) => {
switch (rank) {
case 1: return '🥇';
@@ -21,32 +26,12 @@ export function RankBadge({ rank, size = 'md', showLabel = true }: RankBadgeProp
const medal = getMedalEmoji(rank);
const sizeClasses = {
sm: 'text-sm px-2 py-1',
md: 'text-base px-3 py-1.5',
lg: 'text-lg px-4 py-2'
};
const getRankColor = (rank: number) => {
if (rank <= 3) return 'bg-warning-amber/20 text-warning-amber border-warning-amber/30';
if (rank <= 10) return 'bg-primary-blue/20 text-primary-blue border-primary-blue/30';
if (rank <= 50) return 'bg-purple-500/20 text-purple-400 border-purple-500/30';
return 'bg-charcoal-outline/20 text-gray-300 border-charcoal-outline';
};
return (
<Box
as="span"
display="inline-flex"
alignItems="center"
gap={1.5}
rounded="md"
border
className={`font-medium ${getRankColor(rank)} ${sizeClasses[size]}`}
>
{medal && <Text>{medal}</Text>}
{showLabel && <Text>#{rank}</Text>}
{!showLabel && !medal && <Text>#{rank}</Text>}
</Box>
<Badge variant={getVariant(rank)} size={size}>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.25rem' }}>
{medal && <span>{medal}</span>}
<Text size="xs" weight="bold">#{rank}</Text>
</div>
</Badge>
);
}

View File

@@ -1,8 +1,8 @@
import { MedalDisplay } from '@/lib/display-objects/MedalDisplay';
import { Icon } from '@/ui/Icon';
import { Box } from '@/ui/primitives/Box';
import { Text } from '@/ui/Text';
import { Crown, Medal } from 'lucide-react';
import React from 'react';
interface RankMedalProps {
rank: number;
@@ -12,11 +12,12 @@ interface RankMedalProps {
export function RankMedal({ rank, size = 'md', showIcon = true }: RankMedalProps) {
const isTop3 = rank <= 3;
const variant = MedalDisplay.getVariant(rank);
const sizeMap = {
sm: '7',
md: '8',
lg: '10',
const sizePx = {
sm: '1.75rem',
md: '2rem',
lg: '2.5rem',
};
const textSizeMap = {
@@ -32,22 +33,23 @@ export function RankMedal({ rank, size = 'md', showIcon = true }: RankMedalProps
};
return (
<Box
display="flex"
alignItems="center"
justifyContent="center"
rounded="full"
border
h={sizeMap[size]}
w={sizeMap[size]}
bg={MedalDisplay.getBg(rank)}
color={MedalDisplay.getColor(rank)}
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '9999px',
border: '1px solid var(--ui-color-border-default)',
height: sizePx[size],
width: sizePx[size],
backgroundColor: 'var(--ui-color-bg-surface-muted)'
}}
>
{isTop3 && showIcon ? (
<Icon icon={rank === 1 ? Crown : Medal} size={iconSize[size]} />
<Icon icon={rank === 1 ? Crown : Medal} size={iconSize[size]} intent={variant as any} />
) : (
<Text weight="bold" size={textSizeMap[size]}>{rank}</Text>
<Text weight="bold" size={textSizeMap[size]} variant={variant as any}>{rank}</Text>
)}
</Box>
</div>
);
}

View File

@@ -1,9 +1,9 @@
import { getMediaUrl } from '@/lib/utilities/media';
import { Image } from '@/ui/Image';
import { Box } from '@/ui/primitives/Box';
import { TableCell, TableRow } from '@/ui/Table';
import { Text } from '@/ui/Text';
import { RankMedal } from './RankMedal';
import React from 'react';
interface TeamRankingRowProps {
id: string;
@@ -32,70 +32,62 @@ export function TeamRankingRow({
<TableRow
clickable={!!onClick}
onClick={onClick}
group
>
<TableCell>
<Box w="8" display="flex" justifyContent="center">
<div style={{ width: '2rem', display: 'flex', justifyContent: 'center' }}>
<RankMedal rank={rank} size="md" />
</Box>
</div>
</TableCell>
<TableCell>
<Box display="flex" alignItems="center" gap={3}>
<Box
position="relative"
w="10"
h="10"
rounded="lg"
overflow="hidden"
border
borderColor="border-charcoal-outline"
bg="bg-graphite-black/50"
groupHoverBorderColor="purple-400/50"
transition
>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
<div style={{
position: 'relative',
width: '2.5rem',
height: '2.5rem',
borderRadius: '0.5rem',
overflow: 'hidden',
border: '1px solid var(--ui-color-border-default)',
backgroundColor: 'var(--ui-color-bg-surface-muted)'
}}>
<Image
src={logoUrl || getMediaUrl('team-logo', id)}
alt={name}
width={40}
height={40}
fullWidth
fullHeight
objectFit="cover"
/>
</Box>
<Box minWidth="0">
</div>
<div style={{ minWidth: 0 }}>
<Text
weight="semibold"
color="text-white"
variant="high"
block
truncate
groupHoverTextColor="text-purple-400"
transition
>
{name}
</Text>
<Text size="xs" color="text-gray-500" block mt={0.5}>
<Text size="xs" variant="low" block>
{memberCount} Members
</Text>
</Box>
</Box>
</div>
</div>
</TableCell>
<TableCell textAlign="center">
<Text font="mono" weight="bold" color="text-purple-400">
<Text font="mono" weight="bold" variant="primary">
{rating}
</Text>
</TableCell>
<TableCell textAlign="center">
<Text font="mono" weight="bold" color="text-performance-green">
<Text font="mono" weight="bold" variant="success">
{wins}
</Text>
</TableCell>
<TableCell textAlign="center">
<Text color="text-gray-400" font="mono">{races}</Text>
<Text variant="low" font="mono">{races}</Text>
</TableCell>
</TableRow>
);