Init: projet by Cursor

This commit is contained in:
Michael
2025-07-25 10:31:54 +02:00
commit 4fa29f06be
28 changed files with 5499 additions and 0 deletions

23
.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

180
README.md Normal file
View File

@@ -0,0 +1,180 @@
# Initial D Arcade Stage - Site de Classement
Un site web moderne et responsive reproduisant l'interface du site officiel Initial D Arcade Stage, créé avec React et TypeScript.
## 🏁 Fonctionnalités
- **Page d'accueil** : Présentation du jeu avec design inspiré de l'univers Initial D
- **Page de classement** : Tableau des meilleurs temps avec filtres et onglets
- **Design responsive** : Interface adaptée à tous les écrans
- **Animations fluides** : Utilisation de Framer Motion pour les transitions
- **Thème sombre** : Interface moderne avec couleurs d'Initial D
- **Navigation intuitive** : Menu de navigation avec indicateurs visuels
## 🚗 Technologies Utilisées
- **React 18** : Framework JavaScript pour l'interface utilisateur
- **TypeScript** : Typage statique pour une meilleure maintenabilité
- **Material-UI (MUI)** : Composants UI modernes et personnalisables
- **Framer Motion** : Animations et transitions fluides
- **React Router** : Navigation entre les pages
- **Emotion** : Styling CSS-in-JS
## 🎨 Design
Le site reprend l'esthétique du site officiel Initial D avec :
- Couleurs principales : Orange (#ff6b35) et noir (#0a0a0a)
- Police Orbitron pour les titres
- Effets de lueur et animations
- Interface sombre et moderne
- Icônes Material Design
## 📱 Pages
### Page d'Accueil
- Hero section avec titre animé
- Section des caractéristiques du jeu
- Call-to-action vers le classement
- Design responsive avec animations
### Page de Classement
- Tableau des meilleurs temps
- Filtres par circuit
- Onglets pour différents types de classement
- Statistiques en temps réel
- Avatars et badges pour les joueurs
## 🚀 Installation et Démarrage
1. **Cloner le projet**
```bash
git clone [url-du-repo]
cd initiald-ranking
```
2. **Installer les dépendances**
```bash
npm install
```
3. **Démarrer le serveur de développement**
```bash
npm start
```
4. **Ouvrir dans le navigateur**
```
http://localhost:3000
```
## 📦 Scripts Disponibles
- `npm start` : Démarre le serveur de développement
- `npm run build` : Construit l'application pour la production
- `npm test` : Lance les tests
- `npm run eject` : Éjecte la configuration (irréversible)
## 🏗️ Structure du Projet
```
src/
├── components/ # Composants réutilisables
│ └── Header.tsx # En-tête avec navigation
├── pages/ # Pages de l'application
│ ├── Home.tsx # Page d'accueil
│ └── Ranking.tsx # Page de classement
├── types/ # Définitions TypeScript
│ └── index.ts # Interfaces et types
├── assets/ # Ressources statiques
├── App.tsx # Composant principal
├── index.tsx # Point d'entrée
└── index.css # Styles globaux
```
## 🎯 Fonctionnalités Principales
### Navigation
- Menu de navigation responsive
- Indicateurs visuels pour la page active
- Logo animé avec icône de voiture
### Classement
- Données simulées de joueurs Initial D
- Filtrage par circuit
- Onglets pour différents vues
- Statistiques en temps réel
### Design
- Thème sombre cohérent
- Animations fluides
- Effets de survol
- Typographie optimisée
## 🎨 Personnalisation
### Couleurs
Les couleurs principales peuvent être modifiées dans :
- `src/App.tsx` : Thème Material-UI
- `src/App.css` : Variables CSS personnalisées
### Données
Les données de classement sont simulées dans :
- `src/pages/Ranking.tsx` : Tableau `rankingData`
### Styles
Les styles peuvent être personnalisés dans :
- `src/App.css` : Styles globaux
- `src/index.css` : Reset CSS et styles de base
## 📱 Responsive Design
Le site est entièrement responsive avec :
- Breakpoints Material-UI
- Grille CSS adaptative
- Typographie responsive
- Navigation mobile optimisée
## 🔧 Configuration
### Variables d'Environnement
Créer un fichier `.env` pour les variables d'environnement :
```
REACT_APP_API_URL=your-api-url
REACT_APP_VERSION=1.0.0
```
### Build de Production
```bash
npm run build
```
## 🚀 Déploiement
Le projet peut être déployé sur :
- **Netlify** : Drag & drop du dossier `build`
- **Vercel** : Connexion GitHub automatique
- **GitHub Pages** : Utilisation de `gh-pages`
- **Firebase Hosting** : Configuration Firebase
## 📄 Licence
Ce projet est créé à des fins éducatives et de démonstration. Initial D est une propriété de SEGA.
## 🤝 Contribution
Les contributions sont les bienvenues ! N'hésitez pas à :
1. Fork le projet
2. Créer une branche feature
3. Commiter vos changements
4. Pousser vers la branche
5. Ouvrir une Pull Request
## 📞 Support
Pour toute question ou problème :
- Ouvrir une issue sur GitHub
- Contacter l'équipe de développement
---
**Initial D Arcade Stage** - Reproduit avec ❤️ en React

44
index.html Normal file
View File

@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#ff6b35" />
<meta
name="description"
content="Initial D Arcade Stage - Site de classement officiel. Consultez les meilleurs temps des pilotes du monde entier sur les circuits légendaires du Japon."
/>
<meta name="keywords" content="Initial D, Arcade Stage, course, automobile, classement, Japon, circuits, voitures" />
<meta name="author" content="Initial D Team" />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://initiald-ranking.com/" />
<meta property="og:title" content="Initial D Arcade Stage - Classement Mondial" />
<meta property="og:description" content="Découvrez les meilleurs temps des pilotes Initial D sur les circuits légendaires du Japon." />
<meta property="og:image" content="/og-image.jpg" />
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content="https://initiald-ranking.com/" />
<meta property="twitter:title" content="Initial D Arcade Stage - Classement Mondial" />
<meta property="twitter:description" content="Découvrez les meilleurs temps des pilotes Initial D sur les circuits légendaires du Japon." />
<meta property="twitter:image" content="/og-image.jpg" />
<link rel="apple-touch-icon" href="/logo192.png" />
<link rel="manifest" href="/manifest.json" />
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;600;700;900&display=swap" rel="stylesheet">
<title>Initial D Arcade Stage - Classement Mondial</title>
</head>
<body>
<noscript>Vous devez activer JavaScript pour utiliser cette application.</noscript>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

2994
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

55
package.json Normal file
View File

@@ -0,0 +1,55 @@
{
"name": "initiald-ranking",
"version": "0.1.0",
"private": true,
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.2.0",
"@mui/material": "^7.2.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^20.0.0",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"framer-motion": "^12.23.9",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router-dom": "^7.7.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
},
"scripts": {
"dev": "vite",
"start": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@vitejs/plugin-react": "^4.7.0",
"vite": "^6.3.5"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

43
public/index.html Normal file
View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#ff6b35" />
<meta
name="description"
content="Initial D Arcade Stage - Site de classement officiel. Consultez les meilleurs temps des pilotes du monde entier sur les circuits légendaires du Japon."
/>
<meta name="keywords" content="Initial D, Arcade Stage, course, automobile, classement, Japon, circuits, voitures" />
<meta name="author" content="Initial D Team" />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://initiald-ranking.com/" />
<meta property="og:title" content="Initial D Arcade Stage - Classement Mondial" />
<meta property="og:description" content="Découvrez les meilleurs temps des pilotes Initial D sur les circuits légendaires du Japon." />
<meta property="og:image" content="%PUBLIC_URL%/og-image.jpg" />
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content="https://initiald-ranking.com/" />
<meta property="twitter:title" content="Initial D Arcade Stage - Classement Mondial" />
<meta property="twitter:description" content="Découvrez les meilleurs temps des pilotes Initial D sur les circuits légendaires du Japon." />
<meta property="twitter:image" content="%PUBLIC_URL%/og-image.jpg" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;600;700;900&display=swap" rel="stylesheet">
<title>Initial D Arcade Stage - Classement Mondial</title>
</head>
<body>
<noscript>Vous devez activer JavaScript pour utiliser cette application.</noscript>
<div id="root"></div>
</body>
</html>

BIN
public/logo192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
public/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

30
public/manifest.json Normal file
View File

@@ -0,0 +1,30 @@
{
"short_name": "Initial D",
"name": "Initial D Arcade Stage - Classement Mondial",
"description": "Site de classement officiel Initial D Arcade Stage. Consultez les meilleurs temps des pilotes du monde entier.",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#ff6b35",
"background_color": "#0a0a0a",
"orientation": "portrait-primary",
"categories": ["games", "sports", "entertainment"],
"lang": "fr",
"dir": "ltr"
}

3
public/robots.txt Normal file
View File

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

187
src/App.css Normal file
View File

@@ -0,0 +1,187 @@
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;600;700;900&display=swap');
.App {
text-align: center;
min-height: 100vh;
background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 50%, #0a0a0a 100%);
}
/* Scrollbar personnalisée */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #1a1a1a;
}
::-webkit-scrollbar-thumb {
background: #ff6b35;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #e55a2b;
}
/* Animations personnalisées */
@keyframes glow {
0%, 100% {
box-shadow: 0 0 5px rgba(255, 107, 53, 0.5);
}
50% {
box-shadow: 0 0 20px rgba(255, 107, 53, 0.8);
}
}
@keyframes pulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
}
.glow-effect {
animation: glow 2s ease-in-out infinite;
}
.pulse-effect {
animation: pulse 2s ease-in-out infinite;
}
/* Styles pour les tableaux */
.ranking-table {
border-collapse: collapse;
width: 100%;
}
.ranking-table th,
.ranking-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid rgba(255, 107, 53, 0.2);
}
.ranking-table th {
background-color: rgba(255, 107, 53, 0.1);
color: #ff6b35;
font-weight: 600;
text-transform: uppercase;
}
.ranking-table tr:hover {
background-color: rgba(255, 107, 53, 0.05);
}
/* Styles pour les cartes */
.card-hover {
transition: all 0.3s ease;
}
.card-hover:hover {
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(255, 107, 53, 0.2);
}
/* Styles pour les boutons */
.btn-primary {
background: linear-gradient(45deg, #ff6b35, #f7931e);
border: none;
color: white;
padding: 12px 24px;
border-radius: 8px;
font-weight: 600;
text-transform: uppercase;
transition: all 0.3s ease;
cursor: pointer;
}
.btn-primary:hover {
background: linear-gradient(45deg, #e55a2b, #e0851a);
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(255, 107, 53, 0.3);
}
.btn-outline {
background: transparent;
border: 2px solid #ff6b35;
color: #ff6b35;
padding: 12px 24px;
border-radius: 8px;
font-weight: 600;
text-transform: uppercase;
transition: all 0.3s ease;
cursor: pointer;
}
.btn-outline:hover {
background: rgba(255, 107, 53, 0.1);
border-color: #e55a2b;
color: #e55a2b;
}
/* Styles pour les titres */
.title-main {
font-family: 'Orbitron', monospace;
font-weight: 900;
text-shadow: 3px 3px 6px rgba(255, 107, 53, 0.5);
}
.title-secondary {
font-family: 'Orbitron', monospace;
font-weight: 600;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}
/* Responsive design */
@media (max-width: 768px) {
.title-main {
font-size: 2rem !important;
}
.title-secondary {
font-size: 1.5rem !important;
}
.btn-primary,
.btn-outline {
padding: 10px 20px;
font-size: 0.9rem;
}
}
/* Effets de particules */
.particles {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
pointer-events: none;
}
.particle {
position: absolute;
width: 2px;
height: 2px;
background: #ff6b35;
border-radius: 50%;
animation: float 6s ease-in-out infinite;
}
@keyframes float {
0%, 100% {
transform: translateY(0px) rotate(0deg);
opacity: 0;
}
50% {
opacity: 1;
}
100% {
transform: translateY(-100px) rotate(360deg);
opacity: 0;
}
}

9
src/App.test.tsx Normal file
View File

@@ -0,0 +1,9 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

64
src/App.tsx Normal file
View File

@@ -0,0 +1,64 @@
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import Header from './components/Header';
import Footer from './components/Footer';
import Home from './pages/Home';
import Ranking from './pages/Ranking';
import './App.css';
const theme = createTheme({
palette: {
mode: 'dark',
primary: {
main: '#ff6b35',
},
secondary: {
main: '#f7931e',
},
background: {
default: '#0a0a0a',
paper: '#1a1a1a',
},
text: {
primary: '#ffffff',
secondary: '#cccccc',
},
},
typography: {
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
h1: {
fontWeight: 700,
fontSize: '2.5rem',
},
h2: {
fontWeight: 600,
fontSize: '2rem',
},
h3: {
fontWeight: 600,
fontSize: '1.5rem',
},
},
});
function App() {
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<Router>
<div className="App">
<Header />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/ranking" element={<Ranking />} />
</Routes>
<Footer />
</div>
</Router>
</ThemeProvider>
);
}
export default App;

231
src/components/Footer.tsx Normal file
View File

@@ -0,0 +1,231 @@
import React from 'react';
import {
Box,
Container,
Typography,
Link,
IconButton,
} from '@mui/material';
import DirectionsCarIcon from '@mui/icons-material/DirectionsCar';
import SpeedIcon from '@mui/icons-material/Speed';
import EmojiEventsIcon from '@mui/icons-material/EmojiEvents';
const Footer: React.FC = () => {
const currentYear = new Date().getFullYear();
return (
<Box
component="footer"
sx={{
backgroundColor: 'rgba(26, 26, 26, 0.95)',
borderTop: '2px solid #ff6b35',
py: 4,
mt: 'auto',
}}
>
<Container maxWidth="lg">
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', md: 'repeat(3, 1fr)' }, gap: 4 }}>
{/* Logo et Description */}
<Box>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<DirectionsCarIcon sx={{ fontSize: 40, color: '#ff6b35', mr: 2 }} />
<Typography
variant="h5"
sx={{
color: '#ffffff',
fontWeight: 700,
fontFamily: '"Orbitron", monospace',
}}
>
INITIAL D
</Typography>
</Box>
<Typography
variant="body2"
sx={{
color: '#cccccc',
lineHeight: 1.6,
mb: 2,
}}
>
Découvrez l'univers de la course automobile japonaise avec les meilleurs pilotes du monde.
Affrontez les circuits légendaires et établissez de nouveaux records.
</Typography>
</Box>
{/* Liens Rapides */}
<Box>
<Typography
variant="h6"
sx={{
color: '#ff6b35',
fontWeight: 600,
mb: 3,
textTransform: 'uppercase',
}}
>
Liens Rapides
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Link
href="/"
sx={{
color: '#cccccc',
textDecoration: 'none',
'&:hover': {
color: '#ff6b35',
},
transition: 'color 0.3s ease',
}}
>
Accueil
</Link>
<Link
href="/ranking"
sx={{
color: '#cccccc',
textDecoration: 'none',
'&:hover': {
color: '#ff6b35',
},
transition: 'color 0.3s ease',
}}
>
Classement
</Link>
<Link
href="#"
sx={{
color: '#cccccc',
textDecoration: 'none',
'&:hover': {
color: '#ff6b35',
},
transition: 'color 0.3s ease',
}}
>
Circuits
</Link>
<Link
href="#"
sx={{
color: '#cccccc',
textDecoration: 'none',
'&:hover': {
color: '#ff6b35',
},
transition: 'color 0.3s ease',
}}
>
Voitures
</Link>
</Box>
</Box>
{/* Statistiques */}
<Box>
<Typography
variant="h6"
sx={{
color: '#ff6b35',
fontWeight: 600,
mb: 3,
textTransform: 'uppercase',
}}
>
Statistiques
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<SpeedIcon sx={{ color: '#ff6b35', fontSize: 20 }} />
<Typography variant="body2" sx={{ color: '#cccccc' }}>
1,234+ Joueurs Actifs
</Typography>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<EmojiEventsIcon sx={{ color: '#ff6b35', fontSize: 20 }} />
<Typography variant="body2" sx={{ color: '#cccccc' }}>
8 Circuits Légendaires
</Typography>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<DirectionsCarIcon sx={{ color: '#ff6b35', fontSize: 20 }} />
<Typography variant="body2" sx={{ color: '#cccccc' }}>
50+ Voitures Disponibles
</Typography>
</Box>
</Box>
</Box>
</Box>
{/* Ligne de s<>paration */}
<Box
sx={{
borderTop: '1px solid rgba(255, 107, 53, 0.3)',
mt: 4,
pt: 3,
}}
/>
{/* Copyright et Liens Sociaux */}
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
flexWrap: 'wrap',
gap: 2,
}}
>
<Typography
variant="body2"
sx={{
color: '#888888',
}}
>
© {currentYear} Initial D Arcade Stage. Tous droits réservés.
</Typography>
<Box sx={{ display: 'flex', gap: 1 }}>
<IconButton
sx={{
color: '#cccccc',
'&:hover': {
color: '#ff6b35',
backgroundColor: 'rgba(255, 107, 53, 0.1)',
},
}}
>
<Typography variant="body2">SEGA</Typography>
</IconButton>
<IconButton
sx={{
color: '#cccccc',
'&:hover': {
color: '#ff6b35',
backgroundColor: 'rgba(255, 107, 53, 0.1)',
},
}}
>
<Typography variant="body2">Support</Typography>
</IconButton>
<IconButton
sx={{
color: '#cccccc',
'&:hover': {
color: '#ff6b35',
backgroundColor: 'rgba(255, 107, 53, 0.1)',
},
}}
>
<Typography variant="body2">Mentions Légales</Typography>
</IconButton>
</Box>
</Box>
</Container>
</Box>
);
};
export default Footer;

