Compare commits

...

8 Commits
main ... dev

Author SHA1 Message Date
f418d1a5b6 Merge pull request 'Fix UI' (#10) from website-v2 into dev
Reviewed-on: #10
Reviewed-by: arthur.wambst <arthur.wambst@la-banquise.fr>
2025-05-19 22:25:35 +02:00
619da817e6 Merge branch 'dev' into website-v2 2025-05-19 22:08:16 +02:00
sahamone
3cdf89a1f6 Fix UI 2025-05-19 21:39:25 +02:00
261ff3a8ae Merge pull request 'initial for website-v2' (#7) from website-v2 into dev
Reviewed-on: #7
Reviewed-by: arthur.wambst
2025-05-19 15:02:26 +02:00
Arthur Wambst
4d74567994 removed useless images 2025-05-19 15:00:54 +02:00
Arthur Wambst
4b6c0a60f3 added build.sh 2025-05-19 14:58:18 +02:00
sahamone
368b84ce45 initial for website-v2 2025-05-19 10:13:43 +02:00
51a5314d8e Update README.md 2025-05-18 21:38:54 +02:00
24 changed files with 2421 additions and 219 deletions

View File

@ -1,8 +1,66 @@
# website-front
On nix :
nix-shell
# Website Front pour Banquise
To run the dev env :
Ce projet est une application web React développée avec Vite, TypeScript et TailwindCSS.
cd la-banquise
npm run dev
## Architecture du Projet
```
website-front/
├── banquise-website/ # Application React principale
│ ├── public/ # Fichiers statiques
│ ├── src/ # Code source
│ │ ├── assets/ # Images et ressources
│ │ ├── App.tsx # Composant principal
│ │ └── main.tsx # Point d'entrée de l'application
│ ├── index.html # Template HTML principal
│ ├── package.json # Configuration des dépendances
│ ├── tsconfig.json # Configuration TypeScript
│ ├── vite.config.ts # Configuration Vite
│ └── tailwind.config.js # Configuration TailwindCSS
└── shell.nix # Configuration pour environnement Nix
```
## Technologies Utilisées
- **React 18** - Bibliothèque d'interface utilisateur
- **TypeScript** - Langage de programmation typé
- **Vite** - Outil de build et serveur de développement
- **TailwindCSS** - Framework CSS utilitaire
- **React Router** - Navigation entre les pages
- **Zustand** - Gestion d'état
- **React Query** - Gestion des requêtes API
- **Framer Motion** - Animations
## Pré-requis
- Node.js (v16.0.0 ou supérieur)
- npm ou yarn
## Installation
```bash
# Se déplacer dans le dossier du projet
cd banquise-website
# Installer les dépendances
npm install
# ou avec yarn
yarn
```
## Scripts Disponibles
- `npm run dev` - Lance le serveur de développement
- `npm run build` - Compile le projet pour la production
- `npm run preview` - Prévisualise la version de production localement
- `npm run lint` - Vérifie la qualité du code avec ESLint
## Déploiement
### Compilation pour la Production
```bash
npm run build
```
Cette commande générera un dossier `dist` dans le répertoire `banquise-website/` contenant tous les fichiers optimisés pour la production.

24
banquise-website/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -0,0 +1,54 @@
# React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
```js
export default tseslint.config({
extends: [
// Remove ...tseslint.configs.recommended and replace with this
...tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
...tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
...tseslint.configs.stylisticTypeChecked,
],
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
})
```
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
```js
// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'
export default tseslint.config({
plugins: {
// Add the react-x and react-dom plugins
'react-x': reactX,
'react-dom': reactDom,
},
rules: {
// other rules...
// Enable its recommended typescript rules
...reactX.configs['recommended-typescript'].rules,
...reactDom.configs.recommended.rules,
},
})
```

2
banquise-website/build.sh Executable file
View File

@ -0,0 +1,2 @@
npm install
npm run build

View File

@ -0,0 +1,28 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
)

View File

@ -0,0 +1,19 @@
<!doctype html>
<html lang="fr"> <!-- Changement de "en" à "fr" pour refléter la langue du contenu -->
<head>
<meta charset="UTF-8" />
<!-- Remplacement du favicon par le logo de La Banquise -->
<link rel="icon" type="image/png" href="/src/assets/banquise.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="description" content="Services d'hébergement La Banquise - Accédez à notre Wiki, Gitea et Panel de jeux" />
<title>La Banquise - Services d'hébergement</title>
<!-- Ajout des polices 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=Space+Grotesk:wght@400;500;600;700&family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,44 @@
{
"name": "banquise-website",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.6.5",
"clsx": "^2.1.0",
"framer-motion": "^10.18.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^4.12.0",
"react-router-dom": "^6.22.0",
"@tanstack/react-query": "^5.17.9",
"tailwind-merge": "^2.2.0",
"zustand": "^4.4.7"
},
"devDependencies": {
"@eslint/js": "^9.25.0",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.10",
"@types/react": "^18.2.58",
"@types/react-dom": "^18.2.19",
"@vitejs/plugin-react": "^4.4.1",
"autoprefixer": "^10.4.16",
"eslint": "^9.25.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.19",
"eslint-plugin-tailwindcss": "^3.14.0",
"globals": "^16.0.0",
"postcss": "^8.4.33",
"typescript": "~5.8.3",
"typescript-eslint": "^8.30.1",
"vite": "^6.3.5",
"vite-plugin-compression": "^0.5.1",
"tailwindcss": "^3.4.1"
}
}

