Compare commits
27 Commits
main
...
arthur.wam
Author | SHA1 | Date | |
---|---|---|---|
d3ec4524a7 | |||
5e42042077 | |||
![]() |
5b354d15a6 | ||
![]() |
3d0efdb15c | ||
![]() |
81dad2b6ba | ||
![]() |
34531fd2cf | ||
![]() |
b14490da1b | ||
![]() |
4ec4d6ccb2 | ||
![]() |
0f2d0fdb40 | ||
![]() |
ff5984a727 | ||
![]() |
978e46d0a7 | ||
![]() |
a563a04c65 | ||
![]() |
a6d054a09a | ||
![]() |
04d87e6113 | ||
![]() |
11c26e1ba9 | ||
![]() |
e327231b8a | ||
![]() |
cd6ef409f1 | ||
![]() |
80ee14b7da | ||
![]() |
870c9ac0d8 | ||
![]() |
bdf1124cf6 | ||
![]() |
ed775ee3e5 | ||
![]() |
549a95e797 | ||
![]() |
a5268d3714 | ||
![]() |
88d7694647 | ||
976e7af488 | |||
![]() |
6bebedb515 | ||
![]() |
65ea4ac0be |
31
.gitea/workflows/build.yaml
Normal file
31
.gitea/workflows/build.yaml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
name: Build
|
||||||
|
run-name: CI/CD website
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- dev
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened]
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- dev
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Use Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '24.x'
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
cd banquise-website
|
||||||
|
npm ci
|
||||||
|
- name: Building
|
||||||
|
run: |
|
||||||
|
cd banquise-website
|
||||||
|
npm run build
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -30,7 +30,3 @@ pnpm-debug.log*
|
|||||||
|
|
||||||
# optional: nix store link if using nix develop
|
# optional: nix store link if using nix develop
|
||||||
.result
|
.result
|
||||||
|
|
||||||
# optional: lockfiles you don't use
|
|
||||||
package-lock.json
|
|
||||||
|
|
||||||
|
5039
banquise-website/package-lock.json
generated
Normal file
5039
banquise-website/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,103 +1,67 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Navigation } from './components/layout/Navigation';
|
import { Navigation } from './components/layout/Navigation';
|
||||||
import { Footer } from './components/layout/Footer';
|
|
||||||
import { HeroSection } from './components/sections/HeroSection';
|
import { HeroSection } from './components/sections/HeroSection';
|
||||||
import { TechFeaturesSection } from './components/sections/TechFeaturesSection';
|
|
||||||
import { ServicesSection } from './components/sections/ServicesSection';
|
import { ServicesSection } from './components/sections/ServicesSection';
|
||||||
|
import { TechFeaturesSection } from './components/sections/TechFeaturesSection';
|
||||||
import { AboutSection } from './components/sections/AboutSection';
|
import { AboutSection } from './components/sections/AboutSection';
|
||||||
|
import { Footer } from './components/layout/Footer';
|
||||||
import { Popup } from './components/ui/Popup';
|
import { Popup } from './components/ui/Popup';
|
||||||
import { ScrollToTopButton } from './components/ui/ScrollToTopButton';
|
import { ScrollToTopButton } from './components/ui/ScrollToTopButton';
|
||||||
import { URLS } from './config/constants';
|
import { ParallaxBackground } from './components/ui/ParallaxBackground';
|
||||||
|
import { LanguageSwitcher } from './components/ui/LanguageSwitcher';
|
||||||
// Define Service interface directly in App
|
import { useTranslation } from './hooks/useTranslation';
|
||||||
interface Service {
|
import type { Service } from './types/service';
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
image: string;
|
|
||||||
description: string;
|
|
||||||
features: string[];
|
|
||||||
icon: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
// Define services directly in the component with enhanced data
|
const { t, currentLanguage, changeLanguage, availableLanguages } = useTranslation();
|
||||||
const services: Service[] = [
|
|
||||||
{
|
|
||||||
name: "Wiki",
|
|
||||||
url: URLS.services.wiki,
|
|
||||||
image: "/src/assets/iceberg.png",
|
|
||||||
icon: "📚",
|
|
||||||
description: "Notre wiki collaboratif est votre centre de documentation technique. Accédez à des guides détaillés, des tutoriels et de la documentation API pour tous nos services.",
|
|
||||||
features: [
|
|
||||||
"Documentation collaborative en temps réel",
|
|
||||||
"Guides d'installation détaillés",
|
|
||||||
"API et références techniques",
|
|
||||||
"Tutoriels pas à pas",
|
|
||||||
"Base de connaissances communautaire",
|
|
||||||
"Recherche avancée intégrée"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Gitea",
|
|
||||||
url: URLS.services.gitea,
|
|
||||||
image: "/src/assets/iceberg.png",
|
|
||||||
icon: "🔧",
|
|
||||||
description: "Instance Gitea auto-hébergée pour la gestion de vos dépôts Git. Collaborez sur vos projets avec un contrôle total sur votre code source.",
|
|
||||||
features: [
|
|
||||||
"Dépôts Git illimités",
|
|
||||||
"Issues et pull requests",
|
|
||||||
"Actions CI/CD intégrées",
|
|
||||||
"Gestion d'équipes et permissions",
|
|
||||||
"Interface web intuitive",
|
|
||||||
"Intégration avec outils externes"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Panel",
|
|
||||||
url: URLS.services.panel,
|
|
||||||
image: "/src/assets/iceberg.png",
|
|
||||||
icon: "🎮",
|
|
||||||
description: "Interface de gestion centralisée pour vos serveurs de jeux. Déployez, configurez et surveillez vos serveurs gaming en quelques clics.",
|
|
||||||
features: [
|
|
||||||
"Déploiement automatisé de serveurs",
|
|
||||||
"Monitoring en temps réel",
|
|
||||||
"Gestion des ressources système",
|
|
||||||
"Interface d'administration web",
|
|
||||||
"Support multi-jeux",
|
|
||||||
"Sauvegarde automatique des données"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const [selectedService, setSelectedService] = useState<Service | null>(null);
|
const [selectedService, setSelectedService] = useState<Service | null>(null);
|
||||||
|
|
||||||
// Inline accordion logic
|
|
||||||
const [openAccordion, setOpenAccordion] = useState<string | null>(null);
|
const [openAccordion, setOpenAccordion] = useState<string | null>(null);
|
||||||
|
|
||||||
const toggleAccordion = (title: string) => {
|
const toggleAccordion = (title: string) => {
|
||||||
setOpenAccordion(openAccordion === title ? null : title);
|
setOpenAccordion(openAccordion === title ? null : title);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col min-h-screen w-full">
|
<div className="min-h-screen bg-gradient-to-b from-banquise-blue-dark via-banquise-blue-dark/95 to-banquise-blue-dark text-white overflow-x-hidden relative">
|
||||||
<Navigation />
|
{/* Background Effects */}
|
||||||
|
<ParallaxBackground />
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<div className="relative z-10">
|
||||||
|
{/* Navigation avec sélecteur de langue */}
|
||||||
|
<Navigation
|
||||||
|
translations={t.navigation}
|
||||||
|
languageSwitcher={
|
||||||
|
<LanguageSwitcher
|
||||||
|
currentLanguage={currentLanguage}
|
||||||
|
onLanguageChange={changeLanguage}
|
||||||
|
availableLanguages={availableLanguages}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<HeroSection translations={t.hero} />
|
||||||
|
|
||||||
|
<ServicesSection
|
||||||
|
services={t.services}
|
||||||
|
onServiceClick={setSelectedService}
|
||||||
|
translations={t.common}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TechFeaturesSection />
|
||||||
|
<AboutSection openAccordion={openAccordion} toggleAccordion={toggleAccordion} />
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
|
||||||
<main className="flex-1 flex flex-col overflow-x-hidden overflow-y-auto">
|
{/* UI Components */}
|
||||||
<div className="relative flex-1 bg-ocean-gradient w-full min-h-screen flex flex-col justify-start items-center overflow-x-hidden">
|
|
||||||
|
|
||||||
<HeroSection />
|
|
||||||
<TechFeaturesSection />
|
|
||||||
<ServicesSection services={services} onServiceClick={setSelectedService} />
|
|
||||||
<AboutSection openAccordion={openAccordion} toggleAccordion={toggleAccordion} />
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<Footer />
|
|
||||||
|
|
||||||
{/* Bouton de retour en haut */}
|
|
||||||
<ScrollToTopButton />
|
<ScrollToTopButton />
|
||||||
|
|
||||||
{selectedService && (
|
{selectedService && (
|
||||||
<Popup service={selectedService} onClose={() => setSelectedService(null)} />
|
<Popup
|
||||||
|
service={selectedService}
|
||||||
|
onClose={() => setSelectedService(null)}
|
||||||
|
translations={t.common}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -24,6 +24,26 @@ export const Footer: React.FC = () => (
|
|||||||
Panel
|
Panel
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href={URLS.services.pelican} className="text-banquise-gray/80 no-underline transition-all duration-200 inline-flex items-center hover:text-banquise-gray hover:translate-x-1 text-sm sm:text-base">
|
||||||
|
Pelican
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href={URLS.services.intra} className="text-banquise-gray/80 no-underline transition-all duration-200 inline-flex items-center hover:text-banquise-gray hover:translate-x-1 text-sm sm:text-base">
|
||||||
|
Intranet
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href={URLS.services.mails} className="text-banquise-gray/80 no-underline transition-all duration-200 inline-flex items-center hover:text-banquise-gray hover:translate-x-1 text-sm sm:text-base">
|
||||||
|
Webmail
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href={URLS.services.opencloud} className="text-banquise-gray/80 no-underline transition-all duration-200 inline-flex items-center hover:text-banquise-gray hover:translate-x-1 text-sm sm:text-base">
|
||||||
|
OpenCloud
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -2,13 +2,15 @@ import React, { useEffect } from 'react';
|
|||||||
import banquiseServer from '../../assets/banquise_server.svg'
|
import banquiseServer from '../../assets/banquise_server.svg'
|
||||||
import { URLS, SITE_CONFIG } from '../../config/constants';
|
import { URLS, SITE_CONFIG } from '../../config/constants';
|
||||||
import { commonStyles } from '../../styles/components';
|
import { commonStyles } from '../../styles/components';
|
||||||
|
import type { Translation } from '../../types/i18n';
|
||||||
|
|
||||||
interface MobileMenuProps {
|
interface MobileMenuProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
translations: Translation['navigation'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MobileMenu: React.FC<MobileMenuProps> = ({ isOpen, onClose }) => {
|
export const MobileMenu: React.FC<MobileMenuProps> = ({ isOpen, onClose, translations }) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
document.body.style.overflow = 'hidden';
|
document.body.style.overflow = 'hidden';
|
||||||
@ -76,7 +78,7 @@ export const MobileMenu: React.FC<MobileMenuProps> = ({ isOpen, onClose }) => {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="font-semibold text-lg">Nos Services</span>
|
<span className="font-semibold text-lg">{translations.services}</span>
|
||||||
<p className="text-white/60 text-sm">Découvrir notre offre</p>
|
<p className="text-white/60 text-sm">Découvrir notre offre</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -93,7 +95,7 @@ export const MobileMenu: React.FC<MobileMenuProps> = ({ isOpen, onClose }) => {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="font-semibold text-lg">À propos</span>
|
<span className="font-semibold text-lg">{translations.about}</span>
|
||||||
<p className="text-white/60 text-sm">En savoir plus sur nous</p>
|
<p className="text-white/60 text-sm">En savoir plus sur nous</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -106,7 +108,7 @@ export const MobileMenu: React.FC<MobileMenuProps> = ({ isOpen, onClose }) => {
|
|||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<div className={`${commonStyles.icons.small} ${commonStyles.gradients.discord} group-hover:scale-110 transition-transform duration-200`}>
|
<div className={`${commonStyles.icons.small} ${commonStyles.gradients.discord} group-hover:scale-110 transition-transform duration-200`}>
|
||||||
<svg className="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 24 24">
|
<svg className="w-5 h-5 text-white" 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"/>
|
<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-.30z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -3,8 +3,14 @@ import { MobileMenu } from './MobileMenu';
|
|||||||
import banquiseServer from '/src/assets/banquise_server.svg'
|
import banquiseServer from '/src/assets/banquise_server.svg'
|
||||||
import { URLS, SITE_CONFIG } from '../../config/constants';
|
import { URLS, SITE_CONFIG } from '../../config/constants';
|
||||||
import { commonStyles } from '../../styles/components';
|
import { commonStyles } from '../../styles/components';
|
||||||
|
import type { Translation } from '../../types/i18n';
|
||||||
|
|
||||||
export const Navigation: React.FC = () => {
|
interface NavigationProps {
|
||||||
|
translations: Translation['navigation'];
|
||||||
|
languageSwitcher: React.ReactElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Navigation: React.FC<NavigationProps> = ({ translations, languageSwitcher }) => {
|
||||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||||
const [scrolled, setScrolled] = useState(false);
|
const [scrolled, setScrolled] = useState(false);
|
||||||
|
|
||||||
@ -13,7 +19,6 @@ export const Navigation: React.FC = () => {
|
|||||||
const isScrolled = window.scrollY > 20;
|
const isScrolled = window.scrollY > 20;
|
||||||
setScrolled(isScrolled);
|
setScrolled(isScrolled);
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('scroll', handleScroll);
|
window.addEventListener('scroll', handleScroll);
|
||||||
return () => window.removeEventListener('scroll', handleScroll);
|
return () => window.removeEventListener('scroll', handleScroll);
|
||||||
}, []);
|
}, []);
|
||||||
@ -24,7 +29,6 @@ export const Navigation: React.FC = () => {
|
|||||||
setMobileMenuOpen(false);
|
setMobileMenuOpen(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
return () => window.removeEventListener('resize', handleResize);
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
}, []);
|
}, []);
|
||||||
@ -32,22 +36,21 @@ export const Navigation: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<nav className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
|
<nav className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
|
||||||
scrolled
|
scrolled
|
||||||
? 'bg-banquise-blue-dark/98 backdrop-blur-xl shadow-2xl border-b border-banquise-blue-lightest/30'
|
? 'bg-banquise-blue-dark/98 backdrop-blur-xl shadow-2xl border-b border-banquise-blue-lightest/30'
|
||||||
: 'bg-banquise-blue-dark/95 backdrop-blur-lg shadow-xl border-b border-banquise-blue-lightest/20'
|
: 'bg-banquise-blue-dark/95 backdrop-blur-lg shadow-xl border-b border-banquise-blue-lightest/20'
|
||||||
}`}>
|
}`}>
|
||||||
<div className={commonStyles.layout.container}>
|
<div className={commonStyles.layout.container}>
|
||||||
<div className="flex justify-between items-center h-16 sm:h-18 lg:h-20 px-4 sm:px-6 lg:px-8">
|
<div className="flex justify-between items-center h-16 sm:h-18 lg:h-20 px-4 sm:px-6 lg:px-8">
|
||||||
|
|
||||||
{/* Logo section */}
|
{/* Logo section */}
|
||||||
<div className="flex items-center space-x-3 sm:space-x-4 group">
|
<div className="flex items-center space-x-3 sm:space-x-4 group">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20 rounded-full blur-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
<div className="absolute inset-0 bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20 rounded-full blur-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
<img
|
<img
|
||||||
src={banquiseServer}
|
src={banquiseServer}
|
||||||
alt="Logo La Banquise"
|
alt="Logo La Banquise"
|
||||||
className="h-10 sm:h-12 lg:h-14 w-auto relative z-10 transition-transform duration-300 group-hover:scale-110"
|
className="h-10 sm:h-12 lg:h-14 w-auto relative z-10 transition-transform duration-300 group-hover:scale-110"
|
||||||
style={{ filter: 'drop-shadow(0 0 12px rgba(168, 218, 255, 0.4))' }}
|
style={{ filter: 'drop-shadow(0 0 12px rgba(168, 218, 255, 0.4))' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden sm:block">
|
<div className="hidden sm:block">
|
||||||
@ -62,20 +65,30 @@ export const Navigation: React.FC = () => {
|
|||||||
|
|
||||||
{/* Navigation links desktop */}
|
{/* Navigation links desktop */}
|
||||||
<div className="hidden md:flex items-center space-x-1 lg:space-x-2">
|
<div className="hidden md:flex items-center space-x-1 lg:space-x-2">
|
||||||
<a href="#services" className={commonStyles.nav.link}>
|
<a href="#home" className={commonStyles.nav.link}>
|
||||||
<span className="relative z-10">Services</span>
|
<span className="relative z-10">{translations.home}</span>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
|
</a>
|
||||||
|
<a href="#services" className={commonStyles.nav.link}>
|
||||||
|
<span className="relative z-10">{translations.services}</span>
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
<div className="absolute inset-0 bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="#about" className={commonStyles.nav.link}>
|
<a href="#about" className={commonStyles.nav.link}>
|
||||||
<span className="relative z-10">À propos</span>
|
<span className="relative z-10">{translations.about}</span>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
|
</a>
|
||||||
|
<a href="#contact" className={commonStyles.nav.link}>
|
||||||
|
<span className="relative z-10">{translations.contact}</span>
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
<div className="absolute inset-0 bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Action buttons desktop */}
|
{/* Action buttons desktop */}
|
||||||
<div className="hidden md:flex items-center space-x-3 lg:space-x-4">
|
<div className="hidden md:flex items-center space-x-3 lg:space-x-4">
|
||||||
<a
|
{/* Language switcher */}
|
||||||
|
{languageSwitcher}
|
||||||
|
|
||||||
|
<a
|
||||||
href={URLS.social.discord}
|
href={URLS.social.discord}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
@ -89,20 +102,19 @@ export const Navigation: React.FC = () => {
|
|||||||
<span>Discord</span>
|
<span>Discord</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
<a
|
||||||
<a
|
|
||||||
href={URLS.services.auth}
|
href={URLS.services.auth}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className={`${commonStyles.buttons.auth} ${
|
className={`${commonStyles.buttons.auth} ${
|
||||||
scrolled
|
scrolled
|
||||||
? `${commonStyles.gradients.primary} border border-banquise-blue-lightest/30 hover:shadow-banquise-blue/25`
|
? `${commonStyles.gradients.primary} border border-banquise-blue-lightest/30 hover:shadow-banquise-blue/25`
|
||||||
: 'bg-gradient-to-r from-banquise-blue-light to-banquise-blue border-2 border-white/20 hover:shadow-banquise-blue-light/25'
|
: 'bg-gradient-to-r from-banquise-blue-light to-banquise-blue border-2 border-white/20 hover:shadow-banquise-blue-light/25'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className={`absolute inset-0 transition-opacity duration-300 opacity-0 group-hover:opacity-100 ${
|
<div className={`absolute inset-0 transition-opacity duration-300 opacity-0 group-hover:opacity-100 ${
|
||||||
scrolled
|
scrolled
|
||||||
? 'bg-gradient-to-r from-banquise-blue-light to-banquise-blue'
|
? 'bg-gradient-to-r from-banquise-blue-light to-banquise-blue'
|
||||||
: 'bg-gradient-to-r from-white/10 to-banquise-blue-lightest/20'
|
: 'bg-gradient-to-r from-white/10 to-banquise-blue-lightest/20'
|
||||||
}`}></div>
|
}`}></div>
|
||||||
<div className="relative z-10 flex items-center space-x-2">
|
<div className="relative z-10 flex items-center space-x-2">
|
||||||
@ -115,32 +127,32 @@ export const Navigation: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile menu button */}
|
{/* Mobile menu button */}
|
||||||
<button
|
<button
|
||||||
className="md:hidden relative p-3 rounded-xl bg-white/10 hover:bg-white/20 transition-all duration-300 hover:scale-105 active:scale-95 group"
|
className="md:hidden relative p-3 rounded-xl bg-white/10 hover:bg-white/20 transition-all duration-300 hover:scale-105 active:scale-95 group"
|
||||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||||
aria-label={mobileMenuOpen ? "Fermer le menu" : "Ouvrir le menu"}
|
aria-label={mobileMenuOpen ? "Fermer le menu" : "Ouvrir le menu"}
|
||||||
aria-expanded={mobileMenuOpen}
|
aria-expanded={mobileMenuOpen}
|
||||||
>
|
>
|
||||||
<div className="w-6 h-6 relative">
|
<div className="w-6 h-6 relative">
|
||||||
<span className={`absolute block w-6 h-0.5 bg-white transition-all duration-300 ${mobileMenuOpen ? 'rotate-45 top-3' : 'top-1'}`}></span>
|
<span className={`absolute block w-6 h-6 bg-white transition-all duration-300 ${mobileMenuOpen ? 'rotate-45 top-3' : 'top-1'}`}></span>
|
||||||
<span className={`absolute block w-6 h-0.5 bg-white transition-all duration-300 top-3 ${mobileMenuOpen ? 'opacity-0 scale-0' : 'opacity-100'}`}></span>
|
<span className={`absolute block w-6 h-0.5 bg-white transition-all duration-300 top-3 ${mobileMenuOpen ? 'opacity-0 scale-0' : 'opacity-100'}`}></span>
|
||||||
<span className={`absolute block w-6 h-0.5 bg-white transition-all duration-300 ${mobileMenuOpen ? '-rotate-45 top-3' : 'top-5'}`}></span>
|
<span className={`absolute block w-6 h-0.5 bg-white transition-all duration-300 ${mobileMenuOpen ? '-rotate-45 top-3' : 'top-5'}`}></span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Glassmorphism effect bar */}
|
{/* Glassmorphism effect bar */}
|
||||||
<div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-banquise-blue-lightest/30 to-transparent"></div>
|
<div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-banquise-blue-lightest/30 to-transparent"></div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Spacer pour compenser la navbar fixed */}
|
{/* Spacer pour compenser la navbar fixed */}
|
||||||
<div className="h-16 sm:h-18 lg:h-20"></div>
|
<div className="h-16 sm:h-18 lg:h-20"></div>
|
||||||
|
|
||||||
{/* Menu mobile */}
|
{/* Menu mobile */}
|
||||||
<MobileMenu
|
<MobileMenu
|
||||||
isOpen={mobileMenuOpen}
|
isOpen={mobileMenuOpen}
|
||||||
onClose={() => setMobileMenuOpen(false)}
|
onClose={() => setMobileMenuOpen(false)}
|
||||||
|
translations={translations}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -80,6 +80,38 @@ export const AboutSection: React.FC<AboutSectionProps> = ({ openAccordion, toggl
|
|||||||
<p className="text-banquise-gray/80 text-sm">Interface de gestion pour serveurs de jeux</p>
|
<p className="text-banquise-gray/80 text-sm">Interface de gestion pour serveurs de jeux</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className={`flex items-start space-x-4 p-4 ${commonStyles.gradients.card} rounded-xl ${commonStyles.cards.base}`}>
|
||||||
|
<div className={`${commonStyles.icons.small} ${commonStyles.gradients.primaryBr} font-bold`}>🐧</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold text-banquise-gray mb-1">Pelican</h4>
|
||||||
|
<p className="text-banquise-gray/80 text-sm">Générateur de sites statiques</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={`flex items-start space-x-4 p-4 ${commonStyles.gradients.card} rounded-xl ${commonStyles.cards.base}`}>
|
||||||
|
<div className={`${commonStyles.icons.small} ${commonStyles.gradients.primaryBr} font-bold`}>🏢</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold text-banquise-gray mb-1">Intranet</h4>
|
||||||
|
<p className="text-banquise-gray/80 text-sm">Espace privé de l'association</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={`flex items-start space-x-4 p-4 ${commonStyles.gradients.card} rounded-xl ${commonStyles.cards.base}`}>
|
||||||
|
<div className={`${commonStyles.icons.small} ${commonStyles.gradients.primaryBr} font-bold`}>📧</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold text-banquise-gray mb-1">Webmail</h4>
|
||||||
|
<p className="text-banquise-gray/80 text-sm">Service de messagerie électronique</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={`flex items-start space-x-4 p-4 ${commonStyles.gradients.card} rounded-xl ${commonStyles.cards.base}`}>
|
||||||
|
<div className={`${commonStyles.icons.small} ${commonStyles.gradients.primaryBr} font-bold`}>☁️</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold text-banquise-gray mb-1">OpenCloud</h4>
|
||||||
|
<p className="text-banquise-gray/80 text-sm">Plateforme cloud collaborative</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className={`${commonStyles.text.muted} mt-4`}>
|
<p className={`${commonStyles.text.muted} mt-4`}>
|
||||||
Tous nos services sont maintenus avec soin et régulièrement mis à jour pour garantir une expérience optimale.
|
Tous nos services sont maintenus avec soin et régulièrement mis à jour pour garantir une expérience optimale.
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import banquiseServer from '/src/assets/banquise_server.svg'
|
import banquiseServer from '/src/assets/banquise_server.svg'
|
||||||
|
import type { Translation } from '../../types/i18n';
|
||||||
|
|
||||||
export const HeroSection: React.FC = () => (
|
interface HeroSectionProps {
|
||||||
|
translations: Translation['hero'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HeroSection: React.FC<HeroSectionProps> = ({ translations }) => (
|
||||||
<section className="min-h-[calc(80vh-72px)] flex flex-col justify-center items-center text-center py-12 sm:py-16 md:py-20 w-full max-w-6xl mx-auto px-4 sm:px-6 md:px-8 relative z-3">
|
<section className="min-h-[calc(80vh-72px)] flex flex-col justify-center items-center text-center py-12 sm:py-16 md:py-20 w-full max-w-6xl mx-auto px-4 sm:px-6 md:px-8 relative z-3">
|
||||||
<div className="mb-8 sm:mb-10 md:mb-12 w-32 h-32 sm:w-40 sm:h-40 md:w-48 md:h-48 rounded-full bg-gradient-to-br from-banquise-blue-dark/20 to-banquise-blue/10 p-4 sm:p-5 md:p-6 shadow-2xl backdrop-blur-sm border border-banquise-blue-lightest/30 relative group">
|
<div className="mb-8 sm:mb-10 md:mb-12 w-32 h-32 sm:w-40 sm:h-40 md:w-48 md:h-48 rounded-full bg-gradient-to-br from-banquise-blue-dark/20 to-banquise-blue/10 p-4 sm:p-5 md:p-6 shadow-2xl backdrop-blur-sm border border-banquise-blue-lightest/30 relative group">
|
||||||
<img
|
<img
|
||||||
@ -15,15 +20,15 @@ export const HeroSection: React.FC = () => (
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="text-banquise-gray text-3xl sm:text-4xl md:text-5xl lg:text-6xl mb-6 sm:mb-7 md:mb-8 font-extrabold leading-tight max-w-4xl font-heading px-2 relative z-10" style={{ textShadow: '0 2px 10px rgba(0, 0, 0, 0.3)' }}>
|
<h1 className="text-banquise-gray text-3xl sm:text-4xl md:text-5xl lg:text-6xl mb-6 sm:mb-7 md:mb-8 font-extrabold leading-tight max-w-4xl font-heading px-2 relative z-10" style={{ textShadow: '0 2px 10px rgba(0, 0, 0, 0.3)' }}>
|
||||||
Bienvenue sur La Banquise
|
{translations.title}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-banquise-gray text-lg sm:text-xl md:text-2xl mb-8 sm:mb-10 md:mb-12 max-w-3xl font-normal opacity-90 leading-relaxed px-2 relative z-10" style={{ textShadow: '0 1px 4px rgba(0, 0, 0, 0.2)' }}>
|
<p className="text-banquise-gray text-lg sm:text-xl md:text-2xl mb-8 sm:mb-10 md:mb-12 max-w-3xl font-normal opacity-90 leading-relaxed px-2 relative z-10" style={{ textShadow: '0 1px 4px rgba(0, 0, 0, 0.2)' }}>
|
||||||
Association d'hébergement et lab réseau pour tous les étudiants et associations de l'EPITA !
|
{translations.subtitle}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<a href="#services" className="inline-flex items-center justify-center bg-gradient-to-r from-banquise-gray to-white text-banquise-blue-dark border-0 py-4 sm:py-5 px-8 sm:px-10 md:px-12 rounded-2xl text-base sm:text-lg font-bold no-underline shadow-xl transition-all duration-300 min-w-48 sm:min-w-56 md:min-w-64 hover:-translate-y-2 hover:shadow-2xl hover:scale-105 backdrop-blur-sm border border-banquise-blue-lightest/20 mx-4 group relative z-10">
|
<a href="#services" className="inline-flex items-center justify-center bg-gradient-to-r from-banquise-gray to-white text-banquise-blue-dark border-0 py-4 sm:py-5 px-8 sm:px-10 md:px-12 rounded-2xl text-base sm:text-lg font-bold no-underline shadow-xl transition-all duration-300 min-w-48 sm:min-w-56 md:min-w-64 hover:-translate-y-2 hover:shadow-2xl hover:scale-105 backdrop-blur-sm border border-banquise-blue-lightest/20 mx-4 group relative z-10">
|
||||||
<span className="text-center text-banquise-blue-dark">Découvrir nos services</span>
|
<span className="text-center text-banquise-blue-dark">{translations.cta}</span>
|
||||||
<span className="ml-2 sm:ml-3 text-lg sm:text-xl transition-transform duration-300 group-hover:translate-x-1 text-banquise-blue-dark">→</span>
|
<span className="ml-2 sm:ml-3 text-lg sm:text-xl transition-transform duration-300 group-hover:translate-x-1 text-banquise-blue-dark">→</span>
|
||||||
</a>
|
</a>
|
||||||
</section>
|
</section>
|
||||||
|
@ -1,22 +1,19 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import type { Service } from '../../types/service';
|
||||||
|
|
||||||
// Declare the Service interface here
|
|
||||||
interface Service {
|
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
image: string;
|
|
||||||
icon: string;
|
|
||||||
description: string;
|
|
||||||
features: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define interface directly in the component file
|
|
||||||
interface ServicesSectionProps {
|
interface ServicesSectionProps {
|
||||||
services: Service[];
|
services: Service[];
|
||||||
onServiceClick: (service: Service) => void;
|
onServiceClick: (service: Service) => void;
|
||||||
|
translations: {
|
||||||
|
discoverFeatures: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ServicesSection: React.FC<ServicesSectionProps> = ({ services, onServiceClick }) => (
|
export const ServicesSection: React.FC<ServicesSectionProps> = ({
|
||||||
|
services,
|
||||||
|
onServiceClick,
|
||||||
|
translations
|
||||||
|
}) => (
|
||||||
<section id="services" className="relative z-2 py-12 sm:py-16 md:py-20 w-full max-w-6xl mx-auto px-4 sm:px-6 md:px-8">
|
<section id="services" className="relative z-2 py-12 sm:py-16 md:py-20 w-full max-w-6xl mx-auto px-4 sm:px-6 md:px-8">
|
||||||
<div className="w-20 h-1 bg-gradient-to-r from-banquise-blue-lightest to-banquise-blue mx-auto mb-6 sm:mb-8 rounded-full"></div>
|
<div className="w-20 h-1 bg-gradient-to-r from-banquise-blue-lightest to-banquise-blue mx-auto mb-6 sm:mb-8 rounded-full"></div>
|
||||||
<h2 className="text-banquise-gray text-2xl sm:text-3xl md:text-4xl mb-4 sm:mb-6 text-center font-heading font-bold tracking-tight px-2" style={{ textShadow: '0 2px 4px rgba(0, 0, 0, 0.2)' }}>
|
<h2 className="text-banquise-gray text-2xl sm:text-3xl md:text-4xl mb-4 sm:mb-6 text-center font-heading font-bold tracking-tight px-2" style={{ textShadow: '0 2px 4px rgba(0, 0, 0, 0.2)' }}>
|
||||||
@ -50,7 +47,7 @@ export const ServicesSection: React.FC<ServicesSectionProps> = ({ services, onSe
|
|||||||
|
|
||||||
{/* CTA */}
|
{/* CTA */}
|
||||||
<div className="flex items-center justify-center text-banquise-blue-light font-bold group-hover:text-banquise-blue-lightest transition-colors duration-300 text-sm sm:text-base">
|
<div className="flex items-center justify-center text-banquise-blue-light font-bold group-hover:text-banquise-blue-lightest transition-colors duration-300 text-sm sm:text-base">
|
||||||
<span className="text-center">Découvrir toutes les fonctionnalités</span>
|
<span className="text-center">{translations.discoverFeatures}</span>
|
||||||
<span className="ml-2 text-lg transition-transform duration-300 group-hover:translate-x-2">→</span>
|
<span className="ml-2 text-lg transition-transform duration-300 group-hover:translate-x-2">→</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
37
banquise-website/src/components/ui/LanguageSwitcher.tsx
Normal file
37
banquise-website/src/components/ui/LanguageSwitcher.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import type { Language } from '../../types/i18n';
|
||||||
|
|
||||||
|
interface LanguageSwitcherProps {
|
||||||
|
currentLanguage: Language;
|
||||||
|
onLanguageChange: (language: Language) => void;
|
||||||
|
availableLanguages: Language[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LanguageSwitcher: React.FC<LanguageSwitcherProps> = ({
|
||||||
|
currentLanguage,
|
||||||
|
onLanguageChange,
|
||||||
|
availableLanguages
|
||||||
|
}) => {
|
||||||
|
const languageNames: Record<Language, string> = {
|
||||||
|
fr: '🇫🇷 Français',
|
||||||
|
en: '🇬🇧 English',
|
||||||
|
//es: '🇪🇸 Español',
|
||||||
|
//de: '🇩🇪 Deutsch'
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative inline-block">
|
||||||
|
<select
|
||||||
|
value={currentLanguage}
|
||||||
|
onChange={(e) => onLanguageChange(e.target.value as Language)}
|
||||||
|
className="bg-banquise-blue-dark/20 border border-banquise-blue-lightest/30 text-banquise-gray rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-banquise-blue-light"
|
||||||
|
>
|
||||||
|
{availableLanguages.map((lang) => (
|
||||||
|
<option key={lang} value={lang} className="bg-banquise-blue-dark text-banquise-gray">
|
||||||
|
{languageNames[lang]}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -1,25 +1,19 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { URLS } from '../../config/constants';
|
import { URLS } from '../../config/constants';
|
||||||
|
import type { Service } from '../../types/service';
|
||||||
interface Service {
|
import type { Translation } from '../../types/i18n';
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
image: string;
|
|
||||||
icon: string;
|
|
||||||
description: string;
|
|
||||||
features: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PopupProps {
|
interface PopupProps {
|
||||||
service: Service;
|
service: Service;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
translations: Translation['common'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Popup: React.FC<PopupProps> = ({ service, onClose }) => {
|
export const Popup: React.FC<PopupProps> = ({ service, onClose, translations }) => {
|
||||||
// Empêcher le scroll du body quand la popup est ouverte
|
// Empêcher le scroll du body quand la popup est ouverte
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.body.style.overflow = 'hidden';
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.body.style.overflow = 'unset';
|
document.body.style.overflow = 'unset';
|
||||||
};
|
};
|
||||||
@ -28,18 +22,18 @@ export const Popup: React.FC<PopupProps> = ({ service, onClose }) => {
|
|||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black/60 flex justify-center items-center z-50 p-4 backdrop-blur-md animate-fadeIn">
|
<div className="fixed inset-0 bg-black/60 flex justify-center items-center z-50 p-4 backdrop-blur-md animate-fadeIn">
|
||||||
<div className="bg-white text-banquise-blue-dark rounded-3xl max-w-4xl w-full max-h-[90vh] shadow-2xl relative animate-slideUp border border-banquise-blue-lightest/20 overflow-hidden">
|
<div className="bg-white text-banquise-blue-dark rounded-3xl max-w-4xl w-full max-h-[90vh] shadow-2xl relative animate-slideUp border border-banquise-blue-lightest/20 overflow-hidden">
|
||||||
|
|
||||||
{/* Bouton de fermeture fixe au-dessus du contenu */}
|
{/* Bouton de fermeture fixe au-dessus du contenu */}
|
||||||
<div className="absolute top-4 right-4 z-50">
|
<div className="absolute top-4 right-4 z-50">
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="bg-white/90 hover:bg-white border border-banquise-blue/20 text-xl cursor-pointer text-banquise-blue-dark flex items-center justify-center w-10 h-10 sm:w-12 sm:h-12 rounded-full transition-all duration-200 hover:scale-110 active:scale-95 shadow-lg backdrop-blur-sm"
|
className="bg-white/90 hover:bg-white border border-banquise-blue/20 text-xl cursor-pointer text-banquise-blue-dark flex items-center justify-center w-10 h-10 sm:w-12 sm:h-12 rounded-full transition-all duration-200 hover:scale-110 active:scale-95 shadow-lg backdrop-blur-sm"
|
||||||
aria-label="Fermer la popup"
|
aria-label={translations.close}
|
||||||
>
|
>
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Contenu avec scroll vertical uniquement */}
|
{/* Contenu avec scroll vertical uniquement */}
|
||||||
<div className="overflow-y-auto overflow-x-hidden max-h-[90vh] popup-content">
|
<div className="overflow-y-auto overflow-x-hidden max-h-[90vh] popup-content">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@ -100,7 +94,7 @@ export const Popup: React.FC<PopupProps> = ({ service, onClose }) => {
|
|||||||
{/* Fonctionnalités */}
|
{/* Fonctionnalités */}
|
||||||
<h3 className="text-xl sm:text-2xl lg:text-3xl mb-4 lg:mb-6 text-banquise-blue-dark font-heading font-bold flex items-center">
|
<h3 className="text-xl sm:text-2xl lg:text-3xl mb-4 lg:mb-6 text-banquise-blue-dark font-heading font-bold flex items-center">
|
||||||
<span className="text-xl sm:text-2xl lg:text-3xl mr-3">⚡</span>
|
<span className="text-xl sm:text-2xl lg:text-3xl mr-3">⚡</span>
|
||||||
Fonctionnalités principales
|
{translations.discoverFeatures}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-8">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-8">
|
||||||
{service.features.map((feature, index) => (
|
{service.features.map((feature, index) => (
|
||||||
@ -124,7 +118,7 @@ export const Popup: React.FC<PopupProps> = ({ service, onClose }) => {
|
|||||||
<span className="mr-3 text-xl lg:text-2xl">🚀</span>
|
<span className="mr-3 text-xl lg:text-2xl">🚀</span>
|
||||||
<span>Accéder à {service.name}</span>
|
<span>Accéder à {service.name}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<p className="text-center text-sm text-banquise-blue-dark/60 mt-4">
|
<p className="text-center text-sm text-banquise-blue-dark/60 mt-4">
|
||||||
Besoin d'aide ? Rejoignez notre <a href={URLS.social.discord} className="text-banquise-blue hover:text-banquise-blue-dark transition-colors duration-200 font-medium">Discord</a> pour obtenir du support
|
Besoin d'aide ? Rejoignez notre <a href={URLS.social.discord} className="text-banquise-blue hover:text-banquise-blue-dark transition-colors duration-200 font-medium">Discord</a> pour obtenir du support
|
||||||
</p>
|
</p>
|
||||||
|
@ -3,10 +3,15 @@ export const URLS = {
|
|||||||
wiki: "https://wiki.la-banquise.fr",
|
wiki: "https://wiki.la-banquise.fr",
|
||||||
gitea: "https://git.la-banquise.fr",
|
gitea: "https://git.la-banquise.fr",
|
||||||
panel: "https://panel.la-banquise.fr",
|
panel: "https://panel.la-banquise.fr",
|
||||||
auth: "https://auth.la-banquise.fr"
|
auth: "https://auth.la-banquise.fr",
|
||||||
|
pelican: "https://pelican.la-banquise.fr",
|
||||||
|
intra: "https://intra.la-banquise.fr",
|
||||||
|
mails: "https://mails.la-banquise.fr",
|
||||||
|
opencloud: "https://opencloud.la-banquise.fr",
|
||||||
|
ssp: "https://ssp.la-banquise.fr"
|
||||||
},
|
},
|
||||||
social: {
|
social: {
|
||||||
discord: "https://discord.gg/labanquise"
|
discord: "https://discord.gg/bJhM97wans"
|
||||||
},
|
},
|
||||||
contact: {
|
contact: {
|
||||||
email: "mailto:contact@la-banquise.fr"
|
email: "mailto:contact@la-banquise.fr"
|
||||||
|
153
banquise-website/src/data/translations/en.tsx
Normal file
153
banquise-website/src/data/translations/en.tsx
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
import type { Translation } from '../../types/i18n';
|
||||||
|
import { URLS } from '../../config/constants';
|
||||||
|
|
||||||
|
export const en: Translation = {
|
||||||
|
services: [
|
||||||
|
{
|
||||||
|
name: "Wiki",
|
||||||
|
url: URLS.services.wiki,
|
||||||
|
image: "/path/to/wiki-image.jpg",
|
||||||
|
icon: "📚",
|
||||||
|
description: "Collaborative technical documentation and knowledge sharing platform. Create, edit and organize your guides, tutorials and documentation as a team with integrated versioning system.",
|
||||||
|
features: [
|
||||||
|
"Advanced markdown editor with real-time preview",
|
||||||
|
"Versioning system to track changes",
|
||||||
|
"Real-time collaboration with multiple contributors",
|
||||||
|
"Smart search across all documents",
|
||||||
|
"Predefined templates for different documentation types",
|
||||||
|
"Comment and revision system",
|
||||||
|
"PDF and HTML export for external sharing",
|
||||||
|
"Git integration for backup"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Gitea",
|
||||||
|
url: URLS.services.gitea,
|
||||||
|
image: "/path/to/gitea-image.jpg",
|
||||||
|
icon: "🔧",
|
||||||
|
description: "Lightweight and performant self-hosted Git service for your development projects. Open-source alternative to GitHub with all essential features for managing your repositories.",
|
||||||
|
features: [
|
||||||
|
"Unlimited public and private Git repositories",
|
||||||
|
"Intuitive web interface for project management",
|
||||||
|
"Issues and pull requests with review system",
|
||||||
|
"Integrated wiki for each project",
|
||||||
|
"CI/CD actions for automation",
|
||||||
|
"Fine-grained permissions and team management",
|
||||||
|
"Complete REST API for integration",
|
||||||
|
"Webhooks for external notifications"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Gaming Panel",
|
||||||
|
url: URLS.services.panel,
|
||||||
|
image: "/path/to/panel-image.jpg",
|
||||||
|
icon: "🎮",
|
||||||
|
description: "Centralized management interface for all your game servers. Easily deploy, configure and monitor your Minecraft, CS2, Garry's Mod and many other servers.",
|
||||||
|
features: [
|
||||||
|
"Support for 20+ popular games (Minecraft, CS2, GMod...)",
|
||||||
|
"One-click deployment with pre-configured templates",
|
||||||
|
"Real-time administration console",
|
||||||
|
"File management with integrated editor",
|
||||||
|
"Performance and resource monitoring",
|
||||||
|
"Automatic backup system",
|
||||||
|
"Automated task scheduler"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Pelican",
|
||||||
|
url: URLS.services.pelican,
|
||||||
|
image: "/path/to/pelican-image.jpg",
|
||||||
|
icon: "🐧",
|
||||||
|
description: "Ultra-fast and flexible Python static site generator. Create blogs, portfolios or documentation sites with optimal performance and simplified deployment.",
|
||||||
|
features: [
|
||||||
|
"Game server management with dedicated servers (Minecraft, CS2, Palworld...)",
|
||||||
|
"One-click deployment with pre-configured templates",
|
||||||
|
"Real-time administration console",
|
||||||
|
"File management with integrated editor",
|
||||||
|
"Performance and resource monitoring",
|
||||||
|
"Automatic backup system",
|
||||||
|
"Automated task scheduler"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Intranet",
|
||||||
|
url: URLS.services.intra,
|
||||||
|
image: "/path/to/intra-image.jpg",
|
||||||
|
icon: "🏢",
|
||||||
|
description: "Secure private space for the association to centralize internal resources, communications and collaboration tools between members.",
|
||||||
|
features: [
|
||||||
|
"Personalized dashboard for each member",
|
||||||
|
"Event and meeting calendar",
|
||||||
|
"Secure file sharing",
|
||||||
|
"Private discussion forums",
|
||||||
|
"Member directory with profiles",
|
||||||
|
"Internal notification system",
|
||||||
|
"Project and task management",
|
||||||
|
"Archive of decisions and minutes"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Mails",
|
||||||
|
url: URLS.services.mails,
|
||||||
|
image: "/path/to/mails-image.jpg",
|
||||||
|
icon: "📧",
|
||||||
|
description: "Professional email service with modern web interface. Benefit from a personalized @la-banquise.fr email address with all advanced features.",
|
||||||
|
features: [
|
||||||
|
"Personalized @la-banquise.fr email addresses",
|
||||||
|
"Modern and responsive webmail interface",
|
||||||
|
"Integrated anti-spam and antivirus filters",
|
||||||
|
"Synchronized contacts and calendar",
|
||||||
|
"IMAP/SMTP support for external clients",
|
||||||
|
"Generous storage with archiving",
|
||||||
|
"Communication encryption",
|
||||||
|
"Automatic data backup"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Password Change",
|
||||||
|
url: URLS.services.ssp,
|
||||||
|
image: "/path/to/ssp-image.jpg",
|
||||||
|
icon: "🔐",
|
||||||
|
description: "Secure interface for autonomous password management. Easily change your credentials safely.",
|
||||||
|
features: [
|
||||||
|
"Secure interface to change your password",
|
||||||
|
"Password complexity validation",
|
||||||
|
"Email notifications of changes",
|
||||||
|
"Modification history"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OpenCloud",
|
||||||
|
url: URLS.services.opencloud,
|
||||||
|
image: "/path/to/opencloud-image.jpg",
|
||||||
|
icon: "☁️",
|
||||||
|
description: "Open-source collaborative cloud platform for file storage, sharing and synchronization. Free alternative to Google Drive with full control over your data.",
|
||||||
|
features: [
|
||||||
|
"Secure and encrypted cloud storage",
|
||||||
|
"Multi-device synchronization",
|
||||||
|
"File sharing with secure links",
|
||||||
|
"Collaborative document editing",
|
||||||
|
"Automatic file versioning",
|
||||||
|
"Native mobile applications",
|
||||||
|
"Integration with external tools",
|
||||||
|
"Geo-redundant data backup"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
hero: {
|
||||||
|
title: "Welcome to La Banquise",
|
||||||
|
subtitle: "Your trusted technology partner",
|
||||||
|
cta: "Discover our services"
|
||||||
|
},
|
||||||
|
navigation: {
|
||||||
|
home: "Home",
|
||||||
|
services: "Services",
|
||||||
|
about: "About",
|
||||||
|
contact: "Contact"
|
||||||
|
},
|
||||||
|
common: {
|
||||||
|
discoverFeatures: "Discover all features",
|
||||||
|
close: "Close",
|
||||||
|
loading: "Loading..."
|
||||||
|
}
|
||||||
|
};
|
153
banquise-website/src/data/translations/fr.ts
Normal file
153
banquise-website/src/data/translations/fr.ts
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
import type { Translation } from '../../types/i18n';
|
||||||
|
import { URLS } from '../../config/constants';
|
||||||
|
|
||||||
|
export const fr: Translation = {
|
||||||
|
services: [
|
||||||
|
{
|
||||||
|
name: "Wiki",
|
||||||
|
url: URLS.services.wiki,
|
||||||
|
image: "/path/to/wiki-image.jpg",
|
||||||
|
icon: "📚",
|
||||||
|
description: "Plateforme collaborative de documentation technique et de partage de connaissances. Créez, modifiez et organisez vos guides, tutoriels et documentations en équipe avec un système de versioning intégré.",
|
||||||
|
features: [
|
||||||
|
"Éditeur markdown avancé avec prévisualisation en temps réel",
|
||||||
|
"Système de versioning pour suivre les modifications",
|
||||||
|
"Collaboration en temps réel avec plusieurs contributeurs",
|
||||||
|
"Recherche intelligente dans tous les documents",
|
||||||
|
"Templates prédéfinis pour différents types de documentation",
|
||||||
|
"Système de commentaires et de révisions",
|
||||||
|
"Export PDF et HTML pour partage externe",
|
||||||
|
"Intégration avec Git pour la sauvegarde"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Gitea",
|
||||||
|
url: URLS.services.gitea,
|
||||||
|
image: "/path/to/gitea-image.jpg",
|
||||||
|
icon: "🔧",
|
||||||
|
description: "Service Git auto-hébergé lightweight et performant pour vos projets de développement. Alternative open-source à GitHub avec toutes les fonctionnalités essentielles pour gérer vos repositories.",
|
||||||
|
features: [
|
||||||
|
"Repositories Git illimités publics et privés",
|
||||||
|
"Interface web intuitive pour la gestion des projets",
|
||||||
|
"Issues et pull requests avec système de review",
|
||||||
|
"Wiki intégré pour chaque projet",
|
||||||
|
"Actions CI/CD pour l'automatisation",
|
||||||
|
"Gestion fine des permissions et des équipes",
|
||||||
|
"API REST complète pour l'intégration",
|
||||||
|
"Webhooks pour les notifications externes"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Panel Gaming",
|
||||||
|
url: URLS.services.panel,
|
||||||
|
image: "/path/to/panel-image.jpg",
|
||||||
|
icon: "🎮",
|
||||||
|
description: "Interface de gestion centralisée pour tous vos serveurs de jeux. Déployez, configurez et surveillez facilement vos serveurs Minecraft, CS2, Garry's Mod et bien d'autres.",
|
||||||
|
features: [
|
||||||
|
"Support de 20+ jeux populaires (Minecraft, CS2, GMod...)",
|
||||||
|
"Déploiement en un clic avec templates préconfigurés",
|
||||||
|
"Console d'administration en temps réel",
|
||||||
|
"Gestion des fichiers avec éditeur intégré",
|
||||||
|
"Monitoring des performances et ressources",
|
||||||
|
"Système de sauvegarde automatique",
|
||||||
|
"Planificateur de tâches automatisées"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Pelican",
|
||||||
|
url: URLS.services.pelican,
|
||||||
|
image: "/path/to/pelican-image.jpg",
|
||||||
|
icon: "🐧",
|
||||||
|
description: "Générateur de sites statiques Python ultra-rapide et flexible. Créez des blogs, portfolios ou sites de documentation avec une performance optimale et un déploiement simplifié.",
|
||||||
|
features: [
|
||||||
|
"Gestion de serveurs de jeux avec serveurs dédiés (Minecraft, CS2, Palworld...)",
|
||||||
|
"Déploiement en un clic avec templates préconfigurés",
|
||||||
|
"Console d'administration en temps réel",
|
||||||
|
"Gestion des fichiers avec éditeur intégré",
|
||||||
|
"Monitoring des performances et ressources",
|
||||||
|
"Système de sauvegarde automatique",
|
||||||
|
"Planificateur de tâches automatisées"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Intranet",
|
||||||
|
url: URLS.services.intra,
|
||||||
|
image: "/path/to/intra-image.jpg",
|
||||||
|
icon: "🏢",
|
||||||
|
description: "Espace privé sécurisé de l'association pour centraliser les ressources internes, communications et outils de collaboration entre membres.",
|
||||||
|
features: [
|
||||||
|
"Tableau de bord personnalisé pour chaque membre",
|
||||||
|
"Calendrier des événements et réunions",
|
||||||
|
"Partage de fichiers sécurisé",
|
||||||
|
"Forums de discussion privés",
|
||||||
|
"Annuaire des membres avec profils",
|
||||||
|
"Système de notifications internes",
|
||||||
|
"Gestion des projets et tâches",
|
||||||
|
"Archive des décisions et procès-verbaux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Mails",
|
||||||
|
url: URLS.services.mails,
|
||||||
|
image: "/path/to/mails-image.jpg",
|
||||||
|
icon: "📧",
|
||||||
|
description: "Service de messagerie électronique professionnel avec interface web moderne. Bénéficiez d'une adresse email personnalisée @la-banquise.fr avec toutes les fonctionnalités avancées.",
|
||||||
|
features: [
|
||||||
|
"Adresses email personnalisées @la-banquise.fr",
|
||||||
|
"Interface webmail moderne et responsive",
|
||||||
|
"Filtres anti-spam et antivirus intégrés",
|
||||||
|
"Contacts et calendrier synchronisés",
|
||||||
|
"Support IMAP/SMTP pour clients externes",
|
||||||
|
"Stockage généreux avec archivage",
|
||||||
|
"Chiffrement des communications",
|
||||||
|
"Sauvegarde automatique des données"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Changement de mot de passe",
|
||||||
|
url: URLS.services.ssp,
|
||||||
|
image: "/path/to/ssp-image.jpg",
|
||||||
|
icon: "🔐",
|
||||||
|
description: "Interface sécurisée pour la gestion autonome de vos mots de passe. Changez facilement vos identifiants en toute sécurité.",
|
||||||
|
features: [
|
||||||
|
"Interface sécurisée pour changer votre mot de passe",
|
||||||
|
"Validation de la complexité des mots de passe",
|
||||||
|
"Notifications par email des changements",
|
||||||
|
"Historique des modifications"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OpenCloud",
|
||||||
|
url: URLS.services.opencloud,
|
||||||
|
image: "/path/to/opencloud-image.jpg",
|
||||||
|
icon: "☁️",
|
||||||
|
description: "Plateforme cloud collaborative open-source pour le stockage, le partage et la synchronisation de fichiers. Alternative libre à Google Drive avec contrôle total sur vos données.",
|
||||||
|
features: [
|
||||||
|
"Stockage cloud sécurisé et chiffré",
|
||||||
|
"Synchronisation multi-appareils",
|
||||||
|
"Partage de fichiers avec liens sécurisés",
|
||||||
|
"Édition collaborative de documents",
|
||||||
|
"Versioning automatique des fichiers",
|
||||||
|
"Applications mobiles natives",
|
||||||
|
"Intégration avec outils externes",
|
||||||
|
"Sauvegarde géoredondante des données"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
hero: {
|
||||||
|
title: "Bienvenue chez La Banquise",
|
||||||
|
subtitle: "Votre partenaire technologique de confiance",
|
||||||
|
cta: "Découvrir nos services"
|
||||||
|
},
|
||||||
|
navigation: {
|
||||||
|
home: "Accueil",
|
||||||
|
services: "Services",
|
||||||
|
about: "À propos",
|
||||||
|
contact: "Contact"
|
||||||
|
},
|
||||||
|
common: {
|
||||||
|
discoverFeatures: "Découvrir toutes les fonctionnalités",
|
||||||
|
close: "Fermer",
|
||||||
|
loading: "Chargement..."
|
||||||
|
}
|
||||||
|
};
|
14
banquise-website/src/data/translations/index.ts
Normal file
14
banquise-website/src/data/translations/index.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { fr } from './fr';
|
||||||
|
import { en } from './en';
|
||||||
|
import type { Language, Translation } from '../../types/i18n';
|
||||||
|
|
||||||
|
export const translations: Record<Language, Translation> = {
|
||||||
|
fr,
|
||||||
|
en,
|
||||||
|
// Ajoutez d'autres langues ici :
|
||||||
|
// es,
|
||||||
|
// de,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defaultLanguage: Language = 'fr';
|
||||||
|
export const availableLanguages: Language[] = ['fr', 'en'];
|
31
banquise-website/src/hooks/useTranslation.tsx
Normal file
31
banquise-website/src/hooks/useTranslation.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import type { Language, Translation } from '../types/i18n';
|
||||||
|
import { translations, defaultLanguage } from '../data/translations';
|
||||||
|
|
||||||
|
export const useTranslation = () => {
|
||||||
|
const [currentLanguage, setCurrentLanguage] = useState<Language>(() => {
|
||||||
|
// Récupérer la langue depuis localStorage ou utiliser la langue par défaut
|
||||||
|
const saved = localStorage.getItem('language') as Language;
|
||||||
|
return saved && translations[saved] ? saved : defaultLanguage;
|
||||||
|
});
|
||||||
|
|
||||||
|
const [t, setT] = useState<Translation>(translations[currentLanguage]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setT(translations[currentLanguage]);
|
||||||
|
localStorage.setItem('language', currentLanguage);
|
||||||
|
}, [currentLanguage]);
|
||||||
|
|
||||||
|
const changeLanguage = (language: Language) => {
|
||||||
|
if (translations[language]) {
|
||||||
|
setCurrentLanguage(language);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
t,
|
||||||
|
currentLanguage,
|
||||||
|
changeLanguage,
|
||||||
|
availableLanguages: Object.keys(translations) as Language[]
|
||||||
|
};
|
||||||
|
};
|
23
banquise-website/src/types/i18n.ts
Normal file
23
banquise-website/src/types/i18n.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import type { Service } from './service';
|
||||||
|
|
||||||
|
export interface Translation {
|
||||||
|
services: Service[];
|
||||||
|
hero: {
|
||||||
|
title: string;
|
||||||
|
subtitle: string;
|
||||||
|
cta: string;
|
||||||
|
};
|
||||||
|
navigation: {
|
||||||
|
home: string;
|
||||||
|
services: string;
|
||||||
|
about: string;
|
||||||
|
contact: string;
|
||||||
|
};
|
||||||
|
common: {
|
||||||
|
discoverFeatures: string;
|
||||||
|
close: string;
|
||||||
|
loading: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Language = 'fr' | 'en'; //| 'es' | 'de';
|
8
banquise-website/src/types/service.ts
Normal file
8
banquise-website/src/types/service.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export interface Service {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
image: string;
|
||||||
|
icon: string;
|
||||||
|
description: string;
|
||||||
|
features: string[];
|
||||||
|
}
|
6
package-lock.json
generated
Normal file
6
package-lock.json
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "website-front",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user