72
src/components/Header.tsx Normal file
View File

@@ -0,0 +1,72 @@
import React from 'react';
import {
AppBar,
Toolbar,
Typography,
Button,
Box,
Container,
} from '@mui/material';
import { Link as RouterLink, useLocation } from 'react-router-dom';
import DirectionsCarIcon from '@mui/icons-material/DirectionsCar';
const Header: React.FC = () => {
const location = useLocation();
return (
<AppBar position="static" sx={{ backgroundColor: '#1a1a1a', borderBottom: '2px solid #ff6b35' }}>
<Container maxWidth="lg">
<Toolbar sx={{ justifyContent: 'space-between' }}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<DirectionsCarIcon sx={{ fontSize: 40, color: '#ff6b35', mr: 2 }} />
<Typography
variant="h4"
component={RouterLink}
to="/"
sx={{
color: '#ffffff',
textDecoration: 'none',
fontWeight: 700,
fontFamily: '"Orbitron", monospace',
textShadow: '2px 2px 4px rgba(255, 107, 53, 0.5)',
}}
>
INITIAL D
</Typography>
</Box>
<Box sx={{ display: 'flex', gap: 2 }}>
<Button
component={RouterLink}
to="/"
sx={{
color: location.pathname === '/' ? '#ff6b35' : '#ffffff',
fontWeight: location.pathname === '/' ? 600 : 400,
'&:hover': {
backgroundColor: 'rgba(255, 107, 53, 0.1)',
},
}}
>
ACCUEIL
</Button>
<Button
component={RouterLink}
to="/ranking"
sx={{
color: location.pathname === '/ranking' ? '#ff6b35' : '#ffffff',
fontWeight: location.pathname === '/ranking' ? 600 : 400,
'&:hover': {
backgroundColor: 'rgba(255, 107, 53, 0.1)',
},
}}
>
CLASSEMENT
</Button>
</Box>
</Toolbar>
</Container>
</AppBar>
);
};
export default Header;