View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

1434
banquise-website/src/App.css Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,525 @@
import { FiUser, FiDatabase, FiShield, FiChevronDown } from 'react-icons/fi'
import { FaDiscord, FaArrowRight, FaEnvelope, FaGithub, FaNetworkWired, FaServer, FaLaptopCode, FaCloudUploadAlt, FaExternalLinkAlt } from 'react-icons/fa'
import { FiX, FiExternalLink } from 'react-icons/fi'
import './App.css'
import icebergImage from './assets/iceberg.png'
import logoImage from './assets/banquise.png'
import { useEffect, useState, useMemo, useCallback, useRef } from 'react'
import aboutImage from './assets/banquise.png'
function App() {
const [selectedService, setSelectedService] = useState<number | null>(null);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const mobileMenuRef = useRef<HTMLDivElement>(null);
const services = useMemo(() => [
{
name: "Notre Wiki",
url: "https://wiki.la-banquise.fr/en/home",
description: "Notre Wiki est une base de connaissances collaborative où vous trouverez toute la documentation relative à nos projets et services. Apprenez, contribuez et partagez vos connaissances avec la communauté La Banquise."
},
{
name: "Gitea",
url: "https://git.la-banquise.fr/",
description: "Gitea est notre plateforme de gestion de code source, similaire à GitHub mais hébergée par nos soins. Créez des dépôts, collaborez sur des projets et participez au développement d'applications open-source dans un environnement sécurisé."
},
{
name: "Panel de jeux",
url: "https://panel.la-banquise.fr/auth/login",
description: "Interface de connection à notre panel Pterodactyl, qui vous permet de gérer vos serveurs de jeux. Créez, gérez et configurez vos serveurs de jeux préférés en toute simplicité."
},
], []);
const [icebergs, setIcebergs] = useState<Array<{
id: number,
x: number,
y: number,
scale: number,
rotation: number,
service: typeof services[0],
floatClass: string
}>>([])
const [reducedMotion, setReducedMotion] = useState(false);
useEffect(() => {
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
setReducedMotion(prefersReducedMotion);
const startTime = performance.now();
let count = 0;
while (performance.now() - startTime < 5) {
count++;
}
if (count < 1000) {
setReducedMotion(true);
}
}, []);
const positionIcebergs = useCallback(() => {
const newIcebergs = [];
const positions = [
{ x: 25, y: 35, scale: 0.95, rotation: 0 },
{ x: 50, y: 25, scale: 1.1, rotation: 0 },
{ x: 75, y: 35, scale: 0.95, rotation: 0 },
];
const floatClasses = ['float-1', 'float-2', 'float-3', 'float-4', 'float-5'];
for (let i = 0; i < services.length; i++) {
const position = positions[i % positions.length];
newIcebergs.push({
id: i,
x: position.x,
y: position.y,
scale: position.scale,
rotation: position.rotation,
service: services[i],
floatClass: reducedMotion ? '' : floatClasses[i % floatClasses.length]
});
}
return newIcebergs;
}, [services, reducedMotion]);
useEffect(() => {
setIcebergs(positionIcebergs());
}, [positionIcebergs]);
const renderBubbles = useMemo(() => {
if (reducedMotion) return null;
return (
<div className="bubbles">
<div className="bubble"></div>
<div className="bubble"></div>
<div className="bubble"></div>
<div className="bubble"></div>
<div className="bubble"></div>
<div className="bubble"></div>
<div className="bubble"></div>
</div>
);
}, [reducedMotion]);
const handleIcebergClick = (event: React.MouseEvent, serviceId: number) => {
event.preventDefault();
setSelectedService(serviceId);
};
const handleClosePopup = () => {
setSelectedService(null);
};
useEffect(() => {
const handleOutsideClick = (event: MouseEvent) => {
if (mobileMenuRef.current && !mobileMenuRef.current.contains(event.target as Node)) {
setMobileMenuOpen(false);
}
};
if (mobileMenuOpen) {
document.addEventListener('mousedown', handleOutsideClick);
}
return () => {
document.removeEventListener('mousedown', handleOutsideClick);
};
}, [mobileMenuOpen]);
useEffect(() => {
if (mobileMenuOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = 'auto';
}
return () => {
document.body.style.overflow = 'auto';
};
}, [mobileMenuOpen]);
const [activeAccordion, setActiveAccordion] = useState<number | null>(null);
// FAQ items for the about section
const faqItems = useMemo(() => [
{
question: "Qui sommes-nous ?",
answer: (
<>
<p>La Banquise est une initiative d'étudiants de l'EPITA Lyon dont l'objectif est de rendre plus accessible l'hébergement de services informatiques. Fondée en 2023, notre association à but non lucratif vise à permettre à ceux qui le souhaitent de se former sur des technologies d'hébergement et de réseau.</p>
<p>Notre équipe est composée d'étudiants passionnés par l'informatique, le réseau et le partage de connaissances. Nous mettons notre expertise au service des étudiants et des associations de l'EPITA.</p>
</>
)
},
{
question: "Comment candidater pour rejoindre La Banquise ?",
answer: (
<>
<p>Pour rejoindre notre équipe, rien de plus simple :</p>
<ul>
<li>Rejoignez notre serveur Discord</li>
<li>Présentez-vous et expliquez votre motivation dans un ticket</li>
<li>Précisez vos compétences ou les domaines qui vous intéressent</li>
<li>Un membre de notre équipe vous contactera pour un échange</li>
</ul>
<p>Nous recherchons des personnes motivées, peu importe votre niveau technique actuel !</p>
<a href="https://discord.com/invite/QQWwzX5ptY" className="accordion-cta" target="_blank" rel="noopener noreferrer">
Rejoindre le Discord <FaExternalLinkAlt className="accordion-cta-icon" />
</a>
</>
)
},
{
question: "Quels sont nos objectifs ?",
answer: (
<>
<p>Nos principaux objectifs sont :</p>
<ul>
<li>Former les étudiants aux technologies d'hébergement et de réseau</li>
<li>Fournir des services informatiques de qualité aux étudiants et associations de l'EPITA</li>
<li>Promouvoir le partage de connaissances et l'entraide</li>
<li>Créer un environnement d'apprentissage pratique par l'expérience</li>
<li>Maintenir une infrastructure robuste et sécurisée</li>
</ul>
</>
)
},
{
question: "Comment utiliser nos services ?",
answer: (
<>
<p>Pour accéder à nos services :</p>
<ol>
<li>Créez un compte sur notre plateforme d'authentification</li>
<li>Connectez-vous au service souhaité avec vos identifiants</li>
<li>Consultez notre Wiki pour obtenir de l'aide sur l'utilisation de chaque service</li>
</ol>
<p>Si vous rencontrez des difficultés, n'hésitez pas à demander de l'aide sur notre Discord.</p>
<a href="https://auth.la-banquise.fr/" className="accordion-cta" target="_blank" rel="noopener noreferrer">
Créer un compte <FaExternalLinkAlt className="accordion-cta-icon" />
</a>
</>
)
},
{
question: "Comment contribuer au projet ?",
answer: (
<>
<p>Il existe plusieurs façons de contribuer au projet La Banquise :</p>
<ul>
<li>Rejoindre l'équipe en tant que membre actif</li>
<li>Contribuer au code source de nos projets via Gitea</li>
<li>Rédiger ou améliorer la documentation sur notre Wiki</li>
<li>Proposer de nouvelles idées de services ou d'améliorations</li>
<li>Aider d'autres utilisateurs sur notre Discord</li>
</ul>
<p>Toutes les contributions sont les bienvenues, même les plus modestes !</p>
</>
)
},
], []);
const toggleAccordion = (index: number) => {
setActiveAccordion(activeAccordion === index ? null : index);
};
return (
<div className="app-container">
<a href="#main-content" className="sr-only focus:not-sr-only">Passer au contenu principal</a>
<header>
<nav className="navbar" aria-label="Navigation principale">
<div className="navbar-left">
<img src={logoImage} alt="Logo La Banquise" className="site-logo" />
<h1 className="site-name">La Banquise</h1>
</div>
<button
className={`navbar-mobile-toggle ${mobileMenuOpen ? 'active' : ''}`}
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
aria-expanded={mobileMenuOpen}
aria-label="Menu de navigation"
>
<span></span>
<span></span>
<span></span>
</button>
<div
className={`navbar-right ${mobileMenuOpen ? 'mobile-active' : ''}`}
ref={mobileMenuRef}
>
<a
href="https://discord.com/invite/QQWwzX5ptY"
className="discord-button"
target="_blank"
rel="noopener noreferrer"
aria-label="Rejoindre notre Discord"
onClick={() => setMobileMenuOpen(false)}
>
<FaDiscord className="discord-icon" aria-hidden="true" />
<span>Discord</span>
</a>
<a
href="https://auth.la-banquise.fr/"
className="login-button"
aria-label="Se connecter à votre compte"
onClick={() => setMobileMenuOpen(false)}
>
<FiUser className="login-icon" aria-hidden="true" />
<span>Se connecter</span>
</a>
</div>
<div
className={`mobile-menu-overlay ${mobileMenuOpen ? 'active' : ''}`}
onClick={() => setMobileMenuOpen(false)}
></div>
</nav>
</header>
<main id="main-content" className="content">
<div className="ocean" role="region" aria-label="Services La Banquise">
{renderBubbles}
<section className="page-section hero-section">
<div className="hero-tech-elements">
<div className="tech-element tech-element-1"><FaServer /></div>
<div className="tech-element tech-element-2"><FaLaptopCode /></div>
<div className="tech-element tech-element-3"><FaNetworkWired /></div>
<div className="tech-element tech-element-4"><FaCloudUploadAlt /></div>
</div>
<div className="hero-logo-container">
<img src={logoImage} alt="Logo La Banquise" className="hero-logo" />
</div>
<h2 className="hero-title">Association La Banquise</h2>
<p className="hero-subtitle">
Association d'hébergement et lab réseau pour tous les étudiants et associations de l'EPITA
</p>
<div>
<a href="https://discord.com/invite/QQWwzX5ptY" className="cta-button" target="_blank" rel="noopener noreferrer">
Notre Discord
<FaArrowRight className="cta-icon" />
</a>
</div>
</section>
<section className="page-section tech-features-section">
<div className="section-divider"></div>
<h2 className="section-title">Notre infrastructure</h2>
<p className="section-subtitle">
Des services robustes et sécurisés pour répondre à vos besoins
</p>
<div className="tech-features-grid">
<div className="tech-feature-card">
<div className="tech-feature-icon">
<FaServer />
</div>
<h3 className="tech-feature-title">Serveurs performants</h3>
<p className="tech-feature-description">
Infrastructure optimisée pour assurer des performances élevées et une disponibilité maximale de vos applications
</p>
</div>
<div className="tech-feature-card">
<div className="tech-feature-icon">
<FiDatabase />
</div>
<h3 className="tech-feature-title">Stockage sécurisé</h3>
<p className="tech-feature-description">
Solutions de stockage distribuées avec redondance pour garantir l'intégrité et la durabilité de vos données
</p>
</div>
<div className="tech-feature-card">
<div className="tech-feature-icon">
<FaNetworkWired />
</div>
<h3 className="tech-feature-title">Réseau optimisé</h3>
<p className="tech-feature-description">
Architecture réseau à haute disponibilité avec une faible latence pour vos applications critiques
</p>
</div>
<div className="tech-feature-card">
<div className="tech-feature-icon">
<FiShield />
</div>
<h3 className="tech-feature-title">Sécurité renforcée</h3>
<p className="tech-feature-description">
Protection contre les menaces avec systèmes de sécurité modernes et mises à jour régulières
</p>
</div>
</div>
</section>
<section className="page-section services-section" id="services">
<div className="section-divider"></div>
<h2 className="section-title">Nos services</h2>
<p className="section-subtitle">
Explorez notre écosystème de services conçus pour répondre à vos besoins.
</p>
<div
className="icebergs-container"
role="list"
aria-label="Liste des services disponibles"
>
{icebergs.map((iceberg) => (
<a
key={iceberg.id}
href={iceberg.service.url}
className={`iceberg ${iceberg.floatClass}`}
style={{
transform: `rotate(${iceberg.rotation}deg) scale(${iceberg.scale}) translateZ(0)`,
}}
role="listitem"
aria-label={`En savoir plus sur ${iceberg.service.name}`}
onClick={(e) => handleIcebergClick(e, iceberg.id)}
>
<img
src={icebergImage}
alt=""
className="iceberg-image"
loading="lazy"
aria-hidden="true"
/>
<div className="service-name">{iceberg.service.name}</div>
</a>
))}
</div>
</section>
<section className="page-section about-section" id="about">
<div className="section-divider"></div>
<h2 className="section-title">À propos de nous</h2>
<p className="section-subtitle">
Découvrez notre mission et posez-nous vos questions
</p>
<div className="about-container">
<div className="about-image-container">
<img src={aboutImage} alt="Logo La Banquise" className="about-logo" />
</div>
<p className="about-intro">
La Banquise est une association étudiante dédiée à l'hébergement de services et à la formation sur les technologies réseau, au service de la communauté EPITA.
</p>
<div className="accordion-group" role="region" aria-label="Foire aux questions">
{faqItems.map((item, index) => (
<div
key={index}
className={`accordion-item ${activeAccordion === index ? 'active' : ''}`}
>
<div
className="accordion-header"
onClick={() => toggleAccordion(index)}
role="button"
aria-expanded={activeAccordion === index}
aria-controls={`accordion-content-${index}`}
tabIndex={0}
onKeyPress={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
toggleAccordion(index);
}
}}
>
{item.question}
<FiChevronDown className="accordion-toggle-icon" aria-hidden="true" />
</div>
<div
className="accordion-content"
id={`accordion-content-${index}`}
role="region"
>
{item.answer}
</div>
</div>
))}
</div>
</div>
</section>
{selectedService !== null && (
<div className="popup-overlay" onClick={handleClosePopup} role="dialog" aria-modal="true" aria-labelledby="popup-title">
<div className="popup-content" onClick={(e) => e.stopPropagation()}>
<button className="popup-close" onClick={handleClosePopup} aria-label="Fermer">
<FiX />
</button>
<h3 id="popup-title" className="popup-title">{services[selectedService].name}</h3>
<p className="popup-description">{services[selectedService].description}</p>
<a
href={services[selectedService].url}
className="popup-button"
target="_blank"
rel="noopener noreferrer"
>
<FiExternalLink className="popup-button-icon" />
<span>Accéder au service</span>
</a>
</div>
</div>
)}
<div className="code-background">
<div className="code-line">const banquise = new ServerInfra();</div>
<div className="code-line">banquise.deploy('wiki');</div>
<div className="code-line">banquise.deploy('gitea');</div>
<div className="code-line">banquise.optimize();</div>
<div className="code-line">banquise.monitor();</div>
</div>
<div className="waves" aria-hidden="true">
<div className="wave wave1"></div>
<div className="wave wave2"></div>
<div className="wave wave3"></div>
</div>
</div>
</main>
<footer className="footer">
<div className="footer-content">
<div className="footer-column">
<h4>La Banquise</h4>
<ul>
<li><a href="#about">À propos</a></li>
<li><a href="#services">Services</a></li>
<li><a href="https://wiki.la-banquise.fr/en/home">Documentation</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</div>
<div className="footer-column">
<h4>Services</h4>
<ul>
{services.map((service, index) => (
<li key={index}><a href={service.url}>{service.name}</a></li>
))}
</ul>
</div>
<div className="footer-column">
<h4>Communauté</h4>
<ul>
<li><a href="https://discord.com/invite/QQWwzX5ptY" target="_blank" rel="noopener noreferrer">Discord</a></li>
<li><a href="https://git.la-banquise.fr/" target="_blank" rel="noopener noreferrer">Gitea</a></li>
<li><a href="mailto:contact@la-banquise.fr"><FaEnvelope style={{marginRight: '0.5rem'}} /> contact@la-banquise.fr</a></li>
<li><a href="https://github.com/la-banquise" target="_blank" rel="noopener noreferrer"><FaGithub style={{marginRight: '0.5rem'}} /> GitHub</a></li>
</ul>
</div>
</div>
<div className="footer-bottom">
<p>&copy; {new Date().getFullYear()} La Banquise. Tous droits réservés.</p>
</div>
</footer>
</div>
);
}
export default App;

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

