arthur.wambst/content-refactoring #29
							
								
								
									
										853
									
								
								banquise-website/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										853
									
								
								banquise-website/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,5 +1,5 @@ | |||||||
| import React, { useState } from 'react'; | import React from 'react'; | ||||||
| import { Navigation } from './components/layout/Navigation'; | import { ModernNavigation } from './components/layout/ModernNavigation'; | ||||||
| import { HeroSection } from './components/sections/HeroSection'; | import { HeroSection } from './components/sections/HeroSection'; | ||||||
| import { ServicesSection } from './components/sections/ServicesSection'; | import { ServicesSection } from './components/sections/ServicesSection'; | ||||||
| import { TechFeaturesSection } from './components/sections/TechFeaturesSection'; | import { TechFeaturesSection } from './components/sections/TechFeaturesSection'; | ||||||
| @ -7,168 +7,126 @@ import { AboutSection } from './components/sections/AboutSection'; | |||||||
| import { Footer } from './components/layout/Footer'; | import { Footer } from './components/layout/Footer'; | ||||||
| import { Popup } from './components/ui/Popup'; | import { Popup } from './components/ui/Popup'; | ||||||
| import { ScrollToTopButton } from './components/ui/ScrollToTopButton'; | import { ScrollToTopButton } from './components/ui/ScrollToTopButton'; | ||||||
| import { ParallaxBackground } from './components/ui/ParallaxBackground'; | import { ModernLanguageSwitcher } from './components/ui/ModernLanguageSwitcher'; | ||||||
| import { URLS } from './config/constants'; | import { useTranslation } from './hooks/useTranslation'; | ||||||
| 
 | import { useServiceModal } from './hooks/useServiceModal'; | ||||||