329
src/data/rankingData.ts Normal file
View File

@@ -0,0 +1,329 @@
import { RankingData, Track, Car, Player } from '../types';
export const rankingData: RankingData[] = [
{
rank: 1,
playerName: 'Takumi_Fujiwara',
car: 'Toyota AE86 Trueno',
time: '2:45.123',
track: 'Mount Akina',
date: '2024-01-15',
country: 'Japon',
},
{
rank: 2,
playerName: 'Keisuke_Takahashi',
car: 'Mazda RX-7 FD3S',
time: '2:45.456',
track: 'Mount Akina',
date: '2024-01-14',
country: 'Japon',
},
{
rank: 3,
playerName: 'Ryosuke_Takahashi',
car: 'Mazda RX-7 FC3S',
time: '2:46.789',
track: 'Mount Akina',
date: '2024-01-13',
country: 'Japon',
},
{
rank: 4,
playerName: 'Bunta_Fujiwara',
car: 'Subaru Impreza WRX STI',
time: '2:47.012',
track: 'Mount Akina',
date: '2024-01-12',
country: 'Japon',
},
{
rank: 5,
playerName: 'Itsuki_Takeuchi',
car: 'Toyota AE85 Levin',
time: '2:48.345',
track: 'Mount Akina',
date: '2024-01-11',
country: 'Japon',
},
{
rank: 6,
playerName: 'Kenta_Nakamura',
car: 'Nissan Silvia S13',
time: '2:48.678',
track: 'Mount Akina',
date: '2024-01-10',
country: 'Japon',
},
{
rank: 7,
playerName: 'Shingo_Shoji',
car: 'Honda Civic EG6',
time: '2:49.001',
track: 'Mount Akina',
date: '2024-01-09',
country: 'Japon',
},
{
rank: 8,
playerName: 'Mako_Sato',
car: 'Nissan 180SX',
time: '2:49.234',
track: 'Mount Akina',
date: '2024-01-08',
country: 'Japon',
},
{
rank: 9,
playerName: 'Sayuki',
car: 'Toyota MR2 SW20',
time: '2:49.567',
track: 'Mount Akina',
date: '2024-01-07',
country: 'Japon',
},
{
rank: 10,
playerName: 'Wataru_Akiyama',
car: 'Toyota AE86 Levin',
time: '2:49.890',
track: 'Mount Akina',
date: '2024-01-06',
country: 'Japon',
},
{
rank: 11,
playerName: 'Seiji_Iwaki',
car: 'Nissan Skyline GT-R R32',
time: '2:50.123',
track: 'Mount Akina',
date: '2024-01-05',
country: 'Japon',
},
{
rank: 12,
playerName: 'Kyoichi_Sudo',
car: 'Mitsubishi Lancer Evolution III',
time: '2:50.456',
track: 'Mount Akina',
date: '2024-01-04',
country: 'Japon',
},
{
rank: 13,
playerName: 'Eiji_Kubota',
car: 'Honda Integra Type R',
time: '2:50.789',
track: 'Mount Akina',
date: '2024-01-03',
country: 'Japon',
},
{
rank: 14,
playerName: 'Tomoyuki_Tachi',
car: 'Nissan Silvia S14',
time: '2:51.012',
track: 'Mount Akina',
date: '2024-01-02',
country: 'Japon',
},
{
rank: 15,
playerName: 'Miki',
car: 'Toyota Celica GT-Four',
time: '2:51.345',
track: 'Mount Akina',
date: '2024-01-01',
country: 'Japon',
},
];
export const tracks: Track[] = [
{
id: 'mount-akina',
name: 'Mount Akina',
difficulty: 'Hard',
length: '5.2 km',
bestTime: '2:45.123',
},
{
id: 'mount-haruna',
name: 'Mount Haruna',
difficulty: 'Expert',
length: '6.8 km',
bestTime: '3:12.456',
},
{
id: 'mount-myogi',
name: 'Mount Myogi',
difficulty: 'Hard',
length: '4.9 km',
bestTime: '2:38.789',
},
{
id: 'usui-pass',
name: 'Usui Pass',
difficulty: 'Medium',
length: '3.7 km',
bestTime: '2:15.012',
},
{
id: 'irohazaka',
name: 'Irohazaka',
difficulty: 'Expert',
length: '7.2 km',
bestTime: '3:45.345',
},
{
id: 'happogahara',
name: 'Happogahara',
difficulty: 'Easy',
length: '2.8 km',
bestTime: '1:52.678',
},
{
id: 'tsuchisaka',
name: 'Tsuchisaka',
difficulty: 'Medium',
length: '4.1 km',
bestTime: '2:28.001',
},
{
id: 'akagi',
name: 'Akagi',
difficulty: 'Expert',
length: '8.5 km',
bestTime: '4:12.234',
},
];
export const cars: Car[] = [
{
id: 'ae86-trueno',
name: 'Toyota AE86 Trueno',
manufacturer: 'Toyota',
power: 130,
weight: 950,
drivetrain: 'FR',
},
{
id: 'rx7-fd3s',
name: 'Mazda RX-7 FD3S',
manufacturer: 'Mazda',
power: 280,
weight: 1270,
drivetrain: 'FR',
},
{
id: 'rx7-fc3s',
name: 'Mazda RX-7 FC3S',
manufacturer: 'Mazda',
power: 200,
weight: 1200,
drivetrain: 'FR',
},
{
id: 'impreza-wrx-sti',
name: 'Subaru Impreza WRX STI',
manufacturer: 'Subaru',
power: 300,
weight: 1470,
drivetrain: '4WD',
},
{
id: 'ae85-levin',
name: 'Toyota AE85 Levin',
manufacturer: 'Toyota',
power: 83,
weight: 920,
drivetrain: 'FR',
},
{
id: 'silvia-s13',
name: 'Nissan Silvia S13',
manufacturer: 'Nissan',
power: 205,
weight: 1240,
drivetrain: 'FR',
},
{
id: 'civic-eg6',
name: 'Honda Civic EG6',
manufacturer: 'Honda',
power: 170,
weight: 1050,
drivetrain: 'FF',
},
{
id: '180sx',
name: 'Nissan 180SX',
manufacturer: 'Nissan',
power: 205,
weight: 1260,
drivetrain: 'FR',
},
{
id: 'mr2-sw20',
name: 'Toyota MR2 SW20',
manufacturer: 'Toyota',
power: 220,
weight: 1190,
drivetrain: 'MR',
},
{
id: 'ae86-levin',
name: 'Toyota AE86 Levin',
manufacturer: 'Toyota',
power: 130,
weight: 950,
drivetrain: 'FR',
},
];
export const players: Player[] = [
{
id: 'takumi-fujiwara',
name: 'Takumi_Fujiwara',
country: 'Japon',
totalRaces: 156,
wins: 89,
bestTime: '2:45.123',
favoriteCar: 'Toyota AE86 Trueno',
},
{
id: 'keisuke-takahashi',
name: 'Keisuke_Takahashi',
country: 'Japon',
totalRaces: 142,
wins: 67,
bestTime: '2:45.456',
favoriteCar: 'Mazda RX-7 FD3S',
},
{
id: 'ryosuke-takahashi',
name: 'Ryosuke_Takahashi',
country: 'Japon',
totalRaces: 203,
wins: 134,
bestTime: '2:46.789',
favoriteCar: 'Mazda RX-7 FC3S',
},
{
id: 'bunta-fujiwara',
name: 'Bunta_Fujiwara',
country: 'Japon',
totalRaces: 89,
wins: 67,
bestTime: '2:47.012',
favoriteCar: 'Subaru Impreza WRX STI',
},
{
id: 'itsuki-takeuchi',
name: 'Itsuki_Takeuchi',
country: 'Japon',
totalRaces: 78,
wins: 23,
bestTime: '2:48.345',
favoriteCar: 'Toyota AE85 Levin',
},
];

