feat(wip homepage with new design)

This commit is contained in:
2026-03-28 17:35:28 +01:00
parent 72721adaff
commit 8766552707
37 changed files with 684 additions and 204 deletions

View File

@@ -2,13 +2,6 @@ import { SVGAttributes } from 'react';
export default function AppLogoIcon(props: SVGAttributes<SVGElement>) {
return (
/* <svg {...props} viewBox="0 0 40 42" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M17.2 5.63325L8.6 0.855469L0 5.63325V32.1434L16.2 41.1434L32.4 32.1434V23.699L40 19.4767V9.85547L31.4 5.07769L22.8 9.85547V18.2999L17.2 21.411V5.63325ZM38 18.2999L32.4 21.411V15.2545L38 12.1434V18.2999ZM36.9409 10.4439L31.4 13.5221L25.8591 10.4439L31.4 7.36561L36.9409 10.4439ZM24.8 18.2999V12.1434L30.4 15.2545V21.411L24.8 18.2999ZM23.8 20.0323L29.3409 23.1105L16.2 30.411L10.6591 27.3328L23.8 20.0323ZM7.6 27.9212L15.2 32.1434V38.2999L2 30.9666V7.92116L7.6 11.0323V27.9212ZM8.6 9.29991L3.05913 6.22165L8.6 3.14339L14.1409 6.22165L8.6 9.29991ZM30.4 24.8101L17.2 32.1434V38.2999L30.4 30.9666V24.8101ZM9.6 11.0323L15.2 7.92117V22.5221L9.6 25.6333V11.0323Z"
/>
</svg>*/
<svg {...props} viewBox="0 0 42 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="21.138" cy="16.5" r="15" stroke="#000" strokeWidth="3"/>
<path d="M21.138 15c-4.4-5.2-11-2.167-13.5 0 1.6-10.4 9.333-12 13-12 10.4.4 13.667 8.167 14 12-6.4-5.6-11.5-2.333-13.5 0Z" fill="#000" stroke="#000"/>
@@ -17,6 +10,5 @@ export default function AppLogoIcon(props: SVGAttributes<SVGElement>) {
<path d="M5.638 11.5c-3.5 5.167-8.4 14.2 0 9v-9ZM36.638 11.5c3.5 5.167 8.4 14.2 0 9v-9Z" fill="#000" stroke="#000"/>
<path d="M21.736 18.768a1.28 1.28 0 0 1-1.472 0l-2.879-2.117c-.778-.572-.298-1.651.736-1.651h5.758c1.034 0 1.514 1.079.736 1.651l-2.88 2.117Z" fill="#FAAE2B"/>
</svg>
);
);
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,8 @@
import { cn } from '@/lib/utils';
import React from "react";
export function Container({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div className={cn('w-full max-w-7xl mx-auto px-4', className)} {...props} />
);
}

View File

@@ -0,0 +1,33 @@
import { useEffect, useState } from 'react';
import { ChevronUp } from 'lucide-react';
export function ScrollToTop() {
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const hero = document.getElementById('hero');
if (!hero) return;
const observer = new IntersectionObserver(
([entry]) => setIsVisible(!entry.isIntersecting),
{ threshold: 0 },
);
observer.observe(hero);
return () => observer.disconnect();
}, []);
const scrollToTop = () => window.scrollTo({ top: 0, behavior: 'smooth' });
if (!isVisible) return null;
return (
<button
onClick={scrollToTop}
aria-label="Retour en haut"
className="fixed bottom-6 right-6 z-50 p-3 rounded-full border-3 border-black bg-primary shadow-[4px_4px_0px_rgba(0,0,0,1)] hover:shadow-none hover:translate-x-1 hover:translate-y-1 transition duration-200"
>
<ChevronUp className="size-5" />
</button>
);
}

View File

@@ -0,0 +1,27 @@
import {cn} from '@/lib/utils';
interface SectionHeadingProps {
title: string;
color?: string;
subtitle?: string;
align?: 'left' | 'center';
className?: string;
}
export function SectionHeading({title, color, subtitle, align = 'center', className}: SectionHeadingProps) {
return (
<div className={cn(
'flex gap-10 items-center ',
align === 'center' && 'text-center',
align === 'left' && 'text-left',
className,
)}>
<h2 className={`text-3xl text-black font-medium bg-${color} rounded p-1`}>
{title}
</h2>
{subtitle && (
<p className="text-md text-muted-foreground max-w-2xl">{subtitle}</p>
)}
</div>
);
}

View File

