website refactor

This commit is contained in:
2026-01-18 16:18:18 +01:00
parent 0b301feb61
commit 13567d51af
329 changed files with 4701 additions and 4750 deletions

View File

@@ -3,9 +3,8 @@
import { useState, useRef, useCallback } from 'react';
import { Card } from '@/ui/Card';
import { Button } from '@/ui/Button';
import { Box } from '@/ui/Box';
import { Text } from '@/ui/Text';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { Heading } from '@/ui/Heading';
import { Icon } from '@/ui/Icon';
import {
@@ -174,12 +173,12 @@ export function LeagueDecalPlacementEditor({
return (
<Stack gap={6}>
{/* Header */}
<Box display="flex" alignItems="center" justifyContent="between">
<Box>
<Stack display="flex" alignItems="center" justifyContent="between">
<Stack>
<Heading level={3} fontSize="lg" weight="semibold" color="text-white">{carName}</Heading>
<Text size="sm" color="text-gray-400">Position sponsor decals on this car&apos;s template</Text>
</Box>
<Box display="flex" alignItems="center" gap={2}>
</Stack>
<Stack display="flex" alignItems="center" gap={2}>
<Button
variant="secondary"
onClick={() => setZoom(z => Math.max(0.5, z - 0.25))}
@@ -201,13 +200,13 @@ export function LeagueDecalPlacementEditor({
>
<Icon icon={ZoomIn} size={4} />
</Button>
</Box>
</Box>
</Stack>
</Stack>
<Box display="grid" responsiveGridCols={{ base: 1, lg: 3 }} gap={6}>
<Stack display="grid" responsiveGridCols={{ base: 1, lg: 3 }} gap={6}>
{/* Canvas */}
<Box responsiveColSpan={{ lg: 2 }}>
<Box
<Stack responsiveColSpan={{ lg: 2 }}>
<Stack
ref={canvasRef}
position="relative"
// eslint-disable-next-line gridpilot-rules/component-classification
@@ -220,7 +219,7 @@ export function LeagueDecalPlacementEditor({
>
{/* Base Image or Placeholder */}
{baseImageUrl ? (
<Box
<Stack
as="img"
src={baseImageUrl}
alt="Livery template"
@@ -231,21 +230,21 @@ export function LeagueDecalPlacementEditor({
draggable={false}
/>
) : (
<Box fullWidth fullHeight display="flex" flexDirection="col" alignItems="center" justifyContent="center">
<Stack fullWidth fullHeight display="flex" flexDirection="col" alignItems="center" justifyContent="center">
<Icon icon={ImageIcon} size={16} color="text-gray-600"
// eslint-disable-next-line gridpilot-rules/component-classification
className="mb-2"
/>
<Text size="sm" color="text-gray-500">No base template uploaded</Text>
<Text size="xs" color="text-gray-600">Upload a template image first</Text>
</Box>
</Stack>
)}
{/* Decal Placeholders */}
{placements.map((placement) => {
const decalColors = getSponsorTypeColor(placement.sponsorType);
return (
<Box
<Stack
key={placement.id}
onMouseDown={(e: React.MouseEvent) => handleMouseDown(e, placement.id)}
onClick={() => handleDecalClick(placement.id)}
@@ -272,8 +271,8 @@ export function LeagueDecalPlacementEditor({
transform: `translate(-50%, -50%) rotate(${placement.rotation}deg)`,
}}
>
<Box textAlign="center" truncate px={1}>
<Box
<Stack textAlign="center" truncate px={1}>
<Stack
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
transform="uppercase"
@@ -281,36 +280,36 @@ export function LeagueDecalPlacementEditor({
className="tracking-wide opacity-70"
>
{placement.sponsorType === 'main' ? 'Main' : 'Secondary'}
</Box>
<Box truncate>{placement.sponsorName}</Box>
</Box>
</Stack>
<Stack truncate>{placement.sponsorName}</Stack>
</Stack>
{/* Drag handle indicator */}
{selectedDecal === placement.id && (
<Box position="absolute" top="-1" left="-1" w="3" h="3" bg="bg-white" rounded="full" border borderWidth="2px" borderColor="border-primary-blue" />
<Stack position="absolute" top="-1" left="-1" w="3" h="3" bg="bg-white" rounded="full" border borderWidth="2px" borderColor="border-primary-blue" />
)}
</Box>
</Stack>
);
})}
{/* Grid overlay when dragging */}
{isDragging && (
<Box position="absolute" inset="0" pointerEvents="none">
<Box fullWidth fullHeight
<Stack position="absolute" inset="0" pointerEvents="none">
<Stack fullWidth fullHeight
// eslint-disable-next-line gridpilot-rules/component-classification
style={{
backgroundImage: 'linear-gradient(to right, rgba(255,255,255,0.05) 1px, transparent 1px), linear-gradient(to bottom, rgba(255,255,255,0.05) 1px, transparent 1px)',
backgroundSize: '10% 10%',
}}
/>
</Box>
</Stack>
)}
</Box>
</Stack>
<Text size="xs" color="text-gray-500" mt={2} block>
Click a decal to select it, then drag to reposition. Use controls on the right to adjust size and rotation.
</Text>
</Box>
</Stack>
{/* Controls Panel */}
<Stack gap={4}>
@@ -321,7 +320,7 @@ export function LeagueDecalPlacementEditor({
{placements.map((placement) => {
const decalColors = getSponsorTypeColor(placement.sponsorType);
return (
<Box
<Stack
key={placement.id}
as="button"
onClick={() => setSelectedDecal(placement.id)}
@@ -335,9 +334,9 @@ export function LeagueDecalPlacementEditor({
bg={selectedDecal === placement.id ? decalColors.bg : 'bg-iron-gray/30'}
hoverBg={selectedDecal !== placement.id ? 'bg-iron-gray/50' : undefined}
>
<Box display="flex" alignItems="center" justifyContent="between">
<Box>
<Box
<Stack display="flex" alignItems="center" justifyContent="between">
<Stack>
<Stack
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
weight="medium"
@@ -345,19 +344,19 @@ export function LeagueDecalPlacementEditor({
color={decalColors.text}
>
{placement.sponsorType === 'main' ? 'Main Sponsor' : `Secondary ${placement.sponsorType.split('-')[1]}`}
</Box>
<Box
</Stack>
<Stack
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-gray-500"
mt={0.5}
>
{Math.round(placement.x * 100)}%, {Math.round(placement.y * 100)}% {placement.rotation}°
</Box>
</Box>
</Stack>
</Stack>
<Icon icon={Target} size={4} color={selectedDecal === placement.id ? decalColors.text : 'text-gray-500'} />
</Box>
</Box>
</Stack>
</Stack>
);
})}
</Stack>
@@ -369,12 +368,12 @@ export function LeagueDecalPlacementEditor({
<Heading level={4} fontSize="sm" weight="semibold" color="text-white" mb={3}>Adjust Selected</Heading>
{/* Position */}
<Box mb={4}>
<Stack mb={4}>
<Text as="label" size="xs" color="text-gray-400" block mb={2}>Position</Text>
<Box display="grid" gridCols={2} gap={2}>
<Box>
<Stack display="grid" gridCols={2} gap={2}>
<Stack>
<Text as="label" size="xs" color="text-gray-500" block mb={1}>X</Text>
<Box
<Stack
as="input"
type="range"
min="0"
@@ -388,10 +387,10 @@ export function LeagueDecalPlacementEditor({
// eslint-disable-next-line gridpilot-rules/component-classification
className="appearance-none cursor-pointer accent-primary-blue"
/>
</Box>
<Box>
</Stack>
<Stack>
<Text as="label" size="xs" color="text-gray-500" block mb={1}>Y</Text>
<Box
<Stack
as="input"
type="range"
min="0"
@@ -405,14 +404,14 @@ export function LeagueDecalPlacementEditor({
// eslint-disable-next-line gridpilot-rules/component-classification
className="appearance-none cursor-pointer accent-primary-blue"
/>
</Box>
</Box>
</Box>
</Stack>
</Stack>
</Stack>
{/* Size */}
<Box mb={4}>
<Stack mb={4}>
<Text as="label" size="xs" color="text-gray-400" block mb={2}>Size</Text>
<Box display="flex" gap={2}>
<Stack display="flex" gap={2}>
<Button
variant="secondary"
onClick={() => handleResize(selectedPlacement.id, 0.9)}
@@ -435,14 +434,14 @@ export function LeagueDecalPlacementEditor({
/>
Larger
</Button>
</Box>
</Box>
</Stack>
</Stack>
{/* Rotation */}
<Box mb={4}>
<Stack mb={4}>
<Text as="label" size="xs" color="text-gray-400" block mb={2}>Rotation: {selectedPlacement.rotation}°</Text>
<Box display="flex" alignItems="center" gap={2}>
<Box
<Stack display="flex" alignItems="center" gap={2}>
<Stack
as="input"
type="range"
min="0"
@@ -464,8 +463,8 @@ export function LeagueDecalPlacementEditor({
>
<Icon icon={RotateCw} size={4} />
</Button>
</Box>
</Box>
</Stack>
</Stack>
</Card>
)}
@@ -484,14 +483,14 @@ export function LeagueDecalPlacementEditor({
</Button>
{/* Help Text */}
<Box p={3} rounded="lg" bg="bg-iron-gray/30" border borderColor="border-charcoal-outline/50">
<Stack p={3} rounded="lg" bg="bg-iron-gray/30" border borderColor="border-charcoal-outline/50">
<Text size="xs" color="text-gray-500" block>
<Text weight="bold" color="text-gray-400">Tip:</Text> Main sponsor gets the largest, most prominent placement.
Secondary sponsors get smaller positions. These decals will be burned onto all driver liveries.
</Text>
</Box>
</Stack>
</Stack>
</Box>
</Stack>
</Stack>
);
}