WIP - Big refactoring

This commit is contained in:
Sacha VAUDEY 2025-05-31 21:34:19 +02:00
parent 818833d0e6
commit 463b519346
14 changed files with 540 additions and 2047 deletions

View File

@ -10,7 +10,7 @@
<!-- Ajout des polices Google Fonts --> <!-- Ajout des polices Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <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"> <link href="https://fonts.googleapis.com/css2?family=Dela+Gothic+One&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

File diff suppressed because it is too large Load Diff

View File

@ -1,528 +1,80 @@
import { FiUser, FiDatabase, FiShield, FiChevronDown } from 'react-icons/fi' import React, { useState } from 'react';
import { FaDiscord, FaArrowRight, FaEnvelope, FaGithub, FaNetworkWired, FaServer, FaLaptopCode, FaCloudUploadAlt, FaExternalLinkAlt } from 'react-icons/fa' import { Navigation } from './components/layout/Navigation';
import { FiX, FiExternalLink } from 'react-icons/fi' import { Footer } from './components/layout/Footer';
import './App.css' import { HeroSection } from './components/sections/HeroSection';
import icebergImage from './assets/iceberg.png' import { TechFeaturesSection } from './components/sections/TechFeaturesSection';
import logoImage from './assets/banquise_server.svg' import { ServicesSection } from './components/sections/ServicesSection';
import { useEffect, useState, useMemo, useCallback, useRef } from 'react' import { AboutSection } from './components/sections/AboutSection';
import { Popup } from './components/ui/Popup';
import aboutImage from './assets/banquise.png' // Define Service interface directly in App
interface Service {
name: string;
url: string;
image: string;
}
function App() { const App: React.FC = () => {
const [selectedService, setSelectedService] = useState<number | null>(null); // Define services directly in the component
const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const services: Service[] = [
const mobileMenuRef = useRef<HTMLDivElement>(null); { name: "Wiki", url: "https://wiki.labanquise.org", image: "/src/assets/iceberg.png" },
{ name: "Gitea", url: "https://git.labanquise.org", image: "/src/assets/iceberg.png" },
{ name: "Panel", url: "https://panel.labanquise.org", image: "/src/assets/iceberg.png" }
];
const [selectedService, setSelectedService] = useState<Service | null>(null);
const services = useMemo(() => [ // Inline accordion logic
{ const [openAccordion, setOpenAccordion] = useState<string | null>(null);
name: "Wiki", const toggleAccordion = (title: string) => {
url: "https://wiki.la-banquise.fr/", setOpenAccordion(openAccordion === title ? null : title);
description: "Une instance de wikijs, ou nous essayons de documenter nos projets, nos services ou encore notre infra, et aussi des petits tutoriels pour bien comprendre les outils utilises a EPITA !"
},
{
name: "Git",
url: "https://git.la-banquise.fr/",
description: "Gitea est notre plateforme de gestion de code source, similaire à GitHub, hébergée par nos soins. Nos divers projets necessitant Git, comme par exemple ce site, sont heberges et développes grace a cet outil."
},
{
name: "Panel 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. Celui ci sera remplace dans l ete par pelican."
},
], []);
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 association étudiante de l'EPITA, dont l'objectif est de former les epiteens a diverses notions de reseau et d'hebergement de servcies.
Fondée a la rentree 2022, notre asso permet à ceux qui le souhaitent de se former sur des technologies d'hébergement et de réseau, sur nos serveurs, a dispositon des etudiants.</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>Donnez votre login EPITA dans un ticket, ou expliquer ce dont vous avez besoin si vous n etes pas d'EPITA</li>
<li>Un moderateur vous mettra le role necessaire pour acceder au salons avec tout nos projets sur discord !</li>
</ul>
<p>Si vous etes motivé.e, peu importe votre niveau technique actuel, n'hesitez pas a venir nous poser des question ou venir demander des ressources pour un projet !</p>
<a href="https://discord.com/invite/QQWwzX5ptY" className="accordion-cta" target="_blank" rel="noopener noreferrer">
Rejoindre notre 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>Connectez-vous au service souhaité avec vos identifiants, pour le moment crees par un admin</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">
Se Connecter <FaExternalLinkAlt className="accordion-cta-icon" />
</a>
</>
)
},
{
question: "Comment contribuer a un projet ?",
answer: (
<>
<p>Il existe plusieurs façons de contribuer aux projets La Banquise :</p>
<ul>
<li>Deployer des services</li>
<li>Experimenter et documenter le fonctionnement de technologies (k8s, openstack...)</li>
<li>Aider a gerer les ressources de l'asso</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 ( return (
<div className="app-container"> <div className="flex flex-col min-h-screen w-full">
<a href="#main-content" className="sr-only focus:not-sr-only">Passer au contenu principal</a> <Navigation />
<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"> <main className="flex-1 flex flex-col overflow-x-hidden overflow-y-auto">
<div className="section-divider"></div> <div className="relative flex-1 bg-ocean-gradient w-full min-h-[calc(100vh-72px)] flex flex-col justify-start items-center overflow-x-hidden">
<h2 className="section-title">Notre infrastructure</h2>
<p className="section-subtitle">
25+ serveurs pour répondre à vos besoins
</p>
<p className="section-subtitle">
Un local a EPITA Lyon, prochainement a Paris ?
</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"> <HeroSection />
<div className="section-divider"></div> <TechFeaturesSection />
<h2 className="section-title">Nos services</h2> <ServicesSection services={services} onServiceClick={setSelectedService} />
<p className="section-subtitle"> <AboutSection openAccordion={openAccordion} toggleAccordion={toggleAccordion} />
Explorez notre écosystème de services conçus pour répondre à vos besoins.
</p> {/* Waves effect */}
<div className="absolute bottom-0 left-0 w-full h-52 overflow-hidden z-1 pointer-events-none">
<div <div className="absolute bottom-0 left-0 w-full h-28 bg-wave-pattern bg-repeat-x opacity-30 animate-wave-1 z-1" style={{ backgroundSize: '1200px 100px' }}></div>
className="icebergs-container" <div className="absolute -bottom-3 left-0 w-full h-28 bg-wave-pattern bg-repeat-x opacity-50 animate-wave-2 z-2" style={{ backgroundSize: '1200px 100px', animationDelay: '-5s' }}></div>
role="list" <div className="absolute -bottom-5 left-0 w-full h-28 bg-wave-pattern bg-repeat-x opacity-70 animate-wave-3 z-3" style={{ backgroundSize: '1200px 100px', animationDelay: '-2s' }}></div>
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>
<div className="waves" aria-hidden="true"> {/* Bubbles effect */}
<div className="wave wave1"></div> <div className="absolute w-full h-full overflow-hidden top-0 left-0 z-1 pointer-events-none">
<div className="wave wave2"></div> {[...Array(7)].map((_, i) => (
<div className="wave wave3"></div> <div
key={i}
className={`absolute -bottom-5 rounded-full bg-banquise-blue-lightest/20 opacity-80 animate-rise`}
style={{
width: `${[25, 30, 35, 28, 22, 32, 20][i]}px`,
height: `${[25, 30, 35, 28, 22, 32, 20][i]}px`,
left: `${[10, 20, 35, 50, 65, 80, 90][i]}%`,
animationDuration: `${[8, 9, 10, 7, 12, 9, 8][i]}s`,
animationDelay: `${[0, 1, 2, 0, 3, 1.5, 2.5][i]}s`,
}}
></div>
))}
</div> </div>
</div> </div>
</main> </main>
<footer className="footer"> <Footer />
<div className="footer-content">
<div className="footer-column"> {selectedService && (
<h4>La Banquise</h4> <Popup service={selectedService} onClose={() => setSelectedService(null)} />
<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> </div>
); );
} };
export default App; export default App;

View File

@ -0,0 +1,65 @@
import React from 'react';
export const Footer: React.FC = () => (
<footer className="bg-banquise-blue-dark text-white py-16 px-8 pt-16 relative z-3 border-t border-white/10 w-full box-border">
<div className="flex flex-wrap justify-between max-w-6xl mx-auto">
<div className="flex-1 min-w-64 mb-8 text-left px-6">
<h4 className="text-xl mb-6 text-banquise-blue-lightest relative pb-3 after:content-[''] after:absolute after:bottom-0 after:left-0 after:w-10 after:h-0.5 after:bg-banquise-blue-lightest">
Services
</h4>
<ul className="list-none p-0 m-0">
<li className="mb-3">
<a href="https://wiki.labanquise.org" className="text-banquise-gray/80 no-underline transition-all duration-200 inline-flex items-center hover:text-banquise-gray hover:translate-x-1">
Wiki
</a>
</li>
<li className="mb-3">
<a href="https://git.labanquise.org" className="text-banquise-gray/80 no-underline transition-all duration-200 inline-flex items-center hover:text-banquise-gray hover:translate-x-1">
Gitea
</a>
</li>
<li className="mb-3">
<a href="https://panel.labanquise.org" className="text-banquise-gray/80 no-underline transition-all duration-200 inline-flex items-center hover:text-banquise-gray hover:translate-x-1">
Panel
</a>
</li>
</ul>
</div>
<div className="flex-1 min-w-64 mb-8 text-left px-6">
<h4 className="text-xl mb-6 text-banquise-blue-lightest relative pb-3 after:content-[''] after:absolute after:bottom-0 after:left-0 after:w-10 after:h-0.5 after:bg-banquise-blue-lightest">
Communauté
</h4>
<ul className="list-none p-0 m-0">
<li className="mb-3">
<a href="https://discord.gg/labanquise" className="text-banquise-gray/80 no-underline transition-all duration-200 inline-flex items-center hover:text-banquise-gray hover:translate-x-1">
Discord
</a>
</li>
</ul>
</div>
<div className="flex-1 min-w-64 mb-8 text-left px-6">
<h4 className="text-xl mb-6 text-banquise-blue-lightest relative pb-3 after:content-[''] after:absolute after:bottom-0 after:left-0 after:w-10 after:h-0.5 after:bg-banquise-blue-lightest">
Support
</h4>
<ul className="list-none p-0 m-0">
<li className="mb-3">
<a href="#" className="text-banquise-gray/80 no-underline transition-all duration-200 inline-flex items-center hover:text-banquise-gray hover:translate-x-1">
Documentation
</a>
</li>
<li className="mb-3">
<a href="#" className="text-banquise-gray/80 no-underline transition-all duration-200 inline-flex items-center hover:text-banquise-gray hover:translate-x-1">
Contact
</a>
</li>
</ul>
</div>
</div>
<div className="border-t border-white/10 pt-6 mt-8 text-center text-sm text-white/70 max-w-6xl mx-auto">
© 2024 La Banquise. Tous droits réservés.
</div>
</footer>
);

View File

@ -0,0 +1,42 @@
import React, { useState } from 'react';
export const Navigation: React.FC = () => {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
return (
<nav className="flex justify-between items-center py-3 px-8 bg-banquise-blue-dark/95 text-white shadow-lg w-full box-border z-10 backdrop-blur-lg border-b border-white/10 sticky top-0">
<div className="flex items-center">
<img src="/src/assets/banquise.png" alt="Logo La Banquise" className="h-9 w-auto mr-4 drop-shadow-lg" style={{ filter: 'drop-shadow(0 0 5px rgba(168, 218, 255, 0.4))' }} />
<h1 className="text-2xl font-bold m-0 text-white tracking-wide font-heading">La Banquise</h1>
</div>
<div className="flex items-center gap-3 md:flex hidden">
<a href="https://discord.gg/labanquise" className="flex items-center bg-banquise-blue text-white border-0 py-2 px-5 rounded-md cursor-pointer no-underline font-semibold tracking-wide shadow-lg transition-all duration-200 hover:bg-banquise-blue/80 hover:-translate-y-0.5 hover:shadow-xl hover:text-white hover:shadow-banquise-blue/60 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">
Discord
</a>
<a href="#login" className="flex items-center bg-banquise-blue-light/90 text-white border-0 py-2 px-5 rounded-md cursor-pointer no-underline font-semibold tracking-wide shadow-lg transition-all duration-200 hover:bg-banquise-blue-light hover:-translate-y-0.5 hover:shadow-xl hover:text-white hover:shadow-banquise-blue-light/50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">
<span className="mr-2">👤</span>
Connexion
</a>
</div>
<button
className="md:hidden bg-transparent border-0 cursor-pointer p-1 z-20 w-10 h-10 relative"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
>
<span className={`block w-6 h-0.5 bg-white my-1 mx-auto transition-all duration-300 ${mobileMenuOpen ? 'translate-y-2 rotate-45' : ''}`}></span>
<span className={`block w-6 h-0.5 bg-white my-1 mx-auto transition-all duration-300 ${mobileMenuOpen ? 'opacity-0 scale-0' : ''}`}></span>
<span className={`block w-6 h-0.5 bg-white my-1 mx-auto transition-all duration-300 ${mobileMenuOpen ? '-translate-y-2 -rotate-45' : ''}`}></span>
</button>
{/* Mobile menu overlay */}
<div className={`md:hidden fixed top-0 left-0 w-full h-full bg-black/50 z-14 transition-all duration-300 ${mobileMenuOpen ? 'visible opacity-100' : 'invisible opacity-0'}`} onClick={() => setMobileMenuOpen(false)}></div>
{/* Mobile menu */}
<div className={`md:hidden fixed top-0 right-0 flex-col bg-banquise-blue-dark/98 h-full w-3/5 max-w-xs pt-20 transition-all duration-300 z-15 gap-5 backdrop-blur-lg shadow-xl ${mobileMenuOpen ? 'translate-x-0 visible opacity-100' : 'translate-x-full invisible opacity-0'}`}>
<a href="https://discord.gg/labanquise" className="block p-4 text-white no-underline hover:bg-banquise-blue/20">Discord</a>
<a href="#login" className="block p-4 text-white no-underline hover:bg-banquise-blue/20">👤 Connexion</a>
</div>
</nav>
);
};

View File

@ -0,0 +1,78 @@
import React from 'react';
import { AccordionItem } from '../ui/AccordionItem';
interface AboutSectionProps {
openAccordion: string | null;
toggleAccordion: (title: string) => void;
}
export const AboutSection: React.FC<AboutSectionProps> = ({ openAccordion, toggleAccordion }) => (
<section className="relative bg-banquise-blue-dark/15 backdrop-blur-lg m-0 py-16 px-8 z-2 border-t border-white/10 border-b border-white/10 w-full box-border">
<div className="max-w-5xl mx-auto flex flex-col items-center w-full">
<div className="w-20 h-1 bg-gradient-to-r from-banquise-blue-lightest to-banquise-blue mx-auto mb-8 rounded-full"></div>
<h2 className="text-banquise-gray text-4xl mb-4 text-center font-heading font-bold tracking-tight" style={{ textShadow: '0 2px 4px rgba(0, 0, 0, 0.2)' }}>
À Propos
</h2>
<div className="max-w-48 mx-auto mb-8 relative z-2">
<img
src="/src/assets/banquise.png"
alt="Logo La Banquise"
className="w-full h-auto rounded-full p-4 bg-white/5 border-2 border-banquise-blue-lightest/30 shadow-xl animate-gentle-float"
style={{ boxShadow: '0 10px 25px rgba(31, 93, 137, 0.3)' }}
/>
</div>
<div className="text-center max-w-4xl mx-auto mb-10 text-banquise-gray text-lg leading-relaxed" style={{ textShadow: '0 1px 2px rgba(0, 0, 0, 0.1)' }}>
La Banquise est une communauté passionnée qui propose des services d'hébergement et des outils collaboratifs pour les développeurs et les gamers.
</div>
<div className="w-full flex flex-col gap-4 mt-6">
<AccordionItem
title="🎯 Notre Mission"
isOpen={openAccordion === "mission"}
onToggle={() => toggleAccordion("mission")}
>
<p className="mt-0 mb-4">
Fournir une plateforme stable et accessible pour héberger vos projets, partager vos connaissances et jouer ensemble.
</p>
<p className="mb-0">
Nous croyons en la puissance de la collaboration et mettons à disposition des outils modernes pour faciliter le travail en équipe.
</p>
</AccordionItem>
<AccordionItem
title="🛠️ Nos Services"
isOpen={openAccordion === "services"}
onToggle={() => toggleAccordion("services")}
>
<ul className="mt-2 mb-2 pl-6">
<li className="mb-2"><strong>Wiki :</strong> Documentation collaborative et guides détaillés</li>
<li className="mb-2"><strong>Gitea :</strong> Gestion de versions Git auto-hébergée</li>
<li className="mb-2"><strong>Panel de Jeux :</strong> Interface de gestion pour serveurs de jeux</li>
</ul>
<p className="mb-0">
Tous nos services sont maintenus avec soin et régulièrement mis à jour pour garantir une expérience optimale.
</p>
</AccordionItem>
<AccordionItem
title="🤝 Rejoindre la Communauté"
isOpen={openAccordion === "community"}
onToggle={() => toggleAccordion("community")}
>
<p className="mt-0 mb-4">
Rejoignez notre serveur Discord pour échanger avec la communauté, obtenir de l'aide et rester informé des dernières nouveautés.
</p>
<a
href="https://discord.gg/labanquise"
className="inline-flex items-center bg-banquise-blue text-white border-0 py-3 px-5 rounded-md mt-4 no-underline font-semibold transition-all duration-200 hover:bg-banquise-blue/80 hover:-translate-y-1 hover:shadow-lg"
>
<span className="mr-2 text-sm">💬</span>
Rejoindre Discord
</a>
</AccordionItem>
</div>
</div>
</section>
);

View File

@ -0,0 +1,30 @@
import React from 'react';
export const HeroSection: React.FC = () => (
<section className="min-h-[calc(70vh-72px)] flex flex-col justify-center items-center text-center pt-12 pb-12 mt-0 w-full max-w-6xl mx-auto px-8 relative z-3">
<div className="mb-8 w-40 h-40 rounded-full bg-white/10 p-4 shadow-2xl animate-gentle-float">
<img src="/src/assets/banquise.png" alt="Logo La Banquise" className="w-full h-full object-contain" style={{ filter: 'drop-shadow(0 5px 15px rgba(0, 0, 0, 0.2))' }} />
</div>
<h1 className="text-banquise-gray text-6xl mb-6 font-extrabold leading-tight max-w-4xl font-heading" style={{ textShadow: '0 2px 10px rgba(0, 0, 0, 0.3)' }}>
Bienvenue sur La Banquise
</h1>
<p className="text-banquise-gray text-2xl mb-10 max-w-3xl font-normal opacity-90" style={{ textShadow: '0 1px 4px rgba(0, 0, 0, 0.2)' }}>
Votre plateforme d'hébergement communautaire
</p>
<a href="#services" className="inline-flex items-center justify-center bg-banquise-gray text-banquise-blue-dark border-0 py-4 px-10 rounded-full text-lg font-semibold no-underline shadow-lg transition-all duration-300 min-w-52 hover:-translate-y-1 hover:shadow-xl hover:bg-white">
Découvrir nos services
<span className="ml-3 transition-transform duration-200 hover:translate-x-1"></span>
</a>
{/* Tech elements background */}
<div className="absolute top-0 left-0 w-full h-full pointer-events-none z-1">
<div className="absolute top-[15%] left-[10%] text-4xl text-white/15 animate-tech-float">🖥</div>
<div className="absolute top-[25%] right-[15%] text-4xl text-white/15 animate-tech-float" style={{ animationDelay: '2s' }}></div>
<div className="absolute bottom-[20%] left-[15%] text-4xl text-white/15 animate-tech-float" style={{ animationDelay: '1s' }}>🚀</div>
<div className="absolute bottom-[30%] right-[10%] text-4xl text-white/15 animate-tech-float" style={{ animationDelay: '3s' }}>💻</div>
</div>
</section>
);

View File

@ -0,0 +1,47 @@
import React from 'react';
// Define interface directly in the component file
interface Service {
name: string;
url: string;
image: string;
}
interface ServicesSectionProps {
services: Service[];
onServiceClick: (service: Service) => void;
}
export const ServicesSection: React.FC<ServicesSectionProps> = ({ services, onServiceClick }) => (
<section id="services" className="relative z-2 pt-12 pb-20 mt-0 mb-0 w-full max-w-6xl mx-auto px-8">
<div className="w-20 h-1 bg-gradient-to-r from-banquise-blue-lightest to-banquise-blue mx-auto mb-8 rounded-full"></div>
<h2 className="text-banquise-gray text-4xl mb-4 text-center font-heading font-bold tracking-tight" style={{ textShadow: '0 2px 4px rgba(0, 0, 0, 0.2)' }}>
Nos Services
</h2>
<p className="text-banquise-gray text-xl opacity-90 mb-14 max-w-4xl text-center mx-auto" style={{ textShadow: '0 1px 3px rgba(0, 0, 0, 0.2)' }}>
Cliquez sur un iceberg pour accéder au service correspondant
</p>
<div className="relative w-full max-w-5xl h-auto min-h-96 z-2 flex justify-center items-center mx-auto my-12 p-0 gap-[5%] flex-wrap">
{services.map((service, index) => (
<div
key={service.name}
className={`relative cursor-pointer no-underline w-64 h-52 flex items-center justify-center transition-transform duration-300 z-3 m-6 hover:-translate-y-4 ${
index === 0 ? 'animate-float-1' : index === 1 ? 'animate-float-2' : 'animate-float-3'
}`}
onClick={() => onServiceClick(service)}
>
<img
src={service.image}
alt={`Iceberg ${service.name}`}
className="w-full h-auto max-w-64 max-h-52 object-contain"
style={{ filter: 'drop-shadow(0 10px 15px rgba(0, 0, 0, 0.3))' }}
/>
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-banquise-blue-dark font-bold text-xl bg-white/92 py-3 px-5 rounded-lg text-center shadow-lg z-4 font-heading tracking-tight border border-banquise-blue-lightest/80 backdrop-blur-sm transition-all duration-300 min-w-40 max-w-[90%] w-auto hover:bg-white/97 hover:shadow-xl hover:-translate-y-1 hover:text-banquise-blue" style={{ boxShadow: '0 4px 15px rgba(0, 0, 0, 0.15), 0 0 0 2px rgba(165, 240, 255, 0.5)' }}>
{service.name}
</div>
</div>
))}
</div>
</section>
);

View File

@ -0,0 +1,39 @@
import React from 'react';
export const TechFeaturesSection: React.FC = () => (
<section className="pt-12 pb-16 relative z-2 w-full max-w-6xl mx-auto px-8">
<div className="w-20 h-1 bg-gradient-to-r from-banquise-blue-lightest to-banquise-blue mx-auto mb-8 rounded-full"></div>
<h2 className="text-banquise-gray text-4xl mb-4 text-center font-heading font-bold tracking-tight" style={{ textShadow: '0 2px 4px rgba(0, 0, 0, 0.2)' }}>
Technologies Avancées
</h2>
<p className="text-banquise-gray text-xl opacity-90 mb-14 max-w-4xl text-center mx-auto" style={{ textShadow: '0 1px 3px rgba(0, 0, 0, 0.2)' }}>
Découvrez les outils et services que nous mettons à votre disposition
</p>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 w-full">
<div className="bg-banquise-blue-dark/10 rounded-xl p-8 flex flex-col items-center text-center transition-all duration-300 border border-white/5 hover:-translate-y-2 hover:bg-banquise-blue-dark/20">
<div className="text-4xl mb-6 text-banquise-blue-lightest bg-banquise-blue-dark/30 w-18 h-18 flex items-center justify-center rounded-full shadow-lg">
📚
</div>
<h3 className="text-xl mb-4 text-banquise-gray font-heading font-semibold">Documentation</h3>
<p className="text-banquise-gray/85 leading-relaxed">Accédez à notre wiki complet avec guides et tutoriels détaillés.</p>
</div>
<div className="bg-banquise-blue-dark/10 rounded-xl p-8 flex flex-col items-center text-center transition-all duration-300 border border-white/5 hover:-translate-y-2 hover:bg-banquise-blue-dark/20">
<div className="text-4xl mb-6 text-banquise-blue-lightest bg-banquise-blue-dark/30 w-18 h-18 flex items-center justify-center rounded-full shadow-lg">
🔧
</div>
<h3 className="text-xl mb-4 text-banquise-gray font-heading font-semibold">Gestion de Code</h3>
<p className="text-banquise-gray/85 leading-relaxed">Gitea pour le versioning et la collaboration sur vos projets.</p>
</div>
<div className="bg-banquise-blue-dark/10 rounded-xl p-8 flex flex-col items-center text-center transition-all duration-300 border border-white/5 hover:-translate-y-2 hover:bg-banquise-blue-dark/20">
<div className="text-4xl mb-6 text-banquise-blue-lightest bg-banquise-blue-dark/30 w-18 h-18 flex items-center justify-center rounded-full shadow-lg">
🎮
</div>
<h3 className="text-xl mb-4 text-banquise-gray font-heading font-semibold">Panel de Jeux</h3>
<p className="text-banquise-gray/85 leading-relaxed">Interface de gestion pour vos serveurs de jeux préférés.</p>
</div>
</div>
</section>
);

View File

@ -0,0 +1,28 @@
import React from 'react';
// Define interface directly in the component file
interface AccordionItemProps {
title: string;
children: React.ReactNode;
isOpen: boolean;
onToggle: () => void;
}
export const AccordionItem: React.FC<AccordionItemProps> = ({ title, children, isOpen, onToggle }) => (
<div className={`bg-banquise-blue-dark/10 rounded-xl overflow-hidden border border-banquise-blue-lightest/20 transition-all duration-300 shadow-sm ${isOpen ? 'shadow-lg' : ''} hover:bg-banquise-blue-dark/20 hover:shadow-md`}>
<div
className="p-5 cursor-pointer flex items-center justify-between font-semibold text-banquise-gray bg-banquise-blue-dark/20 transition-all duration-200 text-lg select-none hover:bg-banquise-blue/20"
onClick={onToggle}
>
{title}
<span className={`text-xl transition-transform duration-300 text-banquise-blue-lightest ${isOpen ? 'rotate-180' : ''}`}>
</span>
</div>
<div className={`transition-all duration-500 overflow-hidden bg-banquise-blue-dark/5 ${isOpen ? 'max-h-1000 p-6' : 'max-h-0'}`}>
<div className="text-banquise-gray/90 leading-relaxed">
{children}
</div>
</div>
</div>
);

View File

@ -0,0 +1,41 @@
import React from 'react';
// Define interface directly in the component file
interface Service {
name: string;
url: string;
image: string;
}
interface PopupProps {
service: Service;
onClose: () => void;
}
export const Popup: React.FC<PopupProps> = ({ service, onClose }) => (
<div className="fixed inset-0 bg-black/70 flex justify-center items-center z-50 p-4 backdrop-blur-sm animate-fadeIn">
<div className="bg-gradient-to-br from-banquise-gray to-blue-50 text-banquise-blue-dark rounded-xl p-8 max-w-lg w-full shadow-2xl relative overflow-hidden animate-slideUp">
<button
onClick={onClose}
className="absolute top-4 right-4 bg-transparent border-0 text-2xl cursor-pointer text-banquise-blue-dark flex items-center justify-center w-8 h-8 rounded-full transition-colors hover:bg-banquise-blue/10 p-0"
>
×
</button>
<h2 className="font-heading text-3xl mt-0 mb-4 text-banquise-blue-dark leading-tight">
{service.name}
</h2>
<p className="text-lg leading-relaxed mb-6 text-banquise-blue-dark">
Accédez directement au service {service.name} en cliquant sur le bouton ci-dessous.
</p>
<a
href={service.url}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center bg-banquise-blue text-white border-0 py-3 px-6 rounded-md cursor-pointer no-underline font-semibold tracking-wide shadow-lg transition-all duration-200 hover:bg-banquise-blue/80 hover:-translate-y-0.5 hover:shadow-xl focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-banquise-blue-light"
>
<span className="mr-2 text-lg">🚀</span>
Accéder à {service.name}
</a>
</div>
</div>
);

View File

@ -2,102 +2,8 @@
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
/* Système typographique moderne */ /* Variables CSS pour les polices */
:root { :root {
--font-heading: 'Space Grotesk', sans-serif; --font-heading: 'Dela Gothic One', sans-serif;
--font-body: 'Inter', system-ui, sans-serif; --font-body: 'Roboto', 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

@ -7,11 +7,109 @@ export default {
theme: { theme: {
extend: { extend: {
colors: { colors: {
// Vous pouvez personnaliser vos couleurs ici banquise: {
blue: '#40B4FF',
'blue-dark': '#1F5D89',
'blue-light': '#69B7E2',
'blue-lightest': '#A5F0FF',
gray: '#F6F6F6',
}
}, },
fontFamily: { fontFamily: {
// Personnalisation des polices heading: ['Dela Gothic One', 'sans-serif'],
} body: ['Roboto', 'sans-serif'],
},
animation: {
'float': 'float 6s ease-in-out infinite',
'float-1': 'float1 5s ease-in-out infinite',
'float-2': 'float2 6s ease-in-out infinite',
'float-3': 'float3 7s ease-in-out infinite',
'wave': 'wave 10s linear infinite',
'wave-reverse': 'waveReverse 15s linear infinite',
'wave-1': 'wave 20s linear infinite',
'wave-2': 'waveReverse 15s linear infinite',
'wave-3': 'wave 12s linear infinite',
'rise': 'rise 10s infinite ease-in',
'tech-float': 'tech-float 10s ease-in-out infinite',
'gentle-float': 'gentle-float 6s ease-in-out infinite',
'fadeIn': 'fadeIn 0.2s ease-out',
'slideUp': 'slideUp 0.3s ease-out',
},
keyframes: {
float: {
'0%, 100%': { transform: 'translateY(0)' },
'50%': { transform: 'translateY(-10px)' },
},
float1: {
'0%, 100%': { transform: 'translateY(0)' },
'50%': { transform: 'translateY(-15px)' },
},
float2: {
'0%, 100%': { transform: 'translateY(0)' },
'50%': { transform: 'translateY(-20px)' },
},
float3: {
'0%, 100%': { transform: 'translateY(0)' },
'50%': { transform: 'translateY(-10px)' },
},
'tech-float': {
'0%, 100%': { transform: 'translateY(0) rotate(0deg)', opacity: '0.15' },
'50%': { transform: 'translateY(-20px) rotate(10deg)', opacity: '0.25' },
},
'gentle-float': {
'0%, 100%': { transform: 'translateY(0)' },
'50%': { transform: 'translateY(-10px)' },
},
wave: {
'0%': { backgroundPosition: '0' },
'100%': { backgroundPosition: '1200px' },
},
waveReverse: {
'0%': { backgroundPosition: '1200px' },
'100%': { backgroundPosition: '0' },
},
rise: {
'0%': {
bottom: '-100px',
transform: 'translateX(0)',
opacity: '0.8',
},
'50%': {
transform: 'translateX(40px)',
opacity: '0.4',
},
'100%': {
bottom: '1080px',
transform: 'translateX(-40px)',
opacity: '0',
},
},
fadeIn: {
from: { opacity: '0' },
to: { opacity: '1' },
},
slideUp: {
from: { transform: 'translateY(30px)', opacity: '0' },
to: { transform: 'translateY(0)', opacity: '1' },
},
},
backdropBlur: {
'xs': '2px',
},
backgroundImage: {
'wave-pattern': "url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1200 120\" preserveAspectRatio=\"none\"><path d=\"M0,0V46.29c47.79,22.2,103.59,32.17,158,28,70.36-5.37,136.33-33.31,206.8-37.5C438.64,32.43,512.34,53.67,583,72.05c69.27,18,138.3,24.88,209.4,13.08,36.15-6,69.85-17.84,104.45-29.34C989.49,25,1113-14.29,1200,52.47V0Z\" opacity=\".25\" fill=\"%231F5D89\"/><path d=\"M0,0V15.81C13,36.92,27.64,56.86,47.69,72.05,99.41,111.27,165,111,224.58,91.58c31.15-10.15,60.09-26.07,89.67-39.8,40.92-19,84.73-46,130.83-49.67,36.26-2.85,70.9,9.42,98.6,31.56,31.77,25.39,62.32,62,103.63,73,40.44,10.79,81.35-6.69,119.13-24.28s75.16-39,116.92-43.05c59.73-5.85,113.28,22.88,168.9,38.84,30.2,8.66,59,6.17,87.09-7.5,22.43-10.89,48-26.93,60.65-49.24V0Z\" opacity=\".5\" fill=\"%231F5D89\"/><path d=\"M0,0V5.63C149.93,59,314.09,71.32,475.83,42.57c43-7.64,84.23-20.12,127.61-26.46,59-8.63,112.48,12.24,165.56,35.4C827.93,77.22,886,95.24,951.2,90c86.53-7,172.46-45.71,248.8-84.81V0Z\" fill=\"%231F5D89\"/></svg>')",
'ocean-gradient': 'linear-gradient(180deg, #40B4FF 0%, #69B7E2 50%, #1F5D89 100%)',
},
maxHeight: {
'0': '0',
'1000': '1000px',
},
spacing: {
'72': '18rem',
'80': '20rem',
'88': '22rem',
'96': '24rem',
},
}, },
}, },
plugins: [ plugins: [