View File

@ -0,0 +1,103 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Système typographique moderne */
:root {
--font-heading: 'Space Grotesk', sans-serif;
--font-body: 'Inter', system-ui, sans-serif;
font-family: var(--font-body);
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Hiérarchie typographique */
h1, h2, h3, h4, h5, h6, .site-name, .welcome-title {
font-family: var(--font-heading);
font-weight: 600;
line-height: 1.2;
letter-spacing: -0.025em;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
font-weight: 700;
}
h2 {
font-size: 2.4em;
font-weight: 600;
}
h3 {
font-size: 1.8em;
font-weight: 600;
}
p, span, div, a, button, input {
font-family: var(--font-body);
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
padding: 0;
display: flex;
min-width: 320px;
min-height: 100vh;
width: 100%;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: var(--font-body);
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

View File

@ -0,0 +1,10 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)

1
banquise-website/src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@ -0,0 +1,21 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
colors: {
// Vous pouvez personnaliser vos couleurs ici
},
fontFamily: {
// Personnalisation des polices
}
},
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
],
}

View File

@ -0,0 +1,27 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}

View File

@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}

View File

@ -0,0 +1,27 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import viteCompression from 'vite-plugin-compression'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
viteCompression({
verbose: false,
algorithm: 'gzip',
ext: '.gz',
}),
],
build: {
minify: 'esbuild',
cssMinify: true,
rollupOptions: {
output: {
manualChunks: {
react: ['react', 'react-dom'],
router: ['react-router-dom'],
},
},
},
},
})

