This commit is contained in:
2025-12-07 00:18:02 +01:00
parent 70d5f5689e
commit 5ca2454853
20 changed files with 4461 additions and 790 deletions

View File

@@ -1,9 +1,110 @@
'use client';
import { User, Users2, Info } from 'lucide-react';
import { User, Users2, Info, Check, HelpCircle, X } from 'lucide-react';
import { useState, useRef, useEffect, useMemo } from 'react';
import { createPortal } from 'react-dom';
import Input from '@/components/ui/Input';
import SegmentedControl from '@/components/ui/SegmentedControl';
import type { LeagueConfigFormModel } from '@gridpilot/racing/application';
import { GameConstraints } from '@gridpilot/racing/domain/value-objects/GameConstraints';
// ============================================================================
// INFO FLYOUT COMPONENT
// ============================================================================
interface InfoFlyoutProps {
isOpen: boolean;
onClose: () => void;
title: string;
children: React.ReactNode;
anchorRef: React.RefObject<HTMLElement | null>;
}
function InfoFlyout({ isOpen, onClose, title, children, anchorRef }: InfoFlyoutProps) {
const [position, setPosition] = useState({ top: 0, left: 0 });
const [mounted, setMounted] = useState(false);
const flyoutRef = useRef<HTMLDivElement>(null);
useEffect(() => {
setMounted(true);
}, []);
useEffect(() => {
if (isOpen && anchorRef.current && mounted) {
const rect = anchorRef.current.getBoundingClientRect();
const flyoutWidth = Math.min(320, window.innerWidth - 40);
const flyoutHeight = 300;
const padding = 16;
let left = rect.right + 12;
let top = rect.top;
if (left + flyoutWidth > window.innerWidth - padding) {
left = rect.left - flyoutWidth - 12;
}
if (left < padding) {
left = Math.max(padding, (window.innerWidth - flyoutWidth) / 2);
}
top = rect.top - flyoutHeight / 3;
if (top + flyoutHeight > window.innerHeight - padding) {
top = window.innerHeight - flyoutHeight - padding;
}
if (top < padding) top = padding;
left = Math.max(padding, Math.min(left, window.innerWidth - flyoutWidth - padding));
setPosition({ top, left });
}
}, [isOpen, anchorRef, mounted]);
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};
const handleClickOutside = (e: MouseEvent) => {
if (flyoutRef.current && !flyoutRef.current.contains(e.target as Node)) {
onClose();
}
};
if (isOpen) {
document.addEventListener('keydown', handleEscape);
document.addEventListener('mousedown', handleClickOutside);
}
return () => {
document.removeEventListener('keydown', handleEscape);
document.removeEventListener('mousedown', handleClickOutside);
};
}, [isOpen, onClose]);
if (!isOpen || !mounted) return null;
return createPortal(
<div
ref={flyoutRef}
className="fixed z-50 w-[320px] max-h-[80vh] overflow-y-auto bg-iron-gray border border-charcoal-outline rounded-xl shadow-2xl animate-fade-in"
style={{ top: position.top, left: position.left }}
>
<div className="flex items-center justify-between p-4 border-b border-charcoal-outline/50 sticky top-0 bg-iron-gray z-10">
<div className="flex items-center gap-2">
<HelpCircle className="w-4 h-4 text-primary-blue" />
<span className="text-sm font-semibold text-white">{title}</span>
</div>
<button
type="button"
onClick={onClose}
className="flex h-6 w-6 items-center justify-center rounded-md hover:bg-charcoal-outline transition-colors"
>
<X className="w-4 h-4 text-gray-400" />
</button>
</div>
<div className="p-4">
{children}
</div>
</div>,
document.body
);
}
interface LeagueStructureSectionProps {
form: LeagueConfigFormModel;
@@ -127,123 +228,368 @@ export function LeagueStructureSection({
});
};
// Flyout state
const [showSoloFlyout, setShowSoloFlyout] = useState(false);
const [showTeamsFlyout, setShowTeamsFlyout] = useState(false);
const soloInfoRef = useRef<HTMLButtonElement>(null);
const teamsInfoRef = useRef<HTMLButtonElement>(null);
const isSolo = structure.mode === 'solo';
// Get game-specific constraints
const gameConstraints = useMemo(
() => GameConstraints.forGame(form.basics.gameId),
[form.basics.gameId]
);
return (
<div className="space-y-6">
{/* League structure selection */}
<div className="space-y-3">
<label className="flex items-center gap-2 text-sm font-medium text-gray-300">
<Users2 className="w-4 h-4 text-primary-blue" />
League structure
</label>
<SegmentedControl
options={[
{
value: 'solo',
label: 'Drivers only (Solo)',
description: 'Individual drivers score points.',
},
{
value: 'fixedTeams',
label: 'Teams',
description: 'Teams with fixed drivers per team.',
},
]}
value={structure.mode}
onChange={
disabled
? undefined
: (mode) => handleModeChange(mode as 'solo' | 'fixedTeams')
}
/>
<div className="space-y-8">
{/* Emotional header */}
<div className="text-center pb-2">
<h3 className="text-lg font-semibold text-white mb-2">
How will your drivers compete?
</h3>
<p className="text-sm text-gray-400 max-w-lg mx-auto">
Choose your championship format individual glory or team triumph.
</p>
</div>
{/* Solo mode capacity */}
{structure.mode === 'solo' && (
<div className="rounded-lg border border-charcoal-outline bg-iron-gray/30 p-5 space-y-4">
<div className="flex items-start gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary-blue/10 shrink-0">
<User className="w-5 h-5 text-primary-blue" />
{/* Mode Selection Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Solo Mode Card */}
<div className="relative">
<button
type="button"
disabled={disabled}
onClick={() => handleModeChange('solo')}
className={`w-full group relative flex flex-col gap-4 p-6 text-left rounded-xl border-2 transition-all duration-200 ${
isSolo
? 'border-primary-blue bg-gradient-to-br from-primary-blue/15 to-primary-blue/5 shadow-[0_0_30px_rgba(25,140,255,0.25)]'
: 'border-charcoal-outline bg-iron-gray/30 hover:border-gray-500 hover:bg-iron-gray/50'
} ${disabled ? 'opacity-60 cursor-not-allowed' : 'cursor-pointer'}`}
>
{/* Header */}
<div className="flex items-start justify-between">
<div className="flex items-center gap-3">
<div className={`flex h-12 w-12 items-center justify-center rounded-xl ${
isSolo ? 'bg-primary-blue/30' : 'bg-charcoal-outline/50'
}`}>
<User className={`w-6 h-6 ${isSolo ? 'text-primary-blue' : 'text-gray-400'}`} />
</div>
<div>
<div className={`text-lg font-bold ${isSolo ? 'text-white' : 'text-gray-300'}`}>
Solo Drivers
</div>
<div className={`text-xs ${isSolo ? 'text-primary-blue' : 'text-gray-500'}`}>
Individual competition
</div>
</div>
</div>
{/* Radio indicator */}
<div className={`flex h-6 w-6 items-center justify-center rounded-full border-2 shrink-0 transition-colors ${
isSolo ? 'border-primary-blue bg-primary-blue' : 'border-gray-500'
}`}>
{isSolo && <Check className="w-3.5 h-3.5 text-white" />}
</div>
</div>
<div className="flex-1">
<h3 className="text-sm font-semibold text-white mb-1">Driver capacity</h3>
<p className="text-xs text-gray-500">
Set the maximum number of drivers who can join your league
</p>
{/* Emotional tagline */}
<p className={`text-sm ${isSolo ? 'text-gray-300' : 'text-gray-500'}`}>
Every driver for themselves. Pure skill, pure competition, pure glory.
</p>
{/* Features */}
<div className="space-y-2">
<div className="flex items-center gap-2 text-xs text-gray-400">
<Check className={`w-3.5 h-3.5 ${isSolo ? 'text-performance-green' : 'text-gray-500'}`} />
<span>Individual driver standings</span>
</div>
<div className="flex items-center gap-2 text-xs text-gray-400">
<Check className={`w-3.5 h-3.5 ${isSolo ? 'text-performance-green' : 'text-gray-500'}`} />
<span>Simple, classic format</span>
</div>
<div className="flex items-center gap-2 text-xs text-gray-400">
<Check className={`w-3.5 h-3.5 ${isSolo ? 'text-performance-green' : 'text-gray-500'}`} />
<span>Perfect for any grid size</span>
</div>
</div>
</button>
{/* Info button */}
<button
ref={soloInfoRef}
type="button"
onClick={() => setShowSoloFlyout(true)}
className="absolute top-2 right-2 flex h-6 w-6 items-center justify-center rounded-full text-gray-500 hover:text-primary-blue hover:bg-primary-blue/10 transition-colors"
>
<HelpCircle className="w-4 h-4" />
</button>
</div>
{/* Solo Info Flyout */}
<InfoFlyout
isOpen={showSoloFlyout}
onClose={() => setShowSoloFlyout(false)}
title="Solo Drivers Mode"
anchorRef={soloInfoRef}
>
<div className="space-y-4">
<p className="text-xs text-gray-400">
In solo mode, each driver competes individually. Points are awarded
based on finishing position, and standings track individual performance.
</p>
<div className="space-y-2">
<div className="text-[10px] text-gray-500 uppercase tracking-wide">Best For</div>
<ul className="space-y-1.5">
<li className="flex items-start gap-2 text-xs text-gray-400">
<Check className="w-3.5 h-3.5 text-performance-green shrink-0 mt-0.5" />
<span>Traditional racing championships</span>
</li>
<li className="flex items-start gap-2 text-xs text-gray-400">
<Check className="w-3.5 h-3.5 text-performance-green shrink-0 mt-0.5" />
<span>Smaller grids (10-30 drivers)</span>
</li>
<li className="flex items-start gap-2 text-xs text-gray-400">
<Check className="w-3.5 h-3.5 text-performance-green shrink-0 mt-0.5" />
<span>Quick setup with minimal coordination</span>
</li>
</ul>
</div>
</div>
</InfoFlyout>
<div className="space-y-4">
{/* Teams Mode Card */}
<div className="relative">
<button
type="button"
disabled={disabled}
onClick={() => handleModeChange('fixedTeams')}
className={`w-full group relative flex flex-col gap-4 p-6 text-left rounded-xl border-2 transition-all duration-200 ${
!isSolo
? 'border-neon-aqua bg-gradient-to-br from-neon-aqua/15 to-neon-aqua/5 shadow-[0_0_30px_rgba(67,201,230,0.2)]'
: 'border-charcoal-outline bg-iron-gray/30 hover:border-gray-500 hover:bg-iron-gray/50'
} ${disabled ? 'opacity-60 cursor-not-allowed' : 'cursor-pointer'}`}
>
{/* Header */}
<div className="flex items-start justify-between">
<div className="flex items-center gap-3">
<div className={`flex h-12 w-12 items-center justify-center rounded-xl ${
!isSolo ? 'bg-neon-aqua/30' : 'bg-charcoal-outline/50'
}`}>
<Users2 className={`w-6 h-6 ${!isSolo ? 'text-neon-aqua' : 'text-gray-400'}`} />
</div>
<div>
<div className={`text-lg font-bold ${!isSolo ? 'text-white' : 'text-gray-300'}`}>
Team Racing
</div>
<div className={`text-xs ${!isSolo ? 'text-neon-aqua' : 'text-gray-500'}`}>
Shared destiny
</div>
</div>
</div>
{/* Radio indicator */}
<div className={`flex h-6 w-6 items-center justify-center rounded-full border-2 shrink-0 transition-colors ${
!isSolo ? 'border-neon-aqua bg-neon-aqua' : 'border-gray-500'
}`}>
{!isSolo && <Check className="w-3.5 h-3.5 text-deep-graphite" />}
</div>
</div>
{/* Emotional tagline */}
<p className={`text-sm ${!isSolo ? 'text-gray-300' : 'text-gray-500'}`}>
Victory is sweeter together. Build a team, share the podium.
</p>
{/* Features */}
<div className="space-y-2">
<div className="flex items-center gap-2 text-xs text-gray-400">
<Check className={`w-3.5 h-3.5 ${!isSolo ? 'text-neon-aqua' : 'text-gray-500'}`} />
<span>Team & driver standings</span>
</div>
<div className="flex items-center gap-2 text-xs text-gray-400">
<Check className={`w-3.5 h-3.5 ${!isSolo ? 'text-neon-aqua' : 'text-gray-500'}`} />
<span>Fixed roster per team</span>
</div>
<div className="flex items-center gap-2 text-xs text-gray-400">
<Check className={`w-3.5 h-3.5 ${!isSolo ? 'text-neon-aqua' : 'text-gray-500'}`} />
<span>Great for endurance & pro-am</span>
</div>
</div>
</button>
{/* Info button */}
<button
ref={teamsInfoRef}
type="button"
onClick={() => setShowTeamsFlyout(true)}
className="absolute top-2 right-2 flex h-6 w-6 items-center justify-center rounded-full text-gray-500 hover:text-neon-aqua hover:bg-neon-aqua/10 transition-colors"
>
<HelpCircle className="w-4 h-4" />
</button>
</div>
{/* Teams Info Flyout */}
<InfoFlyout
isOpen={showTeamsFlyout}
onClose={() => setShowTeamsFlyout(false)}
title="Team Racing Mode"
anchorRef={teamsInfoRef}
>
<div className="space-y-4">
<p className="text-xs text-gray-400">
In team mode, drivers are grouped into fixed teams. Points contribute to
both individual and team standings, creating deeper competition.
</p>
<div className="space-y-2">
<div className="text-[10px] text-gray-500 uppercase tracking-wide">Best For</div>
<ul className="space-y-1.5">
<li className="flex items-start gap-2 text-xs text-gray-400">
<Check className="w-3.5 h-3.5 text-neon-aqua shrink-0 mt-0.5" />
<span>Endurance races with driver swaps</span>
</li>
<li className="flex items-start gap-2 text-xs text-gray-400">
<Check className="w-3.5 h-3.5 text-neon-aqua shrink-0 mt-0.5" />
<span>Pro-Am style competitions</span>
</li>
<li className="flex items-start gap-2 text-xs text-gray-400">
<Check className="w-3.5 h-3.5 text-neon-aqua shrink-0 mt-0.5" />
<span>Larger organized leagues</span>
</li>
</ul>
</div>
</div>
</InfoFlyout>
</div>
{/* Configuration Panel */}
<div className="rounded-xl border border-charcoal-outline bg-gradient-to-b from-iron-gray/50 to-iron-gray/30 p-6 space-y-5">
<div className="flex items-center gap-3">
<div className={`flex h-10 w-10 items-center justify-center rounded-lg ${
isSolo ? 'bg-primary-blue/20' : 'bg-neon-aqua/20'
}`}>
{isSolo ? (
<User className="w-5 h-5 text-primary-blue" />
) : (
<Users2 className="w-5 h-5 text-neon-aqua" />
)}
</div>
<div>
<h3 className="text-sm font-semibold text-white">
{isSolo ? 'Grid size' : 'Team configuration'}
</h3>
<p className="text-xs text-gray-500">
{isSolo
? 'How many drivers can join your championship?'
: 'Configure teams and roster sizes'
}
</p>
</div>
</div>
{/* Solo mode capacity */}
{isSolo && (
<div className="space-y-4">
<div className="space-y-3">
<label className="block text-sm font-medium text-gray-300">
Max drivers
Maximum drivers
</label>
<Input
type="number"
value={structure.maxDrivers ?? 24}
value={structure.maxDrivers ?? gameConstraints.defaultMaxDrivers}
onChange={(e) => handleMaxDriversChange(e.target.value)}
disabled={disabled}
min={1}
max={64}
className="w-32"
min={gameConstraints.minDrivers}
max={gameConstraints.maxDrivers}
className="w-40"
/>
<div className="space-y-2">
<p className="text-xs text-gray-500 flex items-start gap-1.5">
<Info className="w-3 h-3 mt-0.5 shrink-0" />
<span>Typical club leagues use 2030 drivers</span>
</p>
<div className="flex flex-wrap gap-2 text-xs">
<button
type="button"
onClick={() => handleMaxDriversChange('20')}
disabled={disabled}
className="px-2 py-1 rounded bg-iron-gray border border-charcoal-outline text-gray-300 hover:border-primary-blue hover:text-primary-blue transition-colors"
>
Small (20)
</button>
<button
type="button"
onClick={() => handleMaxDriversChange('24')}
disabled={disabled}
className="px-2 py-1 rounded bg-iron-gray border border-charcoal-outline text-gray-300 hover:border-primary-blue hover:text-primary-blue transition-colors"
>
Medium (24)
</button>
<p className="text-xs text-gray-500">
{form.basics.gameId.toUpperCase()} supports up to {gameConstraints.maxDrivers} drivers
</p>
</div>
<div className="space-y-2">
<p className="text-xs text-gray-500">Quick select:</p>
<div className="flex flex-wrap gap-2">
<button
type="button"
onClick={() => handleMaxDriversChange('16')}
disabled={disabled || 16 > gameConstraints.maxDrivers}
className={`px-3 py-2 rounded-lg text-xs font-medium transition-all ${
structure.maxDrivers === 16
? 'bg-primary-blue text-white'
: 'bg-iron-gray border border-charcoal-outline text-gray-300 hover:border-primary-blue hover:text-primary-blue disabled:opacity-40 disabled:cursor-not-allowed'
}`}
>
Compact (16)
</button>
<button
type="button"
onClick={() => handleMaxDriversChange('24')}
disabled={disabled || 24 > gameConstraints.maxDrivers}
className={`px-3 py-2 rounded-lg text-xs font-medium transition-all ${
structure.maxDrivers === 24
? 'bg-primary-blue text-white'
: 'bg-iron-gray border border-charcoal-outline text-gray-300 hover:border-primary-blue hover:text-primary-blue disabled:opacity-40 disabled:cursor-not-allowed'
}`}
>
Standard (24)
</button>
{gameConstraints.maxDrivers >= 30 && (
<button
type="button"
onClick={() => handleMaxDriversChange('30')}
disabled={disabled}
className="px-2 py-1 rounded bg-iron-gray border border-charcoal-outline text-gray-300 hover:border-primary-blue hover:text-primary-blue transition-colors"
className={`px-3 py-2 rounded-lg text-xs font-medium transition-all ${
structure.maxDrivers === 30
? 'bg-primary-blue text-white'
: 'bg-iron-gray border border-charcoal-outline text-gray-300 hover:border-primary-blue hover:text-primary-blue'
}`}
>
Large (30)
Full Grid (30)
</button>
</div>
)}
{gameConstraints.maxDrivers >= 40 && (
<button
type="button"
onClick={() => handleMaxDriversChange('40')}
disabled={disabled}
className={`px-3 py-2 rounded-lg text-xs font-medium transition-all ${
structure.maxDrivers === 40
? 'bg-primary-blue text-white'
: 'bg-iron-gray border border-charcoal-outline text-gray-300 hover:border-primary-blue hover:text-primary-blue'
}`}
>
Large (40)
</button>
)}
{gameConstraints.maxDrivers >= 64 && (
<button
type="button"
onClick={() => handleMaxDriversChange(String(gameConstraints.maxDrivers))}
disabled={disabled}
className={`px-3 py-2 rounded-lg text-xs font-medium transition-all ${
structure.maxDrivers === gameConstraints.maxDrivers
? 'bg-primary-blue text-white'
: 'bg-iron-gray border border-charcoal-outline text-gray-300 hover:border-primary-blue hover:text-primary-blue'
}`}
>
Max ({gameConstraints.maxDrivers})
</button>
)}
</div>
</div>
</div>
</div>
)}
)}
{/* Teams mode capacity */}
{structure.mode === 'fixedTeams' && (
<div className="rounded-lg border border-charcoal-outline bg-iron-gray/30 p-5 space-y-4">
<div className="flex items-start gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary-blue/10 shrink-0">
<Users2 className="w-5 h-5 text-primary-blue" />
</div>
<div className="flex-1">
<h3 className="text-sm font-semibold text-white mb-1">Team structure</h3>
<p className="text-xs text-gray-500">
Configure the team composition and maximum grid size
</p>
</div>
</div>
<div className="space-y-4">
<div className="rounded-lg bg-primary-blue/5 border border-primary-blue/20 p-3">
<p className="text-xs text-gray-300">
<span className="font-medium text-primary-blue">Quick setup:</span> Choose a common configuration
</p>
<div className="flex flex-wrap gap-2 mt-2">
{/* Teams mode capacity */}
{!isSolo && (
<div className="space-y-5">
{/* Quick presets */}
<div className="space-y-3">
<p className="text-xs text-gray-500">Popular configurations:</p>
<div className="flex flex-wrap gap-2">
<button
type="button"
onClick={() => {
@@ -251,9 +597,13 @@ export function LeagueStructureSection({
handleDriversPerTeamChange('2');
}}
disabled={disabled}
className="px-3 py-1.5 rounded bg-iron-gray border border-charcoal-outline text-xs text-gray-300 hover:border-primary-blue hover:text-primary-blue transition-colors"
className={`px-3 py-2 rounded-lg text-xs font-medium transition-all ${
structure.maxTeams === 10 && structure.driversPerTeam === 2
? 'bg-neon-aqua text-deep-graphite'
: 'bg-iron-gray border border-charcoal-outline text-gray-300 hover:border-neon-aqua hover:text-neon-aqua'
}`}
>
10 teams × 2 drivers (20 grid)
10 × 2 (20 grid)
</button>
<button
type="button"
@@ -262,9 +612,13 @@ export function LeagueStructureSection({
handleDriversPerTeamChange('2');
}}
disabled={disabled}
className="px-3 py-1.5 rounded bg-iron-gray border border-charcoal-outline text-xs text-gray-300 hover:border-primary-blue hover:text-primary-blue transition-colors"
className={`px-3 py-2 rounded-lg text-xs font-medium transition-all ${
structure.maxTeams === 12 && structure.driversPerTeam === 2
? 'bg-neon-aqua text-deep-graphite'
: 'bg-iron-gray border border-charcoal-outline text-gray-300 hover:border-neon-aqua hover:text-neon-aqua'
}`}
>
12 teams × 2 drivers (24 grid)
12 × 2 (24 grid)
</button>
<button
type="button"
@@ -273,17 +627,37 @@ export function LeagueStructureSection({
handleDriversPerTeamChange('3');
}}
disabled={disabled}
className="px-3 py-1.5 rounded bg-iron-gray border border-charcoal-outline text-xs text-gray-300 hover:border-primary-blue hover:text-primary-blue transition-colors"
className={`px-3 py-2 rounded-lg text-xs font-medium transition-all ${
structure.maxTeams === 8 && structure.driversPerTeam === 3
? 'bg-neon-aqua text-deep-graphite'
: 'bg-iron-gray border border-charcoal-outline text-gray-300 hover:border-neon-aqua hover:text-neon-aqua'
}`}
>
8 teams × 3 drivers (24 grid)
8 × 3 (24 grid)
</button>
<button
type="button"
onClick={() => {
handleMaxTeamsChange('15');
handleDriversPerTeamChange('2');
}}
disabled={disabled}
className={`px-3 py-2 rounded-lg text-xs font-medium transition-all ${
structure.maxTeams === 15 && structure.driversPerTeam === 2
? 'bg-neon-aqua text-deep-graphite'
: 'bg-iron-gray border border-charcoal-outline text-gray-300 hover:border-neon-aqua hover:text-neon-aqua'
}`}
>
15 × 2 (30 grid)
</button>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{/* Manual configuration */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 pt-2 border-t border-charcoal-outline/50">
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-300">
Max teams
Teams
</label>
<Input
type="number"
@@ -292,17 +666,13 @@ export function LeagueStructureSection({
disabled={disabled}
min={1}
max={32}
className="w-32"
className="w-full"
/>
<p className="text-xs text-gray-500 flex items-start gap-1.5">
<Info className="w-3 h-3 mt-0.5 shrink-0" />
<span>Total competing teams</span>
</p>
</div>
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-300">
Drivers per team
Drivers / team
</label>
<Input
type="number"
@@ -311,36 +681,27 @@ export function LeagueStructureSection({
disabled={disabled}
min={1}
max={6}
className="w-32"
className="w-full"
/>
<p className="text-xs text-gray-500 flex items-start gap-1.5">
<Info className="w-3 h-3 mt-0.5 shrink-0" />
<span>Common: 23 drivers</span>
</p>
</div>
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-300">
Total grid size
Total grid
</label>
<div className="flex items-center gap-2">
<Input
type="number"
value={structure.maxDrivers ?? 0}
disabled
className="w-32"
/>
<div className="text-xs text-gray-500">drivers</div>
<div className={`flex items-center justify-center h-10 rounded-lg border ${
!isSolo ? 'bg-neon-aqua/10 border-neon-aqua/30' : 'bg-iron-gray border-charcoal-outline'
}`}>
<span className={`text-lg font-bold ${!isSolo ? 'text-neon-aqua' : 'text-gray-400'}`}>
{structure.maxDrivers ?? 0}
</span>
<span className="text-xs text-gray-500 ml-1">drivers</span>
</div>
<p className="text-xs text-gray-500 flex items-start gap-1.5">
<Info className="w-3 h-3 mt-0.5 shrink-0" />
<span>Auto-calculated from teams × drivers</span>
</p>
</div>
</div>
</div>
</div>
)}
)}
</div>
</div>
);
}