Files
bscscore/src/components/NewGame.tsx
Frank Schwenk 301d5b131c feat(ux): auto-advance on game type and race selection
- Step 4: selecting a game type immediately advances to step 5
- Step 5: selecting Endlos or a quick-pick number immediately finalizes race-to

Improves flow by removing an extra click. No changes to validation.

Refs #26
2025-10-30 10:41:38 +01:00

796 lines
27 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { h } from 'preact';
import { useState, useEffect, useRef } from 'preact/hooks';
import styles from './NewGame.module.css';
import modalStyles from './PlayerSelectModal.module.css';
import {
UI_CONSTANTS,
WIZARD_STEPS,
GAME_TYPE_OPTIONS,
RACE_TO_QUICK_PICKS,
RACE_TO_DEFAULT,
RACE_TO_INFINITY,
ERROR_MESSAGES,
ARIA_LABELS,
FORM_CONFIG,
ERROR_STYLES
} from '../utils/constants';
interface PlayerSelectModalProps {
players: string[];
onSelect: (player: string) => void;
onClose: () => void;
}
export const PlayerSelectModal = ({ players, onSelect, onClose }: PlayerSelectModalProps) => (
<div className={modalStyles.modalOverlay} onClick={onClose}>
<div className={modalStyles.modalContent} onClick={e => e.stopPropagation()}>
<div className={modalStyles.modalHeader}>
<h3>Alle Spieler</h3>
<button className={modalStyles.closeButton} onClick={onClose}>×</button>
</div>
<div className={modalStyles.playerList}>
{players.map(player => (
<button key={player} className={modalStyles.playerItem} onClick={() => onSelect(player)}>
{player}
</button>
))}
</div>
</div>
</div>
);
interface PlayerStepProps {
playerNameHistory: string[];
onNext: (name: string) => void;
onCancel: () => void;
initialValue?: string;
}
/**
* Player 1 input step for multi-step game creation wizard.
*/
const Player1Step = ({ playerNameHistory, onNext, onCancel, initialValue = '' }: PlayerStepProps) => {
const [player1, setPlayer1] = useState(initialValue);
const [error, setError] = useState<string | null>(null);
const [filteredNames, setFilteredNames] = useState(playerNameHistory);
const [isModalOpen, setIsModalOpen] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
const el = inputRef.current;
if (el) {
el.focus();
const end = el.value.length;
try {
el.setSelectionRange(end, end);
} catch {}
}
}, []);
useEffect(() => {
if (!player1) {
setFilteredNames(playerNameHistory);
} else {
setFilteredNames(
playerNameHistory.filter(name =>
name.toLowerCase().includes(player1.toLowerCase())
)
);
}
}, [player1, playerNameHistory]);
const handleSubmit = (e: Event) => {
e.preventDefault();
const trimmedName = player1.trim();
if (!trimmedName) {
setError(ERROR_MESSAGES.PLAYER1_REQUIRED);
if (inputRef.current) {
inputRef.current.focus();
inputRef.current.setAttribute('aria-invalid', 'true');
}
return;
}
if (trimmedName.length > FORM_CONFIG.MAX_PLAYER_NAME_LENGTH) {
setError(`Spielername darf maximal ${FORM_CONFIG.MAX_PLAYER_NAME_LENGTH} Zeichen lang sein`);
if (inputRef.current) {
inputRef.current.focus();
inputRef.current.setAttribute('aria-invalid', 'true');
}
return;
}
setError(null);
if (inputRef.current) {
inputRef.current.setAttribute('aria-invalid', 'false');
}
onNext(trimmedName);
};
const handleQuickPick = (name: string) => {
setError(null);
onNext(name);
};
const handleModalSelect = (name: string) => {
setIsModalOpen(false);
handleQuickPick(name);
};
const handleClear = () => {
setPlayer1('');
setError(null);
if (inputRef.current) inputRef.current.focus();
};
return (
<form className={styles['new-game-form']} onSubmit={handleSubmit} aria-label="Spieler 1 Eingabe" autoComplete="off">
<div className={styles['screen-title']}>Neues Spiel Schritt {WIZARD_STEPS.PLAYER1}/{UI_CONSTANTS.TOTAL_WIZARD_STEPS}</div>
<div className={styles['progress-indicator']} style={{ marginBottom: UI_CONSTANTS.MARGIN_BOTTOM_MEDIUM }}>
<span className={styles['progress-dot'] + ' ' + styles['active']} />
<span className={styles['progress-dot']} />
<span className={styles['progress-dot']} />
<span className={styles['progress-dot']} />
<span className={styles['progress-dot']} />
</div>
<div className={styles['player-input'] + ' ' + styles['player1-input']} style={{ marginBottom: UI_CONSTANTS.MARGIN_BOTTOM_LARGE, position: 'relative' }}>
<label htmlFor="player1-input" style={{ fontSize: UI_CONSTANTS.LABEL_FONT_SIZE, fontWeight: 600 }}>Spieler 1</label>
<div style={{ position: 'relative', width: '100%' }}>
<input
id="player1-input"
className={styles['name-input']}
placeholder="Name Spieler 1"
value={player1}
onInput={(e: Event) => {
const target = e.target as HTMLInputElement;
const value = target.value;
setPlayer1(value);
// Real-time validation feedback
if (value.length > FORM_CONFIG.MAX_PLAYER_NAME_LENGTH) {
setError(`Spielername darf maximal ${FORM_CONFIG.MAX_PLAYER_NAME_LENGTH} Zeichen lang sein`);
target.setAttribute('aria-invalid', 'true');
} else if (value.trim() && error) {
setError(null);
target.setAttribute('aria-invalid', 'false');
}
}}
autoComplete="off"
aria-label="Name Spieler 1"
aria-describedby="player1-help"
style={{
fontSize: UI_CONSTANTS.INPUT_FONT_SIZE,
minHeight: UI_CONSTANTS.INPUT_MIN_HEIGHT,
marginTop: 12,
marginBottom: 12,
width: '100%',
paddingRight: UI_CONSTANTS.INPUT_PADDING_RIGHT
}}
ref={inputRef}
/>
<div id="player1-help" className="sr-only">
Geben Sie den Namen für Spieler 1 ein. Maximal {FORM_CONFIG.MAX_PLAYER_NAME_LENGTH} Zeichen erlaubt.
</div>
{player1.length > FORM_CONFIG.CHARACTER_COUNT_WARNING_THRESHOLD && (
<div style={{
fontSize: '0.875rem',
color: player1.length > FORM_CONFIG.MAX_PLAYER_NAME_LENGTH ? '#f44336' : '#ff9800',
marginTop: '4px',
textAlign: 'right'
}}>
{player1.length}/{FORM_CONFIG.MAX_PLAYER_NAME_LENGTH} Zeichen
</div>
)}
{player1 && (
<button
type="button"
className={styles['clear-input-btn']}
aria-label="Feld leeren"
onClick={handleClear}
style={{
position: 'absolute',
right: 8,
top: '50%',
transform: 'translateY(-50%)',
background: 'none',
border: 'none',
cursor: 'pointer',
fontSize: 24,
color: '#aaa',
padding: 0,
zIndex: 2
}}
tabIndex={0}
>
{/* Unicode heavy multiplication X */}
×
</button>
)}
</div>
{filteredNames.length > 0 && (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 12, marginTop: 8 }}>
{filteredNames.slice(0, UI_CONSTANTS.MAX_QUICK_PICKS).map((name, idx) => (
<button
type="button"
key={name + idx}
className={styles['quick-pick-btn']}
style={{
fontSize: UI_CONSTANTS.QUICK_PICK_FONT_SIZE,
padding: UI_CONSTANTS.QUICK_PICK_PADDING,
borderRadius: 8,
background: '#333',
color: '#fff',
border: 'none',
cursor: 'pointer'
}}
onClick={() => handleQuickPick(name)}
aria-label={ARIA_LABELS.QUICK_PICK(name)}
>
{name}
</button>
))}
{playerNameHistory.length > UI_CONSTANTS.MAX_QUICK_PICKS && (
<button
type="button"
className={styles['quick-pick-btn']}
style={{
fontSize: UI_CONSTANTS.QUICK_PICK_FONT_SIZE,
padding: UI_CONSTANTS.QUICK_PICK_PADDING,
borderRadius: 8,
background: '#333',
color: '#fff',
border: 'none',
cursor: 'pointer'
}}
onClick={() => setIsModalOpen(true)}
aria-label={ARIA_LABELS.SHOW_MORE_PLAYERS}
>
...
</button>
)}
</div>
)}
</div>
{error && (
<div
className={styles['validation-error']}
style={{
marginBottom: 16,
...ERROR_STYLES.CONTAINER
}}
role="alert"
aria-live="polite"
>
<span style={ERROR_STYLES.ICON}></span>
{error}
</div>
)}
{isModalOpen && (
<PlayerSelectModal
players={playerNameHistory}
onSelect={handleModalSelect}
onClose={() => setIsModalOpen(false)}
/>
)}
<div className={styles['arrow-nav']} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 48 }}>
<button
type="button"
className={styles['arrow-btn']}
aria-label="Zurück"
onClick={onCancel}
style={{ fontSize: 48, width: 80, height: 80, borderRadius: '50%', background: '#222', color: '#fff', border: 'none', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', cursor: 'pointer' }}
>
{/* Unicode left arrow */}
&#8592;
</button>
<button
type="submit"
className={styles['arrow-btn']}
aria-label="Weiter"
style={{ fontSize: 48, width: 80, height: 80, borderRadius: '50%', background: '#222', color: '#fff', border: 'none', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', cursor: 'pointer' }}
>
{/* Unicode right arrow */}
&#8594;
</button>
</div>
</form>
);
};
/**
* Player 2 input step for multi-step game creation wizard.
*/
const Player2Step = ({ playerNameHistory, onNext, onCancel, initialValue = '' }: PlayerStepProps) => {
const [player2, setPlayer2] = useState(initialValue);
const [error, setError] = useState<string | null>(null);
const [filteredNames, setFilteredNames] = useState(playerNameHistory);
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
const el = inputRef.current;
if (el) {
el.focus();
const end = el.value.length;
try {
el.setSelectionRange(end, end);
} catch {}
}
}, []);
useEffect(() => {
if (!player2) {
setFilteredNames(playerNameHistory);
} else {
setFilteredNames(
playerNameHistory.filter(name =>
name.toLowerCase().includes(player2.toLowerCase())
)
);
}
}, [player2, playerNameHistory]);
const handleSubmit = (e: Event) => {
e.preventDefault();
if (!player2.trim()) {
setError('Bitte Namen für Spieler 2 eingeben');
return;
}
setError(null);
onNext(player2.trim());
};
const handleQuickPick = (name: string) => {
setError(null);
onNext(name);
};
const handleClear = () => {
setPlayer2('');
setError(null);
if (inputRef.current) inputRef.current.focus();
};
return (
<form className={styles['new-game-form']} onSubmit={handleSubmit} aria-label="Spieler 2 Eingabe" autoComplete="off">
<div className={styles['screen-title']}>Neues Spiel Schritt 2/5</div>
<div className={styles['progress-indicator']} style={{ marginBottom: 24 }}>
<span className={styles['progress-dot']} />
<span className={styles['progress-dot'] + ' ' + styles['active']} />
<span className={styles['progress-dot']} />
<span className={styles['progress-dot']} />
<span className={styles['progress-dot']} />
</div>
<div className={styles['player-input'] + ' ' + styles['player2-input']} style={{ marginBottom: 32, position: 'relative' }}>
<label htmlFor="player2-input" style={{ fontSize: '1.3rem', fontWeight: 600 }}>Spieler 2</label>
<div style={{ position: 'relative', width: '100%' }}>
<input
id="player2-input"
className={styles['name-input']}
placeholder="Name Spieler 2"
value={player2}
onInput={(e: Event) => {
const target = e.target as HTMLInputElement;
setPlayer2(target.value);
}}
autoComplete="off"
aria-label="Name Spieler 2"
style={{ fontSize: '1.2rem', minHeight: 48, marginTop: 12, marginBottom: 12, width: '100%', paddingRight: 44 }}
ref={inputRef}
/>
{player2 && (
<button
type="button"
className={styles['clear-input-btn']}
aria-label="Feld leeren"
onClick={handleClear}
style={{
position: 'absolute',
right: 8,
top: '50%',
transform: 'translateY(-50%)',
background: 'none',
border: 'none',
cursor: 'pointer',
fontSize: 24,
color: '#aaa',
padding: 0,
zIndex: 2
}}
tabIndex={0}
>
×
</button>
)}
</div>
{filteredNames.length > 0 && (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 12, marginTop: 8 }}>
{filteredNames.slice(0, 10).map((name, idx) => (
<button
type="button"
key={name + idx}
className={styles['quick-pick-btn']}
style={{ fontSize: '1.1rem', padding: '12px 20px', borderRadius: 8, background: '#333', color: '#fff', border: 'none', cursor: 'pointer' }}
onClick={() => handleQuickPick(name)}
aria-label={`Schnellauswahl: ${name}`}
>
{name}
</button>
))}
</div>
)}
</div>
{error && <div className={styles['validation-error']} style={{ marginBottom: 16 }}>{error}</div>}
<div className={styles['arrow-nav']} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 48 }}>
<button
type="button"
className={styles['arrow-btn']}
aria-label="Zurück"
onClick={onCancel}
style={{ fontSize: 48, width: 80, height: 80, borderRadius: '50%', background: '#222', color: '#fff', border: 'none', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', cursor: 'pointer' }}
>
&#8592;
</button>
<button
type="submit"
className={styles['arrow-btn']}
aria-label="Weiter"
style={{ fontSize: 48, width: 80, height: 80, borderRadius: '50%', background: '#222', color: '#fff', border: 'none', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', cursor: 'pointer' }}
>
&#8594;
</button>
</div>
</form>
);
};
/**
* Player 3 input step for multi-step game creation wizard.
*/
const Player3Step = ({ playerNameHistory, onNext, onCancel, initialValue = '' }: PlayerStepProps) => {
const [player3, setPlayer3] = useState(initialValue);
const [filteredNames, setFilteredNames] = useState(playerNameHistory);
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
const el = inputRef.current;
if (el) {
el.focus();
const end = el.value.length;
try {
el.setSelectionRange(end, end);
} catch {}
}
}, []);
useEffect(() => {
if (!player3) {
setFilteredNames(playerNameHistory);
} else {
setFilteredNames(
playerNameHistory.filter(name =>
name.toLowerCase().includes(player3.toLowerCase())
)
);
}
}, [player3, playerNameHistory]);
const handleSubmit = (e: Event) => {
e.preventDefault();
// Player 3 is optional, so always allow submission
onNext(player3.trim());
};
const handleQuickPick = (name: string) => {
onNext(name);
};
const handleClear = () => {
setPlayer3('');
if (inputRef.current) inputRef.current.focus();
};
const handleSkip = (e: Event) => {
e.preventDefault();
onNext('');
};
return (
<form className={styles['new-game-form']} onSubmit={handleSubmit} aria-label="Spieler 3 Eingabe" autoComplete="off">
<div className={styles['screen-title']}>Neues Spiel Schritt 3/5</div>
<div className={styles['progress-indicator']} style={{ marginBottom: 24 }}>
<span className={styles['progress-dot']} />
<span className={styles['progress-dot']} />
<span className={styles['progress-dot'] + ' ' + styles['active']} />
<span className={styles['progress-dot']} />
<span className={styles['progress-dot']} />
</div>
<div className={styles['player-input'] + ' ' + styles['player3-input']} style={{ marginBottom: 32, position: 'relative' }}>
<label htmlFor="player3-input" style={{ fontSize: '1.3rem', fontWeight: 600 }}>Spieler 3 (optional)</label>
<div style={{ position: 'relative', width: '100%' }}>
<input
id="player3-input"
className={styles['name-input']}
placeholder="Name Spieler 3 (optional)"
value={player3}
onInput={(e: Event) => {
const target = e.target as HTMLInputElement;
setPlayer3(target.value);
}}
autoComplete="off"
aria-label="Name Spieler 3"
style={{ fontSize: '1.2rem', minHeight: 48, marginTop: 12, marginBottom: 12, width: '100%', paddingRight: 44 }}
ref={inputRef}
/>
{player3 && (
<button
type="button"
className={styles['clear-input-btn']}
aria-label="Feld leeren"
onClick={handleClear}
style={{
position: 'absolute',
right: 8,
top: '50%',
transform: 'translateY(-50%)',
background: 'none',
border: 'none',
cursor: 'pointer',
fontSize: 24,
color: '#aaa',
padding: 0,
zIndex: 2
}}
tabIndex={0}
>
×
</button>
)}
</div>
{filteredNames.length > 0 && (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 12, marginTop: 8 }}>
{filteredNames.slice(0, 10).map((name, idx) => (
<button
type="button"
key={name + idx}
className={styles['quick-pick-btn']}
style={{ fontSize: '1.1rem', padding: '12px 20px', borderRadius: 8, background: '#333', color: '#fff', border: 'none', cursor: 'pointer' }}
onClick={() => handleQuickPick(name)}
aria-label={`Schnellauswahl: ${name}`}
>
{name}
</button>
))}
</div>
)}
</div>
<div className={styles['arrow-nav']} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 48 }}>
<button
type="button"
className={styles['arrow-btn']}
aria-label="Zurück"
onClick={onCancel}
style={{ fontSize: 48, width: 80, height: 80, borderRadius: '50%', background: '#222', color: '#fff', border: 'none', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', cursor: 'pointer' }}
>
&#8592;
</button>
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
<button
type="button"
onClick={handleSkip}
className={styles['quick-pick-btn']}
style={{ fontSize: '1.1rem', padding: '12px 20px', borderRadius: 8, background: '#333', color: '#fff', border: 'none', cursor: 'pointer' }}
>
Überspringen
</button>
<button
type="submit"
className={styles['arrow-btn']}
aria-label="Weiter"
style={{ fontSize: 48, width: 80, height: 80, borderRadius: '50%', background: '#222', color: '#fff', border: 'none', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', cursor: 'pointer' }}
>
&#8594;
</button>
</div>
</div>
</form>
);
};
interface GameTypeStepProps {
onNext: (type: string) => void;
onCancel: () => void;
initialValue?: string;
}
/**
* Game Type selection step for multi-step game creation wizard.
*/
const GameTypeStep = ({ onNext, onCancel, initialValue = '' }: GameTypeStepProps) => {
const [gameType, setGameType] = useState(initialValue);
const gameTypes = ['8-Ball', '9-Ball', '10-Ball'];
const handleSelect = (selectedType: string) => {
setGameType(selectedType);
// Auto-advance to next step on selection
onNext(selectedType);
};
const handleSubmit = (e: Event) => {
e.preventDefault();
if (gameType) {
onNext(gameType);
}
};
return (
<form className={styles['new-game-form']} onSubmit={handleSubmit} aria-label="Spielart auswählen">
<div className={styles['screen-title']}>Neues Spiel Schritt 4/5</div>
<div className={styles['progress-indicator']} style={{ marginBottom: 24 }}>
<span className={styles['progress-dot']} />
<span className={styles['progress-dot']} />
<span className={styles['progress-dot']} />
<span className={styles['progress-dot'] + ' ' + styles['active']} />
<span className={styles['progress-dot']} />
</div>
<div className={styles['game-type-selection']}>
{gameTypes.map(type => (
<button
key={type}
type="button"
className={`${styles['game-type-btn']} ${gameType === type ? styles.selected : ''}`}
onClick={() => handleSelect(type)}
>
{type}
</button>
))}
</div>
<div className={styles['arrow-nav']} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 48 }}>
<button
type="button"
className={styles['arrow-btn']}
aria-label="Zurück"
onClick={onCancel}
style={{ fontSize: 48, width: 80, height: 80, borderRadius: '50%', background: '#222', color: '#fff', border: 'none', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', cursor: 'pointer' }}
>
{/* Unicode left arrow */}
&#8592;
</button>
<button
type="submit"
className={styles['arrow-btn']}
aria-label="Weiter"
disabled={!gameType}
style={{
fontSize: 48,
width: 80,
height: 80,
borderRadius: '50%',
background: '#222',
color: '#fff',
border: 'none',
boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
cursor: 'pointer',
opacity: !gameType ? 0.5 : 1,
}}
>
{/* Unicode right arrow */}
&#8594;
</button>
</div>
</form>
);
};
interface RaceToStepProps {
onNext: (raceTo: string | number) => void;
onCancel: () => void;
initialValue?: string | number;
gameType?: string;
}
/**
* Race To selection step for multi-step game creation wizard.
*/
const RaceToStep = ({ onNext, onCancel, initialValue = '', gameType }: RaceToStepProps) => {
const quickPicks = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const defaultValue = 5;
const [raceTo, setRaceTo] = useState<string | number>(initialValue !== '' ? initialValue : defaultValue);
useEffect(() => {
if ((initialValue === '' || initialValue === undefined) && raceTo !== defaultValue) {
setRaceTo(defaultValue);
}
if (initialValue !== '' && initialValue !== undefined && initialValue !== raceTo) {
setRaceTo(initialValue);
}
}, [gameType, initialValue, defaultValue]);
const handleQuickPick = (value: number) => {
// For endlos (endless) games, use Infinity to prevent automatic completion
const selected = value === 0 ? 'Infinity' : value;
setRaceTo(selected);
// Auto-advance to the next step (finalize) when a quick pick is chosen
const raceToValue = selected === 'Infinity' ? Infinity : (parseInt(String(selected), 10) || 0);
onNext(raceToValue);
};
const handleInputChange = (e: Event) => {
const target = e.target as HTMLInputElement;
setRaceTo(target.value);
};
const handleSubmit = (e: Event) => {
e.preventDefault();
// Handle Infinity for endlos games, otherwise parse as integer
const raceToValue = raceTo === 'Infinity' ? Infinity : (parseInt(String(raceTo), 10) || 0);
onNext(raceToValue);
};
return (
<form className={styles['new-game-form']} onSubmit={handleSubmit} aria-label="Race To auswählen">
<div className={styles['screen-title']}>Neues Spiel Schritt 5/5</div>
<div className={styles['progress-indicator']} style={{ marginBottom: 24 }}>
<span className={styles['progress-dot']} />
<span className={styles['progress-dot']} />
<span className={styles['progress-dot']} />
<span className={styles['progress-dot']} />
<span className={styles['progress-dot'] + ' ' + styles['active']} />
</div>
<div className={styles['endlos-container']}>
<button
type="button"
className={`${styles['race-to-btn']} ${styles['endlos-btn']} ${raceTo === 'Infinity' ? styles.selected : ''}`}
onClick={() => handleQuickPick(0)}
>
Endlos
</button>
</div>
<div className={styles['race-to-selection']}>
{quickPicks.map(value => (
<button
key={value}
type="button"
className={`${styles['race-to-btn']} ${parseInt(raceTo, 10) === value ? styles.selected : ''}`}
onClick={() => handleQuickPick(value)}
>
{value}
</button>
))}
</div>
<div className={styles['custom-race-to']}>
<input
type="number"
pattern="[0-9]*"
value={raceTo}
onInput={handleInputChange}
className={styles['name-input']}
placeholder="manuelle Eingabe"
/>
</div>
<div className={styles['arrow-nav']} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 48 }}>
<button
type="button"
className={styles['arrow-btn']}
aria-label="Zurück"
onClick={onCancel}
style={{ fontSize: 48, width: 80, height: 80, borderRadius: '50%', background: '#222', color: '#fff', border: 'none', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', cursor: 'pointer' }}
>
{/* Unicode left arrow */}
&#8592;
</button>
<button
type="submit"
className={styles['arrow-btn']}
aria-label="Fertigstellen"
style={{ fontSize: 48, width: 80, height: 80, borderRadius: '50%', background: '#222', color: '#fff', border: 'none', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', cursor: 'pointer' }}
>
{/* Unicode checkmark */}
&#10003;
</button>
</div>
</form>
);
};
export { Player1Step, Player2Step, Player3Step, GameTypeStep, RaceToStep };