optimize archi
This commit is contained in:
parent
8b374cf8c4
commit
57f5807876
@ -1,31 +1,78 @@
|
||||
name: Build
|
||||
run-name: CI/CD website
|
||||
name: Build and Test
|
||||
run-name: Website build validation
|
||||
|
||||
on:
|
||||
#push:
|
||||
# branches:
|
||||
# - main
|
||||
# - dev
|
||||
push:
|
||||
branches: [main, dev]
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
branches: [main, dev]
|
||||
|
||||
jobs:
|
||||
build-check:
|
||||
build-classic:
|
||||
runs-on: ubuntu-latest
|
||||
name: Classic Build
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24.x'
|
||||
- name: Install dependencies
|
||||
node-version: '20'
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Install and build
|
||||
run: |
|
||||
cd banquise-website
|
||||
npm ci
|
||||
- name: Building
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm build
|
||||
|
||||
- name: Lint check
|
||||
run: |
|
||||
cd banquise-website
|
||||
npm run build
|
||||
pnpm lint
|
||||
|
||||
build-docker:
|
||||
runs-on: ubuntu-latest
|
||||
name: Docker Build
|
||||
needs: build-classic
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Create Dockerfile if missing
|
||||
run: |
|
||||
cd banquise-website
|
||||
if [ ! -f "Dockerfile" ]; then
|
||||
cat > Dockerfile << 'EOF'
|
||||
FROM node:20-alpine AS builder
|
||||
RUN npm install -g pnpm
|
||||
WORKDIR /app
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile
|
||||
COPY . .
|
||||
RUN pnpm build
|
||||
|
||||
FROM node:20-alpine AS runner
|
||||
RUN npm install -g pnpm
|
||||
WORKDIR /app
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile --prod
|
||||
COPY --from=builder /app/.next ./.next
|
||||
COPY --from=builder /app/public ./public
|
||||
EXPOSE 3000
|
||||
CMD ["pnpm", "start"]
|
||||
EOF
|
||||
fi
|
||||
|
||||
- name: Build and test Docker image
|
||||
run: |
|
||||
cd banquise-website
|
||||
docker build -t banquise-website:test .
|
||||
docker run -d --name test-container -p 3000:3000 banquise-website:test
|
||||
sleep 5
|
||||
docker ps | grep test-container
|
||||
docker stop test-container
|
||||
docker rm test-container
|
||||
|
@ -1,18 +0,0 @@
|
||||
node_modules
|
||||
.next
|
||||
dist
|
||||
build
|
||||
pnpm-debug.log
|
||||
.env
|
||||
.env.local
|
||||
.env.development
|
||||
.git
|
||||
*.log
|
||||
.DS_Store
|
||||
.vscode
|
||||
.idea
|
||||
README.md
|
||||
.gitignore
|
||||
.eslintrc.json
|
||||
*.sh
|
||||
shell.nix
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": ["next/core-web-vitals"]
|
||||
}
|
243
banquise-website/.gitignore
vendored
243
banquise-website/.gitignore
vendored
@ -1,3 +1,159 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
.pnp
|
||||
.pnp.js
|
||||
|
||||
# Testing
|
||||
coverage/
|
||||
.nyc_output
|
||||
|
||||
# Next.js
|
||||
.next/
|
||||
out/
|
||||
build/
|
||||
dist/
|
||||
|
||||
# Production builds
|
||||
*.tgz
|
||||
*.tar.gz
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage/
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
public
|
||||
|
||||
# Vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS)
|
||||
.DS_Store
|
||||
|
||||
# Windows
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
|
||||
# VS Code
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
# ESLint cache
|
||||
.eslintcache
|
||||
|
||||
# Prettier cache
|
||||
.prettiercache
|
||||
|
||||
# Stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
@ -7,18 +163,79 @@ yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
# Diagnostic reports
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Temporary folders
|
||||
tmp/
|
||||
temp/
|
||||
|
||||
# Editor backup files
|
||||
*~
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS generated files
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Local environment files
|
||||
.env*.local
|
||||
|
@ -1,9 +0,0 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* Minimal test CSS */
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
@ -1,13 +1,14 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
/* Configuration Tailwind v4 via CSS custom properties */
|
||||
/* Configuration Tailwind v4 via CSS custom properties - Variables globales */
|
||||
@layer base {
|
||||
:root {
|
||||
/* Polices */
|
||||
--font-heading: 'Dela Gothic One', sans-serif;
|
||||
--font-body: 'Roboto', sans-serif;
|
||||
|
||||
/* Couleurs personnalisées La Banquise */
|
||||
--color-banquise-blue: 64, 180, 255; /* RGB for reuse in rgba() */
|
||||
--color-banquise-blue: 64, 180, 255;
|
||||
--color-banquise-blue-hex: #40B4FF;
|
||||
--color-banquise-blue-dark: 31, 93, 137;
|
||||
--color-banquise-blue-dark-hex: #1F5D89;
|
||||
@ -16,27 +17,17 @@
|
||||
--color-banquise-blue-lightest: 165, 240, 255;
|
||||
--color-banquise-blue-lightest-hex: #A5F0FF;
|
||||
--color-banquise-gray: #F6F6F6;
|
||||
|
||||
/* Transitions communes */
|
||||
--transition-default: all 0.3s ease-in-out;
|
||||
--transition-fast: all 0.2s ease-in-out;
|
||||
|
||||
/* Spacing commun */
|
||||
--spacing-navbar: 4rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Variables CSS pour les polices et couleurs personnalisées */
|
||||
:root {
|
||||
--font-heading: 'Dela Gothic One', sans-serif;
|
||||
--font-body: 'Roboto', sans-serif;
|
||||
|
||||
/* Couleurs personnalisées La Banquise */
|
||||
--color-banquise-blue: 64, 180, 255; /* RGB for reuse in rgba() */
|
||||
--color-banquise-blue-hex: #40B4FF;
|
||||
--color-banquise-blue-dark: 31, 93, 137;
|
||||
--color-banquise-blue-dark-hex: #1F5D89;
|
||||
--color-banquise-blue-light: 105, 183, 226;
|
||||
--color-banquise-blue-light-hex: #69B7E2;
|
||||
--color-banquise-blue-lightest: 165, 240, 255;
|
||||
--color-banquise-blue-lightest-hex: #A5F0FF;
|
||||
--color-banquise-gray: #F6F6F6;
|
||||
}
|
||||
|
||||
/* Minimal, valid utility helpers that rely on CSS variables and rgba() */
|
||||
/* Minimal, valid utility helpers avec variables optimisées */
|
||||
@layer utilities {
|
||||
/* Text colors */
|
||||
.text-banquise-blue { color: var(--color-banquise-blue-hex); }
|
||||
@ -52,7 +43,7 @@
|
||||
.bg-banquise-blue-lightest { background-color: var(--color-banquise-blue-lightest-hex); }
|
||||
.bg-banquise-gray { background-color: var(--color-banquise-gray); }
|
||||
|
||||
/* Opacity helpers using rgba() and the stored RGB variables */
|
||||
/* Opacity helpers using rgba() */
|
||||
.bg-banquise-blue-5 { background-color: rgba(var(--color-banquise-blue), 0.05); }
|
||||
.bg-banquise-blue-10 { background-color: rgba(var(--color-banquise-blue), 0.10); }
|
||||
.bg-banquise-blue-20 { background-color: rgba(var(--color-banquise-blue), 0.20); }
|
||||
@ -62,28 +53,35 @@
|
||||
.border-banquise-blue { border-color: var(--color-banquise-blue-hex); }
|
||||
.border-banquise-blue-lightest-30 { border-color: rgba(var(--color-banquise-blue-lightest), 0.3); }
|
||||
|
||||
/* Gradients shortcuts (use with existing Tailwind gradient utilities) */
|
||||
/* Gradients shortcuts */
|
||||
.from-banquise-blue { --tw-gradient-from: var(--color-banquise-blue-hex); }
|
||||
.from-banquise-blue-dark { --tw-gradient-from: var(--color-banquise-blue-dark-hex); }
|
||||
.via-banquise-blue { --tw-gradient-via: var(--color-banquise-blue-hex); }
|
||||
.to-banquise-blue { --tw-gradient-to: var(--color-banquise-blue-hex); }
|
||||
|
||||
/* Simple shadow helpers */
|
||||
/* Shadow helpers */
|
||||
.shadow-banquise-blue-20 { box-shadow: 0 4px 6px -1px rgba(var(--color-banquise-blue), 0.20); }
|
||||
|
||||
/* Transitions communes */
|
||||
.transition-default { transition: var(--transition-default); }
|
||||
.transition-fast { transition: var(--transition-fast); }
|
||||
}
|
||||
|
||||
/* Animations kept as valid keyframes */
|
||||
/* Animations optimisées */
|
||||
@keyframes gentle-float {
|
||||
0%, 100% { transform: translateY(0) rotate(0deg); }
|
||||
50% { transform: translateY(-15px) rotate(1deg); }
|
||||
}
|
||||
|
||||
.animate-gentle-float { animation: gentle-float 6s ease-in-out infinite; }
|
||||
@keyframes bounce-up {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-4px); }
|
||||
}
|
||||
|
||||
@keyframes bounce-up { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-4px); } }
|
||||
.animate-gentle-float { animation: gentle-float 6s ease-in-out infinite; }
|
||||
.scroll-to-top:hover { animation: bounce-up 0.6s ease-in-out; }
|
||||
|
||||
/* Accessibility: respect reduced motion */
|
||||
/* Configuration globale optimisée */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.animate-gentle-float,
|
||||
.animate-ping,
|
||||
@ -92,13 +90,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Global improvements */
|
||||
html { scroll-behavior: smooth; }
|
||||
body { overflow-x: hidden; }
|
||||
|
||||
/* Scrollbar styles for popup content */
|
||||
.popup-content { scrollbar-width: thin; scrollbar-color: rgba(31,93,137,0.3) transparent; }
|
||||
/* Scrollbar unifié pour tous les éléments */
|
||||
.custom-scrollbar,
|
||||
.popup-content {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(31,93,137,0.3) transparent;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar,
|
||||
.popup-content::-webkit-scrollbar { width: 6px; }
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-track,
|
||||
.popup-content::-webkit-scrollbar-track { background: transparent; }
|
||||
.popup-content::-webkit-scrollbar-thumb { background: rgba(31,93,137,0.3); border-radius: 3px; }
|
||||
.popup-content::-webkit-scrollbar-thumb:hover { background: rgba(31,93,137,0.5); }
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb,
|
||||
.popup-content::-webkit-scrollbar-thumb {
|
||||
background: rgba(31,93,137,0.3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover,
|
||||
.popup-content::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(31,93,137,0.5);
|
||||
}
|
||||
|
@ -1,135 +0,0 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function TestColors() {
|
||||
return (
|
||||
<div className="min-h-screen bg-banquise-slate-50">
|
||||
{/* Header avec gradient moderne */}
|
||||
<div className="bg-gradient-banquise-nav text-white p-8 mb-8">
|
||||
<h1 className="text-4xl font-bold mb-4">Nouvelle Palette Banquise</h1>
|
||||
<p className="text-banquise-blue-200 text-lg">Design professionnel et harmonieux</p>
|
||||
</div>
|
||||
|
||||
<div className="max-w-6xl mx-auto p-8 space-y-12">
|
||||
|
||||
{/* Palette Bleus */}
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold text-banquise-blue-900 mb-6">Palette Bleus Principaux</h2>
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-4">
|
||||
<div className="bg-banquise-blue-900 text-white p-4 rounded-lg text-center">
|
||||
<div className="font-bold">900</div>
|
||||
<div className="text-sm">#0f2a3d</div>
|
||||
<div className="text-xs mt-2">Headers foncés</div>
|
||||
</div>
|
||||
<div className="bg-banquise-blue-800 text-white p-4 rounded-lg text-center">
|
||||
<div className="font-bold">800</div>
|
||||
<div className="text-sm">#1f5078</div>
|
||||
<div className="text-xs mt-2">Navigation</div>
|
||||
</div>
|
||||
<div className="bg-banquise-blue-600 text-white p-4 rounded-lg text-center">
|
||||
<div className="font-bold">600</div>
|
||||
<div className="text-sm">#34a6fc</div>
|
||||
<div className="text-xs mt-2">Boutons primaires</div>
|
||||
</div>
|
||||
<div className="bg-banquise-blue-400 text-white p-4 rounded-lg text-center">
|
||||
<div className="font-bold">400</div>
|
||||
<div className="text-sm">#76beee</div>
|
||||
<div className="text-xs mt-2">Liens</div>
|
||||
</div>
|
||||
<div className="bg-banquise-blue-200 text-banquise-blue-900 p-4 rounded-lg text-center">
|
||||
<div className="font-bold">200</div>
|
||||
<div className="text-sm">#a0ecf9</div>
|
||||
<div className="text-xs mt-2">Accents clairs</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Palette Gris */}
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold text-banquise-blue-900 mb-6">Palette Gris Harmonieux</h2>
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-4">
|
||||
<div className="bg-banquise-slate-900 text-white p-4 rounded-lg text-center">
|
||||
<div className="font-bold">Slate 900</div>
|
||||
<div className="text-sm">#0f172a</div>
|
||||
<div className="text-xs mt-2">Texte principal</div>
|
||||
</div>
|
||||
<div className="bg-banquise-slate-700 text-white p-4 rounded-lg text-center">
|
||||
<div className="font-bold">Slate 700</div>
|
||||
<div className="text-sm">#334155</div>
|
||||
<div className="text-xs mt-2">Texte secondaire</div>
|
||||
</div>
|
||||
<div className="bg-banquise-slate-300 text-banquise-slate-800 p-4 rounded-lg text-center">
|
||||
<div className="font-bold">Slate 300</div>
|
||||
<div className="text-sm">#cbd5e1</div>
|
||||
<div className="text-xs mt-2">Bordures</div>
|
||||
</div>
|
||||
<div className="bg-banquise-slate-100 text-banquise-slate-800 p-4 rounded-lg text-center">
|
||||
<div className="font-bold">Slate 100</div>
|
||||
<div className="text-sm">#f1f5f9</div>
|
||||
<div className="text-xs mt-2">Fonds clairs</div>
|
||||
</div>
|
||||
<div className="bg-banquise-slate-50 text-banquise-slate-800 p-4 rounded-lg text-center border">
|
||||
<div className="font-bold">Slate 50</div>
|
||||
<div className="text-sm">#f8fafc</div>
|
||||
<div className="text-xs mt-2">Fonds de page</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Exemples d'application */}
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold text-banquise-blue-900 mb-6">Exemples d'Application</h2>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
{/* Card moderne */}
|
||||
<div className="bg-white border border-banquise-slate-200 rounded-xl p-6 shadow-banquise">
|
||||
<div className="w-12 h-12 bg-gradient-banquise-primary rounded-lg mb-4 flex items-center justify-center text-white font-bold">
|
||||
B
|
||||
</div>
|
||||
<h3 className="text-lg font-bold text-banquise-blue-900 mb-2">Service Moderne</h3>
|
||||
<p className="text-banquise-slate-600 mb-4">Description avec contraste optimal pour la lisibilité.</p>
|
||||
<button className="bg-gradient-banquise-primary text-white px-4 py-2 rounded-lg hover:shadow-banquise-lg transition-all">
|
||||
Découvrir
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Card subtile */}
|
||||
<div className="bg-gradient-banquise-card border border-banquise-blue-200 rounded-xl p-6">
|
||||
<div className="w-12 h-12 bg-banquise-blue-200 rounded-lg mb-4 flex items-center justify-center text-banquise-blue-800 font-bold">
|
||||
S
|
||||
</div>
|
||||
<h3 className="text-lg font-bold text-banquise-blue-800 mb-2">Service Subtil</h3>
|
||||
<p className="text-banquise-slate-700 mb-4">Design épuré avec couleurs harmonieuses.</p>
|
||||
<button className="bg-banquise-blue-600 text-white px-4 py-2 rounded-lg hover:bg-banquise-blue-700 transition-colors">
|
||||
En savoir plus
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Navigation exemple */}
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold text-banquise-blue-900 mb-6">Exemple Navigation</h2>
|
||||
<div className="bg-gradient-banquise-nav rounded-xl p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-white font-bold text-xl">La Banquise</div>
|
||||
<div className="flex space-x-6">
|
||||
<a href="#" className="text-banquise-blue-200 hover:text-white transition-colors">Accueil</a>
|
||||
<a href="#" className="text-banquise-blue-200 hover:text-white transition-colors">Services</a>
|
||||
<a href="#" className="text-banquise-blue-200 hover:text-white transition-colors">À propos</a>
|
||||
</div>
|
||||
<button className="bg-banquise-blue-600 text-white px-4 py-2 rounded-lg hover:bg-banquise-blue-500 transition-colors">
|
||||
Contact
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="text-center pt-8">
|
||||
<Link href="/" className="text-banquise-blue-600 hover:text-banquise-blue-800 underline text-lg">
|
||||
← Voir le site avec la nouvelle palette
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
"use client"
|
||||
|
||||
export default function TestTailwind() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100 p-8">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<h1 className="text-4xl font-bold text-blue-600 mb-8">Test Tailwind CSS v4</h1>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* Test des couleurs de base */}
|
||||
<div className="bg-white p-6 rounded-lg shadow-lg">
|
||||
<h2 className="text-2xl font-semibold mb-4">Couleurs de base</h2>
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
<div className="h-16 bg-red-500 rounded flex items-center justify-center text-white">Red</div>
|
||||
<div className="h-16 bg-blue-500 rounded flex items-center justify-center text-white">Blue</div>
|
||||
<div className="h-16 bg-green-500 rounded flex items-center justify-center text-white">Green</div>
|
||||
<div className="h-16 bg-yellow-500 rounded flex items-center justify-center text-black">Yellow</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Test des espacements */}
|
||||
<div className="bg-white p-6 rounded-lg shadow-lg">
|
||||
<h2 className="text-2xl font-semibold mb-4">Espacements</h2>
|
||||
<div className="space-y-2">
|
||||
<div className="p-2 bg-gray-200">p-2</div>
|
||||
<div className="p-4 bg-gray-300">p-4</div>
|
||||
<div className="p-6 bg-gray-400">p-6</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Test des couleurs personnalisées */}
|
||||
<div className="bg-white p-6 rounded-lg shadow-lg">
|
||||
<h2 className="text-2xl font-semibold mb-4">Couleurs personnalisées Banquise</h2>
|
||||
<div className="space-y-2">
|
||||
<div className="text-banquise-blue p-2">Texte banquise-blue</div>
|
||||
<div className="bg-banquise-blue text-white p-2 rounded">Background banquise-blue</div>
|
||||
<div className="text-banquise-blue-dark p-2">Texte banquise-blue-dark</div>
|
||||
<div className="bg-banquise-blue-dark text-white p-2 rounded">Background banquise-blue-dark</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Test des animations */}
|
||||
<div className="bg-white p-6 rounded-lg shadow-lg">
|
||||
<h2 className="text-2xl font-semibold mb-4">Animations</h2>
|
||||
<div className="space-y-4">
|
||||
<div className="animate-pulse bg-blue-200 p-4 rounded">Pulse animation</div>
|
||||
<div className="animate-bounce bg-green-200 p-4 rounded inline-block">Bounce animation</div>
|
||||
<div className="animate-gentle-float bg-yellow-200 p-4 rounded inline-block">Custom gentle float</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { cn, commonClasses } from '@/lib/utils';
|
||||
|
||||
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: 'primary' | 'discord' | 'auth' | 'secondary' | 'ghost' | 'outline';
|
||||
@ -10,11 +11,12 @@ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
// Factorisation des classes de taille et de variante
|
||||
const sizeClasses = {
|
||||
sm: 'px-4 py-2 text-sm',
|
||||
md: 'px-6 py-3 text-base',
|
||||
lg: 'px-8 py-4 text-lg',
|
||||
};
|
||||
} as const;
|
||||
|
||||
const variantClasses = {
|
||||
primary: 'bg-gradient-to-r from-blue-600 to-blue-500 text-white shadow-lg hover:shadow-xl hover:from-blue-700 hover:to-blue-600 border-2 border-blue-600/20',
|
||||
@ -23,7 +25,15 @@ const variantClasses = {
|
||||
secondary: 'bg-white text-blue-700 border-2 border-blue-600 shadow-md hover:shadow-lg hover:bg-blue-50',
|
||||
outline: 'bg-transparent text-gray-700 border-2 border-gray-300 hover:bg-gray-50 hover:border-gray-400',
|
||||
ghost: 'bg-transparent text-gray-700 hover:bg-gray-100',
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Composant loading spinner réutilisable
|
||||
const LoadingSpinner = () => (
|
||||
<svg className="animate-spin -ml-1 mr-3 h-5 w-5" 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>
|
||||
);
|
||||
|
||||
export const Button: React.FC<ButtonProps> = ({
|
||||
variant = 'primary',
|
||||
@ -37,28 +47,25 @@ export const Button: React.FC<ButtonProps> = ({
|
||||
disabled,
|
||||
...props
|
||||
}) => {
|
||||
const baseClasses = [
|
||||
'inline-flex items-center justify-center font-semibold rounded-xl transition-all duration-300 transform',
|
||||
'hover:scale-105 active:scale-95 focus:outline-none focus:ring-4 focus:ring-blue-300',
|
||||
sizeClasses[size],
|
||||
variantClasses[variant],
|
||||
fullWidth ? 'w-full' : '',
|
||||
(disabled || loading) ? 'opacity-50 cursor-not-allowed transform-none' : '',
|
||||
className
|
||||
].filter(Boolean).join(' ');
|
||||
const isDisabled = disabled || loading;
|
||||
|
||||
return (
|
||||
<button
|
||||
className={baseClasses}
|
||||
disabled={disabled || loading}
|
||||
className={cn(
|
||||
commonClasses.buttonBase,
|
||||
commonClasses.transition,
|
||||
commonClasses.hoverScale,
|
||||
'focus:ring-4 focus:ring-blue-300',
|
||||
sizeClasses[size],
|
||||
variantClasses[variant],
|
||||
fullWidth && 'w-full',
|
||||
isDisabled && 'opacity-50 cursor-not-allowed transform-none',
|
||||
className
|
||||
)}
|
||||
disabled={isDisabled}
|
||||
{...props}
|
||||
>
|
||||
{loading && (
|
||||
<svg className="animate-spin -ml-1 mr-3 h-5 w-5" 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>
|
||||
)}
|
||||
{loading && <LoadingSpinner />}
|
||||
{leftIcon && !loading && <span className="mr-2">{leftIcon}</span>}
|
||||
{children}
|
||||
{rightIcon && !loading && <span className="ml-2">{rightIcon}</span>}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import Image from 'next/image'
|
||||
import { cn, commonClasses } from '@/lib/utils';
|
||||
import type { Service } from '@/types/service';
|
||||
|
||||
interface ServiceCardProps {
|
||||
@ -8,6 +9,17 @@ interface ServiceCardProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// Composant pour l'icône de flèche factorisant la structure répétitive
|
||||
const HoverArrow = () => (
|
||||
<div className="mt-6 flex justify-center opacity-0 group-hover:opacity-100 transition-all duration-300 transform translate-y-2 group-hover:translate-y-0">
|
||||
<div className="w-8 h-8 bg-blue-600 rounded-full flex items-center justify-center shadow-lg">
|
||||
<svg className="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const ServiceCard: React.FC<ServiceCardProps> = ({
|
||||
service,
|
||||
onServiceClick,
|
||||
@ -19,7 +31,15 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`group relative p-8 bg-white rounded-2xl shadow-lg hover:shadow-2xl border border-gray-200 hover:border-blue-300 transition-all duration-300 cursor-pointer transform hover:-translate-y-4 hover:scale-105 active:scale-95 ${className}`}
|
||||
className={cn(
|
||||
'group relative p-8 bg-white rounded-2xl',
|
||||
'shadow-lg hover:shadow-2xl border border-gray-200 hover:border-blue-300',
|
||||
commonClasses.transition,
|
||||
'cursor-pointer transform hover:-translate-y-4',
|
||||
commonClasses.hoverScale,
|
||||
'active:scale-95',
|
||||
className
|
||||
)}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{/* Indicateur de survol */}
|
||||
@ -28,34 +48,45 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({
|
||||
{/* Contenu de la carte */}
|
||||
<div className="relative z-10">
|
||||
{/* Icône du service */}
|
||||
<div className="mb-8 w-24 h-24 rounded-2xl bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center shadow-xl group-hover:shadow-2xl group-hover:scale-110 transition-all duration-300 mx-auto">
|
||||
<div className={cn(
|
||||
'mb-8 w-24 h-24 rounded-2xl mx-auto',
|
||||
'bg-gradient-to-br from-blue-500 to-blue-600',
|
||||
'flex items-center justify-center shadow-xl',
|
||||
'group-hover:shadow-2xl group-hover:scale-110',
|
||||
commonClasses.transition
|
||||
)}>
|
||||
<Image
|
||||
src={service.image as any}
|
||||
alt={service.icon}
|
||||
className="h-12 w-12 transition-transform duration-300 group-hover:scale-110"
|
||||
className={cn(
|
||||
'h-12 w-12',
|
||||
'transition-transform duration-300 group-hover:scale-110'
|
||||
)}
|
||||
width={48}
|
||||
height={48}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Nom du service */}
|
||||
<h3 className="text-xl md:text-2xl font-bold mb-4 text-gray-900 text-center group-hover:text-blue-700 transition-colors duration-300">
|
||||
<h3 className={cn(
|
||||
'text-xl md:text-2xl font-bold mb-4 text-gray-900 text-center',
|
||||
'group-hover:text-blue-700',
|
||||
commonClasses.transition
|
||||
)}>
|
||||
{service.name}
|
||||
</h3>
|
||||
|
||||
{/* Description courte */}
|
||||
<p className="text-gray-600 leading-relaxed text-center group-hover:text-gray-700 transition-colors duration-300">
|
||||
<p className={cn(
|
||||
'text-gray-600 leading-relaxed text-center',
|
||||
'group-hover:text-gray-700',
|
||||
commonClasses.transition
|
||||
)}>
|
||||
{service.description.split('.')[0]}.
|
||||
</p>
|
||||
|
||||
{/* Flèche indicatrice au hover */}
|
||||
<div className="mt-6 flex justify-center opacity-0 group-hover:opacity-100 transition-all duration-300 transform translate-y-2 group-hover:translate-y-0">
|
||||
<div className="w-8 h-8 bg-blue-600 rounded-full flex items-center justify-center shadow-lg">
|
||||
<svg className="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<HoverArrow />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -2,91 +2,120 @@ import React from 'react';
|
||||
import { URLS, SITE_CONFIG } from '@/lib/config/constants';
|
||||
import { BookOpen, GitBranch, Gamepad2, Cloud, Rocket, Heart } from 'lucide-react';
|
||||
import { useTranslation } from '@/lib/hooks/useTranslation';
|
||||
import { cn, commonClasses } from '@/lib/utils';
|
||||
|
||||
// Factorisation des icônes SVG
|
||||
const DiscordIcon = () => (
|
||||
<svg className="w-6 h-6" 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>
|
||||
);
|
||||
|
||||
const EmailIcon = () => (
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 4.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>
|
||||
);
|
||||
|
||||
// Composant pour les liens de service factorisant la structure répétitive
|
||||
interface ServiceLinkProps {
|
||||
href: string;
|
||||
icon: React.ComponentType<any>;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const ServiceLink: React.FC<ServiceLinkProps> = ({ href, icon: Icon, children }) => (
|
||||
<a
|
||||
href={href}
|
||||
className={cn(
|
||||
'flex items-center text-gray-300 hover:text-blue-400',
|
||||
commonClasses.transition,
|
||||
'hover:translate-x-2 transform'
|
||||
)}
|
||||
>
|
||||
<Icon className="w-5 h-5 mr-3" strokeWidth={2} />
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
|
||||
// Composant pour les boutons sociaux factorisant la structure répétitive
|
||||
interface SocialButtonProps {
|
||||
href: string;
|
||||
label: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const SocialButton: React.FC<SocialButtonProps> = ({ href, label, children }) => (
|
||||
<a
|
||||
href={href}
|
||||
className={cn(
|
||||
'w-12 h-12 bg-gray-800 hover:bg-blue-600 rounded-xl',
|
||||
'flex items-center justify-center',
|
||||
commonClasses.transition,
|
||||
commonClasses.hoverScale,
|
||||
'shadow-lg hover:shadow-blue-500/25'
|
||||
)}
|
||||
aria-label={label}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
|
||||
export const Footer: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<footer className="bg-gray-900 text-white py-16 px-4 sm:px-6 lg:px-8 border-t border-gray-800">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{/* Contenu principal du footer */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-12 mb-12">
|
||||
|
||||
{/* Marque et description */}
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center shadow-lg">
|
||||
<span className="text-white font-bold text-xl">B</span>
|
||||
<footer className="bg-gray-900 text-white py-16 px-4 sm:px-6 lg:px-8 border-t border-gray-800">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{/* Contenu principal du footer */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-12 mb-12">
|
||||
|
||||
{/* Marque et description */}
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center shadow-lg">
|
||||
<span className="text-white font-bold text-xl">B</span>
|
||||
</div>
|
||||
<span className="text-white font-bold text-2xl">
|
||||
{SITE_CONFIG.name}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-300 leading-relaxed">
|
||||
{t.footer.description}
|
||||
</p>
|
||||
{/* Réseaux sociaux */}
|
||||
<div className="flex items-center gap-4">
|
||||
<SocialButton href={URLS.social.discord} label="Discord">
|
||||
<DiscordIcon />
|
||||
</SocialButton>
|
||||
<SocialButton href={URLS.contact.email} label="Email">
|
||||
<EmailIcon />
|
||||
</SocialButton>
|
||||
</div>
|
||||
<span className="text-white font-bold text-2xl">
|
||||
{SITE_CONFIG.name}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-300 leading-relaxed">
|
||||
{t.footer.description}
|
||||
</p>
|
||||
{/* Réseaux sociaux */}
|
||||
<div className="flex items-center gap-4">
|
||||
<a
|
||||
href={URLS.social.discord}
|
||||
className="w-12 h-12 bg-gray-800 hover:bg-blue-600 rounded-xl flex items-center justify-center transition-all duration-300 hover:scale-110 shadow-lg hover:shadow-blue-500/25"
|
||||
aria-label="Discord"
|
||||
>
|
||||
<svg className="w-6 h-6" 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>
|
||||
</a>
|
||||
<a
|
||||
href={URLS.contact.email}
|
||||
className="w-12 h-12 bg-gray-800 hover:bg-blue-600 rounded-xl flex items-center justify-center transition-all duration-300 hover:scale-110 shadow-lg hover:shadow-blue-500/25"
|
||||
aria-label="Email"
|
||||
>
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 4.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>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Liens rapides services */}
|
||||
<div className="space-y-6">
|
||||
<h3 className="text-white font-bold text-lg mb-6">{t.footer.ourServices}</h3>
|
||||
<div className="space-y-4">
|
||||
<a
|
||||
href={URLS.services.wiki}
|
||||
className="flex items-center text-gray-300 hover:text-blue-400 transition-colors duration-200 hover:translate-x-2 transform"
|
||||
>
|
||||
<BookOpen className="w-5 h-5 mr-3" strokeWidth={2} />
|
||||
Wiki
|
||||
</a>
|
||||
<a
|
||||
href={URLS.services.gitea}
|
||||
className="flex items-center text-gray-300 hover:text-blue-400 transition-colors duration-200 hover:translate-x-2 transform"
|
||||
>
|
||||
<GitBranch className="w-5 h-5 mr-3" strokeWidth={2} />
|
||||
Gitea
|
||||
</a>
|
||||
<a
|
||||
href={URLS.services.panel}
|
||||
className="flex items-center text-gray-300 hover:text-blue-400 transition-colors duration-200 hover:translate-x-2 transform"
|
||||
>
|
||||
<Gamepad2 className="w-5 h-5 mr-3" strokeWidth={2} />
|
||||
{t.footer.gamingPanel}
|
||||
</a>
|
||||
<a
|
||||
href={URLS.services.opencloud}
|
||||
className="flex items-center text-gray-300 hover:text-blue-400 transition-colors duration-200 hover:translate-x-2 transform"
|
||||
>
|
||||
<Cloud className="w-5 h-5 mr-3" strokeWidth={2} />
|
||||
OpenCloud
|
||||
</a>
|
||||
{/* Liens rapides services */}
|
||||
<div className="space-y-6">
|
||||
<h3 className="text-white font-bold text-lg mb-6">{t.footer.ourServices}</h3>
|
||||
<div className="space-y-4">
|
||||
<ServiceLink href={URLS.services.wiki} icon={BookOpen}>
|
||||
Wiki
|
||||
</ServiceLink>
|
||||
<ServiceLink href={URLS.services.gitea} icon={GitBranch}>
|
||||
Gitea
|
||||
</ServiceLink>
|
||||
<ServiceLink href={URLS.services.panel} icon={Gamepad2}>
|
||||
{t.footer.gamingPanel}
|
||||
</ServiceLink>
|
||||
<ServiceLink href={URLS.services.opencloud} icon={Cloud}>
|
||||
OpenCloud
|
||||
</ServiceLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Informations communauté */}
|
||||
<div className="space-y-6">
|
||||
<h3 className="text-white font-bold text-lg mb-6">{t.footer.community}</h3>
|
||||
<div className="space-y-4">
|
||||
{/* Informations communauté */}
|
||||
<div className="space-y-6">
|
||||
<h3 className="text-white font-bold text-lg mb-6">{t.footer.community}</h3>
|
||||
<div className="bg-gradient-to-r from-blue-900/30 to-blue-800/30 rounded-xl p-6 border border-blue-800/30">
|
||||
<h4 className="text-blue-400 font-semibold mb-2">{t.footer.joinAssociation}</h4>
|
||||
<p className="text-gray-300 text-sm mb-4">
|
||||
@ -94,7 +123,11 @@ export const Footer: React.FC = () => {
|
||||
</p>
|
||||
<a
|
||||
href={URLS.social.discord}
|
||||
className="inline-flex items-center text-blue-400 hover:text-blue-300 text-sm font-semibold transition-colors duration-200"
|
||||
className={cn(
|
||||
'inline-flex items-center text-blue-400 hover:text-blue-300',
|
||||
'text-sm font-semibold',
|
||||
commonClasses.transition
|
||||
)}
|
||||
>
|
||||
<Rocket className="w-4 h-4 mr-2" strokeWidth={2} />
|
||||
{t.footer.joinNow}
|
||||
@ -102,24 +135,23 @@ export const Footer: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Barre du bas */}
|
||||
<div className="flex flex-col md:flex-row justify-between items-center gap-6 pt-8 border-t border-gray-800">
|
||||
<p className="text-gray-400 text-sm text-center md:text-left">
|
||||
© 2025 {SITE_CONFIG.name}. {t.footer.copyright}
|
||||
</p>
|
||||
<div className="flex items-center gap-6 text-sm text-gray-400">
|
||||
<span className="flex items-center">
|
||||
{t.footer.madeWith}
|
||||
<Heart className="text-red-500 mx-1 w-4 h-4" strokeWidth={2} fill="currentColor" />
|
||||
{t.footer.by}
|
||||
</span>
|
||||
<div className="w-1 h-1 bg-gray-600 rounded-full"></div>
|
||||
<span className="text-blue-400 font-semibold">EPITA 2025</span>
|
||||
{/* Barre du bas */}
|
||||
<div className="flex flex-col md:flex-row justify-between items-center gap-6 pt-8 border-t border-gray-800">
|
||||
<p className="text-gray-400 text-sm text-center md:text-left">
|
||||
© 2025 {SITE_CONFIG.name}. {t.footer.copyright}
|
||||
</p>
|
||||
<div className="flex items-center gap-6 text-sm text-gray-400">
|
||||
<span className="flex items-center">
|
||||
{t.footer.madeWith}
|
||||
<Heart className="text-red-500 mx-1 w-4 h-4" strokeWidth={2} fill="currentColor" />
|
||||
{t.footer.by}
|
||||
</span>
|
||||
<div className="w-1 h-1 bg-gray-600 rounded-full"></div>
|
||||
<span className="text-blue-400 font-semibold">EPITA 2025</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
@ -3,13 +3,9 @@ import { Button } from '@/components/common/Button';
|
||||
import { Logo } from './navbar/Logo';
|
||||
import { URLS } from '@/lib/config/constants';
|
||||
import { useTranslation } from '@/lib/hooks/useTranslation';
|
||||
import { cn, createNavClickHandler, commonClasses } from '@/lib/utils';
|
||||
import type { Translation } from '@/types/i18n';
|
||||
|
||||
// Fonction utilitaire simple pour combiner les classes
|
||||
const cn = (...classes: (string | undefined | null | false)[]): string => {
|
||||
return classes.filter(Boolean).join(' ');
|
||||
};
|
||||
|
||||
interface MobileMenuProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
@ -45,10 +41,8 @@ const MobileNavItem: React.FC<MobileNavItemProps> = ({
|
||||
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]',
|
||||
commonClasses.mobileMenuItem,
|
||||
commonClasses.hoverScale,
|
||||
'hover:shadow-lg hover:shadow-blue-500/20'
|
||||
)}
|
||||
target={isExternal ? '_blank' : undefined}
|
||||
@ -89,40 +83,57 @@ const MobileNavItem: React.FC<MobileNavItemProps> = ({
|
||||
|
||||
export const MobileMenu: React.FC<MobileMenuProps> = ({ isOpen, onClose, translations }) => {
|
||||
const { t } = useTranslation();
|
||||
// Gérer le scroll du body
|
||||
|
||||
// Gérer le scroll du body - simplifié avec notre utilitaire
|
||||
useEffect(() => {
|
||||
const originalStyle = document.body.style.overflow;
|
||||
|
||||
if (isOpen) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else {
|
||||
document.body.style.overflow = 'unset';
|
||||
document.body.style.overflow = originalStyle || 'unset';
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.body.style.overflow = 'unset';
|
||||
document.body.style.overflow = originalStyle || 'unset';
|
||||
};
|
||||
}, [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();
|
||||
// Gestionnaire de navigation optimisé
|
||||
const handleNavClick = createNavClickHandler(onClose);
|
||||
|
||||
// Configuration des icônes SVG - factorisation
|
||||
const icons = {
|
||||
home: (
|
||||
<svg className="w-5 h-5 text-blue-200" 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" />
|
||||
</svg>
|
||||
),
|
||||
services: (
|
||||
<svg className="w-5 h-5 text-blue-200" 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" />
|
||||
</svg>
|
||||
),
|
||||
about: (
|
||||
<svg className="w-5 h-5 text-blue-200" fill="none" stroke="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" />
|
||||
</svg>
|
||||
),
|
||||
contact: (
|
||||
<svg className="w-5 h-5 text-blue-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<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>
|
||||
),
|
||||
discord: (
|
||||
<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>
|
||||
),
|
||||
user: (
|
||||
<svg className="w-5 h-5" 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>
|
||||
)
|
||||
};
|
||||
|
||||
return (
|
||||
@ -156,10 +167,11 @@ export const MobileMenu: React.FC<MobileMenuProps> = ({ isOpen, onClose, transla
|
||||
|
||||
<button
|
||||
className={cn(
|
||||
'group relative p-3 rounded-xl transition-all duration-300',
|
||||
'group relative p-3 rounded-xl',
|
||||
commonClasses.transition,
|
||||
'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',
|
||||
commonClasses.hoverScale,
|
||||
'focus:outline-none focus:ring-2 focus:ring-blue-400/50'
|
||||
)}
|
||||
onClick={onClose}
|
||||
@ -177,11 +189,7 @@ export const MobileMenu: React.FC<MobileMenuProps> = ({ isOpen, onClose, transla
|
||||
{/* Section Navigation */}
|
||||
<div className="space-y-3">
|
||||
<MobileNavItem
|
||||
icon={
|
||||
<svg className="w-5 h-5 text-blue-200" 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" />
|
||||
</svg>
|
||||
}
|
||||
icon={icons.home}
|
||||
title={translations.home}
|
||||
description={t.common.backToHome}
|
||||
href="#home"
|
||||
@ -189,11 +197,7 @@ export const MobileMenu: React.FC<MobileMenuProps> = ({ isOpen, onClose, transla
|
||||
/>
|
||||
|
||||
<MobileNavItem
|
||||
icon={
|
||||
<svg className="w-5 h-5 text-blue-200" 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" />
|
||||
</svg>
|
||||
}
|
||||
icon={icons.services}
|
||||
title={translations.services}
|
||||
description={t.common.discoverOffer}
|
||||
href="#services"
|
||||
@ -201,11 +205,7 @@ export const MobileMenu: React.FC<MobileMenuProps> = ({ isOpen, onClose, transla
|
||||
/>
|
||||
|
||||
<MobileNavItem
|
||||
icon={
|
||||
<svg className="w-5 h-5 text-blue-200" fill="none" stroke="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" />
|
||||
</svg>
|
||||
}
|
||||
icon={icons.about}
|
||||
title={translations.about}
|
||||
description={t.common.learnMoreAboutUs}
|
||||
href="#about"
|
||||
@ -213,11 +213,7 @@ export const MobileMenu: React.FC<MobileMenuProps> = ({ isOpen, onClose, transla
|
||||
/>
|
||||
|
||||
<MobileNavItem
|
||||
icon={
|
||||
<svg className="w-5 h-5 text-blue-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<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>
|
||||
}
|
||||
icon={icons.contact}
|
||||
title={translations.contact}
|
||||
description={t.common.sendEmail}
|
||||
href="mailto:contact@la-banquise.fr"
|
||||
@ -231,11 +227,7 @@ export const MobileMenu: React.FC<MobileMenuProps> = ({ isOpen, onClose, transla
|
||||
{/* 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>
|
||||
}
|
||||
icon={icons.discord}
|
||||
title="Discord"
|
||||
description={t.common.joinCommunity}
|
||||
href={URLS.social.discord}
|
||||
@ -248,11 +240,7 @@ export const MobileMenu: React.FC<MobileMenuProps> = ({ isOpen, onClose, transla
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
leftIcon={
|
||||
<svg className="w-5 h-5" 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>
|
||||
}
|
||||
leftIcon={icons.user}
|
||||
onClick={() => {
|
||||
window.open(URLS.services.auth, '_blank');
|
||||
onClose();
|
||||
|
@ -5,13 +5,9 @@ import { NavLinks } from './navbar/NavLinks';
|
||||
import { ActionButtons } from './navbar/ActionButtons';
|
||||
import { MobileMenuButton } from './navbar/MobileMenuButton';
|
||||
import { MobileMenu } from './MobileMenu';
|
||||
import { cn, useResizeHandler } from '@/lib/utils';
|
||||
import type { Translation } from '@/types/i18n';
|
||||
|
||||
// Fonction utilitaire simple pour combiner les classes
|
||||
const mergeClasses = (...classes: (string | undefined | null | false)[]): string => {
|
||||
return classes.filter(Boolean).join(' ');
|
||||
};
|
||||
|
||||
interface ModernNavigationProps {
|
||||
translations: Translation['navigation'];
|
||||
languageSwitcher: React.ReactElement;
|
||||
@ -24,68 +20,35 @@ export const ModernNavigation: React.FC<ModernNavigationProps> = ({
|
||||
const { scrolled } = useScrollEffects();
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = React.useState(false);
|
||||
|
||||
// Fermer le menu mobile lors du redimensionnement
|
||||
// Fermer le menu mobile lors du redimensionnement - optimisé
|
||||
React.useEffect(() => {
|
||||
const handleResize = () => {
|
||||
if (window.innerWidth >= 768) {
|
||||
setMobileMenuOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
const cleanup = useResizeHandler(() => setMobileMenuOpen(false));
|
||||
return cleanup;
|
||||
}, []);
|
||||
|
||||
// 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 moderne épurée */}
|
||||
<nav className={mergeClasses(
|
||||
// Position et z-index
|
||||
<nav className={cn(
|
||||
'fixed top-0 left-0 right-0 z-50',
|
||||
|
||||
// Style de fond moderne
|
||||
'bg-blue-700/95 backdrop-blur-md border-b border-blue-600/30',
|
||||
|
||||
// Transition fluide
|
||||
'transition-all duration-200 ease-in-out',
|
||||
|
||||
// Effet de scroll
|
||||
'transition-fast',
|
||||
scrolled && 'shadow-lg'
|
||||
)}>
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="flex justify-between items-center px-4 sm:px-6 lg:px-8 h-16">
|
||||
|
||||
{/* Logo Section */}
|
||||
<Logo scrolled={scrolled} />
|
||||
|
||||
{/* Navigation Links (Desktop) - Centré */}
|
||||
<div className="flex-1 flex justify-center">
|
||||
<NavLinks
|
||||
translations={translations}
|
||||
scrolled={scrolled}
|
||||
/>
|
||||
<NavLinks translations={translations} scrolled={scrolled} />
|
||||
</div>
|
||||
|
||||
{/* Action Buttons (Desktop) */}
|
||||
<ActionButtons
|
||||
scrolled={scrolled}
|
||||
languageSwitcher={languageSwitcher}
|
||||
/>
|
||||
|
||||
{/* Mobile Menu Button */}
|
||||
<MobileMenuButton
|
||||
isOpen={mobileMenuOpen}
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
@ -100,7 +63,6 @@ export const ModernNavigation: React.FC<ModernNavigationProps> = ({
|
||||
{/* Spacer pour compenser la navbar fixed */}
|
||||
<div className="h-16" />
|
||||
|
||||
{/* Menu Mobile */}
|
||||
<MobileMenu
|
||||
isOpen={mobileMenuOpen}
|
||||
onClose={() => setMobileMenuOpen(false)}
|
||||
|
@ -1,8 +1,12 @@
|
||||
import React from 'react';
|
||||
import { AccordionItem } from '@/components/ui/AccordionItem';
|
||||
import { SectionHeader } from '@/components/ui/SectionHeader';
|
||||
import { ServiceCardAbout } from '@/components/ui/ServiceCardAbout';
|
||||
import { AccordionTitle } from '@/components/ui/AccordionTitle';
|
||||
import { URLS } from '@/lib/config/constants';
|
||||
import { Target, Settings, HelpCircle, Users, MessageCircle, Rocket, BookOpen, GitBranch, Gamepad2, Bird, Building, Mail, Cloud } from 'lucide-react';
|
||||
import { useTranslation } from '@/lib/hooks/useTranslation';
|
||||
import { cn, commonClasses } from '@/lib/utils';
|
||||
|
||||
interface AboutSectionProps {
|
||||
openAccordion: string | null;
|
||||
@ -15,18 +19,11 @@ export const AboutSection: React.FC<AboutSectionProps> = ({ openAccordion, toggl
|
||||
return (
|
||||
<section id="about" className="py-24 md:py-32 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
{/* Header de section moderne */}
|
||||
<div className="text-center mb-20">
|
||||
{/* Séparateur visuel */}
|
||||
<div className="w-24 h-1.5 bg-gradient-to-r from-blue-600 to-blue-400 rounded-full mx-auto mb-8" />
|
||||
|
||||
<h2 className="text-3xl md:text-4xl lg:text-5xl font-bold text-gray-900 mb-6 leading-tight">
|
||||
{t.about.title}
|
||||
</h2>
|
||||
<p className="text-lg md:text-xl text-gray-700 max-w-4xl mx-auto leading-relaxed font-medium">
|
||||
{t.about.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
{/* Header de section moderne - factorisation */}
|
||||
<SectionHeader
|
||||
title={t.about.title}
|
||||
subtitle={t.about.subtitle}
|
||||
/>
|
||||
|
||||
{/* Section FAQ avec design moderne */}
|
||||
<div className="space-y-8">
|
||||
@ -38,14 +35,7 @@ export const AboutSection: React.FC<AboutSectionProps> = ({ openAccordion, toggl
|
||||
</h3>
|
||||
|
||||
<AccordionItem
|
||||
title={
|
||||
<div className="flex items-center">
|
||||
<div className="w-8 h-8 bg-gradient-to-br from-blue-500 to-blue-600 rounded-lg flex items-center justify-center text-white mr-3">
|
||||
<Target className="w-5 h-5" strokeWidth={2} />
|
||||
</div>
|
||||
{t.about.mission.title}
|
||||
</div>
|
||||
}
|
||||
title={<AccordionTitle icon={Target} title={t.about.mission.title} />}
|
||||
isOpen={openAccordion === "mission"}
|
||||
onToggle={() => toggleAccordion("mission")}
|
||||
>
|
||||
@ -64,151 +54,118 @@ export const AboutSection: React.FC<AboutSectionProps> = ({ openAccordion, toggl
|
||||
</div>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem
|
||||
title={
|
||||
<div className="flex items-center">
|
||||
<div className="w-8 h-8 bg-gradient-to-br from-blue-500 to-blue-600 rounded-lg flex items-center justify-center text-white mr-3">
|
||||
<Settings className="w-5 h-5" strokeWidth={2} />
|
||||
<AccordionItem
|
||||
title={<AccordionTitle icon={Settings} title={t.about.services.title} />}
|
||||
isOpen={openAccordion === "services"}
|
||||
onToggle={() => toggleAccordion("services")}
|
||||
>
|
||||
<div className="space-y-6 p-6 bg-gray-50 rounded-xl">
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
{/* Cartes de services avec design moderne - factorisation */}
|
||||
<ServiceCardAbout
|
||||
icon={BookOpen}
|
||||
title={t.about.services.wiki.title}
|
||||
description={t.about.services.wiki.description}
|
||||
/>
|
||||
|
||||
<ServiceCardAbout
|
||||
icon={GitBranch}
|
||||
title={t.about.services.gitea.title}
|
||||
description={t.about.services.gitea.description}
|
||||
/>
|
||||
|
||||
<ServiceCardAbout
|
||||
icon={Gamepad2}
|
||||
title={t.about.services.panel.title}
|
||||
description={t.about.services.panel.description}
|
||||
/>
|
||||
|
||||
<ServiceCardAbout
|
||||
icon={Bird}
|
||||
title={t.about.services.pelican.title}
|
||||
description={t.about.services.pelican.description}
|
||||
/>
|
||||
|
||||
<ServiceCardAbout
|
||||
icon={Building}
|
||||
title={t.about.services.intranet.title}
|
||||
description={t.about.services.intranet.description}
|
||||
/>
|
||||
|
||||
<ServiceCardAbout
|
||||
icon={Mail}
|
||||
title={t.about.services.mails.title}
|
||||
description={t.about.services.mails.description}
|
||||
/>
|
||||
|
||||
<ServiceCardAbout
|
||||
icon={Cloud}
|
||||
title={t.about.services.opencloud.title}
|
||||
description={t.about.services.opencloud.description}
|
||||
colSpan={true}
|
||||
/>
|
||||
</div>
|
||||
{t.about.services.title}
|
||||
<p className="text-gray-600 mt-6 p-4 bg-blue-50 rounded-lg border border-blue-200">
|
||||
<strong className="text-blue-800 flex items-center">
|
||||
<Settings className="w-5 h-5 mr-2" strokeWidth={2} />
|
||||
{t.about.services.title}
|
||||
</strong> {t.about.services.note}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
isOpen={openAccordion === "services"}
|
||||
onToggle={() => toggleAccordion("services")}
|
||||
>
|
||||
<div className="space-y-6 p-6 bg-gray-50 rounded-xl">
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
{/* Cartes de services avec design moderne */}
|
||||
<div className="flex items-start space-x-4 p-6 bg-white rounded-xl shadow-lg border border-gray-200 hover:shadow-xl transition-all duration-300">
|
||||
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center text-white shadow-lg">
|
||||
<BookOpen className="w-6 h-6" strokeWidth={2} />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold text-gray-900 mb-2 text-lg">{t.about.services.wiki.title}</h4>
|
||||
<p className="text-gray-600">{t.about.services.wiki.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem
|
||||
title={<AccordionTitle icon={Users} title={t.about.community.title} />}
|
||||
isOpen={openAccordion === "community"}
|
||||
onToggle={() => toggleAccordion("community")}
|
||||
>
|
||||
<div className="space-y-8 p-6 bg-gray-50 rounded-xl">
|
||||
<p className="text-gray-700 text-lg leading-relaxed">
|
||||
{t.about.community.description}
|
||||
</p>
|
||||
|
||||
<div className="flex items-start space-x-4 p-6 bg-white rounded-xl shadow-lg border border-gray-200 hover:shadow-xl transition-all duration-300">
|
||||
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center text-white shadow-lg">
|
||||
<GitBranch className="w-6 h-6" strokeWidth={2} />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold text-gray-900 mb-2 text-lg">{t.about.services.gitea.title}</h4>
|
||||
<p className="text-gray-600">{t.about.services.gitea.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start space-x-4 p-6 bg-white rounded-xl shadow-lg border border-gray-200 hover:shadow-xl transition-all duration-300">
|
||||
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center text-white shadow-lg">
|
||||
<Gamepad2 className="w-6 h-6" strokeWidth={2} />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold text-gray-900 mb-2 text-lg">{t.about.services.panel.title}</h4>
|
||||
<p className="text-gray-600">{t.about.services.panel.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start space-x-4 p-6 bg-white rounded-xl shadow-lg border border-gray-200 hover:shadow-xl transition-all duration-300">
|
||||
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center text-white shadow-lg">
|
||||
<Bird className="w-6 h-6" strokeWidth={2} />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold text-gray-900 mb-2 text-lg">{t.about.services.pelican.title}</h4>
|
||||
<p className="text-gray-600">{t.about.services.pelican.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start space-x-4 p-6 bg-white rounded-xl shadow-lg border border-gray-200 hover:shadow-xl transition-all duration-300">
|
||||
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center text-white shadow-lg">
|
||||
<Building className="w-6 h-6" strokeWidth={2} />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold text-gray-900 mb-2 text-lg">{t.about.services.intranet.title}</h4>
|
||||
<p className="text-gray-600">{t.about.services.intranet.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start space-x-4 p-6 bg-white rounded-xl shadow-lg border border-gray-200 hover:shadow-xl transition-all duration-300">
|
||||
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center text-white shadow-lg">
|
||||
<Mail className="w-6 h-6" strokeWidth={2} />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold text-gray-900 mb-2 text-lg">{t.about.services.mails.title}</h4>
|
||||
<p className="text-gray-600">{t.about.services.mails.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start space-x-4 p-6 bg-white rounded-xl shadow-lg border border-gray-200 hover:shadow-xl transition-all duration-300 md:col-span-2">
|
||||
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center text-white shadow-lg">
|
||||
<Cloud className="w-6 h-6" strokeWidth={2} />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold text-gray-900 mb-2 text-lg">{t.about.services.opencloud.title}</h4>
|
||||
<p className="text-gray-600">{t.about.services.opencloud.description}</p>
|
||||
</div>
|
||||
<div className="bg-gradient-to-r from-blue-50 to-indigo-50 border-2 border-blue-200 rounded-2xl p-8">
|
||||
<h4 className="font-bold text-gray-900 mb-6 flex items-center text-xl">
|
||||
<div className="w-8 h-8 bg-gradient-to-br from-blue-500 to-blue-600 rounded-lg flex items-center justify-center text-white mr-3">
|
||||
<MessageCircle className="w-5 h-5" strokeWidth={2} />
|
||||
</div>
|
||||
{t.about.community.howToJoin}
|
||||
</h4>
|
||||
<ul className="space-y-4 text-gray-700 mb-8">
|
||||
<li className="flex items-center text-lg">
|
||||
<span className="w-2 h-2 bg-blue-500 rounded-full mr-4"></span>
|
||||
{t.about.community.steps.step1}
|
||||
</li>
|
||||
<li className="flex items-center text-lg">
|
||||
<span className="w-2 h-2 bg-blue-500 rounded-full mr-4"></span>
|
||||
{t.about.community.steps.step2}
|
||||
</li>
|
||||
<li className="flex items-center text-lg">
|
||||
<span className="w-2 h-2 bg-blue-500 rounded-full mr-4"></span>
|
||||
{t.about.community.steps.step3}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<a
|
||||
href={URLS.social.discord}
|
||||
className={cn(
|
||||
'inline-flex items-center justify-center',
|
||||
'px-8 py-4 text-lg font-bold text-white',
|
||||
'bg-gradient-to-r from-blue-600 to-blue-500 rounded-xl',
|
||||
'shadow-xl hover:shadow-2xl hover:from-blue-700 hover:to-blue-600',
|
||||
commonClasses.transition,
|
||||
commonClasses.hoverScale,
|
||||
'border-2 border-blue-600/20'
|
||||
)}
|
||||
>
|
||||
<Rocket className="w-6 h-6 mr-3" strokeWidth={2} />
|
||||
{t.about.community.joinDiscord}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-gray-600 mt-6 p-4 bg-blue-50 rounded-lg border border-blue-200">
|
||||
<strong className="text-blue-800 flex items-center">
|
||||
<Settings className="w-5 h-5 mr-2" strokeWidth={2} />
|
||||
{t.about.services.title}
|
||||
</strong> {t.about.services.note}
|
||||
</p>
|
||||
</div>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem
|
||||
title={
|
||||
<div className="flex items-center">
|
||||
<div className="w-8 h-8 bg-gradient-to-br from-blue-500 to-blue-600 rounded-lg flex items-center justify-center text-white mr-3">
|
||||
<Users className="w-5 h-5" strokeWidth={2} />
|
||||
</div>
|
||||
{t.about.community.title}
|
||||
</div>
|
||||
}
|
||||
isOpen={openAccordion === "community"}
|
||||
onToggle={() => toggleAccordion("community")}
|
||||
>
|
||||
<div className="space-y-8 p-6 bg-gray-50 rounded-xl">
|
||||
<p className="text-gray-700 text-lg leading-relaxed">
|
||||
{t.about.community.description}
|
||||
</p>
|
||||
|
||||
<div className="bg-gradient-to-r from-blue-50 to-indigo-50 border-2 border-blue-200 rounded-2xl p-8">
|
||||
<h4 className="font-bold text-gray-900 mb-6 flex items-center text-xl">
|
||||
<div className="w-8 h-8 bg-gradient-to-br from-blue-500 to-blue-600 rounded-lg flex items-center justify-center text-white mr-3">
|
||||
<MessageCircle className="w-5 h-5" strokeWidth={2} />
|
||||
</div>
|
||||
{t.about.community.howToJoin}
|
||||
</h4>
|
||||
<ul className="space-y-4 text-gray-700 mb-8">
|
||||
<li className="flex items-center text-lg">
|
||||
<span className="w-2 h-2 bg-blue-500 rounded-full mr-4"></span>
|
||||
{t.about.community.steps.step1}
|
||||
</li>
|
||||
<li className="flex items-center text-lg">
|
||||
<span className="w-2 h-2 bg-blue-500 rounded-full mr-4"></span>
|
||||
{t.about.community.steps.step2}
|
||||
</li>
|
||||
<li className="flex items-center text-lg">
|
||||
<span className="w-2 h-2 bg-blue-500 rounded-full mr-4"></span>
|
||||
{t.about.community.steps.step3}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<a
|
||||
href={URLS.social.discord}
|
||||
className="inline-flex items-center justify-center px-8 py-4 text-lg font-bold text-white bg-gradient-to-r from-blue-600 to-blue-500 rounded-xl shadow-xl hover:shadow-2xl hover:from-blue-700 hover:to-blue-600 transition-all duration-300 transform hover:scale-105 border-2 border-blue-600/20"
|
||||
>
|
||||
<Rocket className="w-6 h-6 mr-3" strokeWidth={2} />
|
||||
{t.about.community.joinDiscord}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionItem>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import Image from 'next/image';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import { cn, commonClasses } from '@/lib/utils';
|
||||
import type { Translation } from '@/types/i18n';
|
||||
|
||||
interface HeroSectionProps {
|
||||
@ -8,12 +9,69 @@ interface HeroSectionProps {
|
||||
commonTranslations: Translation['common'];
|
||||
}
|
||||
|
||||
// Composant pour les boutons CTA factorisant la structure répétitive
|
||||
interface CTAButtonProps {
|
||||
href: string;
|
||||
primary?: boolean;
|
||||
children: React.ReactNode;
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
const CTAButton: React.FC<CTAButtonProps> = ({ href, primary = false, children, icon }) => {
|
||||
const handleClick = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
const targetId = href.replace('#', '');
|
||||
if (targetId === 'services' || targetId === 'about') {
|
||||
document.getElementById(targetId)?.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const baseClasses = cn(
|
||||
'inline-flex items-center justify-center',
|
||||
'text-lg font-bold rounded-2xl',
|
||||
commonClasses.transition,
|
||||
commonClasses.hoverScale,
|
||||
'active:scale-95'
|
||||
);
|
||||
|
||||
const primaryClasses = cn(
|
||||
'px-12 py-5 text-white',
|
||||
'bg-gradient-to-r from-blue-600 to-blue-500',
|
||||
'shadow-2xl hover:shadow-blue-500/50',
|
||||
'hover:-translate-y-2 border-2 border-blue-600/20',
|
||||
'group relative overflow-hidden'
|
||||
);
|
||||
|
||||
const secondaryClasses = cn(
|
||||
'px-8 py-4 text-blue-700 bg-white',
|
||||
'border-2 border-blue-600 shadow-lg',
|
||||
'hover:shadow-xl hover:bg-blue-50'
|
||||
);
|
||||
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
onClick={handleClick}
|
||||
className={cn(baseClasses, primary ? primaryClasses : secondaryClasses)}
|
||||
>
|
||||
{primary && (
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500 rounded-2xl" />
|
||||
)}
|
||||
<span className="relative z-10">{children}</span>
|
||||
{icon && <span className="relative z-10 ml-3">{icon}</span>}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
export const HeroSection: React.FC<HeroSectionProps> = ({ translations, commonTranslations }) => (
|
||||
<section
|
||||
id="home"
|
||||
className="min-h-screen flex flex-col justify-center items-center text-center relative px-4 sm:px-6 lg:px-8 bg-gradient-to-br from-gray-50 via-blue-50/30 to-gray-100"
|
||||
>
|
||||
{/* Motif de fond avec grille plus visible */}
|
||||
{/* Motifs de fond optimisés - factorisation des styles répétitifs */}
|
||||
<div
|
||||
className="absolute inset-0 opacity-40"
|
||||
style={{
|
||||
@ -21,8 +79,6 @@ export const HeroSection: React.FC<HeroSectionProps> = ({ translations, commonTr
|
||||
backgroundSize: '32px 32px'
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Grille secondaire pour plus de profondeur */}
|
||||
<div
|
||||
className="absolute inset-0 opacity-20"
|
||||
style={{
|
||||
@ -37,17 +93,24 @@ export const HeroSection: React.FC<HeroSectionProps> = ({ translations, commonTr
|
||||
{/* Logo principal avec effet moderne */}
|
||||
<div className="mb-16 group">
|
||||
<div className="relative inline-block">
|
||||
{/* Effet de glow au hover */}
|
||||
<div className="absolute inset-0 bg-blue-400/30 rounded-3xl blur-3xl opacity-0 group-hover:opacity-100 transition-all duration-700 scale-150" />
|
||||
|
||||
{/* Container du logo avec glassmorphism */}
|
||||
<div className="relative bg-white/90 backdrop-blur-lg rounded-3xl p-10 border border-blue-200/50 shadow-2xl transition-all duration-500 group-hover:shadow-blue-200/50 group-hover:shadow-3xl group-hover:scale-105">
|
||||
<div className={cn(
|
||||
'relative bg-white/90 backdrop-blur-lg rounded-3xl p-10',
|
||||
'border border-blue-200/50 shadow-2xl',
|
||||
commonClasses.transition,
|
||||
'group-hover:shadow-blue-200/50 group-hover:shadow-3xl',
|
||||
commonClasses.hoverScale
|
||||
)}>
|
||||
<Image
|
||||
src="/assets/banquise_server.svg"
|
||||
alt={translations.title}
|
||||
width={140}
|
||||
height={140}
|
||||
className="w-28 h-28 md:w-32 md:h-32 lg:w-36 lg:h-36 transition-transform duration-500 group-hover:scale-110"
|
||||
className={cn(
|
||||
'w-28 h-28 md:w-32 md:h-32 lg:w-36 lg:h-36',
|
||||
'transition-transform duration-500 group-hover:scale-110'
|
||||
)}
|
||||
style={{
|
||||
filter: 'drop-shadow(0 8px 24px rgba(59, 130, 246, 0.4))'
|
||||
}}
|
||||
@ -66,44 +129,19 @@ export const HeroSection: React.FC<HeroSectionProps> = ({ translations, commonTr
|
||||
{translations.subtitle}
|
||||
</p>
|
||||
|
||||
{/* Call-to-action super mis en valeur */}
|
||||
{/* Call-to-action optimisé */}
|
||||
<div className="flex flex-col sm:flex-row gap-6 justify-center items-center mb-20">
|
||||
{/* Bouton principal très visible */}
|
||||
<a
|
||||
<CTAButton
|
||||
href="#services"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
document.getElementById('services')?.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
}}
|
||||
className="group relative inline-flex items-center justify-center px-12 py-5 text-lg font-bold text-white bg-gradient-to-r from-blue-600 to-blue-500 rounded-2xl shadow-2xl hover:shadow-blue-500/50 transition-all duration-300 transform hover:scale-110 hover:-translate-y-2 active:scale-95 border-2 border-blue-600/20"
|
||||
primary
|
||||
icon={<ArrowRight className="w-6 h-6 transition-transform duration-300 group-hover:translate-x-2" strokeWidth={2.5} />}
|
||||
>
|
||||
{/* Effet de brillance au hover */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500 rounded-2xl" />
|
||||
|
||||
<span className="relative z-10">{translations.cta}</span>
|
||||
<ArrowRight
|
||||
className="relative z-10 ml-3 w-6 h-6 transition-transform duration-300 group-hover:translate-x-2"
|
||||
strokeWidth={2.5}
|
||||
/>
|
||||
</a>
|
||||
{translations.cta}
|
||||
</CTAButton>
|
||||
|
||||
{/* Bouton secondaire épuré */}
|
||||
<a
|
||||
href="#about"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
document.getElementById('about')?.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
}}
|
||||
className="inline-flex items-center justify-center px-8 py-4 text-lg font-semibold text-blue-700 bg-white border-2 border-blue-600 rounded-xl shadow-lg hover:shadow-xl hover:bg-blue-50 hover:scale-105 transition-all duration-300 active:scale-95"
|
||||
>
|
||||
<CTAButton href="#about">
|
||||
{commonTranslations.learnMore}
|
||||
</a>
|
||||
</CTAButton>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { ServiceCard } from '@/components/common/ServiceCard';
|
||||
import { SectionHeader } from '@/components/ui/SectionHeader';
|
||||
import { useTranslation } from '@/lib/hooks/useTranslation';
|
||||
import type { Service } from '@/types/service';
|
||||
|
||||
@ -23,34 +24,24 @@ export const ServicesSection: React.FC<ServicesSectionProps> = ({
|
||||
id="services"
|
||||
className="py-24 md:py-32 px-4 sm:px-6 lg:px-8 bg-gradient-to-br from-white via-blue-50/30 to-gray-50"
|
||||
>
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{/* Header de section moderne avec forte hiérarchie */}
|
||||
<div className="text-center mb-20">
|
||||
{/* Séparateur visuel moderne */}
|
||||
<div className="w-24 h-1.5 bg-gradient-to-r from-blue-600 to-blue-400 rounded-full mx-auto mb-8" />
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{/* Header de section moderne avec forte hiérarchie - factorisation */}
|
||||
<SectionHeader
|
||||
title={t.sections.ourServices}
|
||||
subtitle={translations.discoverFeatures}
|
||||
/>
|
||||
|
||||
{/* Titre principal avec contraste fort */}
|
||||
<h2 className="text-3xl md:text-4xl lg:text-5xl font-bold text-gray-900 mb-6 leading-tight">
|
||||
{t.sections.ourServices}
|
||||
</h2>
|
||||
|
||||
{/* Sous-titre avec bon contraste */}
|
||||
<p className="text-lg md:text-xl text-gray-700 mx-auto max-w-3xl leading-relaxed font-medium">
|
||||
{translations.discoverFeatures}
|
||||
</p>
|
||||
{/* Grille de services avec espacement généreux */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 lg:gap-12">
|
||||
{services.map((service) => (
|
||||
<ServiceCard
|
||||
key={service.name}
|
||||
service={service}
|
||||
onServiceClick={onServiceClick}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Grille de services avec espacement généreux */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 lg:gap-12">
|
||||
{services.map((service) => (
|
||||
<ServiceCard
|
||||
key={service.name}
|
||||
service={service}
|
||||
onServiceClick={onServiceClick}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
17
banquise-website/components/ui/AccordionTitle.tsx
Normal file
17
banquise-website/components/ui/AccordionTitle.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import { LucideIcon } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface AccordionTitleProps {
|
||||
icon: LucideIcon;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const AccordionTitle: React.FC<AccordionTitleProps> = ({ icon: Icon, title }) => (
|
||||
<div className="flex items-center">
|
||||
<div className="w-8 h-8 bg-gradient-to-br from-blue-500 to-blue-600 rounded-lg flex items-center justify-center text-white mr-3">
|
||||
<Icon className="w-5 h-5" strokeWidth={2} />
|
||||
</div>
|
||||
{title}
|
||||
</div>
|
||||
);
|
@ -1,5 +1,7 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { URLS } from '@/lib/config/constants';
|
||||
import { FeatureBadge, FeatureItem, SectionTitle } from './PopupComponents';
|
||||
import { cn, commonClasses } from '@/lib/utils';
|
||||
import type { Service } from '@/types/service';
|
||||
import type { Translation } from '@/types/i18n';
|
||||
import { ClipboardList, Zap, Check, Lock, Rocket } from 'lucide-react';
|
||||
@ -13,10 +15,11 @@ interface PopupProps {
|
||||
export const Popup: React.FC<PopupProps> = ({ service, onClose, translations }) => {
|
||||
// Empêcher le scroll du body quand la popup est ouverte
|
||||
useEffect(() => {
|
||||
const originalStyle = document.body.style.overflow;
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
return () => {
|
||||
document.body.style.overflow = 'unset';
|
||||
document.body.style.overflow = originalStyle || 'unset';
|
||||
};
|
||||
}, []);
|
||||
|
||||
@ -28,7 +31,14 @@ export const Popup: React.FC<PopupProps> = ({ service, onClose, translations })
|
||||
<div className="absolute top-4 right-4 z-50">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="bg-white/90 hover:bg-white border border-gray-300 text-xl cursor-pointer text-gray-700 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={cn(
|
||||
'bg-white/90 hover:bg-white border border-gray-300',
|
||||
'text-xl cursor-pointer text-gray-700',
|
||||
'flex items-center justify-center w-10 h-10 sm:w-12 sm:h-12 rounded-full',
|
||||
commonClasses.transition,
|
||||
commonClasses.hoverScale,
|
||||
'shadow-lg backdrop-blur-sm'
|
||||
)}
|
||||
aria-label={translations.close}
|
||||
>
|
||||
×
|
||||
@ -62,53 +72,30 @@ export const Popup: React.FC<PopupProps> = ({ service, onClose, translations })
|
||||
{/* Content - Forcer le fond blanc */}
|
||||
<div className="p-6 sm:p-8 bg-white">
|
||||
{/* Description */}
|
||||
<h3 className="text-xl sm:text-2xl lg:text-3xl mb-4 lg:mb-6 text-gray-800 font-heading font-bold flex items-center">
|
||||
<div className="w-8 h-8 sm:w-10 sm:h-10 bg-gradient-to-br from-blue-500 to-blue-600 rounded-lg flex items-center justify-center text-white mr-3">
|
||||
<ClipboardList className="w-5 h-5 sm:w-6 sm:h-6" strokeWidth={2} />
|
||||
</div>
|
||||
Description détaillée
|
||||
</h3>
|
||||
<SectionTitle icon={ClipboardList} title="Description détaillée" />
|
||||
<div className="bg-gradient-to-br from-blue-50 to-blue-100/50 rounded-2xl p-4 lg:p-6 border border-blue-200 mb-8">
|
||||
<p className="text-gray-700 leading-relaxed text-base sm:text-lg lg:text-xl mb-4">
|
||||
{service.description}
|
||||
</p>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mt-6">
|
||||
<div className="flex items-center p-3 bg-white/80 rounded-xl border border-gray-200 shadow-sm">
|
||||
<div className="w-10 h-10 bg-gradient-to-br from-green-500 to-green-600 rounded-lg flex items-center justify-center text-white mr-3">
|
||||
<Check className="w-5 h-5" strokeWidth={2} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-semibold text-gray-800 text-sm">99.9% Uptime</div>
|
||||
<div className="text-gray-600 text-xs">Disponibilité garantie</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center p-3 bg-white/80 rounded-xl border border-gray-200 shadow-sm">
|
||||
<div className="w-10 h-10 bg-gradient-to-br from-blue-500 to-blue-600 rounded-lg flex items-center justify-center text-white mr-3">
|
||||
<Lock className="w-5 h-5" strokeWidth={2} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-semibold text-gray-800 text-sm">Sécurisé</div>
|
||||
<div className="text-gray-600 text-xs">SSL & Backups</div>
|
||||
</div>
|
||||
</div>
|
||||
<FeatureBadge
|
||||
icon={Check}
|
||||
title="99.9% Uptime"
|
||||
subtitle="Disponibilité garantie"
|
||||
/>
|
||||
<FeatureBadge
|
||||
icon={Lock}
|
||||
title="Sécurisé"
|
||||
subtitle="SSL & Backups"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Fonctionnalités */}
|
||||
<h3 className="text-xl sm:text-2xl lg:text-3xl mb-4 lg:mb-6 text-gray-800 font-heading font-bold flex items-center">
|
||||
<div className="w-8 h-8 sm:w-10 sm:h-10 bg-gradient-to-br from-blue-500 to-blue-600 rounded-lg flex items-center justify-center text-white mr-3">
|
||||
<Zap className="w-5 h-5 sm:w-6 sm:h-6" strokeWidth={2} />
|
||||
</div>
|
||||
{translations.discoverFeatures}
|
||||
</h3>
|
||||
<SectionTitle icon={Zap} title={translations.discoverFeatures} />
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-8">
|
||||
{service.features.map((feature, index) => (
|
||||
<div key={index} className="flex items-start bg-blue-50 rounded-xl p-4 border border-blue-200 hover:bg-blue-100 transition-colors duration-200 group">
|
||||
<div className="w-6 h-6 bg-gradient-to-br from-blue-600 to-blue-500 rounded-full flex items-center justify-center mr-3 mt-0.5 flex-shrink-0 group-hover:scale-110 transition-transform duration-200">
|
||||
<div className="w-2 h-2 bg-white rounded-full"></div>
|
||||
</div>
|
||||
<span className="text-gray-700 font-medium text-sm lg:text-base leading-relaxed">{feature}</span>
|
||||
</div>
|
||||
<FeatureItem key={index} feature={feature} index={index} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
@ -118,14 +105,23 @@ export const Popup: React.FC<PopupProps> = ({ service, onClose, translations })
|
||||
href={service.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-full inline-flex items-center justify-center bg-gradient-to-r from-blue-600 to-blue-500 text-white border-0 py-4 px-6 sm:px-8 rounded-2xl cursor-pointer no-underline font-bold tracking-wide shadow-lg transition-all duration-300 hover:shadow-xl hover:-translate-y-1 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 text-base lg:text-lg hover:scale-[1.02] active:scale-95"
|
||||
className={cn(
|
||||
'w-full inline-flex items-center justify-center',
|
||||
'bg-gradient-to-r from-blue-600 to-blue-500 text-white',
|
||||
'border-0 py-4 px-6 sm:px-8 rounded-2xl cursor-pointer no-underline',
|
||||
'font-bold tracking-wide shadow-lg',
|
||||
commonClasses.transition,
|
||||
commonClasses.hoverLift,
|
||||
'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500',
|
||||
'text-base lg:text-lg hover:scale-[1.02] active:scale-95'
|
||||
)}
|
||||
>
|
||||
<Rocket className="w-6 h-6 lg:w-7 lg:h-7 mr-3" strokeWidth={2} />
|
||||
<span>Accéder à {service.name}</span>
|
||||
</a>
|
||||
|
||||
<p className="text-center text-sm text-gray-500 mt-4">
|
||||
Besoin d'aide ? Rejoignez notre <a href={URLS.social.discord} className="text-blue-600 hover:text-blue-700 transition-colors duration-200 font-medium">Discord</a> pour obtenir du support
|
||||
Besoin d'aide ? Rejoignez notre <a href={URLS.social.discord} className={cn('text-blue-600 hover:text-blue-700 font-medium', commonClasses.transition)}>Discord</a> pour obtenir du support
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
53
banquise-website/components/ui/PopupComponents.tsx
Normal file
53
banquise-website/components/ui/PopupComponents.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
import { LucideIcon } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface FeatureBadgeProps {
|
||||
icon: LucideIcon;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
}
|
||||
|
||||
export const FeatureBadge: React.FC<FeatureBadgeProps> = ({
|
||||
icon: Icon,
|
||||
title,
|
||||
subtitle
|
||||
}) => (
|
||||
<div className="flex items-center p-3 bg-white/80 rounded-xl border border-gray-200 shadow-sm">
|
||||
<div className="w-10 h-10 bg-gradient-to-br from-green-500 to-green-600 rounded-lg flex items-center justify-center text-white mr-3">
|
||||
<Icon className="w-5 h-5" strokeWidth={2} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-semibold text-gray-800 text-sm">{title}</div>
|
||||
<div className="text-gray-600 text-xs">{subtitle}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
interface FeatureItemProps {
|
||||
feature: string;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export const FeatureItem: React.FC<FeatureItemProps> = ({ feature, index }) => (
|
||||
<div className="flex items-start bg-blue-50 rounded-xl p-4 border border-blue-200 hover:bg-blue-100 transition-colors duration-200 group">
|
||||
<div className="w-6 h-6 bg-gradient-to-br from-blue-600 to-blue-500 rounded-full flex items-center justify-center mr-3 mt-0.5 flex-shrink-0 group-hover:scale-110 transition-transform duration-200">
|
||||
<div className="w-2 h-2 bg-white rounded-full"></div>
|
||||
</div>
|
||||
<span className="text-gray-700 font-medium text-sm lg:text-base leading-relaxed">{feature}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
interface SectionTitleProps {
|
||||
icon: LucideIcon;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const SectionTitle: React.FC<SectionTitleProps> = ({ icon: Icon, title }) => (
|
||||
<h3 className="text-xl sm:text-2xl lg:text-3xl mb-4 lg:mb-6 text-gray-800 font-heading font-bold flex items-center">
|
||||
<div className="w-8 h-8 sm:w-10 sm:h-10 bg-gradient-to-br from-blue-500 to-blue-600 rounded-lg flex items-center justify-center text-white mr-3">
|
||||
<Icon className="w-5 h-5 sm:w-6 sm:h-6" strokeWidth={2} />
|
||||
</div>
|
||||
{title}
|
||||
</h3>
|
||||
);
|
31
banquise-website/components/ui/SectionHeader.tsx
Normal file
31
banquise-website/components/ui/SectionHeader.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface SectionHeaderProps {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const SectionHeader: React.FC<SectionHeaderProps> = ({
|
||||
title,
|
||||
subtitle,
|
||||
className
|
||||
}) => (
|
||||
<div className={cn('text-center mb-20', className)}>
|
||||
{/* Séparateur visuel moderne - factorisation du design répétitif */}
|
||||
<div className="w-24 h-1.5 bg-gradient-to-r from-blue-600 to-blue-400 rounded-full mx-auto mb-8" />
|
||||
|
||||
{/* Titre principal avec contraste fort */}
|
||||
<h2 className="text-3xl md:text-4xl lg:text-5xl font-bold text-gray-900 mb-6 leading-tight">
|
||||
{title}
|
||||
</h2>
|
||||
|
||||
{/* Sous-titre avec bon contraste */}
|
||||
{subtitle && (
|
||||
<p className="text-lg md:text-xl text-gray-700 mx-auto max-w-3xl leading-relaxed font-medium">
|
||||
{subtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
33
banquise-website/components/ui/ServiceCardAbout.tsx
Normal file
33
banquise-website/components/ui/ServiceCardAbout.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import { LucideIcon } from 'lucide-react';
|
||||
import { cn, commonClasses } from '@/lib/utils';
|
||||
|
||||
interface ServiceCardAboutProps {
|
||||
icon: LucideIcon;
|
||||
title: string;
|
||||
description: string;
|
||||
colSpan?: boolean;
|
||||
}
|
||||
|
||||
export const ServiceCardAbout: React.FC<ServiceCardAboutProps> = ({
|
||||
icon: Icon,
|
||||
title,
|
||||
description,
|
||||
colSpan = false
|
||||
}) => (
|
||||
<div className={cn(
|
||||
'flex items-start space-x-4 p-6 bg-white rounded-xl',
|
||||
'shadow-lg border border-gray-200',
|
||||
commonClasses.transition,
|
||||
commonClasses.cardHover,
|
||||
colSpan && 'md:col-span-2'
|
||||
)}>
|
||||
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center text-white shadow-lg">
|
||||
<Icon className="w-6 h-6" strokeWidth={2} />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold text-gray-900 mb-2 text-lg">{title}</h4>
|
||||
<p className="text-gray-600">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
@ -2,7 +2,7 @@ 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
|
||||
* Optimisé avec des callbacks memoized et une API simplifiée
|
||||
*/
|
||||
export const useAccordion = (initialState: string | null = null) => {
|
||||
const [openAccordion, setOpenAccordion] = useState<string | null>(initialState);
|
||||
|
118
banquise-website/lib/hooks/useCommon.ts
Normal file
118
banquise-website/lib/hooks/useCommon.ts
Normal file
@ -0,0 +1,118 @@
|
||||
import { useEffect, useCallback, useState } from 'react';
|
||||
|
||||
/**
|
||||
* Hook centralisé pour la gestion du scroll du body
|
||||
* Factorisation de la logique répétée dans plusieurs composants
|
||||
*/
|
||||
export const useBodyScrollLock = (isLocked: boolean) => {
|
||||
useEffect(() => {
|
||||
const originalStyle = document.body.style.overflow;
|
||||
|
||||
if (isLocked) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else {
|
||||
document.body.style.overflow = originalStyle || 'unset';
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.body.style.overflow = originalStyle || 'unset';
|
||||
};
|
||||
}, [isLocked]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook pour gérer les événements de redimensionnement avec debounce
|
||||
*/
|
||||
export const useResizeEvent = (callback: () => void, breakpoint: number = 768, delay: number = 100) => {
|
||||
useEffect(() => {
|
||||
let timeoutId: NodeJS.Timeout;
|
||||
|
||||
const handleResize = () => {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(() => {
|
||||
if (window.innerWidth >= breakpoint) {
|
||||
callback();
|
||||
}
|
||||
}, delay);
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
clearTimeout(timeoutId);
|
||||
};
|
||||
}, [callback, breakpoint, delay]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook pour les états de toggle simples (booléens)
|
||||
*/
|
||||
export const useToggle = (initialState: boolean = false) => {
|
||||
const [state, setState] = useState(initialState);
|
||||
|
||||
const toggle = useCallback(() => setState(prev => !prev), []);
|
||||
const setTrue = useCallback(() => setState(true), []);
|
||||
const setFalse = useCallback(() => setState(false), []);
|
||||
|
||||
return {
|
||||
state,
|
||||
toggle,
|
||||
setTrue,
|
||||
setFalse,
|
||||
setState
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook pour gérer les modales génériques
|
||||
*/
|
||||
export const useModal = <T = any>(initialState: T | null = null) => {
|
||||
const [data, setData] = useState<T | null>(initialState);
|
||||
|
||||
const open = useCallback((newData: T) => {
|
||||
setData(newData);
|
||||
}, []);
|
||||
|
||||
const close = useCallback(() => {
|
||||
setData(null);
|
||||
}, []);
|
||||
|
||||
const isOpen = data !== null;
|
||||
|
||||
return {
|
||||
data,
|
||||
open,
|
||||
close,
|
||||
isOpen
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook pour la navigation entre sections avec scroll smooth
|
||||
*/
|
||||
export const useNavigation = () => {
|
||||
const scrollToSection = useCallback((sectionId: string) => {
|
||||
if (sectionId === 'home') {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
} else if (sectionId === 'contact') {
|
||||
window.location.href = 'mailto:contact@la-banquise.fr';
|
||||
} else {
|
||||
const element = document.getElementById(sectionId);
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
const createNavHandler = useCallback((onClose?: () => void) => {
|
||||
return (sectionId: string) => {
|
||||
scrollToSection(sectionId);
|
||||
onClose?.();
|
||||
};
|
||||
}, [scrollToSection]);
|
||||
|
||||
return {
|
||||
scrollToSection,
|
||||
createNavHandler
|
||||
};
|
||||
};
|
@ -1,64 +0,0 @@
|
||||
// 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 = {
|
||||
// Gradients - Keep existing structure
|
||||
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"
|
||||
},
|
||||
|
||||
// Buttons - Keep existing structure
|
||||
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",
|
||||
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"
|
||||
},
|
||||
|
||||
// Cards - Keep existing structure
|
||||
cards: {
|
||||
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"
|
||||
},
|
||||
|
||||
// Text - Keep existing structure
|
||||
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"
|
||||
},
|
||||
|
||||
// Layout - Keep existing structure
|
||||
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"
|
||||
},
|
||||
|
||||
// Icons - Keep existing structure
|
||||
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",
|
||||
small: "w-10 h-10 rounded-lg flex items-center justify-center text-white"
|
||||
},
|
||||
|
||||
// Navigation - Keep existing structure
|
||||
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"
|
||||
}
|
||||
} as const;
|
197
banquise-website/lib/styles/designSystem.ts
Normal file
197
banquise-website/lib/styles/designSystem.ts
Normal file
@ -0,0 +1,197 @@
|
||||
// Design System La Banquise - Système centralisé de styles
|
||||
export const designSystem = {
|
||||
// 🎨 Gradients
|
||||
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",
|
||||
hero: "bg-gradient-to-br from-banquise-blue to-banquise-blue-dark",
|
||||
section: "bg-gradient-to-b from-white/95 to-white"
|
||||
},
|
||||
|
||||
// 🎯 Boutons optimisés
|
||||
buttons: {
|
||||
base: "inline-flex items-center justify-center font-bold border-0 rounded-2xl transition-all duration-300 active:scale-95",
|
||||
effects: "hover:shadow-xl hover:-translate-y-1 hover:scale-105",
|
||||
variants: {
|
||||
primary: "text-white",
|
||||
discord: "group relative overflow-hidden text-white font-semibold rounded-xl hover:shadow-indigo-500/25",
|
||||
auth: "group relative overflow-hidden text-white font-semibold rounded-xl",
|
||||
secondary: "bg-white/10 backdrop-blur-sm text-white border border-white/20",
|
||||
outline: "bg-transparent border-2 border-banquise-blue text-banquise-blue hover:bg-banquise-blue hover:text-white"
|
||||
},
|
||||
sizes: {
|
||||
sm: "px-3 py-2 text-sm",
|
||||
md: "px-4 lg:px-6 py-2.5 lg:py-3 text-sm lg:text-base",
|
||||
lg: "px-6 lg:px-8 py-3 lg:py-4 text-base lg:text-lg"
|
||||
}
|
||||
},
|
||||
|
||||
// 🃏 Cartes
|
||||
cards: {
|
||||
base: "backdrop-blur-lg rounded-2xl border transition-all duration-300",
|
||||
borders: {
|
||||
default: "border-banquise-blue-lightest/30",
|
||||
hover: "hover:border-banquise-blue-lightest/50",
|
||||
active: "border-banquise-blue/50"
|
||||
},
|
||||
effects: {
|
||||
hover: "hover:shadow-xl",
|
||||
lift: "hover:-translate-y-4 hover:shadow-2xl",
|
||||
interactive: "cursor-pointer hover:-translate-y-4 hover:shadow-2xl active:scale-95"
|
||||
},
|
||||
backgrounds: {
|
||||
glass: "bg-white/80 backdrop-blur-lg",
|
||||
gradient: "bg-gradient-to-br from-white/90 to-white/80",
|
||||
solid: "bg-white"
|
||||
}
|
||||
},
|
||||
|
||||
// 📝 Typographie
|
||||
typography: {
|
||||
headings: {
|
||||
xl: "text-3xl sm:text-4xl md:text-5xl font-heading font-bold tracking-tight",
|
||||
lg: "text-2xl sm:text-3xl md:text-4xl font-heading font-bold tracking-tight",
|
||||
md: "text-xl sm:text-2xl md:text-3xl font-heading font-bold tracking-tight",
|
||||
sm: "text-lg sm:text-xl md:text-2xl font-heading font-semibold tracking-tight"
|
||||
},
|
||||
body: {
|
||||
xl: "text-lg sm:text-xl md:text-2xl leading-relaxed",
|
||||
lg: "text-base sm:text-lg md:text-xl leading-relaxed",
|
||||
md: "text-sm sm:text-base md:text-lg leading-relaxed",
|
||||
sm: "text-xs sm:text-sm md:text-base leading-relaxed"
|
||||
},
|
||||
colors: {
|
||||
primary: "text-banquise-gray",
|
||||
secondary: "text-banquise-blue-dark",
|
||||
muted: "text-banquise-gray/80",
|
||||
light: "text-banquise-blue-lightest",
|
||||
white: "text-white/90"
|
||||
},
|
||||
weights: {
|
||||
normal: "font-normal",
|
||||
medium: "font-medium",
|
||||
semibold: "font-semibold",
|
||||
bold: "font-bold"
|
||||
}
|
||||
},
|
||||
|
||||
// 📐 Layout
|
||||
layout: {
|
||||
sections: {
|
||||
default: "py-12 sm:py-16 md:py-20",
|
||||
compact: "py-8 sm:py-12 md:py-16",
|
||||
spacious: "py-16 sm:py-20 md:py-24"
|
||||
},
|
||||
containers: {
|
||||
default: "w-full max-w-6xl mx-auto px-4 sm:px-6 md:px-8",
|
||||
narrow: "w-full max-w-4xl mx-auto px-4 sm:px-6 md:px-8",
|
||||
wide: "w-full max-w-7xl mx-auto px-4 sm:px-6 md:px-8"
|
||||
},
|
||||
dividers: {
|
||||
default: "w-20 h-1 bg-gradient-to-r from-banquise-blue-lightest to-banquise-blue mx-auto rounded-full",
|
||||
small: "w-12 h-1 bg-gradient-to-r from-banquise-blue-lightest to-banquise-blue mx-auto rounded-full",
|
||||
large: "w-32 h-1 bg-gradient-to-r from-banquise-blue-lightest to-banquise-blue mx-auto rounded-full"
|
||||
},
|
||||
grids: {
|
||||
responsive: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 lg:gap-8",
|
||||
auto: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 sm:gap-6",
|
||||
services: "grid grid-cols-1 lg:grid-cols-2 gap-6 lg:gap-8"
|
||||
}
|
||||
},
|
||||
|
||||
// 🎯 Icônes
|
||||
icons: {
|
||||
sizes: {
|
||||
xs: "w-4 h-4",
|
||||
sm: "w-6 h-6",
|
||||
md: "w-8 h-8",
|
||||
lg: "w-12 h-12",
|
||||
xl: "w-16 h-16 sm:w-20 sm:h-20 lg:w-24 lg:h-24"
|
||||
},
|
||||
containers: {
|
||||
card: "rounded-2xl flex items-center justify-center shadow-lg",
|
||||
small: "w-10 h-10 rounded-lg flex items-center justify-center text-white",
|
||||
service: "text-3xl sm:text-4xl lg:text-5xl"
|
||||
},
|
||||
effects: {
|
||||
hover: "transition-transform duration-300 hover:scale-110",
|
||||
spin: "animate-spin",
|
||||
bounce: "animate-bounce"
|
||||
}
|
||||
},
|
||||
|
||||
// 🧭 Navigation
|
||||
navigation: {
|
||||
links: {
|
||||
desktop: "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",
|
||||
mobile: "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"
|
||||
},
|
||||
effects: {
|
||||
underline: "after:absolute after:bottom-1 after:left-1/2 after:w-0 after:h-0.5 after:bg-banquise-blue-lightest after:transition-all after:duration-300 group-hover:after:w-3/4 group-hover:after:left-1/8",
|
||||
glow: "hover:drop-shadow-lg hover:drop-shadow-banquise-blue-lightest/50"
|
||||
}
|
||||
},
|
||||
|
||||
// ⚡ Animations & Transitions
|
||||
animations: {
|
||||
transitions: {
|
||||
fast: "transition-all duration-200",
|
||||
default: "transition-all duration-300",
|
||||
slow: "transition-all duration-500"
|
||||
},
|
||||
transforms: {
|
||||
lift: "hover:-translate-y-1",
|
||||
liftLarge: "hover:-translate-y-4",
|
||||
scale: "hover:scale-105",
|
||||
scaleSmall: "hover:scale-102"
|
||||
},
|
||||
loading: {
|
||||
spin: "animate-spin",
|
||||
pulse: "animate-pulse",
|
||||
bounce: "animate-bounce"
|
||||
}
|
||||
},
|
||||
|
||||
// 🌊 Effets spéciaux La Banquise
|
||||
effects: {
|
||||
ocean: {
|
||||
wave: "animate-pulse",
|
||||
depth: "backdrop-blur-xl",
|
||||
surface: "bg-gradient-to-b from-transparent to-banquise-blue/5"
|
||||
},
|
||||
ice: {
|
||||
crystal: "backdrop-blur-lg bg-white/10",
|
||||
frost: "backdrop-blur-sm bg-white/5",
|
||||
shine: "bg-gradient-to-br from-white/20 to-transparent"
|
||||
}
|
||||
}
|
||||
} as const;
|
||||
|
||||
// 🎨 Utilitaires de combinaison
|
||||
export const combineStyles = {
|
||||
// Bouton primaire complet
|
||||
primaryButton: `${designSystem.buttons.base} ${designSystem.buttons.effects} ${designSystem.gradients.primary} ${designSystem.buttons.variants.primary}`,
|
||||
|
||||
// Carte interactive complète
|
||||
interactiveCard: `${designSystem.cards.base} ${designSystem.cards.borders.default} ${designSystem.cards.effects.interactive} ${designSystem.cards.backgrounds.glass}`,
|
||||
|
||||
// Section standard
|
||||
standardSection: `${designSystem.layout.sections.default} ${designSystem.layout.containers.default}`,
|
||||
|
||||
// Titre de section
|
||||
sectionTitle: `${designSystem.typography.headings.lg} ${designSystem.typography.colors.primary}`,
|
||||
|
||||
// Lien de navigation desktop
|
||||
navLink: `${designSystem.navigation.links.desktop} ${designSystem.navigation.effects.underline}`,
|
||||
|
||||
// Grid responsive services
|
||||
servicesGrid: designSystem.layout.grids.services
|
||||
} as const;
|
||||
|
||||
// Type pour l'autocomplétion
|
||||
export type DesignSystemKey = keyof typeof designSystem;
|
||||
export type CombinedStyleKey = keyof typeof combineStyles;
|
87
banquise-website/lib/utils/index.ts
Normal file
87
banquise-website/lib/utils/index.ts
Normal file
@ -0,0 +1,87 @@
|
||||
// Utilitaires centralisés pour éviter la duplication
|
||||
|
||||
/**
|
||||
* Combine les classes CSS en filtrant les valeurs falsy
|
||||
* Remplace les fonctions mergeClasses et cn dupliquées
|
||||
*/
|
||||
export const cn = (...classes: (string | undefined | null | false)[]): string => {
|
||||
return classes.filter(Boolean).join(' ');
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook personnalisé pour gérer le scroll du body
|
||||
* Factorisation de la logique répétée dans plusieurs composants
|
||||
*/
|
||||
export const useBodyScrollLock = (isLocked: boolean) => {
|
||||
const originalStyle = document.body.style.overflow;
|
||||
|
||||
if (isLocked) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else {
|
||||
document.body.style.overflow = originalStyle || 'unset';
|
||||
}
|
||||
|
||||
// Cleanup function
|
||||
return () => {
|
||||
document.body.style.overflow = originalStyle || 'unset';
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Configuration de navigation centralisée
|
||||
*/
|
||||
export const createNavClickHandler = (onClose?: () => void) => {
|
||||
return (sectionId: string) => {
|
||||
if (sectionId === 'home') {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
} else if (sectionId === 'contact') {
|
||||
window.location.href = 'mailto:contact@la-banquise.fr';
|
||||
} else {
|
||||
const element = document.getElementById(sectionId);
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
}
|
||||
onClose?.();
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Gestionnaire d'événements de redimensionnement optimisé
|
||||
*/
|
||||
export const useResizeHandler = (callback: () => void, breakpoint: number = 768) => {
|
||||
const handleResize = () => {
|
||||
if (window.innerWidth >= breakpoint) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
};
|
||||
|
||||
/**
|
||||
* Classes CSS communes pour éviter la répétition
|
||||
*/
|
||||
export const commonClasses = {
|
||||
// Transitions
|
||||
transition: 'transition-all duration-300 ease-in-out',
|
||||
transitionFast: 'transition-all duration-200 ease-in-out',
|
||||
|
||||
// Hover effects communs
|
||||
hoverLift: 'hover:-translate-y-1 hover:shadow-xl hover:scale-105',
|
||||
hoverScale: 'hover:scale-105 active:scale-95',
|
||||
|
||||
// Boutons communs
|
||||
buttonBase: 'inline-flex items-center justify-center font-semibold rounded-xl transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-blue-400/50',
|
||||
|
||||
// Navigation
|
||||
navLink: 'px-4 py-2.5 text-white/90 hover:text-white font-medium rounded-xl transition-all duration-300 hover:bg-white/10',
|
||||
|
||||
// Cards
|
||||
cardBase: 'backdrop-blur-lg rounded-2xl border transition-all duration-300',
|
||||
cardHover: 'hover:shadow-xl hover:-translate-y-1',
|
||||
|
||||
// Mobile menu items
|
||||
mobileMenuItem: 'group flex items-center justify-between p-4 rounded-xl transition-all duration-300 bg-white/5 hover:bg-white/10 border border-white/10 hover:border-white/20',
|
||||
} as const;
|
@ -1,53 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test Couleurs Banquise</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
banquise: {
|
||||
blue: '#34a6fc',
|
||||
'blue-dark': '#1f5078',
|
||||
'blue-light': '#76beee',
|
||||
'blue-lightest': '#a0ecf9',
|
||||
gray: '#F6F6F6',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body class="p-8">
|
||||
<h1 class="text-2xl font-bold mb-4">Test des couleurs Banquise</h1>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="p-4 bg-banquise-blue text-white rounded">
|
||||
Couleur banquise-blue (#34a6fc)
|
||||
</div>
|
||||
|
||||
<div class="p-4 bg-banquise-blue-dark text-white rounded">
|
||||
Couleur banquise-blue-dark (#1f5078)
|
||||
</div>
|
||||
|
||||
<div class="p-4 bg-banquise-blue-light text-white rounded">
|
||||
Couleur banquise-blue-light (#76beee)
|
||||
</div>
|
||||
|
||||
<div class="p-4 bg-banquise-blue-lightest text-black rounded">
|
||||
Couleur banquise-blue-lightest (#a0ecf9)
|
||||
</div>
|
||||
|
||||
<div class="p-4 bg-banquise-gray text-black rounded">
|
||||
Couleur banquise-gray (#F6F6F6)
|
||||
</div>
|
||||
|
||||
<button class="px-4 py-2 bg-gradient-to-r from-banquise-blue to-banquise-blue-light text-white rounded">
|
||||
Bouton avec gradient banquise
|
||||
</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,9 +1,48 @@
|
||||
// Re-export types from their specific modules
|
||||
export type { Service } from './service';
|
||||
export type { Language, Translation } from './i18n';
|
||||
|
||||
// Types communs pour les composants UI
|
||||
export interface AccordionItemProps {
|
||||
title: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
isOpen: boolean;
|
||||
onToggle: () => void;
|
||||
}
|
||||
|
||||
// Types pour les hooks communs
|
||||
export interface UseModalReturn<T> {
|
||||
data: T | null;
|
||||
open: (data: T) => void;
|
||||
close: () => void;
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
export interface UseToggleReturn {
|
||||
state: boolean;
|
||||
toggle: () => void;
|
||||
setTrue: () => void;
|
||||
setFalse: () => void;
|
||||
setState: (state: boolean) => void;
|
||||
}
|
||||
|
||||
// Types pour les composants de navigation
|
||||
export interface NavigationHandler {
|
||||
(sectionId: string): void;
|
||||
}
|
||||
|
||||
// Classes CSS communes
|
||||
export type CommonClassKey =
|
||||
| 'transition'
|
||||
| 'transitionFast'
|
||||
| 'hoverLift'
|
||||
| 'hoverScale'
|
||||
| 'buttonBase'
|
||||
| 'navLink'
|
||||
| 'cardBase'
|
||||
| 'cardHover'
|
||||
| 'mobileMenuItem';
|
||||
|
||||
// Variants pour les boutons optimisés
|
||||
export type ButtonVariant = 'primary' | 'discord' | 'auth' | 'secondary' | 'ghost' | 'outline';
|
||||
export type ButtonSize = 'sm' | 'md' | 'lg';
|
||||
|
Loading…
x
Reference in New Issue
Block a user