@@ -0,0 +1,41 @@
import {SectionHeading} from "@/components/common/SectionHeading";
export function AboutSection() {
return (
<section className="w-full py-16">
<div className="max-w-7xl mx-auto px-4">
<SectionHeading title="Qui sommes-nous ?" color="secondary" subtitle="Le Retzien Libre, cest une association qui promeut lauto-hébergement et la décentralisation des services en ligne depuis 2017." align='left' />
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mt-5">
<div className="flex flex-col gap-3">
<h3 className="text-xl font-semibold">Une association locale</h3>
<p>
Le Retzien Libre est une association engagée pour la promotion du logiciel libre
et la protection de vos données personnelles sur le territoire du Pays de Retz.
</p>
</div>
<div className="flex flex-col gap-3">
<h3 className="text-xl font-semibold">Notre mission</h3>
<p>
Nous sensibilisons et accompagnons les citoyens vers des pratiques numériques
plus respectueuses, libres et indépendantes des grandes plateformes commerciales.
</p>
</div>
<div className="flex flex-col gap-3">
<h3 className="text-xl font-semibold">Surveillance massive</h3>
<p>
Les GAFAM collectent et exploitent vos données personnelles à des fins commerciales,
sans transparence sur l'usage qui en est fait.
</p>
</div>
<div className="flex flex-col gap-3">
<h3 className="text-xl font-semibold">Monopole numérique</h3>
<p>
Concentration excessive du pouvoir et dépendance aux services centralisés.
Il existe des alternatives libres, locales et respectueuses.
</p>
</div>
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,43 @@
import { Link, usePage } from '@inertiajs/react';
import { Button } from '@/components/ui/button';
import { dashboard, register } from '@/routes';
import { type SharedData } from '@/types';
import illustrationImage from '@/img/utils/lrl-illustration.png';
export function AlternativeSection() {
const { auth } = usePage<SharedData>().props;
return (
<section className="w-full py-16">
<div className="max-w-7xl mx-auto px-4">
<div className="flex flex-col lg:flex-row items-center gap-12">
<div className="flex flex-col gap-6 lg:w-1/2">
<h2 className="text-3xl font-bold">
Notre alternative : Le Retzien Libre
</h2>
<p>
Une association locale engagée pour la promotion du logiciel libre
et la protection de vos données personnelles.
</p>
{auth.user ? (
<Link href={dashboard()}>
<Button variant="default">Accéder à mon espace</Button>
</Link>
) : (
<Link href={register()}>
<Button variant="default">Rejoignez-nous</Button>
</Link>
)}
</div>
<div className="lg:w-1/2 flex justify-center">
<img
src={illustrationImage}
alt="Le Retzien Libre"
className="rounded-lg max-w-md w-full"
/>
</div>
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,58 @@
import { Link, usePage } from '@inertiajs/react';
import { ChevronDown } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { dashboard, membership } from '@/routes';
import { type SharedData } from '@/types';
import illustrationImage from '@/img/utils/lrl-illustration.png';
export function HeroSection() {
const { auth } = usePage<SharedData>().props;
const scrollToFirstSection = () => {
document.getElementById('first-section')?.scrollIntoView({ behavior: 'smooth' });
};
return (
<section
id="hero"
className="flex flex-col w-full max-w-[335px] lg:max-w-7xl mx-auto min-h-[calc(100vh-80px)] px-4"
>
{/* Contenu principal */}
<div className="flex flex-1 items-center justify-center gap-4 w-full">
<div className="flex flex-col w-full items-center text-center lg:items-start lg:text-left justify-center gap-4">
<h1 className="text-5xl text-accent max-w-[450px] mb-5">
Pour un internet éthique !
</h1>
<p className="text-xl mb-5">
"Dégooglisons"<br />
nos ordinateurs, nos tablettes et nos smartphones.<br />
<i>"Le chemin est long, mais la voie est libre"</i>
</p>
{auth.user ? (
<Link href={dashboard()}>
<Button variant="secondary">Mon espace</Button>
</Link>
) : (
<Link href={membership()}>
<Button variant="secondary">Adhérer dès maintenant</Button>
</Link>
)}
</div>
<div className="hidden lg:flex w-full items-center justify-center">
<img src={illustrationImage} alt="Illustration Le Retzien Libre" className="max-w-md w-full" />
</div>
</div>
{/* Flèche vers la première section */}
<div className="flex justify-center pb-8">
<button
onClick={scrollToFirstSection}
aria-label="Voir nos services"
className="p-2 rounded-full border-3 border-black animate-bounce hover:animate-none transition"
>
<ChevronDown className="size-6" />
</button>
</div>
</section>
);
}

View File

@@ -0,0 +1,25 @@
import {Service} from "@/types";
export function ServiceCard({title, colorTitle, bgColor, bgTitle, description, link, illustration}: Service) {
return (
<div
className={`flex gap-1 items-center bg-${bgColor} justify-center gap-4 rounded-3xl p-10 border-3 border-black shadow-[4px_4px_0px_rgba(0,0,0,1)] hover:shadow-none hover:translate-2 transition delay-50 duration-200 ease-in-out`}>
<div>
<div className="max-w-[150px]">
<h3 className={`inline text-2xl font-semibold text-${colorTitle} font-medium bg-${bgTitle} rounded p-1 line-clamp-2`}>{title}</h3>
</div>
<p className="text-sm text-muted-foreground mt-5">{description}</p>
<a href={link} className="text-white underline hover:font-medium mt-4 inline-block hover:underline">
En savoir plus
</a>
</div>
<div className="relative w-full h-64">
<img
src={illustration}
alt={title}
className="w-full h-full object-cover rounded-lg"
/>
</div>
</div>
);
}

View File

@@ -0,0 +1,82 @@
import { type Service } from '@/types';
import { ServiceCard } from './ServiceCard';
import { SectionHeading } from '@/components/common/SectionHeading';
import { Container } from '@/components/common/Container';
const services: Service[] = [
{
title: 'Boîte mail',
description: 'Service de messagerie électronique sécurisé et respectueux de votre vie privée',
colorTitle: 'white',
bgTitle: 'accent',
bgColor: 'secondary',
link: '#',
illustration: '../../img/utils/lrl-logo.svg'
},
{
title: 'Stockage Cloud',
description: 'Stockage en ligne et collaboration avec vos données hébergées localement',
colorTitle: 'black',
bgTitle: 'white',
bgColor: 'primary',
link: '#',
illustration: '../../img/utils/lrl-logo.svg'
},
{
title: 'Hébergement de site',
description: "Solutions d'hébergement web éthiques et performantes",
colorTitle: 'black',
bgTitle: 'primary',
bgColor: 'accent',
link: '#',
illustration: '../../img/utils/lrl-logo.svg'
},
{
title: 'Email Marketing',
description: "Gérez vos communications de groupe efficacement",
colorTitle: 'black',
bgTitle: 'secondary',
bgColor: 'gray',
link: '#',
illustration: '../../img/utils/lrl-logo.svg'
},
{
title: 'Partage de fichiers',
description: 'Partager facielement vos fichiers en toute sécurité',
colorTitle: 'black',
bgTitle: 'white',
bgColor: 'primary',
link: '#',
illustration: '../../img/utils/lrl-logo.svg'
},
{
title: 'Outil de Sondage',
description: 'Créez et partagez des sondages en ligne en toute confidentialité',
colorTitle: 'black',
bgTitle: 'primary',
bgColor: 'accent',
link: '#',
illustration: '../../img/utils/lrl-logo.svg'
},
];
export function ServicesSection() {
return (
<section id="first-section" className="w-full bg-white py-16">
<Container className="flex flex-col gap-10">
<SectionHeading
title="Nos services"
color="primary"
subtitle="Nous vous proposons, à travers des outils libres, ouverts et solidaires, de quitter lindustrie du G.A.F.A.M. Nacceptons plus dêtre leur produit !"
align="left"
/>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{services.map((service) => (
<ServiceCard key={service.title} {...service} />
))}
</div>
</Container>
</section>
);
}

View File

@@ -0,0 +1,34 @@
import { Link } from '@inertiajs/react';
import { contact, home, membership } from '@/routes';
import AppLogoIcon from '@/components/app-logo-icon';
export function Footer() {
const currentYear = new Date().getFullYear();
return (
<footer className="w-full border-t-4 border-black py-10 mt-auto">
<div className="max-w-7xl mx-auto px-4 flex flex-col gap-8">
<div className="flex flex-col lg:flex-row justify-between gap-8">
<div className="flex flex-col gap-3">
<Link href={home()} className="flex items-center gap-2 no-underline">
<AppLogoIcon className="size-8 text-[var(--foreground)] dark:text-white" />
<span className="font-bold text-lg">Le Retzien Libre</span>
</Link>
<p className="text-sm max-w-xs">
Une association locale pour un internet éthique, libre et respectueux.
</p>
</div>
<nav className="flex flex-col gap-3">
<span className="font-semibold">Navigation</span>
<Link href={home()} className="text-sm no-underline hover:underline">Accueil</Link>
<Link href={contact()} className="text-sm no-underline hover:underline">Contact</Link>
<Link href={membership()} className="text-sm no-underline hover:underline">Adhérer</Link>
</nav>
</div>
<div className="border-t border-black/20 pt-6 text-sm text-center">
&copy; {currentYear} Le Retzien Libre. Tous droits réservés.
</div>
</div>
</footer>
);
}

View File

@@ -18,7 +18,6 @@ const buttonVariants = cva(
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "bg-accent hover:text-accent-foreground hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2 shadow-[4px_4px_0px_rgba(0,0,0,1)] hover:shadow-none hover:translate-2 transition delay-50 duration-200 ease-in-out",