diff --git a/package-lock.json b/package-lock.json index 0ac9071..587f7f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,6 +45,7 @@ "@eslint/js": "^9.19.0", "@laravel/vite-plugin-wayfinder": "^0.1.3", "@types/node": "^22.13.5", + "baseline-browser-mapping": "^2.10.10", "eslint": "^9.17.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-react": "^7.37.3", @@ -2848,10 +2849,15 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.10", + "version": "2.10.10", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.10.tgz", + "integrity": "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==", "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/brace-expansion": { diff --git a/package.json b/package.json index 06a3c45..001b068 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@eslint/js": "^9.19.0", "@laravel/vite-plugin-wayfinder": "^0.1.3", "@types/node": "^22.13.5", + "baseline-browser-mapping": "^2.10.10", "eslint": "^9.17.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-react": "^7.37.3", diff --git a/public/favicons/apple-touch-icon.png b/public/favicons/apple-touch-icon.png new file mode 100644 index 0000000..0b304e0 Binary files /dev/null and b/public/favicons/apple-touch-icon.png differ diff --git a/public/favicons/favicon-96x96.png b/public/favicons/favicon-96x96.png new file mode 100644 index 0000000..cfdc3d1 Binary files /dev/null and b/public/favicons/favicon-96x96.png differ diff --git a/public/favicons/favicon.ico b/public/favicons/favicon.ico new file mode 100644 index 0000000..105bfb2 Binary files /dev/null and b/public/favicons/favicon.ico differ diff --git a/public/favicons/favicon.svg b/public/favicons/favicon.svg new file mode 100644 index 0000000..40d8f81 --- /dev/null +++ b/public/favicons/favicon.svg @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/public/favicons/site.webmanifest b/public/favicons/site.webmanifest new file mode 100644 index 0000000..c23c086 --- /dev/null +++ b/public/favicons/site.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "Le Retzien Libre", + "short_name": "Retzien", + "icons": [ + { + "src": "/favicons/web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/favicons/web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/public/favicons/web-app-manifest-192x192.png b/public/favicons/web-app-manifest-192x192.png new file mode 100644 index 0000000..4d5dc22 Binary files /dev/null and b/public/favicons/web-app-manifest-192x192.png differ diff --git a/public/favicons/web-app-manifest-512x512.png b/public/favicons/web-app-manifest-512x512.png new file mode 100644 index 0000000..84108e7 Binary files /dev/null and b/public/favicons/web-app-manifest-512x512.png differ diff --git a/resources/css/app.css b/resources/css/app.css index a005745..a5a0876 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -1,5 +1,5 @@ -@import url('https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,300..900;1,300..900&display=swap'); @import 'tailwindcss'; +@import './fonts.css'; @plugin 'tailwindcss-animate'; @@ -8,13 +8,17 @@ @custom-variant dark (&:is(.dark *)); @theme { - --font-sans: 'Figtree', 'Inter', 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, + /* Fonts */ + --font-sans: 'Space Grotesk', 'Inter', 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + /* Radius Sizes */ --radius-lg: var(--radius); --radius-md: calc(var(--radius) - 2px); --radius-sm: calc(var(--radius) - 4px); + --radius: 0.5rem; + /* Colors */ --color-background: var(--background); --color-foreground: var(--foreground); @@ -38,7 +42,7 @@ --color-chart-2: var(--chart-2); --color-chart-3: var(--chart-3); - --radius: 0.5rem; + } :root { diff --git a/resources/css/fonts.css b/resources/css/fonts.css new file mode 100644 index 0000000..aa3f542 --- /dev/null +++ b/resources/css/fonts.css @@ -0,0 +1,44 @@ +/* space-grotesk-300 - latin */ +@font-face { + font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ + font-family: 'Space Grotesk'; + font-style: normal; + font-weight: 300; + src: url('fonts/space-grotesk-v22-latin-300.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ +} + +/* space-grotesk-regular - latin */ +@font-face { + font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ + font-family: 'Space Grotesk'; + font-style: normal; + font-weight: 400; + src: url('fonts/space-grotesk-v22-latin-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ +} + +/* space-grotesk-500 - latin */ +@font-face { + font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ + font-family: 'Space Grotesk'; + font-style: normal; + font-weight: 500; + src: url('fonts/space-grotesk-v22-latin-500.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ +} + +/* space-grotesk-600 - latin */ +@font-face { + font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ + font-family: 'Space Grotesk'; + font-style: normal; + font-weight: 600; + src: url('fonts/space-grotesk-v22-latin-600.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ +} + +/* space-grotesk-700 - latin */ +@font-face { + font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ + font-family: 'Space Grotesk'; + font-style: normal; + font-weight: 700; + src: url('fonts/space-grotesk-v22-latin-700.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ +} diff --git a/resources/css/fonts/space-grotesk-v22-latin-300.woff2 b/resources/css/fonts/space-grotesk-v22-latin-300.woff2 new file mode 100644 index 0000000..8a92c4a Binary files /dev/null and b/resources/css/fonts/space-grotesk-v22-latin-300.woff2 differ diff --git a/resources/css/fonts/space-grotesk-v22-latin-500.woff2 b/resources/css/fonts/space-grotesk-v22-latin-500.woff2 new file mode 100644 index 0000000..0db251f Binary files /dev/null and b/resources/css/fonts/space-grotesk-v22-latin-500.woff2 differ diff --git a/resources/css/fonts/space-grotesk-v22-latin-600.woff2 b/resources/css/fonts/space-grotesk-v22-latin-600.woff2 new file mode 100644 index 0000000..a1db41a Binary files /dev/null and b/resources/css/fonts/space-grotesk-v22-latin-600.woff2 differ diff --git a/resources/css/fonts/space-grotesk-v22-latin-700.woff2 b/resources/css/fonts/space-grotesk-v22-latin-700.woff2 new file mode 100644 index 0000000..44604a0 Binary files /dev/null and b/resources/css/fonts/space-grotesk-v22-latin-700.woff2 differ diff --git a/resources/css/fonts/space-grotesk-v22-latin-regular.woff2 b/resources/css/fonts/space-grotesk-v22-latin-regular.woff2 new file mode 100644 index 0000000..0e63471 Binary files /dev/null and b/resources/css/fonts/space-grotesk-v22-latin-regular.woff2 differ diff --git a/resources/js/components/app-logo-icon.tsx b/resources/js/components/app-logo-icon.tsx index b71bb6f..95bb81d 100644 --- a/resources/js/components/app-logo-icon.tsx +++ b/resources/js/components/app-logo-icon.tsx @@ -2,13 +2,6 @@ import { SVGAttributes } from 'react'; export default function AppLogoIcon(props: SVGAttributes) { return ( - /* - - */ @@ -17,6 +10,5 @@ export default function AppLogoIcon(props: SVGAttributes) { - - ); +); } diff --git a/resources/js/components/app-logo.tsx b/resources/js/components/app-logo.tsx index 3b95df3..0912b3e 100644 --- a/resources/js/components/app-logo.tsx +++ b/resources/js/components/app-logo.tsx @@ -1,16 +1,18 @@ -import AppLogoIcon from './app-logo-icon'; +import {SVGAttributes} from "react"; -export default function AppLogo() { +export default function AppLogo(props: SVGAttributes) { return ( - <> -
- -
-
- - {import.meta.env.VITE_APP_NAME} - -
- + + + + + + + + + + ); } + + diff --git a/resources/js/components/common/Container.tsx b/resources/js/components/common/Container.tsx new file mode 100644 index 0000000..c01e748 --- /dev/null +++ b/resources/js/components/common/Container.tsx @@ -0,0 +1,8 @@ +import { cn } from '@/lib/utils'; +import React from "react"; + +export function Container({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} diff --git a/resources/js/components/common/ScrollToTop.tsx b/resources/js/components/common/ScrollToTop.tsx new file mode 100644 index 0000000..11fc524 --- /dev/null +++ b/resources/js/components/common/ScrollToTop.tsx @@ -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 ( + + ); +} \ No newline at end of file diff --git a/resources/js/components/common/SectionHeading.tsx b/resources/js/components/common/SectionHeading.tsx new file mode 100644 index 0000000..54957ae --- /dev/null +++ b/resources/js/components/common/SectionHeading.tsx @@ -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 ( +
+

+ {title} +

+ {subtitle && ( +

{subtitle}

+ )} +
+ ); +} diff --git a/public/favicon.ico b/resources/js/components/common/ServiceCard.tsx similarity index 100% rename from public/favicon.ico rename to resources/js/components/common/ServiceCard.tsx diff --git a/resources/js/components/features/home/AboutSection.tsx b/resources/js/components/features/home/AboutSection.tsx new file mode 100644 index 0000000..eba87be --- /dev/null +++ b/resources/js/components/features/home/AboutSection.tsx @@ -0,0 +1,41 @@ +import {SectionHeading} from "@/components/common/SectionHeading"; + +export function AboutSection() { + return ( +
+
+ +
+
+

Une association locale

+

+ 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. +

+
+
+

Notre mission

+

+ Nous sensibilisons et accompagnons les citoyens vers des pratiques numériques + plus respectueuses, libres et indépendantes des grandes plateformes commerciales. +

+
+
+

Surveillance massive

+

+ Les GAFAM collectent et exploitent vos données personnelles à des fins commerciales, + sans transparence sur l'usage qui en est fait. +

+
+
+

Monopole numérique

+

+ Concentration excessive du pouvoir et dépendance aux services centralisés. + Il existe des alternatives libres, locales et respectueuses. +

+
+
+
+
+ ); +} diff --git a/resources/js/components/features/home/AlternativeSection.tsx b/resources/js/components/features/home/AlternativeSection.tsx new file mode 100644 index 0000000..7ba7722 --- /dev/null +++ b/resources/js/components/features/home/AlternativeSection.tsx @@ -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().props; + + return ( +
+
+
+
+

+ Notre alternative : Le Retzien Libre +

+

+ Une association locale engagée pour la promotion du logiciel libre + et la protection de vos données personnelles. +

+ {auth.user ? ( + + + + ) : ( + + + + )} +
+
+ Le Retzien Libre +
+
+
+
+ ); +} diff --git a/resources/js/components/features/home/HeroSection.tsx b/resources/js/components/features/home/HeroSection.tsx new file mode 100644 index 0000000..a10897b --- /dev/null +++ b/resources/js/components/features/home/HeroSection.tsx @@ -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().props; + + const scrollToFirstSection = () => { + document.getElementById('first-section')?.scrollIntoView({ behavior: 'smooth' }); + }; + + return ( +
+ {/* Contenu principal */} +
+
+

+ Pour un internet éthique ! +

+

+ "Dégooglisons"
+ nos ordinateurs, nos tablettes et nos smartphones.
+ "Le chemin est long, mais la voie est libre" +

+ {auth.user ? ( + + + + ) : ( + + + + )} +
+
+ Illustration Le Retzien Libre +
+
+ + {/* Flèche vers la première section */} +
+ +
+
+ ); +} diff --git a/resources/js/components/features/home/ServiceCard.tsx b/resources/js/components/features/home/ServiceCard.tsx new file mode 100644 index 0000000..d1d4565 --- /dev/null +++ b/resources/js/components/features/home/ServiceCard.tsx @@ -0,0 +1,25 @@ +import {Service} from "@/types"; + +export function ServiceCard({title, colorTitle, bgColor, bgTitle, description, link, illustration}: Service) { + return ( +
+
+
+

{title}

+
+

{description}

+ + En savoir plus + +
+
+ {title} +
+
+ ); +} diff --git a/resources/js/components/features/home/ServicesSection.tsx b/resources/js/components/features/home/ServicesSection.tsx new file mode 100644 index 0000000..87cb074 --- /dev/null +++ b/resources/js/components/features/home/ServicesSection.tsx @@ -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 ( +
+ + +
+ {services.map((service) => ( + + ))} +
+
+
+ ); +} diff --git a/resources/js/components/footer.tsx b/resources/js/components/footer.tsx new file mode 100644 index 0000000..6e7daf1 --- /dev/null +++ b/resources/js/components/footer.tsx @@ -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 ( +
+
+
+
+ + + Le Retzien Libre + +

+ Une association locale pour un internet éthique, libre et respectueux. +

+
+ +
+
+ © {currentYear} Le Retzien Libre. Tous droits réservés. +
+
+
+ ); +} diff --git a/resources/js/components/ui/button.tsx b/resources/js/components/ui/button.tsx index 71b338a..8c04dc6 100644 --- a/resources/js/components/ui/button.tsx +++ b/resources/js/components/ui/button.tsx @@ -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", diff --git a/resources/js/img/utils/lrl-logo.svg b/resources/js/img/utils/lrl-logo.svg new file mode 100644 index 0000000..a27e25d --- /dev/null +++ b/resources/js/img/utils/lrl-logo.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/resources/js/layouts/app/app-footer-layout.tsx b/resources/js/layouts/app/app-footer-layout.tsx new file mode 100644 index 0000000..e69de29 diff --git a/resources/js/layouts/nav-guest-layout.tsx b/resources/js/layouts/nav-guest-layout.tsx index 2ab4e36..436c5c2 100644 --- a/resources/js/layouts/nav-guest-layout.tsx +++ b/resources/js/layouts/nav-guest-layout.tsx @@ -1,86 +1,91 @@ -import {Link, router, usePage} from "@inertiajs/react"; -import {dashboard, home, login, logout, register, contact, membership} from "@/routes"; -import AppLogoIcon from "@/components/app-logo-icon"; -import {Button} from "@/components/ui/button"; -import type {SharedData} from "@/types"; -import {useMobileNavigation} from "@/hooks/use-mobile-navigation"; -import {LogOut} from "lucide-react"; -import React, {useEffect, useState} from "react"; +import { useEffect, useState } from 'react'; +import { Link, router, usePage } from '@inertiajs/react'; +import { dashboard, home, login, logout, contact, membership } from '@/routes'; +import AppLogoIcon from '@/components/app-logo-icon'; +import { Button } from '@/components/ui/button'; +import { type SharedData } from '@/types'; +import { useMobileNavigation } from '@/hooks/use-mobile-navigation'; +import { useAppearance } from '@/hooks/use-appearance'; +import { Menu, Moon, Sun, X } from 'lucide-react'; +import AppLogo from "@/components/app-logo"; export default function NavGuestLayout() { - const {auth} = usePage().props; - + const { auth } = usePage().props; const cleanup = useMobileNavigation(); + const { appearance, updateAppearance } = useAppearance(); + const [isMenuOpen, setIsMenuOpen] = useState(false); + const handleLogout = () => { cleanup(); router.flushAll(); }; - const [dark, setDark] = useState(false); + const toggleAppearance = () => { + updateAppearance(appearance === 'dark' ? 'light' : 'dark'); + }; + const closeMenu = () => setIsMenuOpen(false); + + // Fermer sur navigation Inertia useEffect(() => { - if (dark) { - document.documentElement.classList.add("dark"); - } else { - document.documentElement.classList.remove("dark"); - } - }, [dark]); + return router.on('navigate', closeMenu); + }, []); + + // Fermer sur touche Escape + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') closeMenu(); + }; + document.addEventListener('keydown', handleKeyDown); + return () => document.removeEventListener('keydown', handleKeyDown); + }, []); + + // Bloquer le scroll quand menu ouvert + useEffect(() => { + document.body.style.overflow = isMenuOpen ? 'hidden' : ''; + return () => { document.body.style.overflow = ''; }; + }, [isMenuOpen]); return ( <> -
-
- -
- -
-

Le Retzien Libre

- -
-
+ + {/* Actions mobile : thème + hamburger */} +
+ + +
+ + {/* Menu mobile */} + {isMenuOpen && ( + <> + {/* Backdrop */} +