460
src/index.css Normal file
View File

@@ -0,0 +1,460 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background: #0a0a0a;
color: #ffffff;
overflow-x: hidden;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
* {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
/* Reset des styles par défaut */
h1, h2, h3, h4, h5, h6 {
margin: 0;
padding: 0;
}
p {
margin: 0;
padding: 0;
}
/* Styles pour les liens */
a {
text-decoration: none;
color: inherit;
}
/* Styles pour les boutons */
button {
border: none;
background: none;
cursor: pointer;
font-family: inherit;
}
/* Styles pour les inputs */
input, select, textarea {
font-family: inherit;
border: none;
outline: none;
}
/* Styles pour les listes */
ul, ol {
list-style: none;
margin: 0;
padding: 0;
}
/* Styles pour les images */
img {
max-width: 100%;
height: auto;
}
/* Styles pour les tableaux */
table {
border-collapse: collapse;
border-spacing: 0;
}
/* Styles pour les formulaires */
form {
margin: 0;
padding: 0;
}
/* Styles pour les sections */
section {
margin: 0;
padding: 0;
}
/* Styles pour les articles */
article {
margin: 0;
padding: 0;
}
/* Styles pour les aside */
aside {
margin: 0;
padding: 0;
}
/* Styles pour les nav */
nav {
margin: 0;
padding: 0;
}
/* Styles pour les header */
header {
margin: 0;
padding: 0;
}
/* Styles pour les footer */
footer {
margin: 0;
padding: 0;
}
/* Styles pour les main */
main {
margin: 0;
padding: 0;
}
/* Styles pour les div */
div {
margin: 0;
padding: 0;
}
/* Styles pour les span */
span {
margin: 0;
padding: 0;
}
/* Styles pour les strong */
strong {
font-weight: 600;
}
/* Styles pour les em */
em {
font-style: italic;
}
/* Styles pour les small */
small {
font-size: 0.875em;
}
/* Styles pour les mark */
mark {
background-color: rgba(255, 107, 53, 0.2);
color: #ff6b35;
padding: 0.2em 0.4em;
border-radius: 0.2em;
}
/* Styles pour les del */
del {
text-decoration: line-through;
opacity: 0.6;
}
/* Styles pour les ins */
ins {
text-decoration: underline;
}
/* Styles pour les sub */
sub {
vertical-align: sub;
font-size: smaller;
}
/* Styles pour les sup */
sup {
vertical-align: super;
font-size: smaller;
}
/* Styles pour les code */
code {
background-color: rgba(255, 107, 53, 0.1);
color: #ff6b35;
padding: 0.2em 0.4em;
border-radius: 0.2em;
font-family: 'Courier New', monospace;
}
/* Styles pour les pre */
pre {
background-color: rgba(26, 26, 26, 0.8);
border: 1px solid rgba(255, 107, 53, 0.3);
border-radius: 0.5em;
padding: 1em;
overflow-x: auto;
font-family: 'Courier New', monospace;
}
/* Styles pour les blockquote */
blockquote {
border-left: 4px solid #ff6b35;
margin: 0;
padding-left: 1em;
font-style: italic;
color: #cccccc;
}
/* Styles pour les hr */
hr {
border: none;
border-top: 1px solid rgba(255, 107, 53, 0.3);
margin: 2em 0;
}
/* Styles pour les fieldset */
fieldset {
border: 1px solid rgba(255, 107, 53, 0.3);
border-radius: 0.5em;
padding: 1em;
margin: 0;
}
/* Styles pour les legend */
legend {
color: #ff6b35;
font-weight: 600;
padding: 0 0.5em;
}
/* Styles pour les datalist */
datalist {
display: none;
}
/* Styles pour les details */
details {
border: 1px solid rgba(255, 107, 53, 0.3);
border-radius: 0.5em;
padding: 1em;
margin: 1em 0;
}
/* Styles pour les summary */
summary {
cursor: pointer;
color: #ff6b35;
font-weight: 600;
}
/* Styles pour les dialog */
dialog {
background-color: rgba(26, 26, 26, 0.95);
border: 2px solid #ff6b35;
border-radius: 0.5em;
color: #ffffff;
padding: 2em;
}
/* Styles pour les meter */
meter {
background-color: rgba(26, 26, 26, 0.8);
border: 1px solid rgba(255, 107, 53, 0.3);
border-radius: 0.2em;
}
/* Styles pour les progress */
progress {
background-color: rgba(26, 26, 26, 0.8);
border: 1px solid rgba(255, 107, 53, 0.3);
border-radius: 0.2em;
}
/* Styles pour les output */
output {
color: #ff6b35;
font-weight: 600;
}
/* Styles pour les canvas */
canvas {
display: block;
}
/* Styles pour les svg */
svg {
display: block;
}
/* Styles pour les iframe */
iframe {
border: none;
}
/* Styles pour les object */
object {
display: block;
}
/* Styles pour les embed */
embed {
display: block;
}
/* Styles pour les video */
video {
display: block;
max-width: 100%;
}
/* Styles pour les audio */
audio {
display: block;
}
/* Styles pour les source */
source {
display: none;
}
/* Styles pour les track */
track {
display: none;
}
/* Styles pour les map */
map {
display: block;
}
/* Styles pour les area */
area {
display: none;
}
/* Styles pour les param */
param {
display: none;
}
/* Styles pour les bdo */
bdo {
direction: rtl;
}
/* Styles pour les bdi */
bdi {
direction: ltr;
}
/* Styles pour les ruby */
ruby {
display: inline;
}
/* Styles pour les rt */
rt {
display: block;
text-align: center;
font-size: 0.5em;
line-height: 1.2;
}
/* Styles pour les rp */
rp {
display: none;
}
/* Styles pour les wbr */
wbr {
display: inline;
}
/* Styles pour les time */
time {
color: #cccccc;
font-style: italic;
}
/* Styles pour les data */
data {
display: none;
}
/* Styles pour les var */
var {
font-style: italic;
color: #ff6b35;
}
/* Styles pour les samp */
samp {
font-family: 'Courier New', monospace;
background-color: rgba(255, 107, 53, 0.1);
color: #ff6b35;
padding: 0.2em 0.4em;
border-radius: 0.2em;
}
/* Styles pour les kbd */
kbd {
background-color: rgba(26, 26, 26, 0.8);
border: 1px solid rgba(255, 107, 53, 0.3);
border-radius: 0.2em;
padding: 0.2em 0.4em;
font-family: 'Courier New', monospace;
color: #ff6b35;
}
/* Styles pour les abbr */
abbr {
border-bottom: 1px dotted #ff6b35;
cursor: help;
}
/* Styles pour les acronym */
acronym {
border-bottom: 1px dotted #ff6b35;
cursor: help;
}
/* Styles pour les dfn */
dfn {
font-style: italic;
color: #ff6b35;
}
/* Styles pour les cite */
cite {
font-style: italic;
color: #cccccc;
}
/* Styles pour les q */
q {
font-style: italic;
color: #cccccc;
}
/* Styles pour les address */
address {
font-style: italic;
color: #cccccc;
}
/* Styles pour les figure */
figure {
margin: 0;
padding: 0;
}
/* Styles pour les figcaption */
figcaption {
text-align: center;
color: #cccccc;
font-style: italic;
margin-top: 0.5em;
}

19
src/index.tsx Normal file
View File

@@ -0,0 +1,19 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

1
src/logo.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

281
src/pages/Home.tsx Normal file
View File

@@ -0,0 +1,281 @@
import React from 'react';
import {
Container,
Typography,
Box,
Card,
CardContent,
CardMedia,
Button,
Paper,
} from '@mui/material';
import Grid from '@mui/material/Grid';
import { motion } from 'framer-motion';
import { useNavigate } from 'react-router-dom';
import DirectionsCarIcon from '@mui/icons-material/DirectionsCar';
import SpeedIcon from '@mui/icons-material/Speed';
import EmojiEventsIcon from '@mui/icons-material/EmojiEvents';
const Home: React.FC = () => {
const navigate = useNavigate();
const features = [
{
icon: <SpeedIcon sx={{ fontSize: 60, color: '#ff6b35' }} />,
title: 'COURSES RAPIDES',
description: 'Affrontez les meilleurs pilotes sur les circuits les plus difficiles du Japon.',
},
{
icon: <EmojiEventsIcon sx={{ fontSize: 60, color: '#ff6b35' }} />,
title: 'CLASSEMENTS',
description: 'Consultez les meilleurs temps et performances des joueurs du monde entier.',
},
{
icon: <DirectionsCarIcon sx={{ fontSize: 60, color: '#ff6b35' }} />,
title: 'VOITURES LÉGENDAIRES',
description: 'Pilotez les voitures les plus emblématiques de la série Initial D.',
},
];
return (
<Box
sx={{
minHeight: '100vh',
background: 'linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 50%, #0a0a0a 100%)',
position: 'relative',
overflow: 'hidden',
}}
>
{/* Background effects */}
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'radial-gradient(circle at 20% 80%, rgba(255, 107, 53, 0.1) 0%, transparent 50%)',
}}
/>
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'radial-gradient(circle at 80% 20%, rgba(247, 147, 30, 0.1) 0%, transparent 50%)',
}}
/>
<Container maxWidth="lg" sx={{ position: 'relative', zIndex: 1 }}>
{/* Hero Section */}
<Box
component={motion.div}
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
sx={{
textAlign: 'center',
py: 8,
mt: 4,
}}
>
<Typography
variant="h1"
sx={{
fontSize: { xs: '3rem', md: '5rem' },
fontWeight: 900,
color: '#ffffff',
textShadow: '3px 3px 6px rgba(255, 107, 53, 0.5)',
mb: 2,
fontFamily: '"Orbitron", monospace',
}}
>
INITIAL D
</Typography>
<Typography
variant="h2"
sx={{
fontSize: { xs: '1.5rem', md: '2.5rem' },
color: '#ff6b35',
mb: 4,
fontWeight: 600,
textShadow: '2px 2px 4px rgba(0, 0, 0, 0.5)',
}}
>
ARCADE STAGE
</Typography>
<Typography
variant="h5"
sx={{
color: '#cccccc',
mb: 6,
maxWidth: 800,
mx: 'auto',
lineHeight: 1.6,
}}
>
Découvrez l'univers de la course automobile japonaise avec les meilleurs pilotes du monde.
Affrontez les circuits légendaires et établissez de nouveaux records.
</Typography>
<Button
variant="contained"
size="large"
onClick={() => navigate('/ranking')}
sx={{
backgroundColor: '#ff6b35',
color: '#ffffff',
fontSize: '1.2rem',
px: 4,
py: 2,
borderRadius: 2,
fontWeight: 600,
textTransform: 'uppercase',
'&:hover': {
backgroundColor: '#e55a2b',
transform: 'translateY(-2px)',
boxShadow: '0 8px 25px rgba(255, 107, 53, 0.3)',
},
transition: 'all 0.3s ease',
}}
>
Voir le Classement
</Button>
</Box>
{/* Features Section */}
<Box sx={{ py: 8 }}>
<Typography
variant="h3"
sx={{
textAlign: 'center',
color: '#ffffff',
mb: 6,
fontWeight: 700,
}}
>
CARACTÉRISTIQUES
</Typography>
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', md: 'repeat(3, 1fr)' }, gap: 4 }}>
{features.map((feature, index) => (
<Box key={index}>
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: index * 0.2 }}
>
<Card
sx={{
backgroundColor: 'rgba(26, 26, 26, 0.8)',
border: '2px solid rgba(255, 107, 53, 0.3)',
borderRadius: 3,
height: '100%',
transition: 'all 0.3s ease',
'&:hover': {
borderColor: '#ff6b35',
transform: 'translateY(-5px)',
boxShadow: '0 10px 30px rgba(255, 107, 53, 0.2)',
},
}}
>
<CardContent sx={{ textAlign: 'center', py: 4 }}>
<Box sx={{ mb: 3 }}>
{feature.icon}
</Box>
<Typography
variant="h5"
sx={{
color: '#ff6b35',
fontWeight: 600,
mb: 2,
}}
>
{feature.title}
</Typography>
<Typography
variant="body1"
sx={{
color: '#cccccc',
lineHeight: 1.6,
}}
>
{feature.description}
</Typography>
</CardContent>
</Card>
</motion.div>
</Box>
))}
</Box>
</Box>
{/* Call to Action */}
<Box
component={motion.div}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.8, delay: 1 }}
sx={{
textAlign: 'center',
py: 8,
}}
>
<Paper
sx={{
backgroundColor: 'rgba(255, 107, 53, 0.1)',
border: '2px solid #ff6b35',
borderRadius: 3,
p: 4,
}}
>
<Typography
variant="h4"
sx={{
color: '#ffffff',
mb: 3,
fontWeight: 600,
}}
>
PRÊT À AFFRONTER LA COMPÉTITION ?
</Typography>
<Typography
variant="h6"
sx={{
color: '#cccccc',
mb: 4,
}}
>
Rejoignez les meilleurs pilotes et établissez votre domination sur les circuits.
</Typography>
<Button
variant="outlined"
size="large"
onClick={() => navigate('/ranking')}
sx={{
borderColor: '#ff6b35',
color: '#ff6b35',
fontSize: '1.1rem',
px: 4,
py: 2,
borderRadius: 2,
fontWeight: 600,
textTransform: 'uppercase',
'&:hover': {
borderColor: '#e55a2b',
backgroundColor: 'rgba(255, 107, 53, 0.1)',
},
}}
>
Commencer Maintenant
</Button>
</Paper>
</Box>
</Container>
</Box>
);
};
export default Home;