View File

@ -1,24 +0,0 @@
<svg width="1800" height="1000" viewBox="0 -300 1800 1000" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Mât du drapeau -->
<rect x="910" y="-220" width="50" height="220" fill="#1F5078"/>
<!-- Petit drapeau -->
<polygon points="960,-220 1250,-110 960,0" fill="#F2F2F2" stroke="#1F5078" stroke-width="1"/>
<!-- Fond + contour -->
<polygon fill="#34A6FC" points="0,676 0,725 1779,725 1778,675 1470,675
1380,600 1275,350 1273,346 1268,338 1263,335 1257,332 1245,338
1239,341 1131,384 1080,300 1073,260 1020,120 956,15 957,14.7431
950,6 950,3 935,0 923,0 915,10 913,15 850,200 796.5,256
730,200 728,200 710,178 697,178 350,676" />
<!-- Autres conversions en polygon identiques aux points du SVG d'origine -->
<polygon fill="#F2F2F2" points="775,300 707.5,239.5 646,552.5 656.5,548.5 775,300" />
<polygon fill="#F2F2F2" points="750.5,663 641.5,603.5 938.5,494.5 750.5,663" />
<polygon fill="#F2F2F2" points="1271,576.5 1277,442 1391.5,637 1271,576.5" />
<polygon fill="#F2F2F2" points="1366.5,675 1267.5,625.5 1303,675 1366.5,675" />
<polygon fill="#76BEEE" points="644.5,316.5 592.5,570.5 398,645.5 644.5,316.5" />
<polygon fill="#76BEEE" points="832,481.5 717.5,523.5 884.5,180.5 832,481.5" />
<polygon fill="#76BEEE" points="1047.5,458.5 803.5,675 1036.5,675.5 1155.5,562.5 1047.5,458.5" />
<polygon fill="#76BEEE" points="1230.5,395 1205.5,404.833 1154.9,424.8 1152.5,426 1206.5,543 1227.5,553.5 1230.5,395" />
<polygon fill="#A0ECF9" points="1021.5,412.5 881.5,463.5 946.5,101.5 1113.5,458.5" />
<polygon fill="#A0ECF9" points="584.5,622.5 443,676 677.5,676 584.5,622.5" />
<polygon fill="#A0ECF9" points="1188,596.5 1161.17,621.667 1106.8,672.8 1104,676 1246,676 1188,596.5" />
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -1,31 +0,0 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>La Banquise</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<h1 class="title">La Banquise</h1>
<div class="grid-container">
<a class="grid-item" href="localhost">
<object data="assets/iceberg.svg" type="image/svg+xml"></object>
<p class="item-name">Placeholder</p>
</a>
</div>
<div class="wave hidden">
<svg viewBox="0 0 1200 120" preserveAspectRatio="none">
<path d="M0,0 C600,100 600,100 1200,0 V120 H0 V0 Z" class="shape-fill"></path>
</svg>
</div>
<script src="./script.js"></script>
</body>
</html>