| interface Service { | import { useAccordion } from './hooks/useAccordion'; | ||||||
|   name: string; | import { useOceanDepthEffect } from './hooks/useOceanDepthEffect'; | ||||||
|   url: string; |  | ||||||
|   image: string; |  | ||||||
|   icon: string; |  | ||||||
|   description: string; |  | ||||||
|   features: string[]; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const services = [ |  | ||||||
|   { |  | ||||||
|     name: "Wiki", |  | ||||||
|     url: URLS.services.wiki, |  | ||||||
|     image: "/path/to/wiki-image.jpg", |  | ||||||
|     icon: "📚", |  | ||||||
|     description: "Plateforme collaborative de documentation technique et de partage de connaissances. Créez, modifiez et organisez vos guides, tutoriels et documentations en équipe avec un système de versioning intégré.", |  | ||||||
|     features: [ |  | ||||||
|       "Éditeur markdown avancé avec prévisualisation en temps réel", |  | ||||||
|       "Système de versioning pour suivre les modifications", |  | ||||||
|       "Collaboration en temps réel avec plusieurs contributeurs", |  | ||||||
|       "Recherche intelligente dans tous les documents", |  | ||||||
|       "Templates prédéfinis pour différents types de documentation", |  | ||||||
|       "Système de commentaires et de révisions", |  | ||||||
|       "Export PDF et HTML pour partage externe", |  | ||||||
|       "Intégration avec Git pour la sauvegarde" |  | ||||||
|     ] |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     name: "Gitea", |  | ||||||
|     url: URLS.services.gitea, |  | ||||||
|     image: "/path/to/gitea-image.jpg", |  | ||||||
|     icon: "🔧", |  | ||||||
|     description: "Service Git auto-hébergé lightweight et performant pour vos projets de développement. Alternative open-source à GitHub avec toutes les fonctionnalités essentielles pour gérer vos repositories.", |  | ||||||
|     features: [ |  | ||||||
|       "Repositories Git illimités publics et privés", |  | ||||||
|       "Interface web intuitive pour la gestion des projets", |  | ||||||
|       "Issues et pull requests avec système de review", |  | ||||||
|       "Wiki intégré pour chaque projet", |  | ||||||
|       "Actions CI/CD pour l'automatisation", |  | ||||||
|       "Gestion fine des permissions et des équipes", |  | ||||||
|       "API REST complète pour l'intégration", |  | ||||||
|       "Webhooks pour les notifications externes" |  | ||||||
|     ] |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     name: "Panel Gaming", |  | ||||||
|     url: URLS.services.panel, |  | ||||||
|     image: "/path/to/panel-image.jpg", |  | ||||||
|     icon: "🎮", |  | ||||||
|     description: "Interface de gestion centralisée pour tous vos serveurs de jeux. Déployez, configurez et surveillez facilement vos serveurs Minecraft, CS2, Garry's Mod et bien d'autres.", |  | ||||||
|     features: [ |  | ||||||
|       "Support de 20+ jeux populaires (Minecraft, CS2, GMod...)", |  | ||||||
|       "Déploiement en un clic avec templates préconfigurés", |  | ||||||
|       "Console d'administration en temps réel", |  | ||||||
|       "Gestion des fichiers avec éditeur intégré", |  | ||||||
|       "Monitoring des performances et ressources", |  | ||||||
|       "Système de sauvegarde automatique", |  | ||||||
|       "Planificateur de tâches automatisées" |  | ||||||
|     ] |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     name: "Pelican", |  | ||||||
|     url: URLS.services.pelican, |  | ||||||
|     image: "/path/to/pelican-image.jpg", |  | ||||||
|     icon: "🐧", |  | ||||||
|     description: "Pelican is the ultimate, free game server control panel offering high flying security.", |  | ||||||
|     features: [ |  | ||||||
|       "Gestion de serveurs de jeux avec serveurs dedies (Minecraft, CS2, Palworld...)", |  | ||||||
|       "Déploiement en un clic avec templates préconfigurés", |  | ||||||
|       "Console d'administration en temps réel", |  | ||||||
|       "Gestion des fichiers avec éditeur intégré", |  | ||||||
|       "Monitoring des performances et ressources", |  | ||||||
|       "Système de sauvegarde automatique", |  | ||||||
|       "Planificateur de tâches automatisées"          |  | ||||||
|     ] |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     name: "Intranet", |  | ||||||
|     url: URLS.services.intra, |  | ||||||
|     image: "/path/to/intra-image.jpg", |  | ||||||
|     icon: "🏢", |  | ||||||
|     description: "Espace privé sécurisé de l'association pour centraliser les ressources internes, communications et outils de collaboration entre membres.", |  | ||||||
|     features: [ |  | ||||||
|       "Tableau de bord personnalisé pour chaque membre", |  | ||||||
|       "Calendrier des événements et réunions", |  | ||||||
|       "Partage de fichiers sécurisé", |  | ||||||
|       "Forums de discussion privés", |  | ||||||
|       "Annuaire des membres avec profils", |  | ||||||
|       "Système de notifications internes", |  | ||||||
|       "Gestion des projets et tâches", |  | ||||||
|       "Archive des décisions et procès-verbaux" |  | ||||||
|     ] |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     name: "Mails", |  | ||||||
|     url: URLS.services.mails, |  | ||||||
|     image: "/path/to/mails-image.jpg", |  | ||||||
|     icon: "📧", |  | ||||||
|     description: "Service de messagerie électronique professionnel avec interface web moderne. Bénéficiez d'une adresse email personnalisée @la-banquise.fr avec toutes les fonctionnalités avancées.", |  | ||||||
|     features: [ |  | ||||||
|       "Adresses email personnalisées @la-banquise.fr", |  | ||||||
|       "Interface webmail moderne et responsive", |  | ||||||
|       "Filtres anti-spam et antivirus intégrés", |  | ||||||
|       "Contacts et calendrier synchronisés", |  | ||||||
|       "Support IMAP/SMTP pour clients externes", |  | ||||||
|       "Stockage généreux avec archivage", |  | ||||||
|       "Chiffrement des communications", |  | ||||||
|       "Sauvegarde automatique des données" |  | ||||||
|     ] |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     name: "Password change", |  | ||||||
|     url: URLS.services.ssp, |  | ||||||
|     image: "/path/to/mails-image.jpg", |  | ||||||
|     icon: "📧", |  | ||||||
|     description: "Password reset.", |  | ||||||
|     features: [ |  | ||||||
|       "Interface pour changer votre mot de passe" |  | ||||||
|     ] |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     name: "Opencloud", |  | ||||||
|     url: URLS.services.opencloud, |  | ||||||
|     image: "/path/to/opencloud-image.jpg", |  | ||||||
|     icon: "☁️", |  | ||||||
|     description: "Plateforme cloud collaborative open-source pour le stockage, le partage et la synchronisation de fichiers. Alternative libre à Google Drive avec contrôle total sur vos données.", |  | ||||||
|     features: [ |  | ||||||
|       "Stockage cloud sécurisé et chiffré", |  | ||||||
|       "Synchronisation multi-appareils", |  | ||||||
|       "Partage de fichiers avec liens sécurisés", |  | ||||||
|       "Édition collaborative de documents", |  | ||||||
|       "Versioning automatique des fichiers", |  | ||||||
|       "Applications mobiles natives", |  | ||||||
|       "Intégration avec outils externes", |  | ||||||
|       "Sauvegarde géoredondante des données" |  | ||||||
|     ] |  | ||||||
|   } |  | ||||||
| ]; |  | ||||||
| 
 | 
 | ||||||
| const App: React.FC = () => { | const App: React.FC = () => { | ||||||
|   const [selectedService, setSelectedService] = useState<Service | null>(null); |   const { t, currentLanguage, changeLanguage, availableLanguages } = useTranslation(); | ||||||
|   const [openAccordion, setOpenAccordion] = useState<string | null>(null); |   const { selectedService, openServiceModal, closeServiceModal } = useServiceModal(); | ||||||
| 
 |   const { openAccordion, toggleAccordion } = useAccordion(); | ||||||
|   const toggleAccordion = (title: string) => { |   const scrollDepth = useOceanDepthEffect(); | ||||||
|     setOpenAccordion(openAccordion === title ? null : title); |  | ||||||
|   }; |  | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div className="min-h-screen bg-gradient-to-b from-banquise-blue-dark via-banquise-blue-dark/95 to-banquise-blue-dark text-white overflow-x-hidden relative"> |     <div className="min-h-screen relative overflow-x-hidden"> | ||||||
|       {/* Background Effects */} |       {/* Arrière-plan océanique uniforme avec assombrissement progressif basé sur le scroll */} | ||||||
|       <ParallaxBackground /> |       <div className="fixed inset-0 pointer-events-none"> | ||||||
|  |         {/* Dégradé principal océanique - Surface (clair) vers abysses (très sombre) */} | ||||||
|  |         <div className="absolute inset-0 bg-gradient-to-b from-banquise-blue-light via-banquise-blue via-banquise-blue-dark to-banquise-blue-dark"></div> | ||||||
|  |          | ||||||
|  |         {/* Couche de profondeur progressive basée sur le scroll - Plus intense */} | ||||||
|  |         <div  | ||||||
|  |           className="absolute inset-0 bg-gradient-to-b from-transparent via-banquise-blue-dark/60 to-banquise-blue-dark transition-opacity duration-500" | ||||||
|  |           style={{ opacity: scrollDepth * 0.9 }} | ||||||
|  |         ></div> | ||||||
|  |          | ||||||
|  |         {/* Effet de profondeur supplémentaire pour les moyennes profondeurs - Plus sombre */} | ||||||
|  |         <div  | ||||||
|  |           className="absolute inset-0 bg-gradient-to-b from-transparent via-banquise-blue-dark/70 to-banquise-blue-dark transition-opacity duration-500" | ||||||
|  |           style={{ opacity: Math.max(0, (scrollDepth - 0.2) * 1.5) }} | ||||||
|  |         ></div> | ||||||
|  |          | ||||||
|  |         {/* Assombrissement pour les grandes profondeurs - Plus intense */} | ||||||
|  |         <div  | ||||||
|  |           className="absolute inset-0 bg-gradient-to-b from-transparent via-banquise-blue-dark/80 to-slate-900 transition-opacity duration-500" | ||||||
|  |           style={{ opacity: Math.max(0, (scrollDepth - 0.5) * 2) }} | ||||||
|  |         ></div> | ||||||
|  |          | ||||||
|  |         {/* Assombrissement final pour les abysses - Très sombre */} | ||||||
|  |         <div  | ||||||
|  |           className="absolute inset-0 bg-gradient-to-b from-banquise-blue-dark/50 to-slate-900 transition-opacity duration-500" | ||||||
|  |           style={{ opacity: Math.max(0, (scrollDepth - 0.7) * 2.5) }} | ||||||
|  |         ></div> | ||||||
|  |          | ||||||
|  |         {/* Rayons de lumière subtils qui percent l'eau depuis la surface */} | ||||||
|  |         <div className="absolute inset-0 bg-gradient-to-br from-banquise-blue-lightest/4 via-transparent to-transparent"></div> | ||||||
|  |         <div className="absolute inset-0 bg-gradient-to-bl from-transparent via-banquise-blue-lightest/2 to-transparent"></div> | ||||||
|  |          | ||||||
|  |         {/* Effet de scintillement subtil avec animations océaniques */} | ||||||
|  |         <div className="absolute inset-0"> | ||||||
|  |           <div className="absolute top-0 left-1/4 w-px h-full bg-gradient-to-b from-banquise-blue-lightest/20 via-banquise-blue-lightest/8 to-transparent animate-ocean-shimmer"></div> | ||||||
|  |           <div className="absolute top-0 left-2/3 w-px h-full bg-gradient-to-b from-banquise-blue-lightest/15 via-banquise-blue-lightest/6 to-transparent animate-ocean-shimmer delay-1000"></div> | ||||||
|  |           <div className="absolute top-0 left-3/4 w-px h-full bg-gradient-to-b from-banquise-blue-lightest/10 via-banquise-blue-lightest/4 to-transparent animate-ocean-shimmer delay-2000"></div> | ||||||
|  |         </div> | ||||||
|  |          | ||||||
|  |         {/* Particules flottantes (bulles) pour l'effet sous-marin avec animations variées */} | ||||||
|  |         <div className="absolute inset-0"> | ||||||
|  |           {/* Bulles réparties sur toute la hauteur pour l'effet océanique continu */} | ||||||
|  |           <div className="absolute top-1/6 left-1/5 w-2 h-2 bg-banquise-blue-lightest/20 rounded-full animate-bubble-float"></div> | ||||||
|  |           <div className="absolute top-1/4 left-3/4 w-1 h-1 bg-banquise-blue-lightest/15 rounded-full animate-bubble-float-fast delay-500"></div> | ||||||
|  |           <div className="absolute top-1/3 left-1/3 w-3 h-3 bg-banquise-blue-lightest/10 rounded-full animate-bubble-float-slow"></div> | ||||||
|  |           <div className="absolute top-1/2 left-4/5 w-1.5 h-1.5 bg-banquise-blue-lightest/18 rounded-full animate-bubble-float delay-1000"></div> | ||||||
|  |           <div className="absolute top-2/3 left-1/6 w-2.5 h-2.5 bg-banquise-blue-lightest/8 rounded-full animate-bubble-float-slow delay-1500"></div> | ||||||
|  |           <div className="absolute top-3/4 left-2/3 w-1 h-1 bg-banquise-blue-lightest/22 rounded-full animate-bubble-float-fast delay-700"></div> | ||||||
|  |           <div className="absolute top-5/6 left-1/2 w-1.5 h-1.5 bg-banquise-blue-lightest/12 rounded-full animate-bubble-float delay-300"></div> | ||||||
|  |           <div className="absolute top-11/12 left-3/5 w-2 h-2 bg-banquise-blue-lightest/6 rounded-full animate-bubble-float-slow delay-2000"></div> | ||||||
|  |            | ||||||
|  |           {/* Bulles supplémentaires pour un effet plus dense */} | ||||||
|  |           <div className="absolute top-1/8 left-1/2 w-1.5 h-1.5 bg-banquise-blue-lightest/25 rounded-full animate-bubble-float-fast delay-800"></div> | ||||||
|  |           <div className="absolute top-3/8 left-1/8 w-2 h-2 bg-banquise-blue-lightest/12 rounded-full animate-bubble-float delay-1200"></div> | ||||||
|  |           <div className="absolute top-5/8 left-7/8 w-1 h-1 bg-banquise-blue-lightest/20 rounded-full animate-bubble-float-slow delay-600"></div> | ||||||
|  |           <div className="absolute top-7/8 left-1/4 w-2.5 h-2.5 bg-banquise-blue-lightest/8 rounded-full animate-bubble-float-fast delay-1800"></div> | ||||||
|  |           <div className="absolute top-1/7 left-5/6 w-1 h-1 bg-banquise-blue-lightest/28 rounded-full animate-bubble-float delay-400"></div> | ||||||
|  |           <div className="absolute top-2/7 left-2/5 w-1.5 h-1.5 bg-banquise-blue-lightest/15 rounded-full animate-bubble-float-slow delay-900"></div> | ||||||
|  |           <div className="absolute top-4/7 left-3/8 w-2 h-2 bg-banquise-blue-lightest/10 rounded-full animate-bubble-float-fast delay-1400"></div> | ||||||
|  |           <div className="absolute top-6/7 left-4/5 w-1 h-1 bg-banquise-blue-lightest/18 rounded-full animate-bubble-float delay-200"></div> | ||||||
|  |            | ||||||
|  |           {/* Bulles très petites pour densité */} | ||||||
|  |           <div className="absolute top-1/10 left-3/10 w-0.5 h-0.5 bg-banquise-blue-lightest/30 rounded-full animate-bubble-float-fast delay-100"></div> | ||||||
|  |           <div className="absolute top-3/10 left-7/10 w-0.5 h-0.5 bg-banquise-blue-lightest/25 rounded-full animate-bubble-float delay-1100"></div> | ||||||
|  |           <div className="absolute top-7/10 left-1/10 w-0.5 h-0.5 bg-banquise-blue-lightest/20 rounded-full animate-bubble-float-slow delay-1700"></div> | ||||||
|  |           <div className="absolute top-9/10 left-9/10 w-0.5 h-0.5 bg-banquise-blue-lightest/15 rounded-full animate-bubble-float-fast delay-2200"></div> | ||||||
|  |            | ||||||
|  |           {/* Bulles moyennes pour variation */} | ||||||
|  |           <div className="absolute top-1/5 left-4/7 w-3 h-3 bg-banquise-blue-lightest/5 rounded-full animate-bubble-float-slow delay-1600"></div> | ||||||
|  |           <div className="absolute top-2/5 left-6/7 w-2.5 h-2.5 bg-banquise-blue-lightest/7 rounded-full animate-bubble-float delay-800"></div> | ||||||
|  |           <div className="absolute top-4/5 left-2/7 w-3.5 h-3.5 bg-banquise-blue-lightest/4 rounded-full animate-bubble-float-slow delay-2400"></div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|        |        | ||||||
|       {/* Main Content */} |       {/* Contenu principal avec arrière-plan océanique uniforme */} | ||||||
|       <div className="relative z-10"> |       <div className="relative z-10 text-white"> | ||||||
|         <Navigation /> |         {/* Navigation flottante */} | ||||||
|         <HeroSection /> |         <ModernNavigation  | ||||||
|         <ServicesSection services={services} onServiceClick={setSelectedService} /> |           translations={t.navigation} | ||||||
|  |           languageSwitcher={ | ||||||
|  |             <ModernLanguageSwitcher  | ||||||
|  |               currentLanguage={currentLanguage} | ||||||
|  |               onLanguageChange={changeLanguage} | ||||||
|  |               availableLanguages={availableLanguages} | ||||||
|  |             /> | ||||||
|  |           } | ||||||
|  |         /> | ||||||
|  |          | ||||||
|  |         {/* Section Hero - Surface de l'océan */} | ||||||
|  |         <HeroSection translations={t.hero} /> | ||||||
|  |          | ||||||
|  |         {/* Section Services */} | ||||||
|  |         <ServicesSection  | ||||||
|  |           services={t.services}  | ||||||
|  |           onServiceClick={openServiceModal} | ||||||
|  |           translations={t.common} | ||||||
|  |         /> | ||||||
|  |          | ||||||
|  |         {/* Section TechFeatures */} | ||||||
|         <TechFeaturesSection /> |         <TechFeaturesSection /> | ||||||
|  |          | ||||||
|  |         {/* Section About */} | ||||||
|         <AboutSection openAccordion={openAccordion} toggleAccordion={toggleAccordion} /> |         <AboutSection openAccordion={openAccordion} toggleAccordion={toggleAccordion} /> | ||||||
|  |          | ||||||
|  |         {/* Footer */} | ||||||
|         <Footer /> |         <Footer /> | ||||||
|       </div> |       </div> | ||||||
| 
 | 
 | ||||||
| @ -178,7 +136,8 @@ const App: React.FC = () => { | |||||||
|       {selectedService && ( |       {selectedService && ( | ||||||
|         <Popup  |         <Popup  | ||||||
|           service={selectedService}  |           service={selectedService}  | ||||||
|           onClose={() => setSelectedService(null)}  |           onClose={closeServiceModal} | ||||||
|  |           translations={t.common} | ||||||
|         /> |         /> | ||||||
|       )} |       )} | ||||||
|     </div> |     </div> | ||||||
|  | |||||||
							
								
								
									
										65
									
								
								banquise-website/src/components/common/Button.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								banquise-website/src/components/common/Button.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,65 @@ | |||||||
|  | import React from 'react'; | ||||||
|  | import { componentStyles, mergeClasses } from '../../styles/designSystem'; | ||||||
|  | 
 | ||||||
|  | interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { | ||||||
|  |   variant?: 'primary' | 'discord' | 'auth' | 'secondary'; | ||||||
|  |   size?: 'sm' | 'md' | 'lg'; | ||||||
|  |   fullWidth?: boolean; | ||||||
|  |   leftIcon?: React.ReactNode; | ||||||
|  |   rightIcon?: React.ReactNode; | ||||||
|  |   loading?: boolean; | ||||||
|  |   children: React.ReactNode; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const sizeClasses = { | ||||||
|  |   sm: 'px-3 py-1.5 text-sm', | ||||||
|  |   md: 'px-4 lg:px-6 py-2.5 lg:py-3 text-sm lg:text-base', | ||||||
|  |   lg: 'px-6 py-3 text-base lg:text-lg', | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const variantClasses = { | ||||||
|  |   primary: 'bg-gradient-to-r from-banquise-blue to-banquise-blue-light hover:shadow-banquise-blue/25', | ||||||
|  |   discord: 'bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-500 hover:to-purple-500 hover:shadow-indigo-500/25', | ||||||
|  |   auth: 'bg-gradient-to-r from-banquise-blue-light to-banquise-blue hover:shadow-banquise-blue-light/25', | ||||||
|  |   secondary: 'bg-white/10 hover:bg-white/20 border border-white/20', | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const Button: React.FC<ButtonProps> = ({ | ||||||
|  |   variant = 'primary', | ||||||
|  |   size = 'md', | ||||||
|  |   fullWidth = false, | ||||||
|  |   leftIcon, | ||||||
|  |   rightIcon, | ||||||
|  |   loading = false, | ||||||
|  |   children, | ||||||
|  |   className = '', | ||||||
|  |   disabled, | ||||||
|  |   ...props | ||||||
|  | }) => { | ||||||
|  |   const baseClasses = mergeClasses( | ||||||
|  |     componentStyles.button.base, | ||||||
|  |     sizeClasses[size], | ||||||
|  |     variantClasses[variant], | ||||||
|  |     fullWidth ? 'w-full' : '', | ||||||
|  |     (disabled || loading) ? 'opacity-50 cursor-not-allowed' : '', | ||||||
|  |     className | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <button | ||||||
|  |       className={baseClasses} | ||||||
|  |       disabled={disabled || loading} | ||||||
|  |       {...props} | ||||||
|  |     > | ||||||
|  |       {loading && ( | ||||||
|  |         <svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> | ||||||
|  |           <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle> | ||||||
|  |           <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> | ||||||
|  |         </svg> | ||||||
|  |       )} | ||||||
|  |       {leftIcon && !loading && <span className="mr-2">{leftIcon}</span>} | ||||||
|  |       {children} | ||||||
|  |       {rightIcon && !loading && <span className="ml-2">{rightIcon}</span>} | ||||||
|  |     </button> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
							
								
								
									
										40
									
								
								banquise-website/src/components/common/Card.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								banquise-website/src/components/common/Card.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | |||||||
|  | import React from 'react'; | ||||||
|  | import { componentStyles, mergeClasses } from '../../styles/designSystem'; | ||||||
|  | 
 | ||||||
|  | interface CardProps { | ||||||
|  |   variant?: 'default' | 'interactive' | 'service'; | ||||||
|  |   className?: string; | ||||||
|  |   children: React.ReactNode; | ||||||
|  |   onClick?: () => void; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const variantClasses = { | ||||||
|  |   default: 'bg-gradient-to-br from-banquise-blue-dark/10 to-banquise-blue-dark/5', | ||||||
|  |   interactive: 'bg-gradient-to-br from-banquise-blue-dark/10 to-banquise-blue-dark/5 cursor-pointer hover:-translate-y-4 hover:shadow-2xl hover:border-banquise-blue-lightest/50 hover:from-banquise-blue-dark/15 hover:to-banquise-blue-dark/8 active:scale-95', | ||||||
|  |   service: 'bg-gradient-to-br from-banquise-blue-dark/10 to-banquise-blue-dark/5 cursor-pointer hover:-translate-y-4 hover:shadow-2xl hover:border-banquise-blue-lightest/50 hover:from-banquise-blue-dark/15 hover:to-banquise-blue-dark/8 active:scale-95', | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const Card: React.FC<CardProps> = ({ | ||||||
|  |   variant = 'default', | ||||||
|  |   className = '', | ||||||
|  |   children, | ||||||
|  |   onClick, | ||||||
|  | }) => { | ||||||
|  |   const cardClasses = mergeClasses( | ||||||
|  |     componentStyles.card.base, | ||||||
|  |     variantClasses[variant], | ||||||
|  |     className | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   const Component = onClick ? 'button' : 'div'; | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <Component | ||||||
|  |       className={cardClasses} | ||||||
|  |       onClick={onClick} | ||||||
|  |       {...(onClick ? { type: 'button' } : {})} | ||||||
|  |     > | ||||||
|  |       {children} | ||||||
|  |     </Component> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
							
								
								
									
										57
									
								
								banquise-website/src/components/common/ServiceCard.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								banquise-website/src/components/common/ServiceCard.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,57 @@ | |||||||
|  | import React from 'react'; | ||||||
|  | import { componentStyles, mergeClasses } from '../../styles/designSystem'; | ||||||
|  | import type { Service } from '../../types/service'; | ||||||
|  | 
 | ||||||
|  | interface ServiceCardProps { | ||||||
|  |   service: Service; | ||||||
|  |   onServiceClick: (service: Service) => void; | ||||||
|  |   discoverFeaturesText: string; | ||||||
|  |   className?: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const ServiceCard: React.FC<ServiceCardProps> = ({ | ||||||
|  |   service, | ||||||
|  |   onServiceClick, | ||||||
|  |   discoverFeaturesText, | ||||||
|  |   className = '', | ||||||
|  | }) => { | ||||||
|  |   const cardClasses = mergeClasses( | ||||||
|  |     'group relative p-6 sm:p-8 transition-all duration-300 cursor-pointer', | ||||||
|  |     componentStyles.card.base, | ||||||
|  |     componentStyles.card.gradient, | ||||||
|  |     'hover:-translate-y-4 hover:shadow-2xl hover:border-banquise-blue-lightest/50 hover:from-banquise-blue-dark/15 hover:to-banquise-blue-dark/8 active:scale-95', | ||||||
|  |     className | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   const handleClick = () => { | ||||||
|  |     onServiceClick(service); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <div className={cardClasses} onClick={handleClick}> | ||||||
|  |       {/* Icon */} | ||||||
|  |       <div className="mb-6 sm:mb-8 w-20 h-20 sm:w-24 sm:h-24 bg-gradient-to-br from-banquise-blue to-banquise-blue-light rounded-2xl flex items-center justify-center text-3xl sm:text-4xl shadow-lg group-hover:scale-110 transition-transform duration-300 mx-auto"> | ||||||
|  |         {service.icon} | ||||||
|  |       </div> | ||||||
|  |        | ||||||
|  |       {/* Service name */} | ||||||
|  |       <h3 className="text-xl sm:text-2xl font-bold text-banquise-gray mb-4 sm:mb-6 font-heading text-center group-hover:text-banquise-blue-lightest transition-colors duration-300"> | ||||||
|  |         {service.name} | ||||||
|  |       </h3> | ||||||
|  |        | ||||||
|  |       {/* Short description */} | ||||||
|  |       <p className="text-banquise-gray/80 leading-relaxed mb-6 sm:mb-8 text-center text-sm sm:text-base"> | ||||||
|  |         {service.description.split('.')[0]}. | ||||||
|  |       </p> | ||||||
|  |        | ||||||
|  |       {/* CTA */} | ||||||
|  |       <div className="flex items-center justify-center text-banquise-blue-light font-bold group-hover:text-banquise-blue-lightest transition-colors duration-300 text-sm sm:text-base"> | ||||||
|  |         <span className="text-center">{discoverFeaturesText}</span> | ||||||
|  |         <span className="ml-2 text-lg transition-transform duration-300 group-hover:translate-x-2">→</span> | ||||||
|  |       </div> | ||||||
|  |        | ||||||
|  |       {/* Hover effect */} | ||||||
|  |       <div className="absolute inset-0 bg-gradient-to-br from-banquise-blue-lightest/10 to-banquise-blue/5 rounded-2xl opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none"></div> | ||||||
|  |     </div> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
| @ -2,85 +2,78 @@ import React from 'react'; | |||||||
| import { URLS, SITE_CONFIG } from '../../config/constants'; | import { URLS, SITE_CONFIG } from '../../config/constants'; | ||||||
| 
 | 
 | ||||||
| export const Footer: React.FC = () => ( | export const Footer: React.FC = () => ( | ||||||
|   <footer className="bg-banquise-blue-dark text-white py-12 sm:py-16 md:py-20 px-4 sm:px-6 md:px-8 relative z-10 border-t border-banquise-blue-lightest/20 w-full box-border"> |   <footer className="bg-banquise-blue-dark/95 backdrop-blur-sm text-white py-8 px-4 sm:px-6 md:px-8 relative z-10 border-t border-banquise-blue-lightest/10 w-full box-border"> | ||||||
|     <div className="flex flex-col md:flex-row justify-between max-w-6xl mx-auto gap-6 sm:gap-8"> |     <div className="max-w-6xl mx-auto"> | ||||||
|       <div className="flex-1 min-w-0 mb-6 sm:mb-8 md:mb-0 text-left"> |       {/* Main Footer Content */} | ||||||
|         <h4 className="text-lg sm:text-xl mb-6 sm:mb-8 text-banquise-blue-lightest relative pb-3 sm:pb-4 after:content-[''] after:absolute after:bottom-0 after:left-0 after:w-12 after:h-0.5 after:bg-gradient-to-r after:from-banquise-blue-lightest after:to-banquise-blue"> |       <div className="flex flex-col md:flex-row justify-between items-center gap-6 mb-6"> | ||||||
|           Services |         {/* Logo/Brand */} | ||||||
|         </h4> |         <div className="flex items-center gap-3"> | ||||||
|         <ul className="list-none p-0 m-0 space-y-2 sm:space-y-3"> |           <div className="w-8 h-8 bg-gradient-to-br from-banquise-blue-light to-banquise-blue rounded-lg flex items-center justify-center"> | ||||||
|           <li> |             <span className="text-white font-bold text-sm">B</span> | ||||||
|             <a href={URLS.services.wiki} className="text-banquise-gray/80 no-underline transition-all duration-200 inline-flex items-center hover:text-banquise-gray hover:translate-x-1 text-sm sm:text-base"> |           </div> | ||||||
|               Wiki |           <span className="text-banquise-blue-lightest font-semibold text-lg"> | ||||||
|             </a> |             {SITE_CONFIG.name} | ||||||
|           </li> |           </span> | ||||||
|           <li> |         </div> | ||||||
|             <a href={URLS.services.gitea} className="text-banquise-gray/80 no-underline transition-all duration-200 inline-flex items-center hover:text-banquise-gray hover:translate-x-1 text-sm sm:text-base"> | 
 | ||||||
|               Gitea |         {/* Quick Links */} | ||||||
|             </a> |         <div className="flex flex-wrap items-center gap-6 text-sm"> | ||||||
|           </li> |           <a  | ||||||
|           <li> |             href={URLS.services.wiki}  | ||||||
|             <a href={URLS.services.panel} className="text-banquise-gray/80 no-underline transition-all duration-200 inline-flex items-center hover:text-banquise-gray hover:translate-x-1 text-sm sm:text-base"> |             className="text-banquise-gray/80 hover:text-banquise-blue-lightest transition-colors duration-200" | ||||||
|               Panel |           > | ||||||
|             </a> |             Wiki | ||||||
|           </li> |           </a> | ||||||
|           <li> |           <a  | ||||||
|             <a href={URLS.services.pelican} className="text-banquise-gray/80 no-underline transition-all duration-200 inline-flex items-center hover:text-banquise-gray hover:translate-x-1 text-sm sm:text-base"> |             href={URLS.services.gitea}  | ||||||
|               Pelican |             className="text-banquise-gray/80 hover:text-banquise-blue-lightest transition-colors duration-200" | ||||||
|             </a> |           > | ||||||
|           </li> |             Gitea | ||||||
|           <li> |           </a> | ||||||
|             <a href={URLS.services.intra} className="text-banquise-gray/80 no-underline transition-all duration-200 inline-flex items-center hover:text-banquise-gray hover:translate-x-1 text-sm sm:text-base"> |           <a  | ||||||
|               Intranet |             href={URLS.services.panel}  | ||||||
|             </a> |             className="text-banquise-gray/80 hover:text-banquise-blue-lightest transition-colors duration-200" | ||||||
|           </li> |           > | ||||||
|           <li> |             Panel | ||||||
|             <a href={URLS.services.mails} className="text-banquise-gray/80 no-underline transition-all duration-200 inline-flex items-center hover:text-banquise-gray hover:translate-x-1 text-sm sm:text-base"> |           </a> | ||||||
|               Webmail |           <a  | ||||||
|             </a> |             href={URLS.services.opencloud}  | ||||||
|           </li> |             className="text-banquise-gray/80 hover:text-banquise-blue-lightest transition-colors duration-200" | ||||||
|           <li> |           > | ||||||
|             <a href={URLS.services.opencloud} className="text-banquise-gray/80 no-underline transition-all duration-200 inline-flex items-center hover:text-banquise-gray hover:translate-x-1 text-sm sm:text-base"> |             OpenCloud | ||||||
|               OpenCloud |           </a> | ||||||
|             </a> |         </div> | ||||||
|           </li> | 
 | ||||||
|         </ul> |         {/* Social Links */} | ||||||
|  |         <div className="flex items-center gap-4"> | ||||||
|  |           <a  | ||||||
|  |             href={URLS.social.discord}  | ||||||
|  |             className="w-10 h-10 bg-banquise-blue/20 hover:bg-banquise-blue/30 rounded-full flex items-center justify-center transition-all duration-200 hover:scale-110" | ||||||
|  |             aria-label="Discord" | ||||||
|  |           > | ||||||
|  |             <span className="text-banquise-blue-lightest text-sm">💬</span> | ||||||
|  |           </a> | ||||||
|  |           <a  | ||||||
|  |             href={URLS.contact.email}  | ||||||
|  |             className="w-10 h-10 bg-banquise-blue/20 hover:bg-banquise-blue/30 rounded-full flex items-center justify-center transition-all duration-200 hover:scale-110" | ||||||
|  |             aria-label="Email" | ||||||
|  |           > | ||||||
|  |             <span className="text-banquise-blue-lightest text-sm">📧</span> | ||||||
|  |           </a> | ||||||
|  |         </div> | ||||||
|       </div> |       </div> | ||||||
|        | 
 | ||||||
|       <div className="flex-1 min-w-0 mb-6 sm:mb-8 md:mb-0 text-left"> |       {/* Bottom Bar */} | ||||||
|         <h4 className="text-lg sm:text-xl mb-6 sm:mb-8 text-banquise-blue-lightest relative pb-3 sm:pb-4 after:content-[''] after:absolute after:bottom-0 after:left-0 after:w-12 after:h-0.5 after:bg-gradient-to-r after:from-banquise-blue-lightest after:to-banquise-blue"> |       <div className="flex flex-col sm:flex-row justify-between items-center gap-4 pt-6 border-t border-banquise-blue-lightest/5"> | ||||||
|           Communauté |         <p className="text-banquise-gray/60 text-xs text-center sm:text-left"> | ||||||
|         </h4> |           © 2024 {SITE_CONFIG.name}. Hébergement communautaire pour développeurs et gamers. | ||||||
|         <ul className="list-none p-0 m-0 space-y-2 sm:space-y-3"> |         </p> | ||||||
|           <li> |         <div className="flex items-center gap-4 text-xs text-banquise-gray/60"> | ||||||
|             <a href={URLS.social.discord} className="text-banquise-gray/80 no-underline transition-all duration-200 inline-flex items-center hover:text-banquise-gray hover:translate-x-1 text-sm sm:text-base"> |           <span>Fait avec ❤️ par la communauté</span> | ||||||
|               Discord |           <div className="w-1 h-1 bg-banquise-gray/40 rounded-full"></div> | ||||||
|             </a> |           <span>EPITA 2024</span> | ||||||
|           </li> |         </div> | ||||||
|         </ul> |  | ||||||
|       </div> |       </div> | ||||||
|        |  | ||||||
|       <div className="flex-1 min-w-0 mb-6 sm:mb-8 md:mb-0 text-left"> |  | ||||||
|         <h4 className="text-lg sm:text-xl mb-6 sm:mb-8 text-banquise-blue-lightest relative pb-3 sm:pb-4 after:content-[''] after:absolute after:bottom-0 after:left-0 after:w-12 after:h-0.5 after:bg-gradient-to-r after:from-banquise-blue-lightest after:to-banquise-blue"> |  | ||||||
|           Support |  | ||||||
|         </h4> |  | ||||||
|         <ul className="list-none p-0 m-0 space-y-2 sm:space-y-3"> |  | ||||||
|           <li> |  | ||||||
|             <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 text-sm sm:text-base"> |  | ||||||
|               Documentation |  | ||||||
|             </a> |  | ||||||
|           </li> |  | ||||||
|           <li> |  | ||||||
|             <a href={URLS.contact.email} className="text-banquise-gray/80 no-underline transition-all duration-200 inline-flex items-center hover:text-banquise-gray hover:translate-x-1 text-sm sm:text-base"> |  | ||||||
|               Contact |  | ||||||
|             </a> |  | ||||||
|           </li> |  | ||||||
|         </ul> |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
|      |  | ||||||
|     <div className="border-t border-banquise-blue-lightest/20 pt-6 sm:pt-8 mt-8 sm:mt-12 text-center text-xs sm:text-sm text-banquise-gray/70 max-w-6xl mx-auto"> |  | ||||||
|       © 2024 {SITE_CONFIG.name}. Tous droits réservés. |  | ||||||
|     </div> |     </div> | ||||||
|   </footer> |   </footer> | ||||||
| ); | ); | ||||||
|  | |||||||
| @ -1,14 +1,89 @@ | |||||||
| import React, { useEffect } from 'react'; | import React, { useEffect } from 'react'; | ||||||
| import banquiseServer from '../../assets/banquise_server.svg' | import { Button } from '../common/Button'; | ||||||
| import { URLS, SITE_CONFIG } from '../../config/constants'; | import { mergeClasses as cn } from '../../styles/designSystem'; | ||||||
| import { commonStyles } from '../../styles/components'; | import { Logo } from './navbar/Logo'; | ||||||
|  | import { URLS } from '../../config/constants'; | ||||||
|  | import type { Translation } from '../../types/i18n'; | ||||||
| 
 | 
 | ||||||
| interface MobileMenuProps { | interface MobileMenuProps { | ||||||
|   isOpen: boolean; |   isOpen: boolean; | ||||||
|   onClose: () => void; |   onClose: () => void; | ||||||
|  |   translations: Translation['navigation']; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const MobileMenu: React.FC<MobileMenuProps> = ({ isOpen, onClose }) => { | interface MobileNavItemProps { | ||||||
|  |   icon: React.ReactNode; | ||||||
|  |   title: string; | ||||||
|  |   description: string; | ||||||
|  |   href: string; | ||||||
|  |   isExternal?: boolean; | ||||||
|  |   onClick?: () => void; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const MobileNavItem: React.FC<MobileNavItemProps> = ({  | ||||||
|  |   icon,  | ||||||
|  |   title,  | ||||||
|  |   description,  | ||||||
|  |   href,  | ||||||
|  |   isExternal = false,  | ||||||
|  |   onClick  | ||||||
|  | }) => { | ||||||
|  |   const handleClick = (e: React.MouseEvent) => { | ||||||
|  |     if (onClick) { | ||||||
|  |       e.preventDefault(); | ||||||
|  |       onClick(); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <a | ||||||
|  |       href={href} | ||||||
|  |       onClick={handleClick} | ||||||
|  |       className={cn( | ||||||
|  |         'group flex items-center justify-between p-4 rounded-xl transition-all duration-300', | ||||||
|  |         'bg-white/5 hover:bg-white/10 active:bg-white/15', | ||||||
|  |         'border border-white/10 hover:border-white/20', | ||||||
|  |         'hover:scale-[1.02] active:scale-[0.98]', | ||||||
|  |         'hover:shadow-lg hover:shadow-banquise-blue/20' | ||||||
|  |       )} | ||||||
|  |       target={isExternal ? '_blank' : undefined} | ||||||
|  |       rel={isExternal ? 'noopener noreferrer' : undefined} | ||||||
|  |     > | ||||||
|  |       <div className="flex items-center space-x-4"> | ||||||
|  |         <div className={cn( | ||||||
|  |           'flex items-center justify-center w-10 h-10 rounded-xl', | ||||||
|  |           'bg-gradient-to-br from-banquise-blue-light/20 to-banquise-blue/20', | ||||||
|  |           'border border-banquise-blue-lightest/20', | ||||||
|  |           'group-hover:scale-110 transition-transform duration-300' | ||||||
|  |         )}> | ||||||
|  |           {icon} | ||||||
|  |         </div> | ||||||
|  |         <div className="flex-1"> | ||||||
|  |           <span className="block text-white font-semibold text-base group-hover:text-banquise-blue-lightest transition-colors"> | ||||||
|  |             {title} | ||||||
|  |           </span> | ||||||
|  |           <p className="text-white/60 text-sm mt-0.5 group-hover:text-white/80 transition-colors"> | ||||||
|  |             {description} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |        | ||||||
|  |       {/* Arrow Icon */} | ||||||
|  |       <div className={cn( | ||||||
|  |         'flex items-center justify-center w-6 h-6 rounded-full', | ||||||
|  |         'text-white/40 group-hover:text-white/80 transition-all duration-300', | ||||||
|  |         'group-hover:translate-x-1' | ||||||
|  |       )}> | ||||||
|  |         <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | ||||||
|  |           <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" /> | ||||||
|  |         </svg> | ||||||
|  |       </div> | ||||||
|  |     </a> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const MobileMenu: React.FC<MobileMenuProps> = ({ isOpen, onClose, translations }) => { | ||||||
|  |   // Gérer le scroll du body
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (isOpen) { |     if (isOpen) { | ||||||
|       document.body.style.overflow = 'hidden'; |       document.body.style.overflow = 'hidden'; | ||||||
| @ -21,133 +96,171 @@ export const MobileMenu: React.FC<MobileMenuProps> = ({ isOpen, onClose }) => { | |||||||
|     }; |     }; | ||||||
|   }, [isOpen]); |   }, [isOpen]); | ||||||
| 
 | 
 | ||||||
|  |   const handleNavClick = (sectionId: string) => { | ||||||
|  |     if (sectionId === 'home') { | ||||||
|  |       // Scroll to top for home section
 | ||||||
|  |       window.scrollTo({  | ||||||
|  |         top: 0,  | ||||||
|  |         behavior: 'smooth'  | ||||||
|  |       }); | ||||||
|  |     } else if (sectionId === 'contact') { | ||||||
|  |       // Open email client for contact
 | ||||||
|  |       window.location.href = 'mailto:contact@la-banquise.fr'; | ||||||
|  |     } else { | ||||||
|  |       // Scroll to specific section
 | ||||||
|  |       const element = document.getElementById(sectionId); | ||||||
|  |       if (element) { | ||||||
|  |         element.scrollIntoView({  | ||||||
|  |           behavior: 'smooth', | ||||||
|  |           block: 'start' | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     onClose(); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div className={`md:hidden fixed inset-0 z-[100] transition-all duration-300 ${isOpen ? 'visible' : 'invisible'}`}> |     <div className={cn( | ||||||
|       {/* Overlay */} |       'md:hidden fixed inset-0 z-[100] transition-all duration-300', | ||||||
|  |       isOpen ? 'visible' : 'invisible' | ||||||
|  |     )}> | ||||||
|  |       {/* Overlay avec effet de blur moderne */} | ||||||
|       <div  |       <div  | ||||||
|         className={`absolute inset-0 bg-gradient-to-br from-black/70 via-banquise-blue-dark/50 to-black/70 backdrop-blur-md transition-opacity duration-300 ${isOpen ? 'opacity-100' : 'opacity-0'}`} |         className={cn( | ||||||
|  |           'absolute inset-0 transition-all duration-300', | ||||||
|  |           'bg-gradient-to-br from-black/80 via-banquise-blue-dark/60 to-black/80', | ||||||
|  |           'backdrop-blur-lg', | ||||||
|  |           isOpen ? 'opacity-100' : 'opacity-0' | ||||||
|  |         )} | ||||||
|         onClick={onClose} |         onClick={onClose} | ||||||
|       /> |       /> | ||||||
|        |        | ||||||
|       {/* Menu mobile */} |       {/* Menu Panel */} | ||||||
|       <div className={`absolute top-0 right-0 h-full w-72 max-w-[85vw] bg-gradient-to-b from-banquise-blue-dark via-banquise-blue-dark/98 to-banquise-blue-dark/95 backdrop-blur-2xl shadow-2xl transition-transform duration-300 border-l border-banquise-blue-lightest/20 ${isOpen ? 'translate-x-0' : 'translate-x-full'}`}> |       <div className={cn( | ||||||
|  |         'absolute top-0 right-0 h-full w-80 max-w-[90vw]', | ||||||
|  |         'bg-gradient-to-b from-banquise-blue-dark/98 via-banquise-blue-dark/95 to-banquise-blue-dark/90', | ||||||
|  |         'backdrop-blur-2xl shadow-2xl', | ||||||
|  |         'border-l border-banquise-blue-lightest/20', | ||||||
|  |         'transition-transform duration-300 ease-out', | ||||||
|  |         isOpen ? 'translate-x-0' : 'translate-x-full' | ||||||
|  |       )}> | ||||||
|          |          | ||||||
|         {/* Header */} |         {/* Header avec Logo */} | ||||||
|         <div className="flex items-center justify-between p-4 sm:p-6 pt-6 sm:pt-8 border-b border-banquise-blue-lightest/20 bg-gradient-to-r from-banquise-blue-dark/50 to-transparent"> |         <div className="flex items-center justify-between p-6 pt-8 border-b border-banquise-blue-lightest/20"> | ||||||
|           <div className="flex items-center space-x-3"> |           <Logo scrolled={false} /> | ||||||
|             <div className="relative"> |  | ||||||
|               <div className="absolute inset-0 bg-banquise-blue-light/20 rounded-full blur-md"></div> |  | ||||||
|               <img  |  | ||||||
|                 src={banquiseServer} |  | ||||||
|                 alt="Logo"  |  | ||||||
|                 className="h-8 sm:h-10 w-auto relative z-10"  |  | ||||||
|                 style={{ filter: 'drop-shadow(0 0 8px rgba(168, 218, 255, 0.6))' }}  |  | ||||||
|               /> |  | ||||||
|             </div> |  | ||||||
|             <div> |  | ||||||
|               <span className={`text-base sm:text-lg font-bold text-white ${commonStyles.text.heading}`}> |  | ||||||
|                 {SITE_CONFIG.name} |  | ||||||
|               </span> |  | ||||||
|               <p className="text-banquise-blue-lightest/70 text-xs">Menu Navigation</p> |  | ||||||
|             </div> |  | ||||||
|           </div> |  | ||||||
|            |            | ||||||
|           <button  |           <button  | ||||||
|             className="group relative p-3 bg-white/10 hover:bg-white/20 rounded-xl transition-all duration-200 hover:scale-105 active:scale-95" |             className={cn( | ||||||
|  |               'group relative p-3 rounded-xl transition-all duration-300', | ||||||
|  |               'bg-white/10 hover:bg-white/20 active:bg-white/25', | ||||||
|  |               'border border-white/20 hover:border-white/30', | ||||||
|  |               'hover:scale-105 active:scale-95', | ||||||
|  |               'focus:outline-none focus:ring-2 focus:ring-banquise-blue-light/50' | ||||||
|  |             )} | ||||||
|             onClick={onClose} |             onClick={onClose} | ||||||
|             aria-label="Fermer" |             aria-label="Fermer le menu" | ||||||
|           > |           > | ||||||
|             <svg width="20" height="20" viewBox="0 0 20 20" fill="none" className="text-white"> |             <svg className="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | ||||||
|               <path d="M15 5L5 15M5 5L15 15" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/> |               <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> | ||||||
|             </svg> |             </svg> | ||||||
|           </button> |           </button> | ||||||
|         </div> |         </div> | ||||||
| 
 | 
 | ||||||
|         {/* Navigation */} |         {/* Navigation Items */} | ||||||
|         <div className="flex flex-col justify-start px-6 py-8 space-y-6 overflow-y-auto" style={{ height: 'calc(100vh - 120px)' }}> |         <div className="flex flex-col h-full overflow-y-auto p-6 space-y-4"> | ||||||
|            |            | ||||||
|           {/* Navigation Links */} |           {/* Section Navigation */} | ||||||
|           <div className="space-y-3"> |           <div className="space-y-3"> | ||||||
|             <a href="#services" className={commonStyles.nav.mobileItem} onClick={onClose}> |             <MobileNavItem | ||||||
|               <div className="flex items-center space-x-4"> |               icon={ | ||||||
|                 <div className={`${commonStyles.icons.small} ${commonStyles.gradients.primaryBr} group-hover:scale-110 transition-transform duration-200`}> |                 <svg className="w-5 h-5 text-banquise-blue-lightest" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | ||||||
|                   <svg className="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |                   <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" /> | ||||||
|                     <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" /> |                 </svg> | ||||||
|                   </svg> |               } | ||||||
|                 </div> |               title={translations.home} | ||||||
|                 <div> |               description="Retour à l'accueil" | ||||||
|                   <span className="font-semibold text-lg">Nos Services</span> |               href="#home" | ||||||
|                   <p className="text-white/60 text-sm">Découvrir notre offre</p> |               onClick={() => handleNavClick('home')} | ||||||
|                 </div> |             /> | ||||||
|               </div> |  | ||||||
|               <svg className="w-5 h-5 ml-auto opacity-50 group-hover:opacity-100 group-hover:translate-x-1 transition-all duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |  | ||||||
|                 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" /> |  | ||||||
|               </svg> |  | ||||||
|             </a> |  | ||||||
| 
 | 
 | ||||||
|             <a href="#about" className={commonStyles.nav.mobileItem} onClick={onClose}> |             <MobileNavItem | ||||||
|               <div className="flex items-center space-x-4"> |               icon={ | ||||||
|                 <div className={`${commonStyles.icons.small} ${commonStyles.gradients.primaryBr} group-hover:scale-110 transition-transform duration-200`}> |                 <svg className="w-5 h-5 text-banquise-blue-lightest" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | ||||||
|                   <svg className="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |                   <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" /> | ||||||
|                     <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> |                 </svg> | ||||||
|                   </svg> |               } | ||||||
|                 </div> |               title={translations.services} | ||||||
|                 <div> |               description="Découvrir notre offre" | ||||||
|                   <span className="font-semibold text-lg">À propos</span> |               href="#services" | ||||||
|                   <p className="text-white/60 text-sm">En savoir plus sur nous</p> |               onClick={() => handleNavClick('services')} | ||||||
|                 </div> |             /> | ||||||
|               </div> |  | ||||||
|               <svg className="w-5 h-5 ml-auto opacity-50 group-hover:opacity-100 group-hover:translate-x-1 transition-all duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |  | ||||||
|                 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" /> |  | ||||||
|               </svg> |  | ||||||
|             </a> |  | ||||||
| 
 | 
 | ||||||
|             <a href={URLS.social.discord} className={commonStyles.nav.mobileItem} onClick={onClose}> |             <MobileNavItem | ||||||
|               <div className="flex items-center space-x-4"> |               icon={ | ||||||
|                 <div className={`${commonStyles.icons.small} ${commonStyles.gradients.discord} group-hover:scale-110 transition-transform duration-200`}> |                 <svg className="w-5 h-5 text-banquise-blue-lightest" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | ||||||
|                   <svg className="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 24 24"> |                   <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> | ||||||
|                     <path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.211.375-.445.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03z"/> |                 </svg> | ||||||
|                   </svg> |               } | ||||||
|                 </div> |               title={translations.about} | ||||||
|                 <div> |               description="En savoir plus sur nous" | ||||||
|                   <span className="font-semibold text-lg">Discord</span> |               href="#about" | ||||||
|                   <p className="text-white/60 text-sm">Rejoindre la communauté</p> |               onClick={() => handleNavClick('about')} | ||||||
|                 </div> |             /> | ||||||
|               </div> | 
 | ||||||
|               <svg className="w-5 h-5 ml-auto opacity-50 group-hover:opacity-100 group-hover:translate-x-1 transition-all duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |             <MobileNavItem | ||||||
|                 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> |               icon={ | ||||||
|               </svg> |                 <svg className="w-5 h-5 text-banquise-blue-lightest" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | ||||||
|             </a> |                   <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" /> | ||||||
|  |                 </svg> | ||||||
|  |               } | ||||||
|  |               title={translations.contact} | ||||||
|  |               description="Nous envoyer un email" | ||||||
|  |               href="mailto:contact@la-banquise.fr" | ||||||
|  |               onClick={() => handleNavClick('contact')} | ||||||
|  |             /> | ||||||
|  |           </div> | ||||||
|  | 
 | ||||||
|  |           {/* Divider */} | ||||||
|  |           <div className="border-t border-banquise-blue-lightest/20 my-6" /> | ||||||
|  | 
 | ||||||
|  |           {/* Social & External Links */} | ||||||
|  |           <div className="space-y-3"> | ||||||
|  |             <MobileNavItem | ||||||
|  |               icon={ | ||||||
|  |                 <svg className="w-5 h-5 text-[#5865F2]" fill="currentColor" viewBox="0 0 24 24"> | ||||||
|  |                   <path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.211.375-.445.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03z"/> | ||||||
|  |                 </svg> | ||||||
|  |               } | ||||||
|  |               title="Discord" | ||||||
|  |               description="Rejoindre la communauté" | ||||||
|  |               href={URLS.social.discord} | ||||||
|  |               isExternal={true} | ||||||
|  |             /> | ||||||
|           </div> |           </div> | ||||||
| 
 | 
 | ||||||
|           {/* CTA Button */} |           {/* CTA Button */} | ||||||
|           <div className="pt-6 border-t border-banquise-blue-lightest/20"> |           <div className="mt-8 pb-6"> | ||||||
|             <a  |             <Button | ||||||
|               href={URLS.services.auth} |               variant="primary" | ||||||
|               target="_blank" |               size="lg" | ||||||
|               rel="noopener noreferrer" |               leftIcon={ | ||||||
|               className={`w-full ${commonStyles.buttons.primary} ${commonStyles.gradients.primary} py-4 px-6 text-lg shadow-xl border border-banquise-blue-lightest/20`} |                 <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | ||||||
|               onClick={onClose} |                   <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /> | ||||||
|  |                 </svg> | ||||||
|  |               } | ||||||
|  |               onClick={() => { | ||||||
|  |                 window.open(URLS.services.auth, '_blank'); | ||||||
|  |                 onClose(); | ||||||
|  |               }} | ||||||
|  |               className="w-full shadow-xl" | ||||||
|             > |             > | ||||||
|               <svg className="w-6 h-6 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |  | ||||||
|                 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /> |  | ||||||
|               </svg> |  | ||||||
|               Se connecter |               Se connecter | ||||||
|             </a> |             </Button> | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
| 
 | 
 | ||||||
|         {/* Footer */} |         {/* Effet de gradient overlay */} | ||||||
|         <div className="absolute bottom-6 left-6 right-6"> |         <div className="absolute inset-0 bg-gradient-to-b from-transparent via-transparent to-banquise-blue-dark/10 pointer-events-none" /> | ||||||
|           <div className="text-center py-4 border-t border-banquise-blue-lightest/20"> |  | ||||||
|             <p className="text-white/50 text-sm"> |  | ||||||
|               © 2024 {SITE_CONFIG.name} |  | ||||||
|             </p> |  | ||||||
|           </div> |  | ||||||
|         </div> |  | ||||||
| 
 |  | ||||||
|         {/* Effet glassmorphism */} |  | ||||||
|         <div className="absolute inset-0 bg-gradient-to-b from-transparent via-transparent to-banquise-blue-dark/20 pointer-events-none"></div> |  | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|   ); |   ); | ||||||
|  | |||||||
							
								
								
									
										120
									
								
								banquise-website/src/components/layout/ModernNavigation.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								banquise-website/src/components/layout/ModernNavigation.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,120 @@ | |||||||
|  | import React from 'react'; | ||||||
|  | import { useScrollEffects } from '../../hooks/useScrollEffects'; | ||||||
|  | import { mergeClasses as cn } from '../../styles/designSystem'; | ||||||
|  | import { Logo } from './navbar/Logo'; | ||||||
|  | import { NavLinks } from './navbar/NavLinks'; | ||||||
|  | import { ActionButtons } from './navbar/ActionButtons'; | ||||||
|  | import { MobileMenuButton } from './navbar/MobileMenuButton'; | ||||||
|  | import { MobileMenu } from './MobileMenu'; | ||||||
|  | import type { Translation } from '../../types/i18n'; | ||||||
|  | 
 | ||||||
|  | interface ModernNavigationProps { | ||||||
|  |   translations: Translation['navigation']; | ||||||
|  |   languageSwitcher: React.ReactElement; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const ModernNavigation: React.FC<ModernNavigationProps> = ({  | ||||||
|  |   translations,  | ||||||
|  |   languageSwitcher  | ||||||
|  | }) => { | ||||||
|  |   const { scrolled } = useScrollEffects(); | ||||||
|  |   const [mobileMenuOpen, setMobileMenuOpen] = React.useState(false); | ||||||
|  | 
 | ||||||
|  |   // Fermer le menu mobile lors du redimensionnement
 | ||||||
|  |   React.useEffect(() => { | ||||||
|  |     const handleResize = () => { | ||||||
|  |       if (window.innerWidth >= 768) { | ||||||
|  |         setMobileMenuOpen(false); | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     window.addEventListener('resize', handleResize); | ||||||
|  |     return () => window.removeEventListener('resize', handleResize); | ||||||
|  |   }, []); | ||||||
|  | 
 | ||||||
|  |   // Empêcher le scroll du body quand le menu mobile est ouvert
 | ||||||
|  |   React.useEffect(() => { | ||||||
|  |     if (mobileMenuOpen) { | ||||||
|  |       document.body.style.overflow = 'hidden'; | ||||||
|  |     } else { | ||||||
|  |       document.body.style.overflow = 'unset'; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return () => { | ||||||
|  |       document.body.style.overflow = 'unset'; | ||||||
|  |     }; | ||||||
|  |   }, [mobileMenuOpen]); | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <> | ||||||
|  |       {/* Navigation Bar */} | ||||||
|  |       <nav  | ||||||
|  |         className={cn( | ||||||
|  |           'fixed top-0 left-0 right-0 z-50 transition-all duration-500 ease-out', | ||||||
|  |           // Background adaptatif selon le scroll
 | ||||||
|  |           scrolled  | ||||||
|  |             ? 'bg-banquise-blue-dark/95 backdrop-blur-xl shadow-2xl border-b border-banquise-blue-lightest/30'  | ||||||
|  |             : 'bg-banquise-blue-dark/90 backdrop-blur-lg shadow-xl border-b border-banquise-blue-lightest/20', | ||||||
|  |           // Animation de hauteur
 | ||||||
|  |           'will-change-auto' | ||||||
|  |         )} | ||||||
|  |       > | ||||||
|  |         <div className="max-w-7xl mx-auto"> | ||||||
|  |           <div className={cn( | ||||||
|  |             'flex justify-between items-center px-4 sm:px-6 lg:px-8 transition-all duration-300', | ||||||
|  |             scrolled ? 'h-16' : 'h-18 lg:h-20' | ||||||
|  |           )}> | ||||||
|  |              | ||||||
|  |             {/* Logo Section */} | ||||||
|  |             <Logo scrolled={scrolled} /> | ||||||
|  | 
 | ||||||
|  |             {/* Navigation Links (Desktop) */} | ||||||
|  |             <NavLinks  | ||||||
|  |               translations={translations} | ||||||
|  |               scrolled={scrolled} | ||||||
|  |               className="flex-1 justify-center ml-8" | ||||||
|  |             /> | ||||||
|  | 
 | ||||||
|  |             {/* Action Buttons (Desktop) */} | ||||||
|  |             <ActionButtons  | ||||||
|  |               scrolled={scrolled} | ||||||
|  |               languageSwitcher={languageSwitcher} | ||||||
|  |             /> | ||||||
|  | 
 | ||||||
|  |             {/* Mobile Menu Button */} | ||||||
|  |             <MobileMenuButton | ||||||
|  |               isOpen={mobileMenuOpen} | ||||||
|  |               onClick={() => setMobileMenuOpen(!mobileMenuOpen)} | ||||||
|  |             /> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         {/* Effet de bordure dégradée moderne */} | ||||||
|  |         <div className="absolute bottom-0 left-0 right-0"> | ||||||
|  |           <div className="h-px bg-gradient-to-r from-transparent via-banquise-blue-lightest/40 to-transparent" /> | ||||||
|  |           <div className="h-px bg-gradient-to-r from-transparent via-banquise-blue-light/20 to-transparent blur-sm" /> | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         {/* Effet de glow subtil */} | ||||||
|  |         <div className={cn( | ||||||
|  |           'absolute inset-0 pointer-events-none transition-opacity duration-500', | ||||||
|  |           'bg-gradient-to-b from-banquise-blue-light/5 to-transparent', | ||||||
|  |           scrolled ? 'opacity-60' : 'opacity-100' | ||||||
|  |         )} /> | ||||||
|  |       </nav> | ||||||
|  | 
 | ||||||
|  |       {/* Spacer pour compenser la navbar fixed */} | ||||||
|  |       <div className={cn( | ||||||
|  |         'transition-all duration-300', | ||||||
|  |         scrolled ? 'h-16' : 'h-18 lg:h-20' | ||||||
|  |       )} /> | ||||||
|  | 
 | ||||||
|  |       {/* Menu Mobile */} | ||||||
|  |       <MobileMenu | ||||||
|  |         isOpen={mobileMenuOpen} | ||||||
|  |         onClose={() => setMobileMenuOpen(false)} | ||||||
|  |         translations={translations} | ||||||
|  |       /> | ||||||
|  |     </> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
| @ -3,8 +3,14 @@ import { MobileMenu } from './MobileMenu'; | |||||||
| import banquiseServer from '/src/assets/banquise_server.svg' | import banquiseServer from '/src/assets/banquise_server.svg' | ||||||
| import { URLS, SITE_CONFIG } from '../../config/constants'; | import { URLS, SITE_CONFIG } from '../../config/constants'; | ||||||
| import { commonStyles } from '../../styles/components'; | import { commonStyles } from '../../styles/components'; | ||||||
|  | import type { Translation } from '../../types/i18n'; | ||||||
| 
 | 
 | ||||||
| export const Navigation: React.FC = () => { | interface NavigationProps { | ||||||
|  |   translations: Translation['navigation']; | ||||||
|  |   languageSwitcher: React.ReactElement; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const Navigation: React.FC<NavigationProps> = ({ translations, languageSwitcher }) => { | ||||||
|   const [mobileMenuOpen, setMobileMenuOpen] = useState(false); |   const [mobileMenuOpen, setMobileMenuOpen] = useState(false); | ||||||
|   const [scrolled, setScrolled] = useState(false); |   const [scrolled, setScrolled] = useState(false); | ||||||
| 
 | 
 | ||||||
| @ -13,7 +19,6 @@ export const Navigation: React.FC = () => { | |||||||
|       const isScrolled = window.scrollY > 20; |       const isScrolled = window.scrollY > 20; | ||||||
|       setScrolled(isScrolled); |       setScrolled(isScrolled); | ||||||
|     }; |     }; | ||||||
| 
 |  | ||||||
|     window.addEventListener('scroll', handleScroll); |     window.addEventListener('scroll', handleScroll); | ||||||
|     return () => window.removeEventListener('scroll', handleScroll); |     return () => window.removeEventListener('scroll', handleScroll); | ||||||
|   }, []); |   }, []); | ||||||
| @ -24,7 +29,6 @@ export const Navigation: React.FC = () => { | |||||||
|         setMobileMenuOpen(false); |         setMobileMenuOpen(false); | ||||||
|       } |       } | ||||||
|     }; |     }; | ||||||
| 
 |  | ||||||
|     window.addEventListener('resize', handleResize); |     window.addEventListener('resize', handleResize); | ||||||
|     return () => window.removeEventListener('resize', handleResize); |     return () => window.removeEventListener('resize', handleResize); | ||||||
|   }, []); |   }, []); | ||||||
| @ -32,22 +36,21 @@ export const Navigation: React.FC = () => { | |||||||
|   return ( |   return ( | ||||||
|     <> |     <> | ||||||
|       <nav className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${ |       <nav className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${ | ||||||
|         scrolled  |         scrolled | ||||||
|           ? 'bg-banquise-blue-dark/98 backdrop-blur-xl shadow-2xl border-b border-banquise-blue-lightest/30'  |           ? 'bg-banquise-blue-dark/98 backdrop-blur-xl shadow-2xl border-b border-banquise-blue-lightest/30' | ||||||
|           : 'bg-banquise-blue-dark/95 backdrop-blur-lg shadow-xl border-b border-banquise-blue-lightest/20' |           : 'bg-banquise-blue-dark/95 backdrop-blur-lg shadow-xl border-b border-banquise-blue-lightest/20' | ||||||
|       }`}>
 |       }`}>
 | ||||||
|         <div className={commonStyles.layout.container}> |         <div className={commonStyles.layout.container}> | ||||||
|           <div className="flex justify-between items-center h-16 sm:h-18 lg:h-20 px-4 sm:px-6 lg:px-8"> |           <div className="flex justify-between items-center h-16 sm:h-18 lg:h-20 px-4 sm:px-6 lg:px-8"> | ||||||
|              |  | ||||||
|             {/* Logo section */} |             {/* Logo section */} | ||||||
|             <div className="flex items-center space-x-3 sm:space-x-4 group"> |             <div className="flex items-center space-x-3 sm:space-x-4 group"> | ||||||
|               <div className="relative"> |               <div className="relative"> | ||||||
|                 <div className="absolute inset-0 bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20 rounded-full blur-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div> |                 <div className="absolute inset-0 bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20 rounded-full blur-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div> | ||||||
|                 <img  |                 <img | ||||||
|                   src={banquiseServer} |                   src={banquiseServer} | ||||||
|                   alt="Logo La Banquise"  |                   alt="Logo La Banquise" | ||||||
|                   className="h-10 sm:h-12 lg:h-14 w-auto relative z-10 transition-transform duration-300 group-hover:scale-110"  |                   className="h-10 sm:h-12 lg:h-14 w-auto relative z-10 transition-transform duration-300 group-hover:scale-110" | ||||||
|                   style={{ filter: 'drop-shadow(0 0 12px rgba(168, 218, 255, 0.4))' }}  |                   style={{ filter: 'drop-shadow(0 0 12px rgba(168, 218, 255, 0.4))' }} | ||||||
|                 /> |                 /> | ||||||
|               </div> |               </div> | ||||||
|               <div className="hidden sm:block"> |               <div className="hidden sm:block"> | ||||||
| @ -62,20 +65,30 @@ export const Navigation: React.FC = () => { | |||||||
| 
 | 
 | ||||||
|             {/* Navigation links desktop */} |             {/* Navigation links desktop */} | ||||||
|             <div className="hidden md:flex items-center space-x-1 lg:space-x-2"> |             <div className="hidden md:flex items-center space-x-1 lg:space-x-2"> | ||||||
|               <a href="#services" className={commonStyles.nav.link}> |               <a href="#home" className={commonStyles.nav.link}> | ||||||
|                 <span className="relative z-10">Services</span> |                 <span className="relative z-10">{translations.home}</span> | ||||||
|  |                 <div className="absolute inset-0 bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div> | ||||||
|  |               </a> | ||||||
|  |               <a href="#services" className={commonStyles.nav.link}> | ||||||
|  |                 <span className="relative z-10">{translations.services}</span> | ||||||
|                 <div className="absolute inset-0 bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div> |                 <div className="absolute inset-0 bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div> | ||||||
|               </a> |               </a> | ||||||
|                |  | ||||||
|               <a href="#about" className={commonStyles.nav.link}> |               <a href="#about" className={commonStyles.nav.link}> | ||||||
|                 <span className="relative z-10">À propos</span> |                 <span className="relative z-10">{translations.about}</span> | ||||||
|  |                 <div className="absolute inset-0 bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div> | ||||||
|  |               </a> | ||||||
|  |               <a href="#contact" className={commonStyles.nav.link}> | ||||||
|  |                 <span className="relative z-10">{translations.contact}</span> | ||||||
|                 <div className="absolute inset-0 bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div> |                 <div className="absolute inset-0 bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div> | ||||||
|               </a> |               </a> | ||||||
|             </div> |             </div> | ||||||
| 
 | 
 | ||||||
|             {/* Action buttons desktop */} |             {/* Action buttons desktop */} | ||||||
|             <div className="hidden md:flex items-center space-x-3 lg:space-x-4"> |             <div className="hidden md:flex items-center space-x-3 lg:space-x-4"> | ||||||
|               <a  |               {/* Language switcher */} | ||||||
|  |               {languageSwitcher} | ||||||
|  |                | ||||||
|  |               <a | ||||||
|                 href={URLS.social.discord} |                 href={URLS.social.discord} | ||||||
|                 target="_blank" |                 target="_blank" | ||||||
|                 rel="noopener noreferrer" |                 rel="noopener noreferrer" | ||||||
| @ -89,20 +102,19 @@ export const Navigation: React.FC = () => { | |||||||
|                   <span>Discord</span> |                   <span>Discord</span> | ||||||
|                 </div> |                 </div> | ||||||
|               </a> |               </a> | ||||||
|                |               <a | ||||||
|               <a  |  | ||||||
|                 href={URLS.services.auth} |                 href={URLS.services.auth} | ||||||
|                 target="_blank" |                 target="_blank" | ||||||
|                 rel="noopener noreferrer" |                 rel="noopener noreferrer" | ||||||
|                 className={`${commonStyles.buttons.auth} ${ |                 className={`${commonStyles.buttons.auth} ${ | ||||||
|                   scrolled  |                   scrolled | ||||||
|                     ? `${commonStyles.gradients.primary} border border-banquise-blue-lightest/30 hover:shadow-banquise-blue/25`  |                     ? `${commonStyles.gradients.primary} border border-banquise-blue-lightest/30 hover:shadow-banquise-blue/25` | ||||||
|                     : 'bg-gradient-to-r from-banquise-blue-light to-banquise-blue border-2 border-white/20 hover:shadow-banquise-blue-light/25' |                     : 'bg-gradient-to-r from-banquise-blue-light to-banquise-blue border-2 border-white/20 hover:shadow-banquise-blue-light/25' | ||||||
|                 }`}
 |                 }`}
 | ||||||
|               > |               > | ||||||
|                 <div className={`absolute inset-0 transition-opacity duration-300 opacity-0 group-hover:opacity-100 ${ |                 <div className={`absolute inset-0 transition-opacity duration-300 opacity-0 group-hover:opacity-100 ${ | ||||||
|                   scrolled  |                   scrolled | ||||||
|                     ? 'bg-gradient-to-r from-banquise-blue-light to-banquise-blue'  |                     ? 'bg-gradient-to-r from-banquise-blue-light to-banquise-blue' | ||||||
|                     : 'bg-gradient-to-r from-white/10 to-banquise-blue-lightest/20' |                     : 'bg-gradient-to-r from-white/10 to-banquise-blue-lightest/20' | ||||||
|                 }`}></div>
 |                 }`}></div>
 | ||||||
|                 <div className="relative z-10 flex items-center space-x-2"> |                 <div className="relative z-10 flex items-center space-x-2"> | ||||||
| @ -115,32 +127,32 @@ export const Navigation: React.FC = () => { | |||||||
|             </div> |             </div> | ||||||
| 
 | 
 | ||||||
|             {/* Mobile menu button */} |             {/* Mobile menu button */} | ||||||
|             <button  |             <button | ||||||
|               className="md:hidden relative p-3 rounded-xl bg-white/10 hover:bg-white/20 transition-all duration-300 hover:scale-105 active:scale-95 group" |               className="md:hidden relative p-3 rounded-xl bg-white/10 hover:bg-white/20 transition-all duration-300 hover:scale-105 active:scale-95 group" | ||||||
|               onClick={() => setMobileMenuOpen(!mobileMenuOpen)} |               onClick={() => setMobileMenuOpen(!mobileMenuOpen)} | ||||||
|               aria-label={mobileMenuOpen ? "Fermer le menu" : "Ouvrir le menu"} |               aria-label={mobileMenuOpen ? "Fermer le menu" : "Ouvrir le menu"} | ||||||
|               aria-expanded={mobileMenuOpen} |               aria-expanded={mobileMenuOpen} | ||||||
|             > |             > | ||||||
|               <div className="w-6 h-6 relative"> |               <div className="w-6 h-6 relative"> | ||||||
|                 <span className={`absolute block w-6 h-0.5 bg-white transition-all duration-300 ${mobileMenuOpen ? 'rotate-45 top-3' : 'top-1'}`}></span> |                 <span className={`absolute block w-6 h-6 bg-white transition-all duration-300 ${mobileMenuOpen ? 'rotate-45 top-3' : 'top-1'}`}></span> | ||||||
|                 <span className={`absolute block w-6 h-0.5 bg-white transition-all duration-300 top-3 ${mobileMenuOpen ? 'opacity-0 scale-0' : 'opacity-100'}`}></span> |                 <span className={`absolute block w-6 h-0.5 bg-white transition-all duration-300 top-3 ${mobileMenuOpen ? 'opacity-0 scale-0' : 'opacity-100'}`}></span> | ||||||
|                 <span className={`absolute block w-6 h-0.5 bg-white transition-all duration-300 ${mobileMenuOpen ? '-rotate-45 top-3' : 'top-5'}`}></span> |                 <span className={`absolute block w-6 h-0.5 bg-white transition-all duration-300 ${mobileMenuOpen ? '-rotate-45 top-3' : 'top-5'}`}></span> | ||||||
|               </div> |               </div> | ||||||
|             </button> |             </button> | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
| 
 |  | ||||||
|         {/* Glassmorphism effect bar */} |         {/* Glassmorphism effect bar */} | ||||||
|         <div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-banquise-blue-lightest/30 to-transparent"></div> |         <div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-banquise-blue-lightest/30 to-transparent"></div> | ||||||
|       </nav> |       </nav> | ||||||
| 
 |        | ||||||
|       {/* Spacer pour compenser la navbar fixed */} |       {/* Spacer pour compenser la navbar fixed */} | ||||||
|       <div className="h-16 sm:h-18 lg:h-20"></div> |       <div className="h-16 sm:h-18 lg:h-20"></div> | ||||||
| 
 |        | ||||||
|       {/* Menu mobile */} |       {/* Menu mobile */} | ||||||
|       <MobileMenu  |       <MobileMenu | ||||||
|         isOpen={mobileMenuOpen}  |         isOpen={mobileMenuOpen} | ||||||
|         onClose={() => setMobileMenuOpen(false)}  |         onClose={() => setMobileMenuOpen(false)} | ||||||
|  |         translations={translations} | ||||||
|       /> |       /> | ||||||
|     </> |     </> | ||||||
|   ); |   ); | ||||||
|  | |||||||
| @ -0,0 +1,74 @@ | |||||||
|  | import React from 'react'; | ||||||
|  | import { Button } from '../../common/Button'; | ||||||
|  | import { mergeClasses as cn } from '../../../styles/designSystem'; | ||||||
|  | import { URLS } from '../../../config/constants'; | ||||||
|  | 
 | ||||||
|  | interface ActionButtonsProps { | ||||||
|  |   scrolled: boolean; | ||||||
|  |   languageSwitcher: React.ReactElement; | ||||||
|  |   className?: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const ActionButtons: React.FC<ActionButtonsProps> = ({  | ||||||
|  |   scrolled,  | ||||||
|  |   languageSwitcher,  | ||||||
|  |   className  | ||||||
|  | }) => { | ||||||
|  |   return ( | ||||||
|  |     <div className={cn('hidden md:flex items-center space-x-3', className)}> | ||||||
|  |       {/* Language Switcher */} | ||||||
|  |       <div className="relative"> | ||||||
|  |         {languageSwitcher} | ||||||
|  |       </div> | ||||||
|  |        | ||||||
|  |       {/* Discord Button */} | ||||||
|  |       <Button | ||||||
|  |         variant="discord" | ||||||
|  |         size="sm" | ||||||
|  |         leftIcon={ | ||||||
|  |           <svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"> | ||||||
|  |             <path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.211.375-.445.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03z"/> | ||||||
|  |           </svg> | ||||||
|  |         } | ||||||
|  |         onClick={() => window.open(URLS.social.discord, '_blank')} | ||||||
|  |         className="hidden lg:flex" | ||||||
|  |       > | ||||||
|  |         Discord | ||||||
|  |       </Button> | ||||||
|  | 
 | ||||||
|  |       {/* Discord Icon Only (tablet) */} | ||||||
|  |       <Button | ||||||
|  |         variant="discord" | ||||||
|  |         size="sm" | ||||||
|  |         onClick={() => window.open(URLS.social.discord, '_blank')} | ||||||
|  |         className="lg:hidden" | ||||||
|  |         aria-label="Rejoindre Discord" | ||||||
|  |       > | ||||||
|  |         <svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"> | ||||||
|  |           <path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.211.375-.445.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03z"/> | ||||||
|  |         </svg> | ||||||
|  |       </Button> | ||||||
|  |        | ||||||
|  |       {/* Auth Button */} | ||||||
|  |       <Button | ||||||
|  |         variant="primary" | ||||||
|  |         size="sm" | ||||||
|  |         leftIcon={ | ||||||
|  |           <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | ||||||
|  |             <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /> | ||||||
|  |           </svg> | ||||||
|  |         } | ||||||
|  |         onClick={() => window.open(URLS.services.auth, '_blank')} | ||||||
|  |         className={cn( | ||||||
|  |           'transition-all duration-300', | ||||||
|  |           scrolled  | ||||||
|  |             ? 'shadow-md hover:shadow-lg'  | ||||||
|  |             : 'shadow-lg hover:shadow-xl' | ||||||
|  |         )} | ||||||
|  |       > | ||||||
|  |         <span className="hidden lg:inline">Connexion</span> | ||||||
|  |         <span className="lg:hidden">Se connecter</span> | ||||||
|  |       </Button> | ||||||
|  |     </div> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
							
								
								
									
										52
									
								
								banquise-website/src/components/layout/navbar/Logo.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								banquise-website/src/components/layout/navbar/Logo.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | |||||||
|  | import React from 'react'; | ||||||
|  | import { mergeClasses as cn } from '../../../styles/designSystem'; | ||||||
|  | import banquiseServer from '/src/assets/banquise_server.svg'; | ||||||
|  | import { SITE_CONFIG } from '../../../config/constants'; | ||||||
|  | 
 | ||||||
|  | interface LogoProps { | ||||||
|  |   scrolled: boolean; | ||||||
|  |   className?: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const Logo: React.FC<LogoProps> = ({ scrolled, className }) => { | ||||||
|  |   return ( | ||||||
|  |     <div className={cn('flex items-center group cursor-pointer', className)}> | ||||||
|  |       {/* Logo avec effet glow */} | ||||||
|  |       <div className="relative flex items-center"> | ||||||
|  |         <div className="absolute inset-0 bg-gradient-to-r from-banquise-blue-light/30 to-banquise-blue/30 rounded-full blur-md opacity-0 group-hover:opacity-100 transition-all duration-300 scale-110"></div> | ||||||
|  |         <div className={cn( | ||||||
|  |           'relative flex items-center justify-center rounded-full p-2 bg-white/10 backdrop-blur-sm border border-white/20 transition-all duration-300', | ||||||
|  |           'group-hover:bg-white/20 group-hover:scale-105 group-hover:border-white/30' | ||||||
|  |         )}> | ||||||
|  |           <img | ||||||
|  |             src={banquiseServer} | ||||||
|  |             alt="Logo La Banquise" | ||||||
|  |             className={cn( | ||||||
|  |               'transition-all duration-300 group-hover:scale-110', | ||||||
|  |               scrolled ? 'h-8 w-8' : 'h-10 w-10' | ||||||
|  |             )} | ||||||
|  |             style={{ filter: 'drop-shadow(0 4px 12px rgba(168, 218, 255, 0.4))' }} | ||||||
|  |           /> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |        | ||||||
|  |       {/* Brand text avec animation */} | ||||||
|  |       <div className="ml-3 hidden sm:block"> | ||||||
|  |         <h1 className={cn( | ||||||
|  |           'font-heading font-bold text-white tracking-tight transition-all duration-300', | ||||||
|  |           scrolled ? 'text-lg' : 'text-xl lg:text-2xl', | ||||||
|  |           'group-hover:text-banquise-blue-lightest' | ||||||
|  |         )}> | ||||||
|  |           {SITE_CONFIG.name} | ||||||
|  |         </h1> | ||||||
|  |         <p className={cn( | ||||||
|  |           'text-banquise-blue-lightest/70 font-medium transition-all duration-300', | ||||||
|  |           scrolled ? 'text-xs' : 'text-sm', | ||||||
|  |           'group-hover:text-banquise-blue-lightest/90' | ||||||
|  |         )}> | ||||||
|  |           {SITE_CONFIG.tagline} | ||||||
|  |         </p> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
| @ -0,0 +1,49 @@ | |||||||
|  | import React from 'react'; | ||||||
|  | import { mergeClasses as cn } from '../../../styles/designSystem'; | ||||||
|  | 
 | ||||||
|  | interface MobileMenuButtonProps { | ||||||
|  |   isOpen: boolean; | ||||||
|  |   onClick: () => void; | ||||||
|  |   className?: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const MobileMenuButton: React.FC<MobileMenuButtonProps> = ({  | ||||||
|  |   isOpen,  | ||||||
|  |   onClick,  | ||||||
|  |   className  | ||||||
|  | }) => { | ||||||
|  |   return ( | ||||||
|  |     <button | ||||||
|  |       className={cn( | ||||||
|  |         'md:hidden relative p-3 rounded-xl transition-all duration-300 group', | ||||||
|  |         'bg-white/10 hover:bg-white/20 active:bg-white/25', | ||||||
|  |         'border border-white/20 hover:border-white/30', | ||||||
|  |         'hover:scale-105 active:scale-95', | ||||||
|  |         'focus:outline-none focus:ring-2 focus:ring-banquise-blue-light/50', | ||||||
|  |         className | ||||||
|  |       )} | ||||||
|  |       onClick={onClick} | ||||||
|  |       aria-label={isOpen ? "Fermer le menu" : "Ouvrir le menu"} | ||||||
|  |       aria-expanded={isOpen} | ||||||
|  |     > | ||||||
|  |       {/* Hamburger Icon avec animation moderne */} | ||||||
|  |       <div className="w-6 h-6 relative flex flex-col justify-center items-center"> | ||||||
|  |         <span className={cn( | ||||||
|  |           'absolute block h-0.5 w-6 bg-white rounded-full transition-all duration-300 ease-out transform', | ||||||
|  |           isOpen ? 'rotate-45 translate-y-0' : '-translate-y-2' | ||||||
|  |         )} /> | ||||||
|  |         <span className={cn( | ||||||
|  |           'absolute block h-0.5 w-6 bg-white rounded-full transition-all duration-300 ease-out', | ||||||
|  |           isOpen ? 'opacity-0 scale-0' : 'opacity-100 scale-100' | ||||||
|  |         )} /> | ||||||
|  |         <span className={cn( | ||||||
|  |           'absolute block h-0.5 w-6 bg-white rounded-full transition-all duration-300 ease-out transform', | ||||||
|  |           isOpen ? '-rotate-45 translate-y-0' : 'translate-y-2' | ||||||
|  |         )} /> | ||||||
|  |       </div> | ||||||
|  |        | ||||||
|  |       {/* Subtle glow effect on hover */} | ||||||
|  |       <div className="absolute inset-0 rounded-xl bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20 opacity-0 group-hover:opacity-100 transition-opacity duration-300" /> | ||||||
|  |     </button> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
							
								
								
									
										174
									
								
								banquise-website/src/components/layout/navbar/NavLinks.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								banquise-website/src/components/layout/navbar/NavLinks.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,174 @@ | |||||||
|  | import React from 'react'; | ||||||
|  | import { mergeClasses as cn } from '../../../styles/designSystem'; | ||||||
|  | import type { Translation } from '../../../types/i18n'; | ||||||
|  | 
 | ||||||
|  | interface NavLinksProps { | ||||||
|  |   translations: Translation['navigation']; | ||||||
|  |   scrolled: boolean; | ||||||
|  |   className?: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface NavLinkProps { | ||||||
|  |   href: string; | ||||||
|  |   children: React.ReactNode; | ||||||
|  |   isActive?: boolean; | ||||||
|  |   onClick?: () => void; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const NavLink: React.FC<NavLinkProps> = ({ href, children, isActive = false, onClick }) => { | ||||||
|  |   return ( | ||||||
|  |     <a | ||||||
|  |       href={href} | ||||||
|  |       onClick={onClick} | ||||||
|  |       className={cn( | ||||||
|  |         'relative px-4 py-2 text-sm font-medium transition-all duration-300 rounded-lg group', | ||||||
|  |         'hover:text-white focus:outline-none focus:ring-2 focus:ring-banquise-blue-light/50', | ||||||
|  |         isActive  | ||||||
|  |           ? 'text-white bg-white/20 shadow-lg'  | ||||||
|  |           : 'text-white/80 hover:bg-white/10' | ||||||
|  |       )} | ||||||
|  |     > | ||||||
|  |       <span className="relative z-10">{children}</span> | ||||||
|  |        | ||||||
|  |       {/* Hover effect */} | ||||||
|  |       <div className={cn( | ||||||
|  |         'absolute inset-0 rounded-lg bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20', | ||||||
|  |         'opacity-0 group-hover:opacity-100 transition-all duration-300 scale-95 group-hover:scale-100' | ||||||
|  |       )} /> | ||||||
|  |        | ||||||
|  |       {/* Active indicator */} | ||||||
|  |       {isActive && ( | ||||||
|  |         <div className="absolute bottom-0 left-1/2 transform -translate-x-1/2 w-6 h-0.5 bg-banquise-blue-lightest rounded-full" /> | ||||||
|  |       )} | ||||||
|  |     </a> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const NavLinks: React.FC<NavLinksProps> = ({ translations, className }) => { | ||||||
|  |   const [activeSection, setActiveSection] = React.useState<string>('home'); | ||||||
|  | 
 | ||||||
|  |   // Observer pour détecter la section active
 | ||||||
|  |   React.useEffect(() => { | ||||||
|  |     const handleScroll = () => { | ||||||
|  |       const scrollPosition = window.scrollY; | ||||||
|  |       const windowHeight = window.innerHeight; | ||||||
|  |        | ||||||
|  |       // Si on est en haut de la page (moins de 100px du haut), on active "home"
 | ||||||
|  |       if (scrollPosition < 100) { | ||||||
|  |         setActiveSection('home'); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // Sinon, on utilise l'intersection observer logic
 | ||||||
|  |       const sections = ['home', 'services', 'about']; | ||||||
|  |       let currentSection = 'home'; | ||||||
|  | 
 | ||||||
|  |       sections.forEach((sectionId) => { | ||||||
|  |         const element = document.getElementById(sectionId); | ||||||
|  |         if (element) { | ||||||
|  |           const rect = element.getBoundingClientRect(); | ||||||
|  |           const sectionTop = rect.top + scrollPosition; | ||||||
|  |            | ||||||
|  |           // Si la section est visible dans le viewport
 | ||||||
|  |           if (scrollPosition >= sectionTop - windowHeight / 3) { | ||||||
|  |             currentSection = sectionId; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       setActiveSection(currentSection); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     // Écouter le scroll
 | ||||||
|  |     window.addEventListener('scroll', handleScroll); | ||||||
|  |     // Appeler une fois au chargement
 | ||||||
|  |     handleScroll(); | ||||||
|  | 
 | ||||||
|  |     return () => { | ||||||
|  |       window.removeEventListener('scroll', handleScroll); | ||||||
|  |     }; | ||||||
|  |   }, []); | ||||||
|  | 
 | ||||||
|  |   // Observer pour détecter la section active avec IntersectionObserver (fallback)
 | ||||||
|  |   React.useEffect(() => { | ||||||
|  |     const observer = new IntersectionObserver( | ||||||
|  |       (entries) => { | ||||||
|  |         entries.forEach((entry) => { | ||||||
|  |           if (entry.isIntersecting && window.scrollY > 100) { | ||||||
|  |             setActiveSection(entry.target.id); | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |       }, | ||||||
|  |       {  | ||||||
|  |         threshold: 0.3, | ||||||
|  |         rootMargin: '-100px 0px -100px 0px' | ||||||
|  |       } | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     const sections = ['home', 'services', 'about']; | ||||||
|  |     sections.forEach((id) => { | ||||||
|  |       const element = document.getElementById(id); | ||||||
|  |       if (element) observer.observe(element); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     return () => observer.disconnect(); | ||||||
|  |   }, []); | ||||||
|  | 
 | ||||||
|  |   const handleNavClick = (sectionId: string) => { | ||||||
|  |     if (sectionId === 'home') { | ||||||
|  |       // Scroll to top for home section
 | ||||||
|  |       window.scrollTo({  | ||||||
|  |         top: 0,  | ||||||
|  |         behavior: 'smooth'  | ||||||
|  |       }); | ||||||
|  |     } else if (sectionId === 'contact') { | ||||||
|  |       // Open email client for contact
 | ||||||
|  |       window.location.href = 'mailto:contact@la-banquise.fr'; | ||||||
|  |     } else { | ||||||
|  |       // Scroll to specific section
 | ||||||
|  |       const element = document.getElementById(sectionId); | ||||||
|  |       if (element) { | ||||||
|  |         element.scrollIntoView({  | ||||||
|  |           behavior: 'smooth', | ||||||
|  |           block: 'start' | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <nav className={cn('hidden md:flex items-center space-x-1', className)}> | ||||||
|  |       <NavLink  | ||||||
|  |         href="#home"  | ||||||
|  |         isActive={activeSection === 'home'} | ||||||
|  |         onClick={() => handleNavClick('home')} | ||||||
|  |       > | ||||||
|  |         {translations.home} | ||||||
|  |       </NavLink> | ||||||
|  |        | ||||||
|  |       <NavLink  | ||||||
|  |         href="#services"  | ||||||
|  |         isActive={activeSection === 'services'} | ||||||
|  |         onClick={() => handleNavClick('services')} | ||||||
|  |       > | ||||||
|  |         {translations.services} | ||||||
|  |       </NavLink> | ||||||
|  |        | ||||||
|  |       <NavLink  | ||||||
|  |         href="#about"  | ||||||
|  |         isActive={activeSection === 'about'} | ||||||
|  |         onClick={() => handleNavClick('about')} | ||||||
|  |       > | ||||||
|  |         {translations.about} | ||||||
|  |       </NavLink> | ||||||
|  |        | ||||||
|  |       <NavLink  | ||||||
|  |         href="mailto:contact@la-banquise.fr"  | ||||||
|  |         isActive={false} | ||||||
|  |         onClick={() => handleNavClick('contact')} | ||||||
|  |       > | ||||||
|  |         {translations.contact} | ||||||
|  |       </NavLink> | ||||||
|  |     </nav> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
| @ -9,11 +9,10 @@ interface AboutSectionProps { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const AboutSection: React.FC<AboutSectionProps> = ({ openAccordion, toggleAccordion }) => ( | export const AboutSection: React.FC<AboutSectionProps> = ({ openAccordion, toggleAccordion }) => ( | ||||||
|   <section id="about" className="relative bg-gradient-to-b from-banquise-blue-dark/15 to-banquise-blue-dark/20 backdrop-blur-lg py-16 sm:py-20 md:py-24 px-4 sm:px-6 md:px-8 z-2 border-t border-banquise-blue-lightest/20 w-full box-border"> |   <section id="about" className="relative py-16 sm:py-20 md:py-24 px-4 sm:px-6 md:px-8 z-2 w-full box-border"> | ||||||
|     <div className="max-w-4xl mx-auto"> |     <div className="max-w-4xl mx-auto"> | ||||||
|       {/* Header */} |       {/* Header */} | ||||||
|       <div className="text-center mb-12 sm:mb-16 md:mb-20"> |       <div className="text-center mb-12 sm:mb-16 md:mb-20"> | ||||||
|         <div className={commonStyles.layout.divider}></div> |  | ||||||
|         <h2 className={`${commonStyles.text.headingXl} mb-6 sm:mb-8 px-2`} style={{ textShadow: '0 2px 4px rgba(0, 0, 0, 0.2)' }}> |         <h2 className={`${commonStyles.text.headingXl} mb-6 sm:mb-8 px-2`} style={{ textShadow: '0 2px 4px rgba(0, 0, 0, 0.2)' }}> | ||||||
|           À Propos de La Banquise |           À Propos de La Banquise | ||||||
|         </h2> |         </h2> | ||||||
|  | |||||||
| @ -1,8 +1,13 @@ | |||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import banquiseServer from '/src/assets/banquise_server.svg' | import banquiseServer from '/src/assets/banquise_server.svg' | ||||||
|  | import type { Translation } from '../../types/i18n'; | ||||||
| 
 | 
 | ||||||
| export const HeroSection: React.FC = () => ( | interface HeroSectionProps { | ||||||
|   <section className="min-h-[calc(80vh-72px)] flex flex-col justify-center items-center text-center py-12 sm:py-16 md:py-20 w-full max-w-6xl mx-auto px-4 sm:px-6 md:px-8 relative z-3"> |   translations: Translation['hero']; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const HeroSection: React.FC<HeroSectionProps> = ({ translations }) => ( | ||||||
|  |   <section id="home" className="min-h-[calc(80vh-72px)] flex flex-col justify-center items-center text-center py-12 sm:py-16 md:py-20 w-full max-w-6xl mx-auto px-4 sm:px-6 md:px-8 relative z-3"> | ||||||
|     <div className="mb-8 sm:mb-10 md:mb-12 w-32 h-32 sm:w-40 sm:h-40 md:w-48 md:h-48 rounded-full bg-gradient-to-br from-banquise-blue-dark/20 to-banquise-blue/10 p-4 sm:p-5 md:p-6 shadow-2xl backdrop-blur-sm border border-banquise-blue-lightest/30 relative group"> |     <div className="mb-8 sm:mb-10 md:mb-12 w-32 h-32 sm:w-40 sm:h-40 md:w-48 md:h-48 rounded-full bg-gradient-to-br from-banquise-blue-dark/20 to-banquise-blue/10 p-4 sm:p-5 md:p-6 shadow-2xl backdrop-blur-sm border border-banquise-blue-lightest/30 relative group"> | ||||||
|       <img  |       <img  | ||||||
|         src={banquiseServer}  |         src={banquiseServer}  | ||||||
| @ -15,15 +20,15 @@ export const HeroSection: React.FC = () => ( | |||||||
|     </div> |     </div> | ||||||
|      |      | ||||||
|     <h1 className="text-banquise-gray text-3xl sm:text-4xl md:text-5xl lg:text-6xl mb-6 sm:mb-7 md:mb-8 font-extrabold leading-tight max-w-4xl font-heading px-2 relative z-10" style={{ textShadow: '0 2px 10px rgba(0, 0, 0, 0.3)' }}> |     <h1 className="text-banquise-gray text-3xl sm:text-4xl md:text-5xl lg:text-6xl mb-6 sm:mb-7 md:mb-8 font-extrabold leading-tight max-w-4xl font-heading px-2 relative z-10" style={{ textShadow: '0 2px 10px rgba(0, 0, 0, 0.3)' }}> | ||||||
|       Bienvenue sur La Banquise |       {translations.title} | ||||||
|     </h1> |     </h1> | ||||||
|      |      | ||||||
|     <p className="text-banquise-gray text-lg sm:text-xl md:text-2xl mb-8 sm:mb-10 md:mb-12 max-w-3xl font-normal opacity-90 leading-relaxed px-2 relative z-10" style={{ textShadow: '0 1px 4px rgba(0, 0, 0, 0.2)' }}> |     <p className="text-banquise-gray text-lg sm:text-xl md:text-2xl mb-8 sm:mb-10 md:mb-12 max-w-3xl font-normal opacity-90 leading-relaxed px-2 relative z-10" style={{ textShadow: '0 1px 4px rgba(0, 0, 0, 0.2)' }}> | ||||||
|       Association d'hébergement et lab réseau pour tous les étudiants et associations de l'EPITA ! |       {translations.subtitle} | ||||||
|     </p> |     </p> | ||||||
|      |      | ||||||
|     <a href="#services" className="inline-flex items-center justify-center bg-gradient-to-r from-banquise-gray to-white text-banquise-blue-dark border-0 py-4 sm:py-5 px-8 sm:px-10 md:px-12 rounded-2xl text-base sm:text-lg font-bold no-underline shadow-xl transition-all duration-300 min-w-48 sm:min-w-56 md:min-w-64 hover:-translate-y-2 hover:shadow-2xl hover:scale-105 backdrop-blur-sm border border-banquise-blue-lightest/20 mx-4 group relative z-10"> |     <a href="#services" className="inline-flex items-center justify-center bg-gradient-to-r from-banquise-gray to-white text-banquise-blue-dark border-0 py-4 sm:py-5 px-8 sm:px-10 md:px-12 rounded-2xl text-base sm:text-lg font-bold no-underline shadow-xl transition-all duration-300 min-w-48 sm:min-w-56 md:min-w-64 hover:-translate-y-2 hover:shadow-2xl hover:scale-105 backdrop-blur-sm border border-banquise-blue-lightest/20 mx-4 group relative z-10"> | ||||||
|       <span className="text-center text-banquise-blue-dark">Découvrir nos services</span> |       <span className="text-center text-banquise-blue-dark">{translations.cta}</span> | ||||||
|       <span className="ml-2 sm:ml-3 text-lg sm:text-xl transition-transform duration-300 group-hover:translate-x-1 text-banquise-blue-dark">→</span> |       <span className="ml-2 sm:ml-3 text-lg sm:text-xl transition-transform duration-300 group-hover:translate-x-1 text-banquise-blue-dark">→</span> | ||||||
|     </a> |     </a> | ||||||
|   </section> |   </section> | ||||||
|  | |||||||
| @ -1,23 +1,21 @@ | |||||||
| import React from 'react'; | import React from 'react'; | ||||||
|  | import { ServiceCard } from '../common/ServiceCard'; | ||||||
|  | //import { componentStyles } from '../../styles/designSystem';
 | ||||||
|  | import type { Service } from '../../types/service'; | ||||||
| 
 | 
 | ||||||
| // Update the Service interface to match your actual service objects
 |  | ||||||
| interface Service { |  | ||||||
|   name: string; |  | ||||||
|   url: string; |  | ||||||
|   image: string; |  | ||||||
|   icon: string; |  | ||||||
|   description: string; |  | ||||||
|   features: string[]; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Define interface directly in the component file
 |  | ||||||
| interface ServicesSectionProps { | interface ServicesSectionProps { | ||||||
|   services: Service[]; |   services: Service[]; | ||||||
| 
 |  | ||||||
|   onServiceClick: (service: Service) => void; |   onServiceClick: (service: Service) => void; | ||||||
|  |   translations: { | ||||||
|  |     discoverFeatures: string; | ||||||
|  |   }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const ServicesSection: React.FC<ServicesSectionProps> = ({ services, onServiceClick }) => ( | export const ServicesSection: React.FC<ServicesSectionProps> = ({  | ||||||
|  |   services,  | ||||||
|  |   onServiceClick,  | ||||||
|  |   translations  | ||||||
|  | }) => ( | ||||||
|   <section id="services" className="relative z-2 py-12 sm:py-16 md:py-20 w-full max-w-6xl mx-auto px-4 sm:px-6 md:px-8"> |   <section id="services" className="relative z-2 py-12 sm:py-16 md:py-20 w-full max-w-6xl mx-auto px-4 sm:px-6 md:px-8"> | ||||||
|     <div className="w-20 h-1 bg-gradient-to-r from-banquise-blue-lightest to-banquise-blue mx-auto mb-6 sm:mb-8 rounded-full"></div> |     <div className="w-20 h-1 bg-gradient-to-r from-banquise-blue-lightest to-banquise-blue mx-auto mb-6 sm:mb-8 rounded-full"></div> | ||||||
|     <h2 className="text-banquise-gray text-2xl sm:text-3xl md:text-4xl mb-4 sm:mb-6 text-center font-heading font-bold tracking-tight px-2" style={{ textShadow: '0 2px 4px rgba(0, 0, 0, 0.2)' }}> |     <h2 className="text-banquise-gray text-2xl sm:text-3xl md:text-4xl mb-4 sm:mb-6 text-center font-heading font-bold tracking-tight px-2" style={{ textShadow: '0 2px 4px rgba(0, 0, 0, 0.2)' }}> | ||||||
| @ -29,35 +27,12 @@ export const ServicesSection: React.FC<ServicesSectionProps> = ({ services, onSe | |||||||
|      |      | ||||||
|     <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6 sm:gap-8 w-full"> |     <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6 sm:gap-8 w-full"> | ||||||
|       {services.map((service) => ( |       {services.map((service) => ( | ||||||
|         <div |         <ServiceCard | ||||||
|           key={service.name} |           key={service.name} | ||||||
|           className="group relative bg-gradient-to-br from-banquise-blue-dark/10 to-banquise-blue-dark/5 backdrop-blur-lg rounded-2xl p-6 sm:p-8 border border-banquise-blue-lightest/30 transition-all duration-300 cursor-pointer hover:-translate-y-4 hover:shadow-2xl hover:border-banquise-blue-lightest/50 hover:from-banquise-blue-dark/15 hover:to-banquise-blue-dark/8 active:scale-95" |           service={service} | ||||||
|           onClick={() => onServiceClick(service)} |           onServiceClick={onServiceClick} | ||||||
|         > |           discoverFeaturesText={translations.discoverFeatures} | ||||||
|           {/* Icon */} |         /> | ||||||
|           <div className="mb-6 sm:mb-8 w-20 h-20 sm:w-24 sm:h-24 bg-gradient-to-br from-banquise-blue to-banquise-blue-light rounded-2xl flex items-center justify-center text-3xl sm:text-4xl shadow-lg group-hover:scale-110 transition-transform duration-300 mx-auto"> |  | ||||||
|             {service.icon} |  | ||||||
|           </div> |  | ||||||
|            |  | ||||||
|           {/* Service name */} |  | ||||||
|           <h3 className="text-xl sm:text-2xl font-bold text-banquise-gray mb-4 sm:mb-6 font-heading text-center group-hover:text-banquise-blue-lightest transition-colors duration-300"> |  | ||||||
|             {service.name} |  | ||||||
|           </h3> |  | ||||||
|            |  | ||||||
|           {/* Short teaser description */} |  | ||||||
|           <p className="text-banquise-gray/80 leading-relaxed mb-6 sm:mb-8 text-center text-sm sm:text-base"> |  | ||||||
|             {service.description.split('.')[0]}. |  | ||||||
|           </p> |  | ||||||
|            |  | ||||||
|           {/* CTA */} |  | ||||||
|           <div className="flex items-center justify-center text-banquise-blue-light font-bold group-hover:text-banquise-blue-lightest transition-colors duration-300 text-sm sm:text-base"> |  | ||||||
|             <span className="text-center">Découvrir toutes les fonctionnalités</span> |  | ||||||
|             <span className="ml-2 text-lg transition-transform duration-300 group-hover:translate-x-2">→</span> |  | ||||||
|           </div> |  | ||||||
|            |  | ||||||
|           {/* Subtle hover effect */} |  | ||||||
|           <div className="absolute inset-0 bg-gradient-to-br from-banquise-blue-lightest/10 to-banquise-blue/5 rounded-2xl opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none"></div> |  | ||||||
|         </div> |  | ||||||
|       ))} |       ))} | ||||||
|     </div> |     </div> | ||||||
|   </section> |   </section> | ||||||
|  | |||||||
| @ -1,12 +1,5 @@ | |||||||
| import React from 'react'; | import React from 'react'; | ||||||
| 
 | import type { AccordionItemProps } from '../../types'; | ||||||
| // Définir l'interface localement :
 |  | ||||||
| interface AccordionItemProps { |  | ||||||
|   title: string; |  | ||||||
|   children: React.ReactNode; |  | ||||||
|   isOpen: boolean; |  | ||||||
|   onToggle: () => void; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| export const AccordionItem: React.FC<AccordionItemProps> = ({ title, children, isOpen, onToggle }) => ( | export const AccordionItem: React.FC<AccordionItemProps> = ({ title, children, isOpen, onToggle }) => ( | ||||||
|   <div className={`bg-gradient-to-br from-banquise-blue-dark/15 to-banquise-blue-dark/5 backdrop-blur-lg rounded-2xl overflow-hidden border border-banquise-blue-lightest/30 transition-all duration-300 shadow-sm ${isOpen ? 'shadow-xl border-banquise-blue-lightest/50 scale-[1.01]' : ''} hover:shadow-lg hover:border-banquise-blue-lightest/40`}> |   <div className={`bg-gradient-to-br from-banquise-blue-dark/15 to-banquise-blue-dark/5 backdrop-blur-lg rounded-2xl overflow-hidden border border-banquise-blue-lightest/30 transition-all duration-300 shadow-sm ${isOpen ? 'shadow-xl border-banquise-blue-lightest/50 scale-[1.01]' : ''} hover:shadow-lg hover:border-banquise-blue-lightest/40`}> | ||||||
|  | |||||||
							
								
								
									
										37
									
								
								banquise-website/src/components/ui/LanguageSwitcher.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								banquise-website/src/components/ui/LanguageSwitcher.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | |||||||
|  | import React from 'react'; | ||||||
|  | import type { Language } from '../../types/i18n'; | ||||||
|  | 
 | ||||||
|  | interface LanguageSwitcherProps { | ||||||
|  |   currentLanguage: Language; | ||||||
|  |   onLanguageChange: (language: Language) => void; | ||||||
|  |   availableLanguages: Language[]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const LanguageSwitcher: React.FC<LanguageSwitcherProps> = ({ | ||||||
|  |   currentLanguage, | ||||||
|  |   onLanguageChange, | ||||||
|  |   availableLanguages | ||||||
|  | }) => { | ||||||
|  |   const languageNames: Record<Language, string> = { | ||||||
|  |     fr: '🇫🇷 Français', | ||||||
|  |     en: '🇬🇧 English', | ||||||
|  |     //es: '🇪🇸 Español',
 | ||||||
|  |     //de: '🇩🇪 Deutsch'
 | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <div className="relative inline-block"> | ||||||
|  |       <select | ||||||
|  |         value={currentLanguage} | ||||||
|  |         onChange={(e) => onLanguageChange(e.target.value as Language)} | ||||||
|  |         className="bg-banquise-blue-dark/20 border border-banquise-blue-lightest/30 text-banquise-gray rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-banquise-blue-light" | ||||||
|  |       > | ||||||
|  |         {availableLanguages.map((lang) => ( | ||||||
|  |           <option key={lang} value={lang} className="bg-banquise-blue-dark text-banquise-gray"> | ||||||
|  |             {languageNames[lang]} | ||||||
|  |           </option> | ||||||
|  |         ))} | ||||||
|  |       </select> | ||||||
|  |     </div> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
							
								
								
									
										110
									
								
								banquise-website/src/components/ui/ModernLanguageSwitcher.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								banquise-website/src/components/ui/ModernLanguageSwitcher.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,110 @@ | |||||||
|  | import React, { useState } from 'react'; | ||||||
|  | import { mergeClasses as cn } from '../../styles/designSystem'; | ||||||
|  | import type { Language } from '../../types/i18n'; | ||||||
|  | 
 | ||||||
|  | interface ModernLanguageSwitcherProps { | ||||||
|  |   currentLanguage: Language; | ||||||
|  |   onLanguageChange: (language: Language) => void; | ||||||
|  |   availableLanguages: Language[]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const ModernLanguageSwitcher: React.FC<ModernLanguageSwitcherProps> = ({ | ||||||
|  |   currentLanguage, | ||||||
|  |   onLanguageChange, | ||||||
|  |   availableLanguages | ||||||
|  | }) => { | ||||||
|  |   const [isOpen, setIsOpen] = useState(false); | ||||||
|  | 
 | ||||||
|  |   const languageConfig: Record<Language, { name: string; flag: string; nativeName: string }> = { | ||||||
|  |     fr: { name: 'Français', flag: '🇫🇷', nativeName: 'FR' }, | ||||||
|  |     en: { name: 'English', flag: '🇬🇧', nativeName: 'EN' }, | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const currentConfig = languageConfig[currentLanguage]; | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <div className="relative"> | ||||||
|  |       {/* Trigger Button */} | ||||||
|  |       <button | ||||||
|  |         onClick={() => setIsOpen(!isOpen)} | ||||||
|  |         className={cn( | ||||||
|  |           'flex items-center space-x-2 px-3 py-2 rounded-lg transition-all duration-200', | ||||||
|  |           'bg-white/10 hover:bg-white/20 border border-white/20 hover:border-white/30', | ||||||
|  |           'text-white text-sm font-medium', | ||||||
|  |           'focus:outline-none focus:ring-2 focus:ring-banquise-blue-light/50', | ||||||
|  |           'group' | ||||||
|  |         )} | ||||||
|  |         aria-expanded={isOpen} | ||||||
|  |         aria-haspopup="listbox" | ||||||
|  |       > | ||||||
|  |         <span className="text-lg">{currentConfig.flag}</span> | ||||||
|  |         <span className="hidden sm:inline">{currentConfig.nativeName}</span> | ||||||
|  |          | ||||||
|  |         {/* Chevron Icon */} | ||||||
|  |         <svg  | ||||||
|  |           className={cn( | ||||||
|  |             'w-4 h-4 transition-transform duration-200', | ||||||
|  |             isOpen ? 'rotate-180' : 'rotate-0' | ||||||
|  |           )} | ||||||
|  |           fill="none"  | ||||||
|  |           stroke="currentColor"  | ||||||
|  |           viewBox="0 0 24 24" | ||||||
|  |         > | ||||||
|  |           <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /> | ||||||
|  |         </svg> | ||||||
|  |       </button> | ||||||
|  | 
 | ||||||
|  |       {/* Dropdown Menu */} | ||||||
|  |       {isOpen && ( | ||||||
|  |         <> | ||||||
|  |           {/* Backdrop */} | ||||||
|  |           <div  | ||||||
|  |             className="fixed inset-0 z-10"  | ||||||
|  |             onClick={() => setIsOpen(false)} | ||||||
|  |           /> | ||||||
|  |            | ||||||
|  |           {/* Menu */} | ||||||
|  |           <div className={cn( | ||||||
|  |             'absolute right-0 top-full mt-2 z-20', | ||||||
|  |             'bg-white/95 backdrop-blur-xl rounded-xl shadow-2xl border border-white/20', | ||||||
|  |             'min-w-[140px] py-2', | ||||||
|  |             'animate-slideUp' | ||||||
|  |           )}> | ||||||
|  |             {availableLanguages.map((lang) => { | ||||||
|  |               const config = languageConfig[lang]; | ||||||
|  |               const isSelected = lang === currentLanguage; | ||||||
|  |                | ||||||
|  |               return ( | ||||||
|  |                 <button | ||||||
|  |                   key={lang} | ||||||
|  |                   onClick={() => { | ||||||
|  |                     onLanguageChange(lang); | ||||||
|  |                     setIsOpen(false); | ||||||
|  |                   }} | ||||||
|  |                   className={cn( | ||||||
|  |                     'w-full flex items-center space-x-3 px-4 py-2.5 text-sm transition-all duration-200', | ||||||
|  |                     'hover:bg-banquise-blue/10 focus:bg-banquise-blue/10', | ||||||
|  |                     'focus:outline-none', | ||||||
|  |                     isSelected  | ||||||
|  |                       ? 'text-banquise-blue-dark font-semibold bg-banquise-blue/10'  | ||||||
|  |                       : 'text-gray-700 hover:text-banquise-blue-dark' | ||||||
|  |                   )} | ||||||
|  |                   role="option" | ||||||
|  |                   aria-selected={isSelected} | ||||||
|  |                 > | ||||||
|  |                   <span className="text-lg">{config.flag}</span> | ||||||
|  |                   <span className="flex-1 text-left">{config.name}</span> | ||||||
|  |                   {isSelected && ( | ||||||
|  |                     <svg className="w-4 h-4 text-banquise-blue" fill="currentColor" viewBox="0 0 20 20"> | ||||||
|  |                       <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" /> | ||||||
|  |                     </svg> | ||||||
|  |                   )} | ||||||
|  |                 </button> | ||||||
|  |               ); | ||||||
|  |             })} | ||||||
|  |           </div> | ||||||
|  |         </> | ||||||
|  |       )} | ||||||
|  |     </div> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
| @ -1,146 +0,0 @@ | |||||||
| import React, { useEffect, useState } from 'react'; |  | ||||||
| 
 |  | ||||||
| export const ParallaxBackground: React.FC = () => { |  | ||||||
|   const [scrollY, setScrollY] = useState(0); |  | ||||||
| 
 |  | ||||||
|   useEffect(() => { |  | ||||||
|     const handleScroll = () => { |  | ||||||
|       setScrollY(window.scrollY); |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     window.addEventListener('scroll', handleScroll); |  | ||||||
|     return () => window.removeEventListener('scroll', handleScroll); |  | ||||||
|   }, []); |  | ||||||
| 
 |  | ||||||
|   // Éléments flottants avec différentes vitesses de parallaxe
 |  | ||||||
|   const floatingElements = [ |  | ||||||
|     // Serveurs et équipements
 |  | ||||||
|     { icon: '🖥️', x: 10, y: 20, speed: 0.3, size: 'text-2xl', opacity: 0.1 }, |  | ||||||
|     { icon: '🖲️', x: 85, y: 15, speed: 0.2, size: 'text-xl', opacity: 0.08 }, |  | ||||||
|     { icon: '⚙️', x: 75, y: 45, speed: 0.4, size: 'text-3xl', opacity: 0.12 }, |  | ||||||
|     { icon: '🔧', x: 15, y: 60, speed: 0.25, size: 'text-lg', opacity: 0.06 }, |  | ||||||
|     { icon: '💾', x: 90, y: 70, speed: 0.35, size: 'text-2xl', opacity: 0.1 }, |  | ||||||
|      |  | ||||||
|     // Code et développement
 |  | ||||||
|     { icon: '<>', x: 30, y: 35, speed: 0.15, size: 'text-xl', opacity: 0.08, isText: true }, |  | ||||||
|     { icon: '{ }', x: 60, y: 25, speed: 0.28, size: 'text-2xl', opacity: 0.1, isText: true }, |  | ||||||
|     { icon: '#!/bin', x: 5, y: 80, speed: 0.2, size: 'text-sm', opacity: 0.06, isText: true }, |  | ||||||
|     { icon: 'git', x: 80, y: 85, speed: 0.32, size: 'text-lg', opacity: 0.08, isText: true }, |  | ||||||
|      |  | ||||||
|     // Réseau et connectivité
 |  | ||||||
|     { icon: '🌐', x: 45, y: 10, speed: 0.22, size: 'text-2xl', opacity: 0.09 }, |  | ||||||
|     { icon: '🔗', x: 25, y: 75, speed: 0.18, size: 'text-xl', opacity: 0.07 }, |  | ||||||
|     { icon: '📡', x: 70, y: 55, speed: 0.26, size: 'text-lg', opacity: 0.08 }, |  | ||||||
|      |  | ||||||
|     // Sécurité
 |  | ||||||
|     { icon: '🔒', x: 55, y: 40, speed: 0.3, size: 'text-xl', opacity: 0.09 }, |  | ||||||
|     { icon: '🛡️', x: 35, y: 65, speed: 0.24, size: 'text-2xl', opacity: 0.1 }, |  | ||||||
|     { icon: '🔑', x: 85, y: 30, speed: 0.16, size: 'text-lg', opacity: 0.07 }, |  | ||||||
|      |  | ||||||
|     // Données et stockage
 |  | ||||||
|     { icon: '💿', x: 20, y: 45, speed: 0.28, size: 'text-xl', opacity: 0.08 }, |  | ||||||
|     { icon: '📊', x: 65, y: 75, speed: 0.22, size: 'text-2xl', opacity: 0.09 }, |  | ||||||
|     { icon: '📈', x: 40, y: 20, speed: 0.34, size: 'text-lg', opacity: 0.07 }, |  | ||||||
|      |  | ||||||
|     // Éléments techniques supplémentaires
 |  | ||||||
|     { icon: 'sudo', x: 12, y: 90, speed: 0.19, size: 'text-sm', opacity: 0.06, isText: true }, |  | ||||||
|     { icon: 'SSH', x: 78, y: 12, speed: 0.31, size: 'text-base', opacity: 0.08, isText: true }, |  | ||||||
|     { icon: 'API', x: 92, y: 50, speed: 0.27, size: 'text-lg', opacity: 0.09, isText: true }, |  | ||||||
|     { icon: 'TCP', x: 8, y: 30, speed: 0.23, size: 'text-base', opacity: 0.07, isText: true }, |  | ||||||
|     { icon: 'HTTP', x: 50, y: 80, speed: 0.29, size: 'text-sm', opacity: 0.06, isText: true }, |  | ||||||
|   ]; |  | ||||||
| 
 |  | ||||||
|   return ( |  | ||||||
|     <div className="fixed inset-0 pointer-events-none overflow-hidden z-0"> |  | ||||||
|       {/* Grille de fond subtile */} |  | ||||||
|       <div className="absolute inset-0 opacity-[0.02]"> |  | ||||||
|         <div  |  | ||||||
|           className="absolute inset-0 bg-gradient-to-br from-banquise-blue-dark/20 to-transparent" |  | ||||||
|           style={{ |  | ||||||
|             backgroundImage: ` |  | ||||||
|               linear-gradient(rgba(31, 93, 137, 0.03) 1px, transparent 1px), |  | ||||||
|               linear-gradient(90deg, rgba(31, 93, 137, 0.03) 1px, transparent 1px) |  | ||||||
|             `,
 |  | ||||||
|             backgroundSize: '50px 50px', |  | ||||||
|             transform: `translateY(${scrollY * 0.1}px)` |  | ||||||
|           }} |  | ||||||
|         /> |  | ||||||
|       </div> |  | ||||||
| 
 |  | ||||||
|       {/* Particules de code flottantes */} |  | ||||||
|       <div className="absolute inset-0"> |  | ||||||
|         {floatingElements.map((element, index) => ( |  | ||||||
|           <div |  | ||||||
|             key={index} |  | ||||||
|             className={`absolute ${element.size} font-mono select-none transition-all duration-1000 ease-out`} |  | ||||||
|             style={{ |  | ||||||
|               left: `${element.x}%`, |  | ||||||
|               top: `${element.y}%`, |  | ||||||
|               transform: `translateY(${scrollY * element.speed}px) rotate(${scrollY * 0.01}deg)`, |  | ||||||
|               opacity: element.opacity, |  | ||||||
|               color: element.isText ? '#a8daff' : 'inherit', |  | ||||||
|               textShadow: element.isText ? '0 0 10px rgba(168, 218, 255, 0.3)' : 'none', |  | ||||||
|               filter: 'blur(0.5px)', |  | ||||||
|               animation: `float-${index % 3} ${6 + (index % 4)}s ease-in-out infinite` |  | ||||||
|             }} |  | ||||||
|           > |  | ||||||
|             {element.icon} |  | ||||||
|           </div> |  | ||||||
|         ))} |  | ||||||
|       </div> |  | ||||||
| 
 |  | ||||||
|       {/* Lignes de connexion animées */} |  | ||||||
|       <svg  |  | ||||||
|         className="absolute inset-0 w-full h-full opacity-[0.03]"  |  | ||||||
|         style={{ |  | ||||||
|           transform: `translateY(${scrollY * 0.2}px)` |  | ||||||
|         }} |  | ||||||
|       > |  | ||||||
|         <defs> |  | ||||||
|           <linearGradient id="lineGradient" x1="0%" y1="0%" x2="100%" y2="100%"> |  | ||||||
|             <stop offset="0%" stopColor="#a8daff" stopOpacity="0" /> |  | ||||||
|             <stop offset="50%" stopColor="#a8daff" stopOpacity="0.5" /> |  | ||||||
|             <stop offset="100%" stopColor="#a8daff" stopOpacity="0" /> |  | ||||||
|           </linearGradient> |  | ||||||
|         </defs> |  | ||||||
|          |  | ||||||
|         {/* Lignes de connexion entre les éléments */} |  | ||||||
|         {[...Array(8)].map((_, i) => ( |  | ||||||
|           <line |  | ||||||
|             key={i} |  | ||||||
|             x1={`${10 + i * 12}%`} |  | ||||||
|             y1={`${20 + i * 8}%`} |  | ||||||
|             x2={`${30 + i * 15}%`} |  | ||||||
|             y2={`${40 + i * 12}%`} |  | ||||||
|             stroke="url(#lineGradient)" |  | ||||||
|             strokeWidth="1" |  | ||||||
|             className="animate-pulse" |  | ||||||
|             style={{ |  | ||||||
|               animationDelay: `${i * 0.5}s`, |  | ||||||
|               animationDuration: `${3 + i}s` |  | ||||||
|             }} |  | ||||||
|           /> |  | ||||||
|         ))} |  | ||||||
|       </svg> |  | ||||||
| 
 |  | ||||||
|       {/* Cercles de données en mouvement */} |  | ||||||
|       <div className="absolute inset-0"> |  | ||||||
|         {[...Array(6)].map((_, i) => ( |  | ||||||
|           <div |  | ||||||
|             key={i} |  | ||||||
|             className="absolute rounded-full border border-banquise-blue-lightest/5 animate-ping" |  | ||||||
|             style={{ |  | ||||||
|               left: `${15 + i * 15}%`, |  | ||||||
|               top: `${25 + i * 12}%`, |  | ||||||
|               width: `${40 + i * 20}px`, |  | ||||||
|               height: `${40 + i * 20}px`, |  | ||||||
|               transform: `translateY(${scrollY * (0.1 + i * 0.05)}px)`, |  | ||||||
|               animationDelay: `${i * 1.2}s`, |  | ||||||
|               animationDuration: `${4 + i}s` |  | ||||||
|             }} |  | ||||||
|           /> |  | ||||||
|         ))} |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
| @ -1,25 +1,19 @@ | |||||||
| import React, { useEffect } from 'react'; | import React, { useEffect } from 'react'; | ||||||
| import { URLS } from '../../config/constants'; | import { URLS } from '../../config/constants'; | ||||||
| 
 | import type { Service } from '../../types/service'; | ||||||
| interface Service { | import type { Translation } from '../../types/i18n'; | ||||||
|   name: string; |  | ||||||
|   url: string; |  | ||||||
|   image: string; |  | ||||||
|   icon: string; |  | ||||||
|   description: string; |  | ||||||
|   features: string[]; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| interface PopupProps { | interface PopupProps { | ||||||
|   service: Service; |   service: Service; | ||||||
|   onClose: () => void; |   onClose: () => void; | ||||||
|  |   translations: Translation['common']; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const Popup: React.FC<PopupProps> = ({ service, onClose }) => { | export const Popup: React.FC<PopupProps> = ({ service, onClose, translations }) => { | ||||||
|   // Empêcher le scroll du body quand la popup est ouverte
 |   // Empêcher le scroll du body quand la popup est ouverte
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     document.body.style.overflow = 'hidden'; |     document.body.style.overflow = 'hidden'; | ||||||
|      | 
 | ||||||
|     return () => { |     return () => { | ||||||
|       document.body.style.overflow = 'unset'; |       document.body.style.overflow = 'unset'; | ||||||
|     }; |     }; | ||||||
| @ -28,18 +22,18 @@ export const Popup: React.FC<PopupProps> = ({ service, onClose }) => { | |||||||
|   return ( |   return ( | ||||||
|     <div className="fixed inset-0 bg-black/60 flex justify-center items-center z-50 p-4 backdrop-blur-md animate-fadeIn"> |     <div className="fixed inset-0 bg-black/60 flex justify-center items-center z-50 p-4 backdrop-blur-md animate-fadeIn"> | ||||||
|       <div className="bg-white text-banquise-blue-dark rounded-3xl max-w-4xl w-full max-h-[90vh] shadow-2xl relative animate-slideUp border border-banquise-blue-lightest/20 overflow-hidden"> |       <div className="bg-white text-banquise-blue-dark rounded-3xl max-w-4xl w-full max-h-[90vh] shadow-2xl relative animate-slideUp border border-banquise-blue-lightest/20 overflow-hidden"> | ||||||
|          | 
 | ||||||
|         {/* Bouton de fermeture fixe au-dessus du contenu */} |         {/* Bouton de fermeture fixe au-dessus du contenu */} | ||||||
|         <div className="absolute top-4 right-4 z-50"> |         <div className="absolute top-4 right-4 z-50"> | ||||||
|           <button |           <button | ||||||
|             onClick={onClose} |             onClick={onClose} | ||||||
|             className="bg-white/90 hover:bg-white border border-banquise-blue/20 text-xl cursor-pointer text-banquise-blue-dark flex items-center justify-center w-10 h-10 sm:w-12 sm:h-12 rounded-full transition-all duration-200 hover:scale-110 active:scale-95 shadow-lg backdrop-blur-sm" |             className="bg-white/90 hover:bg-white border border-banquise-blue/20 text-xl cursor-pointer text-banquise-blue-dark flex items-center justify-center w-10 h-10 sm:w-12 sm:h-12 rounded-full transition-all duration-200 hover:scale-110 active:scale-95 shadow-lg backdrop-blur-sm" | ||||||
|             aria-label="Fermer la popup" |             aria-label={translations.close} | ||||||
|           > |           > | ||||||
|             × |             × | ||||||
|           </button> |           </button> | ||||||
|         </div> |         </div> | ||||||
|          | 
 | ||||||
|         {/* Contenu avec scroll vertical uniquement */} |         {/* Contenu avec scroll vertical uniquement */} | ||||||
|         <div className="overflow-y-auto overflow-x-hidden max-h-[90vh] popup-content"> |         <div className="overflow-y-auto overflow-x-hidden max-h-[90vh] popup-content"> | ||||||
|           {/* Header */} |           {/* Header */} | ||||||
| @ -100,7 +94,7 @@ export const Popup: React.FC<PopupProps> = ({ service, onClose }) => { | |||||||
|             {/* Fonctionnalités */} |             {/* Fonctionnalités */} | ||||||
|             <h3 className="text-xl sm:text-2xl lg:text-3xl mb-4 lg:mb-6 text-banquise-blue-dark font-heading font-bold flex items-center"> |             <h3 className="text-xl sm:text-2xl lg:text-3xl mb-4 lg:mb-6 text-banquise-blue-dark font-heading font-bold flex items-center"> | ||||||
|               <span className="text-xl sm:text-2xl lg:text-3xl mr-3">⚡</span> |               <span className="text-xl sm:text-2xl lg:text-3xl mr-3">⚡</span> | ||||||
|               Fonctionnalités principales |               {translations.discoverFeatures} | ||||||
|             </h3> |             </h3> | ||||||
|             <div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-8"> |             <div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-8"> | ||||||
|               {service.features.map((feature, index) => ( |               {service.features.map((feature, index) => ( | ||||||
| @ -124,7 +118,7 @@ export const Popup: React.FC<PopupProps> = ({ service, onClose }) => { | |||||||
|                 <span className="mr-3 text-xl lg:text-2xl">🚀</span> |                 <span className="mr-3 text-xl lg:text-2xl">🚀</span> | ||||||
|                 <span>Accéder à {service.name}</span> |                 <span>Accéder à {service.name}</span> | ||||||
|               </a> |               </a> | ||||||
|                | 
 | ||||||
|               <p className="text-center text-sm text-banquise-blue-dark/60 mt-4"> |               <p className="text-center text-sm text-banquise-blue-dark/60 mt-4"> | ||||||
|                 Besoin d'aide ? Rejoignez notre <a href={URLS.social.discord} className="text-banquise-blue hover:text-banquise-blue-dark transition-colors duration-200 font-medium">Discord</a> pour obtenir du support |                 Besoin d'aide ? Rejoignez notre <a href={URLS.social.discord} className="text-banquise-blue hover:text-banquise-blue-dark transition-colors duration-200 font-medium">Discord</a> pour obtenir du support | ||||||
|               </p> |               </p> | ||||||
|  | |||||||
| @ -1,24 +1,8 @@ | |||||||
| import React, { useState, useEffect } from 'react'; | import React from 'react'; | ||||||
|  | import { useScrollEffects } from '../../hooks/useScrollEffects'; | ||||||
| 
 | 
 | ||||||
| export const ScrollToTopButton: React.FC = () => { | export const ScrollToTopButton: React.FC = () => { | ||||||
|   const [isVisible, setIsVisible] = useState(false); |   const { isVisible, scrollToTop } = useScrollEffects(); | ||||||
| 
 |  | ||||||
|   useEffect(() => { |  | ||||||
|     const toggleVisibility = () => { |  | ||||||
|       // Afficher le bouton après avoir scrollé 300px
 |  | ||||||
|       setIsVisible(window.scrollY > 300); |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     window.addEventListener('scroll', toggleVisibility); |  | ||||||
|     return () => window.removeEventListener('scroll', toggleVisibility); |  | ||||||
|   }, []); |  | ||||||
| 
 |  | ||||||
|   const scrollToTop = () => { |  | ||||||
|     window.scrollTo({ |  | ||||||
|       top: 0, |  | ||||||
|       behavior: 'smooth' |  | ||||||
|     }); |  | ||||||
|   }; |  | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <button |     <button | ||||||
|  | |||||||
| @ -11,7 +11,7 @@ export const URLS = { | |||||||
|     ssp: "https://ssp.la-banquise.fr" |     ssp: "https://ssp.la-banquise.fr" | ||||||
|   }, |   }, | ||||||
|   social: { |   social: { | ||||||
|     discord: "https://discord.gg/QQWwzX5ptY" |     discord: "https://discord.gg/bJhM97wans" | ||||||
|   }, |   }, | ||||||
|   contact: { |   contact: { | ||||||
|     email: "mailto:contact@la-banquise.fr" |     email: "mailto:contact@la-banquise.fr" | ||||||
|  | |||||||
							
								
								
									
										153
									
								
								banquise-website/src/data/translations/en.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								banquise-website/src/data/translations/en.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,153 @@ | |||||||
|  | import type { Translation } from '../../types/i18n'; | ||||||
|  | import { URLS } from '../../config/constants'; | ||||||
|  | 
 | ||||||
|  | export const en: Translation = { | ||||||
|  |   services: [ | ||||||
|  |     { | ||||||
|  |       name: "Wiki", | ||||||
|  |       url: URLS.services.wiki, | ||||||
|  |       image: "/path/to/wiki-image.jpg", | ||||||
|  |       icon: "📚", | ||||||
|  |       description: "Collaborative technical documentation and knowledge sharing platform. Create, edit and organize your guides, tutorials and documentation as a team with integrated versioning system.", | ||||||
|  |       features: [ | ||||||
|  |         "Advanced markdown editor with real-time preview", | ||||||
|  |         "Versioning system to track changes", | ||||||
|  |         "Real-time collaboration with multiple contributors", | ||||||
|  |         "Smart search across all documents", | ||||||
|  |         "Predefined templates for different documentation types", | ||||||
|  |         "Comment and revision system", | ||||||
|  |         "PDF and HTML export for external sharing", | ||||||
|  |         "Git integration for backup" | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       name: "Gitea", | ||||||
|  |       url: URLS.services.gitea, | ||||||
|  |       image: "/path/to/gitea-image.jpg", | ||||||
|  |       icon: "🔧", | ||||||
|  |       description: "Lightweight and performant self-hosted Git service for your development projects. Open-source alternative to GitHub with all essential features for managing your repositories.", | ||||||
|  |       features: [ | ||||||
|  |         "Unlimited public and private Git repositories", | ||||||
|  |         "Intuitive web interface for project management", | ||||||
|  |         "Issues and pull requests with review system", | ||||||
|  |         "Integrated wiki for each project", | ||||||
|  |         "CI/CD actions for automation", | ||||||
|  |         "Fine-grained permissions and team management", | ||||||
|  |         "Complete REST API for integration", | ||||||
|  |         "Webhooks for external notifications" | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       name: "Gaming Panel", | ||||||
|  |       url: URLS.services.panel, | ||||||
|  |       image: "/path/to/panel-image.jpg", | ||||||
|  |       icon: "🎮", | ||||||
|  |       description: "Centralized management interface for all your game servers. Easily deploy, configure and monitor your Minecraft, CS2, Garry's Mod and many other servers.", | ||||||
|  |       features: [ | ||||||
|  |         "Support for 20+ popular games (Minecraft, CS2, GMod...)", | ||||||
|  |         "One-click deployment with pre-configured templates", | ||||||
|  |         "Real-time administration console", | ||||||
|  |         "File management with integrated editor", | ||||||
|  |         "Performance and resource monitoring", | ||||||
|  |         "Automatic backup system", | ||||||
|  |         "Automated task scheduler" | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       name: "Pelican", | ||||||
|  |       url: URLS.services.pelican, | ||||||
|  |       image: "/path/to/pelican-image.jpg", | ||||||
|  |       icon: "🐧", | ||||||
|  |       description: "Ultra-fast and flexible Python static site generator. Create blogs, portfolios or documentation sites with optimal performance and simplified deployment.", | ||||||
|  |       features: [ | ||||||
|  |         "Game server management with dedicated servers (Minecraft, CS2, Palworld...)", | ||||||
|  |         "One-click deployment with pre-configured templates", | ||||||
|  |         "Real-time administration console", | ||||||
|  |         "File management with integrated editor", | ||||||
|  |         "Performance and resource monitoring", | ||||||
|  |         "Automatic backup system", | ||||||
|  |         "Automated task scheduler" | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       name: "Intranet", | ||||||
|  |       url: URLS.services.intra, | ||||||
|  |       image: "/path/to/intra-image.jpg", | ||||||
|  |       icon: "🏢", | ||||||
|  |       description: "Secure private space for the association to centralize internal resources, communications and collaboration tools between members.", | ||||||
|  |       features: [ | ||||||
|  |         "Personalized dashboard for each member", | ||||||
|  |         "Event and meeting calendar", | ||||||
|  |         "Secure file sharing", | ||||||
|  |         "Private discussion forums", | ||||||
|  |         "Member directory with profiles", | ||||||
|  |         "Internal notification system", | ||||||
|  |         "Project and task management", | ||||||
|  |         "Archive of decisions and minutes" | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       name: "Mails", | ||||||
|  |       url: URLS.services.mails, | ||||||
|  |       image: "/path/to/mails-image.jpg", | ||||||
|  |       icon: "📧", | ||||||
|  |       description: "Professional email service with modern web interface. Benefit from a personalized @la-banquise.fr email address with all advanced features.", | ||||||
|  |       features: [ | ||||||
|  |         "Personalized @la-banquise.fr email addresses", | ||||||
|  |         "Modern and responsive webmail interface", | ||||||
|  |         "Integrated anti-spam and antivirus filters", | ||||||
|  |         "Synchronized contacts and calendar", | ||||||
|  |         "IMAP/SMTP support for external clients", | ||||||
|  |         "Generous storage with archiving", | ||||||
|  |         "Communication encryption", | ||||||
|  |         "Automatic data backup" | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       name: "Password Change", | ||||||
|  |       url: URLS.services.ssp, | ||||||
|  |       image: "/path/to/ssp-image.jpg", | ||||||
|  |       icon: "🔐", | ||||||
|  |       description: "Secure interface for autonomous password management. Easily change your credentials safely.", | ||||||
|  |       features: [ | ||||||
|  |         "Secure interface to change your password", | ||||||
|  |         "Password complexity validation", | ||||||
|  |         "Email notifications of changes", | ||||||
|  |         "Modification history" | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       name: "OpenCloud", | ||||||
|  |       url: URLS.services.opencloud, | ||||||
|  |       image: "/path/to/opencloud-image.jpg", | ||||||
|  |       icon: "☁️", | ||||||
|  |       description: "Open-source collaborative cloud platform for file storage, sharing and synchronization. Free alternative to Google Drive with full control over your data.", | ||||||
|  |       features: [ | ||||||
|  |         "Secure and encrypted cloud storage", | ||||||
|  |         "Multi-device synchronization", | ||||||
|  |         "File sharing with secure links", | ||||||
|  |         "Collaborative document editing", | ||||||
|  |         "Automatic file versioning", | ||||||
|  |         "Native mobile applications", | ||||||
|  |         "Integration with external tools", | ||||||
|  |         "Geo-redundant data backup" | ||||||
|  |       ] | ||||||
|  |     } | ||||||
|  |   ], | ||||||
|  |   hero: { | ||||||
|  |     title: "Welcome to La Banquise", | ||||||
|  |     subtitle: "Hosting provider, for students, by students.", | ||||||
|  |     cta: "Discover our services" | ||||||
|  |   }, | ||||||
|  |   navigation: { | ||||||
|  |     home: "Home", | ||||||
|  |     services: "Services", | ||||||
|  |     about: "About", | ||||||
|  |     contact: "Contact" | ||||||
|  |   }, | ||||||
|  |   common: { | ||||||
|  |     discoverFeatures: "Discover all features", | ||||||
|  |     close: "Close", | ||||||
|  |     loading: "Loading..." | ||||||
|  |   } | ||||||
|  | }; | ||||||
							
								
								
									
										153
									
								
								banquise-website/src/data/translations/fr.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								banquise-website/src/data/translations/fr.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,153 @@ | |||||||
|  | import type { Translation } from '../../types/i18n'; | ||||||
|  | import { URLS } from '../../config/constants'; | ||||||
|  | 
 | ||||||
|  | export const fr: Translation = { | ||||||
|  |   services: [ | ||||||
|  |     { | ||||||
|  |       name: "Wiki", | ||||||
|  |       url: URLS.services.wiki, | ||||||
|  |       image: "/path/to/wiki-image.jpg", | ||||||
|  |       icon: "📚", | ||||||
|  |       description: "Plateforme collaborative de documentation technique et de partage de connaissances. Créez, modifiez et organisez vos guides, tutoriels et documentations en équipe avec un système de versioning intégré.", | ||||||
|  |       features: [ | ||||||
|  |         "Éditeur markdown avancé avec prévisualisation en temps réel", | ||||||
|  |         "Système de versioning pour suivre les modifications", | ||||||
|  |         "Collaboration en temps réel avec plusieurs contributeurs", | ||||||
|  |         "Recherche intelligente dans tous les documents", | ||||||
|  |         "Templates prédéfinis pour différents types de documentation", | ||||||
|  |         "Système de commentaires et de révisions", | ||||||
|  |         "Export PDF et HTML pour partage externe", | ||||||
|  |         "Intégration avec Git pour la sauvegarde" | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       name: "Gitea", | ||||||
|  |       url: URLS.services.gitea, | ||||||
|  |       image: "/path/to/gitea-image.jpg", | ||||||
|  |       icon: "🔧", | ||||||
|  |       description: "Service Git auto-hébergé lightweight et performant pour vos projets de développement. Alternative open-source à GitHub avec toutes les fonctionnalités essentielles pour gérer vos repositories.", | ||||||
|  |       features: [ | ||||||
|  |         "Repositories Git illimités publics et privés", | ||||||
|  |         "Interface web intuitive pour la gestion des projets", | ||||||
|  |         "Issues et pull requests avec système de review", | ||||||
|  |         "Wiki intégré pour chaque projet", | ||||||
|  |         "Actions CI/CD pour l'automatisation", | ||||||
|  |         "Gestion fine des permissions et des équipes", | ||||||
|  |         "API REST complète pour l'intégration", | ||||||
|  |         "Webhooks pour les notifications externes" | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       name: "Panel Gaming", | ||||||
|  |       url: URLS.services.panel, | ||||||
|  |       image: "/path/to/panel-image.jpg", | ||||||
|  |       icon: "🎮", | ||||||
|  |       description: "Interface de gestion centralisée pour tous vos serveurs de jeux. Déployez, configurez et surveillez facilement vos serveurs Minecraft, CS2, Garry's Mod et bien d'autres.", | ||||||
|  |       features: [ | ||||||
|  |         "Support de 20+ jeux populaires (Minecraft, CS2, GMod...)", | ||||||
|  |         "Déploiement en un clic avec templates préconfigurés", | ||||||
|  |         "Console d'administration en temps réel", | ||||||
|  |         "Gestion des fichiers avec éditeur intégré", | ||||||
|  |         "Monitoring des performances et ressources", | ||||||
|  |         "Système de sauvegarde automatique", | ||||||
|  |         "Planificateur de tâches automatisées" | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       name: "Pelican", | ||||||
|  |       url: URLS.services.pelican, | ||||||
|  |       image: "/path/to/pelican-image.jpg", | ||||||
|  |       icon: "🐧", | ||||||
|  |       description: "Générateur de sites statiques Python ultra-rapide et flexible. Créez des blogs, portfolios ou sites de documentation avec une performance optimale et un déploiement simplifié.", | ||||||
|  |       features: [ | ||||||
|  |         "Gestion de serveurs de jeux avec serveurs dédiés (Minecraft, CS2, Palworld...)", | ||||||
|  |         "Déploiement en un clic avec templates préconfigurés", | ||||||
|  |         "Console d'administration en temps réel", | ||||||
|  |         "Gestion des fichiers avec éditeur intégré", | ||||||
|  |         "Monitoring des performances et ressources", | ||||||
|  |         "Système de sauvegarde automatique", | ||||||
|  |         "Planificateur de tâches automatisées" | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       name: "Intranet", | ||||||
|  |       url: URLS.services.intra, | ||||||
|  |       image: "/path/to/intra-image.jpg", | ||||||
|  |       icon: "🏢", | ||||||
|  |       description: "Espace privé sécurisé de l'association pour centraliser les ressources internes, communications et outils de collaboration entre membres.", | ||||||
|  |       features: [ | ||||||
|  |         "Tableau de bord personnalisé pour chaque membre", | ||||||
|  |         "Calendrier des événements et réunions", | ||||||
|  |         "Partage de fichiers sécurisé", | ||||||
|  |         "Forums de discussion privés", | ||||||
|  |         "Annuaire des membres avec profils", | ||||||
|  |         "Système de notifications internes", | ||||||
|  |         "Gestion des projets et tâches", | ||||||
|  |         "Archive des décisions et procès-verbaux" | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       name: "Mails", | ||||||
|  |       url: URLS.services.mails, | ||||||
|  |       image: "/path/to/mails-image.jpg", | ||||||
|  |       icon: "📧", | ||||||
|  |       description: "Service de messagerie électronique professionnel avec interface web moderne. Bénéficiez d'une adresse email personnalisée @la-banquise.fr avec toutes les fonctionnalités avancées.", | ||||||
|  |       features: [ | ||||||
|  |         "Adresses email personnalisées @la-banquise.fr", | ||||||
|  |         "Interface webmail moderne et responsive", | ||||||
|  |         "Filtres anti-spam et antivirus intégrés", | ||||||
|  |         "Contacts et calendrier synchronisés", | ||||||
|  |         "Support IMAP/SMTP pour clients externes", | ||||||
|  |         "Stockage généreux avec archivage", | ||||||
|  |         "Chiffrement des communications", | ||||||
|  |         "Sauvegarde automatique des données" | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       name: "Changement de mot de passe", | ||||||
|  |       url: URLS.services.ssp, | ||||||
|  |       image: "/path/to/ssp-image.jpg", | ||||||
|  |       icon: "🔐", | ||||||
|  |       description: "Interface sécurisée pour la gestion autonome de vos mots de passe. Changez facilement vos identifiants en toute sécurité.", | ||||||
|  |       features: [ | ||||||
|  |         "Interface sécurisée pour changer votre mot de passe", | ||||||
|  |         "Validation de la complexité des mots de passe", | ||||||
|  |         "Notifications par email des changements", | ||||||
|  |         "Historique des modifications" | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       name: "OpenCloud", | ||||||
|  |       url: URLS.services.opencloud, | ||||||
|  |       image: "/path/to/opencloud-image.jpg", | ||||||
|  |       icon: "☁️", | ||||||
|  |       description: "Plateforme cloud collaborative open-source pour le stockage, le partage et la synchronisation de fichiers. Alternative libre à Google Drive avec contrôle total sur vos données.", | ||||||
|  |       features: [ | ||||||
|  |         "Stockage cloud sécurisé et chiffré", | ||||||
|  |         "Synchronisation multi-appareils", | ||||||
|  |         "Partage de fichiers avec liens sécurisés", | ||||||
|  |         "Édition collaborative de documents", | ||||||
|  |         "Versioning automatique des fichiers", | ||||||
|  |         "Applications mobiles natives", | ||||||
|  |         "Intégration avec outils externes", | ||||||
|  |         "Sauvegarde géoredondante des données" | ||||||
|  |       ] | ||||||
|  |     } | ||||||
|  |   ], | ||||||
|  |   hero: { | ||||||
|  |     title: "Bienvenue chez La Banquise", | ||||||
|  |     subtitle: "Hébergeur, pour les étudiants, par des étudiants.", | ||||||
|  |     cta: "Découvrir nos services" | ||||||
|  |   }, | ||||||
|  |   navigation: { | ||||||
|  |     home: "Accueil", | ||||||
|  |     services: "Services", | ||||||
|  |     about: "À propos", | ||||||
|  |     contact: "Contact" | ||||||
|  |   }, | ||||||
|  |   common: { | ||||||
|  |     discoverFeatures: "Découvrir toutes les fonctionnalités", | ||||||
|  |     close: "Fermer", | ||||||
|  |     loading: "Chargement..." | ||||||
|  |   } | ||||||
|  | }; | ||||||
							
								
								
									
										14
									
								
								banquise-website/src/data/translations/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								banquise-website/src/data/translations/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | |||||||
|  | import { fr } from './fr'; | ||||||
|  | import { en } from './en'; | ||||||
|  | import type { Language, Translation } from '../../types/i18n'; | ||||||
|  | 
 | ||||||
|  | export const translations: Record<Language, Translation> = { | ||||||
|  |   fr, | ||||||
|  |   en, | ||||||
|  |   // Ajoutez d'autres langues ici :
 | ||||||
|  |   // es,
 | ||||||
|  |   // de,
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const defaultLanguage: Language = 'fr'; | ||||||
|  | export const availableLanguages: Language[] = ['fr', 'en']; | ||||||
							
								
								
									
										33
									
								
								banquise-website/src/hooks/useAccordion.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								banquise-website/src/hooks/useAccordion.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | |||||||
|  | import { useState, useCallback } from 'react'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Hook personnalisé pour gérer l'état des accordéons | ||||||
|  |  * Remplace la logique dans App.tsx et simplifie la gestion d'état | ||||||
|  |  */ | ||||||
|  | export const useAccordion = (initialState: string | null = null) => { | ||||||
|  |   const [openAccordion, setOpenAccordion] = useState<string | null>(initialState); | ||||||
|  | 
 | ||||||
|  |   const toggleAccordion = useCallback((title: string) => { | ||||||
|  |     setOpenAccordion(prev => prev === title ? null : title); | ||||||
|  |   }, []); | ||||||
|  | 
 | ||||||
|  |   const openSpecificAccordion = useCallback((title: string) => { | ||||||
|  |     setOpenAccordion(title); | ||||||
|  |   }, []); | ||||||
|  | 
 | ||||||
|  |   const closeAccordion = useCallback(() => { | ||||||
|  |     setOpenAccordion(null); | ||||||
|  |   }, []); | ||||||
|  | 
 | ||||||
|  |   const isOpen = useCallback((title: string) => { | ||||||
|  |     return openAccordion === title; | ||||||
|  |   }, [openAccordion]); | ||||||
|  | 
 | ||||||
|  |   return { | ||||||
|  |     openAccordion, | ||||||
|  |     toggleAccordion, | ||||||
|  |     openSpecificAccordion, | ||||||
|  |     closeAccordion, | ||||||
|  |     isOpen, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
							
								
								
									
										22
									
								
								banquise-website/src/hooks/useOceanDepthEffect.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								banquise-website/src/hooks/useOceanDepthEffect.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  | import { useState, useEffect } from 'react'; | ||||||
|  | 
 | ||||||
|  | export const useOceanDepthEffect = () => { | ||||||
|  |   const [scrollDepth, setScrollDepth] = useState(0); | ||||||
|  | 
 | ||||||
|  |   useEffect(() => { | ||||||
|  |     const handleScroll = () => { | ||||||
|  |       const scrollPosition = window.scrollY; | ||||||
|  |       const documentHeight = document.documentElement.scrollHeight - window.innerHeight; | ||||||
|  |       const scrollPercentage = Math.min(scrollPosition / documentHeight, 1); | ||||||
|  |        | ||||||
|  |       setScrollDepth(scrollPercentage); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     window.addEventListener('scroll', handleScroll); | ||||||
|  |     handleScroll(); // Initial call
 | ||||||
|  | 
 | ||||||
|  |     return () => window.removeEventListener('scroll', handleScroll); | ||||||
|  |   }, []); | ||||||
|  | 
 | ||||||
|  |   return scrollDepth; | ||||||
|  | }; | ||||||
							
								
								
									
										49
									
								
								banquise-website/src/hooks/useScrollEffects.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								banquise-website/src/hooks/useScrollEffects.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | |||||||
|  | import { useState, useEffect, useCallback } from 'react'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Hook personnalisé pour gérer les effets de scroll | ||||||
|  |  * Remplace la logique répétée dans Navigation.tsx et ScrollToTopButton.tsx | ||||||
|  |  */ | ||||||
|  | export const useScrollEffects = () => { | ||||||
|  |   const [scrolled, setScrolled] = useState(false); | ||||||
|  |   const [isVisible, setIsVisible] = useState(false); | ||||||
|  | 
 | ||||||
|  |   useEffect(() => { | ||||||
|  |     const handleScroll = () => { | ||||||
|  |       const scrollY = window.scrollY; | ||||||
|  |       setScrolled(scrollY > 20); | ||||||
|  |       setIsVisible(scrollY > 300); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     window.addEventListener('scroll', handleScroll, { passive: true }); | ||||||
|  |      | ||||||
|  |     // Call once to set initial state
 | ||||||
|  |     handleScroll(); | ||||||
|  | 
 | ||||||
|  |     return () => window.removeEventListener('scroll', handleScroll); | ||||||
|  |   }, []); | ||||||
|  | 
 | ||||||
|  |   const scrollToTop = useCallback(() => { | ||||||
|  |     window.scrollTo({ | ||||||
|  |       top: 0, | ||||||
|  |       behavior: 'smooth' | ||||||
|  |     }); | ||||||
|  |   }, []); | ||||||
|  | 
 | ||||||
|  |   const scrollToElement = useCallback((elementId: string) => { | ||||||
|  |     const element = document.getElementById(elementId); | ||||||
|  |     if (element) { | ||||||
|  |       element.scrollIntoView({  | ||||||
|  |         behavior: 'smooth', | ||||||
|  |         block: 'start' | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   }, []); | ||||||
|  | 
 | ||||||
|  |   return { | ||||||
|  |     scrolled, | ||||||
|  |     isVisible, | ||||||
|  |     scrollToTop, | ||||||
|  |     scrollToElement, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
							
								
								
									
										27
									
								
								banquise-website/src/hooks/useServiceModal.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								banquise-website/src/hooks/useServiceModal.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | |||||||
|  | import { useState, useCallback } from 'react'; | ||||||
|  | import type { Service } from '../types/service'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Hook personnalisé pour gérer l'état des modales de services | ||||||
|  |  * Remplace la logique dans App.tsx et simplifie la gestion d'état | ||||||
|  |  */ | ||||||
|  | export const useServiceModal = () => { | ||||||
|  |   const [selectedService, setSelectedService] = useState<Service | null>(null); | ||||||
|  | 
 | ||||||
|  |   const openServiceModal = useCallback((service: Service) => { | ||||||
|  |     setSelectedService(service); | ||||||
|  |   }, []); | ||||||
|  | 
 | ||||||
|  |   const closeServiceModal = useCallback(() => { | ||||||
|  |     setSelectedService(null); | ||||||
|  |   }, []); | ||||||
|  | 
 | ||||||
|  |   const isModalOpen = selectedService !== null; | ||||||
|  | 
 | ||||||
|  |   return { | ||||||
|  |     selectedService, | ||||||
|  |     openServiceModal, | ||||||
|  |     closeServiceModal, | ||||||
|  |     isModalOpen, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
							
								
								
									
										34
									
								
								banquise-website/src/hooks/useTranslation.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								banquise-website/src/hooks/useTranslation.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | |||||||
|  | import { useState, useEffect, useMemo } from 'react'; | ||||||
|  | import type { Language, Translation } from '../types/i18n'; | ||||||
|  | import { translations, defaultLanguage } from '../data/translations'; | ||||||
|  | 
 | ||||||
|  | export const useTranslation = () => { | ||||||
|  |   const [currentLanguage, setCurrentLanguage] = useState<Language>(() => { | ||||||
|  |     // Récupérer la langue depuis localStorage ou utiliser la langue par défaut
 | ||||||
|  |     const saved = localStorage.getItem('language') as Language; | ||||||
|  |     return saved && translations[saved] ? saved : defaultLanguage; | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   // Memoize the translation object to prevent unnecessary re-renders
 | ||||||
|  |   const t = useMemo<Translation>(() => translations[currentLanguage], [currentLanguage]); | ||||||
|  | 
 | ||||||
|  |   // Memoize available languages array
 | ||||||
|  |   const availableLanguages = useMemo(() => Object.keys(translations) as Language[], []); | ||||||
|  | 
 | ||||||
|  |   useEffect(() => { | ||||||
|  |     localStorage.setItem('language', currentLanguage); | ||||||
|  |   }, [currentLanguage]); | ||||||
|  | 
 | ||||||
|  |   const changeLanguage = (language: Language) => { | ||||||
|  |     if (translations[language]) { | ||||||
|  |       setCurrentLanguage(language); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   return { | ||||||
|  |     t, | ||||||
|  |     currentLanguage, | ||||||
|  |     changeLanguage, | ||||||
|  |     availableLanguages | ||||||
|  |   }; | ||||||
|  | }; | ||||||
| @ -1,5 +1,10 @@ | |||||||
|  | // DEPRECATED: This file is being replaced by designSystem.ts
 | ||||||
|  | // Please use the new design system for new components
 | ||||||
|  | // This file is kept for backward compatibility during migration
 | ||||||
|  | 
 | ||||||
|  | // Re-export the legacy commonStyles structure for backward compatibility
 | ||||||
| export const commonStyles = { | export const commonStyles = { | ||||||
|   // Gradients
 |   // Gradients - Keep existing structure
 | ||||||
|   gradients: { |   gradients: { | ||||||
|     primary: "bg-gradient-to-r from-banquise-blue to-banquise-blue-light", |     primary: "bg-gradient-to-r from-banquise-blue to-banquise-blue-light", | ||||||
|     primaryBr: "bg-gradient-to-br from-banquise-blue to-banquise-blue-light", |     primaryBr: "bg-gradient-to-br from-banquise-blue to-banquise-blue-light", | ||||||
| @ -9,53 +14,49 @@ export const commonStyles = { | |||||||
|     discordHover: "hover:from-indigo-500 hover:to-purple-500" |     discordHover: "hover:from-indigo-500 hover:to-purple-500" | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   // Buttons
 |   // Buttons - Keep existing structure
 | ||||||
|   buttons: { |   buttons: { | ||||||
|     primary: "inline-flex items-center justify-center font-bold text-white border-0 rounded-2xl transition-all duration-300 hover:shadow-xl hover:-translate-y-1 hover:scale-105 active:scale-95", |     primary: "inline-flex items-center justify-center font-bold text-white border-0 rounded-2xl transition-all duration-300 hover:shadow-xl hover:-translate-y-1 hover:scale-105 active:scale-95", | ||||||
|     discord: "group relative overflow-hidden px-4 lg:px-6 py-2.5 lg:py-3 text-white font-semibold text-sm lg:text-base rounded-xl transition-all duration-300 hover:shadow-xl hover:shadow-indigo-500/25 hover:-translate-y-1 hover:scale-105", |     discord: "group relative overflow-hidden px-4 lg:px-6 py-2.5 lg:py-3 text-white font-semibold text-sm lg:text-base rounded-xl transition-all duration-300 hover:shadow-xl hover:shadow-indigo-500/25 hover:-translate-y-1 hover:scale-105", | ||||||
|     auth: "group relative overflow-hidden px-4 lg:px-6 py-2.5 lg:py-3 text-white font-semibold text-sm lg:text-base rounded-xl transition-all duration-300 hover:shadow-xl hover:-translate-y-1 hover:scale-105" |     auth: "group relative overflow-hidden px-4 lg:px-6 py-2.5 lg:py-3 text-white font-semibold text-sm lg:text-base rounded-xl transition-all duration-300 hover:shadow-xl hover:-translate-y-1 hover:scale-105" | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   // Cards
 |   // Cards - Keep existing structure
 | ||||||
|   cards: { |   cards: { | ||||||
|     base: "backdrop-blur-lg rounded-2xl border border-banquise-blue-lightest/30 transition-all duration-300", |     base: "backdrop-blur-lg rounded-2xl border border-banquise-blue-lightest/30 transition-all duration-300", | ||||||
|     hover: "hover:shadow-xl hover:border-banquise-blue-lightest/50", |     hover: "hover:shadow-xl hover:border-banquise-blue-lightest/50", | ||||||
|     interactive: "cursor-pointer hover:-translate-y-4 hover:shadow-2xl active:scale-95" |     interactive: "cursor-pointer hover:-translate-y-4 hover:shadow-2xl active:scale-95" | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   // Text - Hiérarchie améliorée
 |   // Text - Keep existing structure
 | ||||||
|   text: { |   text: { | ||||||
|     heading: "font-heading font-bold tracking-tight", |     heading: "font-heading font-bold tracking-tight", | ||||||
|     // Titres principaux de section
 |  | ||||||
|     headingXl: "text-3xl sm:text-4xl md:text-5xl text-banquise-gray font-heading font-bold tracking-tight", |     headingXl: "text-3xl sm:text-4xl md:text-5xl text-banquise-gray font-heading font-bold tracking-tight", | ||||||
|     headingLg: "text-2xl sm:text-3xl md:text-4xl text-banquise-gray font-heading font-bold tracking-tight", |     headingLg: "text-2xl sm:text-3xl md:text-4xl text-banquise-gray font-heading font-bold tracking-tight", | ||||||
|     headingMd: "text-xl sm:text-2xl md:text-3xl text-banquise-blue-dark font-heading font-bold tracking-tight", |     headingMd: "text-xl sm:text-2xl md:text-3xl text-banquise-blue-dark font-heading font-bold tracking-tight", | ||||||
|     headingSm: "text-lg sm:text-xl md:text-2xl text-banquise-blue-dark font-heading font-semibold tracking-tight", |     headingSm: "text-lg sm:text-xl md:text-2xl text-banquise-blue-dark font-heading font-semibold tracking-tight", | ||||||
|     // Sous-titres
 |  | ||||||
|     subheading: "text-base sm:text-lg md:text-xl text-banquise-gray/90 font-medium leading-relaxed", |     subheading: "text-base sm:text-lg md:text-xl text-banquise-gray/90 font-medium leading-relaxed", | ||||||
|     // Corps de texte
 |  | ||||||
|     body: "text-sm sm:text-base md:text-lg text-banquise-blue-dark/90 leading-relaxed", |     body: "text-sm sm:text-base md:text-lg text-banquise-blue-dark/90 leading-relaxed", | ||||||
|     description: "text-banquise-gray/80 leading-relaxed", |     description: "text-banquise-gray/80 leading-relaxed", | ||||||
|     muted: "text-banquise-gray/90 leading-relaxed", |     muted: "text-banquise-gray/90 leading-relaxed", | ||||||
|     // Texte sur fond sombre
 |  | ||||||
|     lightHeading: "text-banquise-blue-lightest font-heading font-bold tracking-tight", |     lightHeading: "text-banquise-blue-lightest font-heading font-bold tracking-tight", | ||||||
|     lightBody: "text-white/90 leading-relaxed" |     lightBody: "text-white/90 leading-relaxed" | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   // Layout
 |   // Layout - Keep existing structure
 | ||||||
|   layout: { |   layout: { | ||||||
|     section: "py-12 sm:py-16 md:py-20 w-full max-w-6xl mx-auto px-4 sm:px-6 md:px-8", |     section: "py-12 sm:py-16 md:py-20 w-full max-w-6xl mx-auto px-4 sm:px-6 md:px-8", | ||||||
|     container: "max-w-6xl mx-auto", |     container: "max-w-6xl mx-auto", | ||||||
|     divider: "w-20 h-1 bg-gradient-to-r from-banquise-blue-lightest to-banquise-blue mx-auto mb-6 sm:mb-8 rounded-full" |     divider: "w-20 h-1 bg-gradient-to-r from-banquise-blue-lightest to-banquise-blue mx-auto mb-6 sm:mb-8 rounded-full" | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   // Icons and decorative elements
 |   // Icons - Keep existing structure
 | ||||||
|   icons: { |   icons: { | ||||||
|     base: "w-16 h-16 sm:w-20 sm:h-20 lg:w-24 lg:h-24 rounded-2xl flex items-center justify-center text-3xl sm:text-4xl lg:text-5xl shadow-lg", |     base: "w-16 h-16 sm:w-20 sm:h-20 lg:w-24 lg:h-24 rounded-2xl flex items-center justify-center text-3xl sm:text-4xl lg:text-5xl shadow-lg", | ||||||
|     small: "w-10 h-10 rounded-lg flex items-center justify-center text-white" |     small: "w-10 h-10 rounded-lg flex items-center justify-center text-white" | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   // Navigation
 |   // Navigation - Keep existing structure
 | ||||||
|   nav: { |   nav: { | ||||||
|     link: "px-4 lg:px-6 py-2.5 lg:py-3 text-white/90 hover:text-white font-medium text-sm lg:text-base rounded-xl transition-all duration-300 hover:bg-white/10 hover:backdrop-blur-sm relative group", |     link: "px-4 lg:px-6 py-2.5 lg:py-3 text-white/90 hover:text-white font-medium text-sm lg:text-base rounded-xl transition-all duration-300 hover:bg-white/10 hover:backdrop-blur-sm relative group", | ||||||
|     mobileItem: "group flex items-center p-4 text-white/90 hover:text-white no-underline rounded-2xl hover:bg-gradient-to-r hover:from-banquise-blue/20 hover:to-banquise-blue-light/20 transition-all duration-300 border border-transparent hover:border-banquise-blue-lightest/20" |     mobileItem: "group flex items-center p-4 text-white/90 hover:text-white no-underline rounded-2xl hover:bg-gradient-to-r hover:from-banquise-blue/20 hover:to-banquise-blue-light/20 transition-all duration-300 border border-transparent hover:border-banquise-blue-lightest/20" | ||||||
|  | |||||||
							
								
								
									
										176
									
								
								banquise-website/src/styles/designSystem.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								banquise-website/src/styles/designSystem.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,176 @@ | |||||||
|  | // Design System - Centralized design tokens and reusable styles
 | ||||||
|  | export const designTokens = { | ||||||
|  |   // Colors
 | ||||||
|  |   colors: { | ||||||
|  |     banquise: { | ||||||
|  |       blue: '#40B4FF', | ||||||
|  |       blueDark: '#1F5D89', | ||||||
|  |       blueLight: '#69B7E2', | ||||||
|  |       blueLightest: '#A5F0FF', | ||||||
|  |       gray: '#F6F6F6', | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   // Spacing
 | ||||||
|  |   spacing: { | ||||||
|  |     xs: '0.25rem', | ||||||
|  |     sm: '0.5rem', | ||||||
|  |     md: '1rem', | ||||||
|  |     lg: '1.5rem', | ||||||
|  |     xl: '2rem', | ||||||
|  |     xxl: '3rem', | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   // Border radius
 | ||||||
|  |   borderRadius: { | ||||||
|  |     sm: '0.5rem', | ||||||
|  |     md: '0.75rem', | ||||||
|  |     lg: '1rem', | ||||||
|  |     xl: '1.5rem', | ||||||
|  |     xxl: '2rem', | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   // Shadows
 | ||||||
|  |   shadows: { | ||||||
|  |     sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', | ||||||
|  |     md: '0 4px 6px -1px rgba(0, 0, 0, 0.1)', | ||||||
|  |     lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1)', | ||||||
|  |     xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1)', | ||||||
|  |     xxl: '0 25px 50px -12px rgba(0, 0, 0, 0.25)', | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   // Typography
 | ||||||
|  |   typography: { | ||||||
|  |     fontFamily: { | ||||||
|  |       heading: ['Dela Gothic One', 'sans-serif'], | ||||||
|  |       body: ['Roboto', 'sans-serif'], | ||||||
|  |     }, | ||||||
|  |     fontSize: { | ||||||
|  |       xs: '0.75rem', | ||||||
|  |       sm: '0.875rem', | ||||||
|  |       base: '1rem', | ||||||
|  |       lg: '1.125rem', | ||||||
|  |       xl: '1.25rem', | ||||||
|  |       '2xl': '1.5rem', | ||||||
|  |       '3xl': '1.875rem', | ||||||
|  |       '4xl': '2.25rem', | ||||||
|  |       '5xl': '3rem', | ||||||
|  |       '6xl': '3.75rem', | ||||||
|  |     }, | ||||||
|  |     fontWeight: { | ||||||
|  |       normal: '400', | ||||||
|  |       medium: '500', | ||||||
|  |       semibold: '600', | ||||||
|  |       bold: '700', | ||||||
|  |       extrabold: '800', | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   // Transitions
 | ||||||
|  |   transitions: { | ||||||
|  |     fast: 'all 0.15s ease-in-out', | ||||||
|  |     normal: 'all 0.3s ease-in-out', | ||||||
|  |     slow: 'all 0.5s ease-in-out', | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   // Z-index
 | ||||||
|  |   zIndex: { | ||||||
|  |     dropdown: 1000, | ||||||
|  |     sticky: 1020, | ||||||
|  |     fixed: 1030, | ||||||
|  |     modalBackdrop: 1040, | ||||||
|  |     modal: 1050, | ||||||
|  |     popover: 1060, | ||||||
|  |     tooltip: 1070, | ||||||
|  |   }, | ||||||
|  | } as const; | ||||||
|  | 
 | ||||||
|  | // Reusable gradient combinations
 | ||||||
|  | export const gradients = { | ||||||
|  |   primary: 'bg-gradient-to-r from-banquise-blue to-banquise-blue-light', | ||||||
|  |   primaryBr: 'bg-gradient-to-br from-banquise-blue to-banquise-blue-light', | ||||||
|  |   card: 'bg-gradient-to-br from-banquise-blue-dark/10 to-banquise-blue-dark/5', | ||||||
|  |   cardHover: 'hover:from-banquise-blue-dark/15 hover:to-banquise-blue-dark/8', | ||||||
|  |   discord: 'bg-gradient-to-r from-indigo-600 to-purple-600', | ||||||
|  |   discordHover: 'hover:from-indigo-500 hover:to-purple-500', | ||||||
|  |   background: 'bg-gradient-to-b from-banquise-blue-dark via-banquise-blue-dark/95 to-banquise-blue-dark', | ||||||
|  | } as const; | ||||||
|  | 
 | ||||||
|  | // Reusable component styles
 | ||||||
|  | export const componentStyles = { | ||||||
|  |   // Buttons
 | ||||||
|  |   button: { | ||||||
|  |     base: 'inline-flex items-center justify-center font-bold text-white border-0 rounded-2xl transition-all duration-300 hover:shadow-xl hover:-translate-y-1 hover:scale-105 active:scale-95', | ||||||
|  |     primary: 'bg-gradient-to-r from-banquise-blue to-banquise-blue-light', | ||||||
|  |     discord: 'group relative overflow-hidden px-4 lg:px-6 py-2.5 lg:py-3 text-white font-semibold text-sm lg:text-base rounded-xl bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-500 hover:to-purple-500', | ||||||
|  |     auth: 'group relative overflow-hidden px-4 lg:px-6 py-2.5 lg:py-3 text-white font-semibold text-sm lg:text-base rounded-xl', | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   // Cards
 | ||||||
|  |   card: { | ||||||
|  |     base: 'backdrop-blur-lg rounded-2xl border border-banquise-blue-lightest/30 transition-all duration-300', | ||||||
|  |     hover: 'hover:shadow-xl hover:border-banquise-blue-lightest/50', | ||||||
|  |     interactive: 'cursor-pointer hover:-translate-y-4 hover:shadow-2xl active:scale-95', | ||||||
|  |     gradient: 'bg-gradient-to-br from-banquise-blue-dark/10 to-banquise-blue-dark/5', | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   // Navigation
 | ||||||
|  |   nav: { | ||||||
|  |     link: 'px-4 lg:px-6 py-2.5 lg:py-3 text-white/90 hover:text-white font-medium text-sm lg:text-base rounded-xl transition-all duration-300 hover:bg-white/10 hover:backdrop-blur-sm relative group', | ||||||
|  |     mobileItem: 'group flex items-center p-4 text-white/90 hover:text-white no-underline rounded-2xl hover:bg-gradient-to-r hover:from-banquise-blue/20 hover:to-banquise-blue-light/20 transition-all duration-300 border border-transparent hover:border-banquise-blue-lightest/20', | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   // Text styles
 | ||||||
|  |   text: { | ||||||
|  |     heading: 'font-heading font-bold tracking-tight', | ||||||
|  |     headingXl: 'text-3xl sm:text-4xl md:text-5xl text-banquise-gray font-heading font-bold tracking-tight', | ||||||
|  |     headingLg: 'text-2xl sm:text-3xl md:text-4xl text-banquise-gray font-heading font-bold tracking-tight', | ||||||
|  |     headingMd: 'text-xl sm:text-2xl md:text-3xl text-banquise-blue-dark font-heading font-bold tracking-tight', | ||||||
|  |     headingSm: 'text-lg sm:text-xl md:text-2xl text-banquise-blue-dark font-heading font-semibold tracking-tight', | ||||||
|  |     subheading: 'text-base sm:text-lg md:text-xl text-banquise-gray/90 font-medium leading-relaxed', | ||||||
|  |     body: 'text-sm sm:text-base md:text-lg text-banquise-blue-dark/90 leading-relaxed', | ||||||
|  |     description: 'text-banquise-gray/80 leading-relaxed', | ||||||
|  |     muted: 'text-banquise-gray/90 leading-relaxed', | ||||||
|  |     lightHeading: 'text-banquise-blue-lightest font-heading font-bold tracking-tight', | ||||||
|  |     lightBody: 'text-white/90 leading-relaxed', | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   // Icons
 | ||||||
|  |   icon: { | ||||||
|  |     base: 'w-16 h-16 sm:w-20 sm:h-20 lg:w-24 lg:h-24 rounded-2xl flex items-center justify-center text-3xl sm:text-4xl lg:text-5xl shadow-lg', | ||||||
|  |     small: 'w-10 h-10 rounded-lg flex items-center justify-center text-white', | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   // Layout
 | ||||||
|  |   layout: { | ||||||
|  |     section: 'py-12 sm:py-16 md:py-20 w-full max-w-6xl mx-auto px-4 sm:px-6 md:px-8', | ||||||
|  |     container: 'max-w-6xl mx-auto', | ||||||
|  |     divider: 'w-20 h-1 bg-gradient-to-r from-banquise-blue-lightest to-banquise-blue mx-auto mb-6 sm:mb-8 rounded-full', | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   // Animations
 | ||||||
|  |   animation: { | ||||||
|  |     hover: 'hover:-translate-y-4 hover:shadow-2xl transition-all duration-300', | ||||||
|  |     scale: 'hover:scale-105 active:scale-95 transition-transform duration-300', | ||||||
|  |     fadeIn: 'animate-fadeIn', | ||||||
|  |     slideUp: 'animate-slideUp', | ||||||
|  |   }, | ||||||
|  | } as const; | ||||||
|  | 
 | ||||||
|  | // Utility function to merge classes
 | ||||||
|  | export const mergeClasses = (...classes: (string | undefined | null | false)[]): string => { | ||||||
|  |   return classes.filter(Boolean).join(' '); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // Responsive breakpoints
 | ||||||
|  | export const breakpoints = { | ||||||
|  |   sm: '640px', | ||||||
|  |   md: '768px', | ||||||
|  |   lg: '1024px', | ||||||
|  |   xl: '1280px', | ||||||
|  |   '2xl': '1536px', | ||||||
|  | } as const; | ||||||
|  | 
 | ||||||
|  | export type DesignTokens = typeof designTokens; | ||||||
|  | export type Gradients = typeof gradients; | ||||||
|  | export type ComponentStyles = typeof componentStyles; | ||||||
							
								
								
									
										23
									
								
								banquise-website/src/types/i18n.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								banquise-website/src/types/i18n.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | |||||||
|  | import type { Service } from './service'; | ||||||
|  | 
 | ||||||
|  | export interface Translation { | ||||||
|  |   services: Service[]; | ||||||
|  |   hero: { | ||||||
|  |     title: string; | ||||||
|  |     subtitle: string; | ||||||
|  |     cta: string; | ||||||
|  |   }; | ||||||
|  |   navigation: { | ||||||
|  |     home: string; | ||||||
|  |     services: string; | ||||||
|  |     about: string; | ||||||
|  |     contact: string; | ||||||
|  |   }; | ||||||
|  |   common: { | ||||||
|  |     discoverFeatures: string; | ||||||
|  |     close: string; | ||||||
|  |     loading: string; | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export type Language = 'fr' | 'en'; //| 'es' | 'de';
 | ||||||
| @ -1,11 +1,5 @@ | |||||||
| export interface Service { | // Re-export types from their specific modules
 | ||||||
|   name: string; | export type { Service } from './service'; | ||||||
|   url: string; |  | ||||||
|   image: string; |  | ||||||
|   description: string; |  | ||||||
|   features: string[]; |  | ||||||
|   icon: string; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| export interface AccordionItemProps { | export interface AccordionItemProps { | ||||||
|   title: string; |   title: string; | ||||||
|  | |||||||
							
								
								
									
										8
									
								
								banquise-website/src/types/service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								banquise-website/src/types/service.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | |||||||
|  | export interface Service { | ||||||
|  |   name: string; | ||||||
|  |   url: string; | ||||||
|  |   image: string; | ||||||
|  |   icon: string; | ||||||
|  |   description: string; | ||||||
|  |   features: string[]; | ||||||
|  | } | ||||||
							
								
								
									
										30
									
								
								banquise-website/src/utils/classNames.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								banquise-website/src/utils/classNames.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | |||||||
|  | /** | ||||||
|  |  * Utility function to conditionally merge CSS classes | ||||||
|  |  * Filters out falsy values and joins valid class names | ||||||
|  |  */ | ||||||
|  | export const cn = (...classes: (string | undefined | null | false)[]): string => { | ||||||
|  |   return classes.filter(Boolean).join(' '); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Alias for cn function for backward compatibility | ||||||
|  |  */ | ||||||
|  | export const mergeClasses = cn; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Utility to create conditional classes based on state | ||||||
|  |  */ | ||||||
|  | export const conditionalClass = (condition: boolean, trueClass: string, falseClass: string = ''): string => { | ||||||
|  |   return condition ? trueClass : falseClass; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Utility to create variant-based classes | ||||||
|  |  */ | ||||||
|  | export const variantClass = <T extends string>( | ||||||
|  |   variant: T, | ||||||
|  |   variants: Record<T, string>, | ||||||
|  |   defaultVariant?: T | ||||||
|  | ): string => { | ||||||
|  |   return variants[variant] || (defaultVariant ? variants[defaultVariant] : ''); | ||||||
|  | }; | ||||||
							
								
								
									
										2
									
								
								banquise-website/src/vite-env.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								banquise-website/src/vite-env.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -1 +1,3 @@ | |||||||
| /// <reference types="vite/client" />
 | /// <reference types="vite/client" />
 | ||||||
|  | /// <reference types="react" />
 | ||||||
|  | /// <reference types="react-dom" />
 | ||||||
|  | |||||||
| @ -34,6 +34,10 @@ export default { | |||||||
|         'gentle-float': 'gentle-float 6s ease-in-out infinite', |         'gentle-float': 'gentle-float 6s ease-in-out infinite', | ||||||
|         'fadeIn': 'fadeIn 0.2s ease-out', |         'fadeIn': 'fadeIn 0.2s ease-out', | ||||||
|         'slideUp': 'slideUp 0.3s ease-out', |         'slideUp': 'slideUp 0.3s ease-out', | ||||||
|  |         'bubble-float': 'bubble-float 8s ease-in-out infinite', | ||||||
|  |         'bubble-float-slow': 'bubble-float-slow 12s ease-in-out infinite', | ||||||
|  |         'bubble-float-fast': 'bubble-float-fast 6s ease-in-out infinite', | ||||||
|  |         'ocean-shimmer': 'ocean-shimmer 10s ease-in-out infinite', | ||||||
|       }, |       }, | ||||||
|       keyframes: { |       keyframes: { | ||||||
|         float: { |         float: { | ||||||
| @ -92,6 +96,35 @@ export default { | |||||||
|           from: { transform: 'translateY(30px)', opacity: '0' }, |           from: { transform: 'translateY(30px)', opacity: '0' }, | ||||||
|           to: { transform: 'translateY(0)', opacity: '1' }, |           to: { transform: 'translateY(0)', opacity: '1' }, | ||||||
|         }, |         }, | ||||||
|  |         'bubble-float': { | ||||||
|  |           '0%': { transform: 'translateY(0) translateX(0) scale(1)', opacity: '0.6' }, | ||||||
|  |           '25%': { transform: 'translateY(-15px) translateX(5px) scale(1.05)', opacity: '0.7' }, | ||||||
|  |           '50%': { transform: 'translateY(-30px) translateX(-3px) scale(1.1)', opacity: '0.5' }, | ||||||
|  |           '75%': { transform: 'translateY(-45px) translateX(8px) scale(1.05)', opacity: '0.4' }, | ||||||
|  |           '100%': { transform: 'translateY(-60px) translateX(0) scale(1)', opacity: '0.2' }, | ||||||
|  |         }, | ||||||
|  |         'bubble-float-slow': { | ||||||
|  |           '0%': { transform: 'translateY(0) translateX(0) scale(0.8)', opacity: '0.4' }, | ||||||
|  |           '20%': { transform: 'translateY(-20px) translateX(-8px) scale(0.9)', opacity: '0.5' }, | ||||||
|  |           '40%': { transform: 'translateY(-40px) translateX(6px) scale(1.1)', opacity: '0.4' }, | ||||||
|  |           '60%': { transform: 'translateY(-60px) translateX(-4px) scale(1.2)', opacity: '0.3' }, | ||||||
|  |           '80%': { transform: 'translateY(-80px) translateX(10px) scale(1.0)', opacity: '0.2' }, | ||||||
|  |           '100%': { transform: 'translateY(-100px) translateX(0) scale(0.8)', opacity: '0.1' }, | ||||||
|  |         }, | ||||||
|  |         'bubble-float-fast': { | ||||||
|  |           '0%': { transform: 'translateY(0) translateX(0) scale(1.2)', opacity: '0.8' }, | ||||||
|  |           '15%': { transform: 'translateY(-10px) translateX(4px) scale(1.1)', opacity: '0.7' }, | ||||||
|  |           '30%': { transform: 'translateY(-20px) translateX(-2px) scale(0.9)', opacity: '0.6' }, | ||||||
|  |           '45%': { transform: 'translateY(-30px) translateX(6px) scale(1.0)', opacity: '0.5' }, | ||||||
|  |           '60%': { transform: 'translateY(-40px) translateX(-5px) scale(1.1)', opacity: '0.4' }, | ||||||
|  |           '75%': { transform: 'translateY(-50px) translateX(3px) scale(1.0)', opacity: '0.3' }, | ||||||
|  |           '90%': { transform: 'translateY(-60px) translateX(7px) scale(0.9)', opacity: '0.2' }, | ||||||
|  |           '100%': { transform: 'translateY(-70px) translateX(0) scale(1.2)', opacity: '0.1' }, | ||||||
|  |         }, | ||||||
|  |         'ocean-shimmer': { | ||||||
|  |           '0%, 100%': { opacity: '0.1', transform: 'translateX(-10px)' }, | ||||||
|  |           '50%': { opacity: '0.3', transform: 'translateX(10px)' }, | ||||||
|  |         }, | ||||||
|       }, |       }, | ||||||
|       backdropBlur: { |       backdropBlur: { | ||||||
|         'xs': '2px', |         'xs': '2px', | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user