376
src/pages/Ranking.tsx Normal file
View File

@@ -0,0 +1,376 @@
import React, { useState } from 'react';
import {
Container,
Typography,
Box,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Select,
MenuItem,
FormControl,
InputLabel,
Button,
Chip,
Avatar,
Tabs,
Tab,
} from '@mui/material';
import { motion } from 'framer-motion';
import DirectionsCarIcon from '@mui/icons-material/DirectionsCar';
import SpeedIcon from '@mui/icons-material/Speed';
import EmojiEventsIcon from '@mui/icons-material/EmojiEvents';
import PersonIcon from '@mui/icons-material/Person';
import { rankingData, tracks } from '../data/rankingData';
const Ranking: React.FC = () => {
const [selectedTrack, setSelectedTrack] = useState('all');
const [selectedTab, setSelectedTab] = useState(0);
const trackNames = tracks.map(track => track.name);
const getRankColor = (rank: number) => {
switch (rank) {
case 1:
return '#FFD700'; // Or
case 2:
return '#C0C0C0'; // Argent
case 3:
return '#CD7F32'; // Bronze
default:
return '#ffffff';
}
};
const getRankIcon = (rank: number) => {
if (rank <= 3) {
return <EmojiEventsIcon sx={{ color: getRankColor(rank), fontSize: 20 }} />;
}
return null;
};
return (
<Box
sx={{
minHeight: '100vh',
background: 'linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 50%, #0a0a0a 100%)',
position: 'relative',
overflow: 'hidden',
}}
>
{/* Background effects */}
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'radial-gradient(circle at 20% 80%, rgba(255, 107, 53, 0.1) 0%, transparent 50%)',
}}
/>
<Container maxWidth="lg" sx={{ position: 'relative', zIndex: 1, py: 4 }}>
{/* Header */}
<Box
component={motion.div}
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
sx={{ textAlign: 'center', mb: 4 }}
>
<Typography
variant="h2"
sx={{
color: '#ffffff',
fontWeight: 700,
mb: 2,
textShadow: '2px 2px 4px rgba(255, 107, 53, 0.5)',
}}
>
CLASSEMENT MONDIAL
</Typography>
<Typography
variant="h5"
sx={{
color: '#ff6b35',
mb: 4,
fontWeight: 600,
}}
>
Les meilleurs temps sur les circuits légendaires
</Typography>
</Box>
{/* Tabs */}
<Box sx={{ mb: 4 }}>
<Tabs
value={selectedTab}
onChange={(_, newValue) => setSelectedTab(newValue)}
sx={{
'& .MuiTab-root': {
color: '#cccccc',
fontWeight: 600,
textTransform: 'uppercase',
'&.Mui-selected': {
color: '#ff6b35',
},
},
'& .MuiTabs-indicator': {
backgroundColor: '#ff6b35',
},
}}
>
<Tab label="Classement Général" />
<Tab label="Par Circuit" />
<Tab label="Par Voiture" />
</Tabs>
</Box>
{/* Filters */}
<Box
component={motion.div}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
sx={{
display: 'flex',
gap: 2,
mb: 4,
flexWrap: 'wrap',
justifyContent: 'center',
}}
>
<FormControl sx={{ minWidth: 200 }}>
<InputLabel sx={{ color: '#cccccc' }}>Circuit</InputLabel>
<Select
value={selectedTrack}
onChange={(e) => setSelectedTrack(e.target.value)}
sx={{
color: '#ffffff',
'& .MuiOutlinedInput-notchedOutline': {
borderColor: '#ff6b35',
},
'&:hover .MuiOutlinedInput-notchedOutline': {
borderColor: '#e55a2b',
},
'& .MuiSvgIcon-root': {
color: '#ff6b35',
},
}}
>
<MenuItem value="all">Tous les circuits</MenuItem>
{trackNames.map((trackName) => (
<MenuItem key={trackName} value={trackName}>
{trackName}
</MenuItem>
))}
</Select>
</FormControl>
<Button
variant="outlined"
sx={{
borderColor: '#ff6b35',
color: '#ff6b35',
'&:hover': {
borderColor: '#e55a2b',
backgroundColor: 'rgba(255, 107, 53, 0.1)',
},
}}
>
Actualiser
</Button>
</Box>
{/* Ranking Table */}
<Box
component={motion.div}
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.4 }}
>
<TableContainer
component={Paper}
sx={{
backgroundColor: 'rgba(26, 26, 26, 0.9)',
border: '2px solid rgba(255, 107, 53, 0.3)',
borderRadius: 3,
overflow: 'hidden',
}}
>
<Table>
<TableHead>
<TableRow sx={{ backgroundColor: 'rgba(255, 107, 53, 0.1)' }}>
<TableCell sx={{ color: '#ff6b35', fontWeight: 600, fontSize: '1.1rem' }}>
RANG
</TableCell>
<TableCell sx={{ color: '#ff6b35', fontWeight: 600, fontSize: '1.1rem' }}>
JOUEUR
</TableCell>
<TableCell sx={{ color: '#ff6b35', fontWeight: 600, fontSize: '1.1rem' }}>
VOITURE
</TableCell>
<TableCell sx={{ color: '#ff6b35', fontWeight: 600, fontSize: '1.1rem' }}>
TEMPS
</TableCell>
<TableCell sx={{ color: '#ff6b35', fontWeight: 600, fontSize: '1.1rem' }}>
CIRCUIT
</TableCell>
<TableCell sx={{ color: '#ff6b35', fontWeight: 600, fontSize: '1.1rem' }}>
DATE
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rankingData.map((row) => (
<TableRow
key={row.rank}
sx={{
'&:hover': {
backgroundColor: 'rgba(255, 107, 53, 0.05)',
},
'&:nth-of-type(odd)': {
backgroundColor: 'rgba(255, 255, 255, 0.02)',
},
}}
>
<TableCell sx={{ color: getRankColor(row.rank), fontWeight: 600, fontSize: '1.1rem' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{getRankIcon(row.rank)}
#{row.rank}
</Box>
</TableCell>
<TableCell>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<Avatar sx={{ bgcolor: '#ff6b35', width: 32, height: 32 }}>
<PersonIcon />
</Avatar>
<Typography sx={{ color: '#ffffff', fontWeight: 500 }}>
{row.playerName}
</Typography>
<Chip
label={row.country}
size="small"
sx={{
backgroundColor: 'rgba(255, 107, 53, 0.2)',
color: '#ff6b35',
fontSize: '0.7rem',
}}
/>
</Box>
</TableCell>
<TableCell>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<DirectionsCarIcon sx={{ color: '#ff6b35', fontSize: 20 }} />
<Typography sx={{ color: '#cccccc' }}>
{row.car}
</Typography>
</Box>
</TableCell>
<TableCell>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<SpeedIcon sx={{ color: '#ff6b35', fontSize: 20 }} />
<Typography
sx={{
color: '#ffffff',
fontWeight: 600,
fontFamily: 'monospace',
fontSize: '1.1rem',
}}
>
{row.time}
</Typography>
</Box>
</TableCell>
<TableCell>
<Typography sx={{ color: '#cccccc' }}>
{row.track}
</Typography>
</TableCell>
<TableCell>
<Typography sx={{ color: '#cccccc', fontSize: '0.9rem' }}>
{row.date}
</Typography>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Box>
{/* Stats */}
<Box
component={motion.div}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.8, delay: 0.6 }}
sx={{
display: 'grid',
gridTemplateColumns: { xs: '1fr', md: 'repeat(3, 1fr)' },
gap: 3,
mt: 6,
}}
>
<Paper
sx={{
backgroundColor: 'rgba(26, 26, 26, 0.8)',
border: '2px solid rgba(255, 107, 53, 0.3)',
borderRadius: 3,
p: 3,
textAlign: 'center',
}}
>
<Typography variant="h4" sx={{ color: '#ff6b35', fontWeight: 700, mb: 1 }}>
1,234
</Typography>
<Typography variant="body1" sx={{ color: '#cccccc' }}>
Joueurs Actifs
</Typography>
</Paper>
<Paper
sx={{
backgroundColor: 'rgba(26, 26, 26, 0.8)',
border: '2px solid rgba(255, 107, 53, 0.3)',
borderRadius: 3,
p: 3,
textAlign: 'center',
}}
>
<Typography variant="h4" sx={{ color: '#ff6b35', fontWeight: 700, mb: 1 }}>
8
</Typography>
<Typography variant="body1" sx={{ color: '#cccccc' }}>
Circuits Disponibles
</Typography>
</Paper>
<Paper
sx={{
backgroundColor: 'rgba(26, 26, 26, 0.8)',
border: '2px solid rgba(255, 107, 53, 0.3)',
borderRadius: 3,
p: 3,
textAlign: 'center',
}}
>
<Typography variant="h4" sx={{ color: '#ff6b35', fontWeight: 700, mb: 1 }}>
2:45.123
</Typography>
<Typography variant="body1" sx={{ color: '#cccccc' }}>
Meilleur Temps
</Typography>
</Paper>
</Box>
</Container>
</Box>
);
};
export default Ranking;

1
src/react-app-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="react-scripts" />

15
src/reportWebVitals.ts Normal file
View File

@@ -0,0 +1,15 @@
import { ReportHandler } from 'web-vitals';
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

5
src/setupTests.ts Normal file
View File

@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

36
src/types/index.ts Normal file
View File

@@ -0,0 +1,36 @@
export interface RankingData {
rank: number;
playerName: string;
car: string;
time: string;
track: string;
date: string;
country: string;
}
export interface Track {
id: string;
name: string;
difficulty: 'Easy' | 'Medium' | 'Hard' | 'Expert';
length: string;
bestTime: string;
}
export interface Car {
id: string;
name: string;
manufacturer: string;
power: number;
weight: number;
drivetrain: 'FR' | 'FF' | '4WD' | 'MR' | 'RR';
}
export interface Player {
id: string;
name: string;
country: string;
totalRaces: number;
wins: number;
bestTime: string;
favoriteCar: string;
}

26
tsconfig.json Normal file
View File

@@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}

15
vite.config.ts Normal file
View File

@@ -0,0 +1,15 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
server: {
port: 5173,
open: true
},
build: {
outDir: 'build',
sourcemap: true
}
})