View File

@ -1,22 +0,0 @@
const tile = document.querySelectorAll(".grid-container > *")[0];
const parent = tile.parentNode;
tile.remove();
const tiles = [
{ name: "Notre wiki", href: "https://wiki.la-banquise.fr" },
{ name: "Notre git", href: "https://git.la-banquise.fr" },
{ name: "Panel jeux", href: "https://panel.la-banquise.fr" },
{ name: "Discord", href: "https://discord.gg/QQWwzX5ptY" },
{ name: "Login", href: "https://auth.la-banquise.fr" },
{ name: "Change password", href: "https://ssp.la-banquise.fr" },
]
tiles.forEach((info) => {
const newTile = tile.cloneNode(true);
newTile.querySelectorAll(".item-name").forEach((el) => {
el.innerText = info.name;
});
newTile.href = info.href;
parent.appendChild(newTile);
});

View File

@ -1,136 +0,0 @@
@import url('https://fonts.googleapis.com/css2?family=League+Spartan:wght@100..900&family=Roboto:ital,wght@0,100..900;1,100..900&family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: Roboto, sans-serif;
}
body {
margin: 0;
padding: 30px;
min-height: 100vh;
background: linear-gradient(45deg, #001e6c, #5dadc2, #001e6c);
background-size: 400% 400%;
animation: gradient 100s ease infinite;
}
@keyframes gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
h1.title {
display: flex;
justify-content: center;
}
.hidden {
visibility: hidden;
}
/* Style de la vague en bas de page */
.wave {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
overflow: hidden;
line-height: 0;
}
.wave svg {
position: relative;
display: block;
width: calc(100% + 1.3px);
height: 150px;
}
.wave .shape-fill {
fill: rgba(255, 255, 255, 0.2);
}
.title {
font-size: 2.5rem;
margin-bottom: 2rem;
}
/* En-tête du site */
header {
text-align: center;
color: #fff;
padding: 1rem;
}
/* Container en flexbox pour agencer les éléments dans une "grid" */
.grid-container {
flex: 1;
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: stretch;
gap: 20px;
padding: 1rem;
}
/* Chaque élément de la grid */
.grid-item {
background: rgba(255, 255, 255, 0);
padding: 2px;
border-radius: 8px;
color: #fff;
text-align: center;
/* Permet de s'étendre sur au moins 300px et de grandir si besoin */
flex: 0 1 calc(10% - 40px);
/* 3 items par ligne avec 20px de gap */
max-width: calc(10% - 40px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0);
transition: transform 0.3s;
}
.grid-item:hover {
transform: scale(1.05);
background: rgba(255, 255, 255, 0.1);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
}
.grid-item img,
.grid-item object {
max-width: 100%;
height: auto;
display: block;
margin: 0 auto 10px;
/* Pour donner une vue isométrique */
transform: rotateX(0deg) rotateZ(0deg);
transform-origin: center;
}
.grid-item p {
font-size: 1.5rem;
margin-top: 0rem;
font-weight: bold;
}
@media (max-width: 900px) {
.grid-item {
flex: 0 1 calc(25% - 40px);
max-width: calc(25% - 40px);
}
}
@media (max-width: 600px) {
.grid-item {
flex: 0 1 calc(50% - 40px);
max-width: calc(50% - 40px);
}
}