Add dark mode and accessibility features for improved user experience
Integrate a dark mode, an accessibility panel with options for dyslexia, high contrast, and text scaling, and enhance keyboard navigation. Update documentation to reflect these changes. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 923ae0e3-a363-4db8-b04a-e8baca2a1330 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: bbd001b6-1b5f-4425-9310-55a9081dabf8 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8af7d2ec-2cc3-4ece-8af3-9f071488d072/923ae0e3-a363-4db8-b04a-e8baca2a1330/vOeFCU4 Replit-Helium-Checkpoint-Created: true
This commit is contained in:
@@ -17,16 +17,16 @@ title = "Tutoriel Gitea — inchangé"
|
|||||||
id = "wdBpdE1lSme8lM2xYd3oJ"
|
id = "wdBpdE1lSme8lM2xYd3oJ"
|
||||||
uri = "file://docs/DAT.md"
|
uri = "file://docs/DAT.md"
|
||||||
type = "text"
|
type = "text"
|
||||||
title = "DAT v1.2 — Architecture Technique"
|
title = "DAT v1.3"
|
||||||
|
|
||||||
[[outputs]]
|
[[outputs]]
|
||||||
id = "NXFvDFOIzX862xNq15Mak"
|
id = "NXFvDFOIzX862xNq15Mak"
|
||||||
uri = "file://docs/DEX.md"
|
uri = "file://docs/DEX.md"
|
||||||
type = "text"
|
type = "text"
|
||||||
title = "DEX v1.2 — Exploitation"
|
title = "DEX v1.3"
|
||||||
|
|
||||||
[[outputs]]
|
[[outputs]]
|
||||||
id = "kJNXgVnYp_LQmPcWr6Osb"
|
id = "kJNXgVnYp_LQmPcWr6Osb"
|
||||||
uri = "file://docs/WIKI.md"
|
uri = "file://docs/WIKI.md"
|
||||||
type = "text"
|
type = "text"
|
||||||
title = "Wiki v1.2 — La Voix du Peuple"
|
title = "Wiki v1.3"
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import Home from "@/pages/home";
|
|||||||
import About from "@/pages/about";
|
import About from "@/pages/about";
|
||||||
import Transparence from "@/pages/transparence";
|
import Transparence from "@/pages/transparence";
|
||||||
import Flyer from "@/pages/flyer";
|
import Flyer from "@/pages/flyer";
|
||||||
|
import { AccessibilityProvider } from "@/hooks/use-accessibility";
|
||||||
|
import { AccessibilityPanel } from "@/components/accessibility-panel";
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
@@ -24,13 +26,14 @@ function Navbar() {
|
|||||||
<Link href="/" className="flex items-center gap-2 mr-6 font-serif text-xl font-bold tracking-tight text-primary" data-testid="nav-home-link">
|
<Link href="/" className="flex items-center gap-2 mr-6 font-serif text-xl font-bold tracking-tight text-primary" data-testid="nav-home-link">
|
||||||
La Voix du Peuple
|
La Voix du Peuple
|
||||||
</Link>
|
</Link>
|
||||||
<nav className="flex items-center gap-6 text-sm font-medium">
|
<nav className="flex items-center gap-6 text-sm font-medium" aria-label="Navigation principale">
|
||||||
<Link href="/" className="transition-colors hover:text-foreground/80 text-foreground/60" data-testid="nav-manifesto-link">Manifeste</Link>
|
<Link href="/" className="transition-colors hover:text-foreground/80 text-foreground/60" data-testid="nav-manifesto-link">Manifeste</Link>
|
||||||
<Link href="/about" className="transition-colors hover:text-foreground/80 text-foreground/60" data-testid="nav-about-link">À propos</Link>
|
<Link href="/about" className="transition-colors hover:text-foreground/80 text-foreground/60" data-testid="nav-about-link">À propos</Link>
|
||||||
<Link href="/transparence" className="transition-colors hover:text-foreground/80 text-foreground/60" data-testid="nav-transparence-link">Fonctionnement</Link>
|
<Link href="/transparence" className="transition-colors hover:text-foreground/80 text-foreground/60" data-testid="nav-transparence-link">Fonctionnement</Link>
|
||||||
<Link href="/flyer" className="transition-colors hover:text-foreground/80 text-foreground/60" data-testid="nav-flyer-link">Flyer QR</Link>
|
<Link href="/flyer" className="transition-colors hover:text-foreground/80 text-foreground/60" data-testid="nav-flyer-link">Flyer QR</Link>
|
||||||
</nav>
|
</nav>
|
||||||
<div className="ml-auto flex items-center">
|
<div className="ml-auto flex items-center gap-2">
|
||||||
|
<AccessibilityPanel />
|
||||||
<span
|
<span
|
||||||
title="République française"
|
title="République française"
|
||||||
aria-label="Drapeau français"
|
aria-label="Drapeau français"
|
||||||
@@ -50,8 +53,15 @@ function Navbar() {
|
|||||||
function Router() {
|
function Router() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex flex-col font-sans">
|
<div className="min-h-screen flex flex-col font-sans">
|
||||||
|
{/* Lien d'évitement pour lecteurs d'écran */}
|
||||||
|
<a
|
||||||
|
href="#main-content"
|
||||||
|
className="sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 focus:z-[100] focus:bg-primary focus:text-primary-foreground focus:px-4 focus:py-2 focus:rounded-sm focus:text-sm focus:font-semibold"
|
||||||
|
>
|
||||||
|
Aller au contenu principal
|
||||||
|
</a>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<main className="flex-1 flex flex-col">
|
<main id="main-content" className="flex-1 flex flex-col" tabIndex={-1}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/" component={Home} />
|
<Route path="/" component={Home} />
|
||||||
<Route path="/about" component={About} />
|
<Route path="/about" component={About} />
|
||||||
@@ -68,10 +78,12 @@ function App() {
|
|||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
|
<AccessibilityProvider>
|
||||||
<WouterRouter base={import.meta.env.BASE_URL.replace(/\/$/, "")}>
|
<WouterRouter base={import.meta.env.BASE_URL.replace(/\/$/, "")}>
|
||||||
<Router />
|
<Router />
|
||||||
</WouterRouter>
|
</WouterRouter>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
|
</AccessibilityProvider>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Accessibility, Moon, Sun, Type, Contrast, ZoomIn } from "lucide-react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
|
import { useAccessibility } from "@/hooks/use-accessibility";
|
||||||
|
|
||||||
|
interface ToggleRowProps {
|
||||||
|
icon: React.ReactNode;
|
||||||
|
label: string;
|
||||||
|
description: string;
|
||||||
|
active: boolean;
|
||||||
|
onToggle: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToggleRow({ icon, label, description, active, onToggle }: ToggleRowProps) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={onToggle}
|
||||||
|
className={`w-full flex items-center gap-3 p-3 rounded-sm transition-colors text-left
|
||||||
|
${active
|
||||||
|
? "bg-primary/15 text-primary ring-1 ring-primary/30"
|
||||||
|
: "hover:bg-muted text-foreground/70 hover:text-foreground"
|
||||||
|
}`}
|
||||||
|
aria-pressed={active}
|
||||||
|
>
|
||||||
|
<span className="flex-shrink-0 text-current">{icon}</span>
|
||||||
|
<div className="min-w-0">
|
||||||
|
<p className="text-sm font-medium leading-none mb-0.5">{label}</p>
|
||||||
|
<p className="text-[11px] text-muted-foreground leading-snug">{description}</p>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
className={`ml-auto flex-shrink-0 w-8 h-4 rounded-full transition-colors relative
|
||||||
|
${active ? "bg-primary" : "bg-border"}`}
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<span className={`absolute top-0.5 w-3 h-3 rounded-full bg-white shadow transition-transform
|
||||||
|
${active ? "translate-x-4" : "translate-x-0.5"}`} />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AccessibilityPanel() {
|
||||||
|
const { darkMode, dyslexiaFont, highContrast, largeText,
|
||||||
|
toggleDark, toggleDyslexia, toggleHighContrast, toggleLargeText } = useAccessibility();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-8 w-8 p-0 text-muted-foreground hover:text-foreground"
|
||||||
|
aria-label="Options d'accessibilité"
|
||||||
|
title="Options d'accessibilité"
|
||||||
|
>
|
||||||
|
<Accessibility className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent
|
||||||
|
align="end"
|
||||||
|
sideOffset={8}
|
||||||
|
className="w-72 p-3 space-y-1"
|
||||||
|
aria-label="Panneau d'accessibilité"
|
||||||
|
>
|
||||||
|
<p className="text-[10px] font-mono font-bold uppercase tracking-widest text-muted-foreground px-1 pb-1">
|
||||||
|
Accessibilité
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ToggleRow
|
||||||
|
icon={darkMode ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
|
||||||
|
label="Mode sombre"
|
||||||
|
description="Réduit la fatigue visuelle en faible luminosité"
|
||||||
|
active={darkMode}
|
||||||
|
onToggle={toggleDark}
|
||||||
|
/>
|
||||||
|
<ToggleRow
|
||||||
|
icon={<Type className="h-4 w-4" />}
|
||||||
|
label="Police dyslexie"
|
||||||
|
description="Espacement et police optimisés pour la lecture"
|
||||||
|
active={dyslexiaFont}
|
||||||
|
onToggle={toggleDyslexia}
|
||||||
|
/>
|
||||||
|
<ToggleRow
|
||||||
|
icon={<Contrast className="h-4 w-4" />}
|
||||||
|
label="Contraste élevé"
|
||||||
|
description="Améliore la lisibilité pour les malvoyants et daltoniens"
|
||||||
|
active={highContrast}
|
||||||
|
onToggle={toggleHighContrast}
|
||||||
|
/>
|
||||||
|
<ToggleRow
|
||||||
|
icon={<ZoomIn className="h-4 w-4" />}
|
||||||
|
label="Texte agrandi"
|
||||||
|
description="Augmente la taille de tous les textes (+20 %)"
|
||||||
|
active={largeText}
|
||||||
|
onToggle={toggleLargeText}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<p className="text-[10px] text-muted-foreground/60 px-1 pt-1 leading-relaxed">
|
||||||
|
Vos préférences sont enregistrées localement.
|
||||||
|
</p>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import React, { createContext, useContext, useState, useEffect, useCallback } from "react";
|
||||||
|
|
||||||
|
interface AccessibilityState {
|
||||||
|
darkMode: boolean;
|
||||||
|
dyslexiaFont: boolean;
|
||||||
|
highContrast: boolean;
|
||||||
|
largeText: boolean;
|
||||||
|
toggleDark: () => void;
|
||||||
|
toggleDyslexia: () => void;
|
||||||
|
toggleHighContrast: () => void;
|
||||||
|
toggleLargeText: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AccessibilityContext = createContext<AccessibilityState | null>(null);
|
||||||
|
|
||||||
|
function load(key: string, fallback: boolean): boolean {
|
||||||
|
try {
|
||||||
|
const v = localStorage.getItem(key);
|
||||||
|
return v !== null ? v === "true" : fallback;
|
||||||
|
} catch {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function save(key: string, value: boolean) {
|
||||||
|
try { localStorage.setItem(key, String(value)); } catch { /* noop */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AccessibilityProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
const [darkMode, setDarkMode] = useState(() => load("a11y-dark",
|
||||||
|
window.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false
|
||||||
|
));
|
||||||
|
const [dyslexiaFont, setDyslexiaFont] = useState(() => load("a11y-dyslexia", false));
|
||||||
|
const [highContrast, setHighContrast] = useState(() => load("a11y-contrast", false));
|
||||||
|
const [largeText, setLargeText] = useState(() => load("a11y-large", false));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const html = document.documentElement;
|
||||||
|
html.classList.toggle("dark", darkMode);
|
||||||
|
html.classList.toggle("dyslexia", dyslexiaFont);
|
||||||
|
html.classList.toggle("high-contrast", highContrast);
|
||||||
|
html.classList.toggle("large-text", largeText);
|
||||||
|
}, [darkMode, dyslexiaFont, highContrast, largeText]);
|
||||||
|
|
||||||
|
const toggleDark = useCallback(() => setDarkMode(v => { save("a11y-dark", !v); return !v; }), []);
|
||||||
|
const toggleDyslexia = useCallback(() => setDyslexiaFont(v => { save("a11y-dyslexia", !v); return !v; }), []);
|
||||||
|
const toggleHighContrast = useCallback(() => setHighContrast(v => { save("a11y-contrast", !v); return !v; }), []);
|
||||||
|
const toggleLargeText = useCallback(() => setLargeText(v => { save("a11y-large", !v); return !v; }), []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AccessibilityContext.Provider value={{
|
||||||
|
darkMode, dyslexiaFont, highContrast, largeText,
|
||||||
|
toggleDark, toggleDyslexia, toggleHighContrast, toggleLargeText,
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</AccessibilityContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAccessibility() {
|
||||||
|
const ctx = useContext(AccessibilityContext);
|
||||||
|
if (!ctx) throw new Error("useAccessibility must be used inside AccessibilityProvider");
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
@@ -162,51 +162,52 @@
|
|||||||
--elevate-1: rgba(255,255,255, .04);
|
--elevate-1: rgba(255,255,255, .04);
|
||||||
--elevate-2: rgba(255,255,255, .09);
|
--elevate-2: rgba(255,255,255, .09);
|
||||||
|
|
||||||
/* Deep rich dark blue-black */
|
/* Fond sombre neutre */
|
||||||
--background: 220 40% 6%;
|
--background: 200 20% 8%;
|
||||||
--foreground: 40 20% 90%;
|
--foreground: 40 15% 90%;
|
||||||
|
|
||||||
--border: 220 30% 20%;
|
--border: 200 15% 22%;
|
||||||
--input: 220 30% 20%;
|
--input: 200 15% 22%;
|
||||||
--ring: 40 20% 90%;
|
--ring: 185 55% 58%;
|
||||||
|
|
||||||
--card: 220 40% 8%;
|
--card: 200 20% 10%;
|
||||||
--card-foreground: 40 20% 90%;
|
--card-foreground: 40 15% 90%;
|
||||||
--card-border: 220 30% 15%;
|
--card-border: 200 15% 18%;
|
||||||
|
|
||||||
--popover: 220 40% 8%;
|
--popover: 200 20% 10%;
|
||||||
--popover-foreground: 40 20% 90%;
|
--popover-foreground: 40 15% 90%;
|
||||||
--popover-border: 220 30% 15%;
|
--popover-border: 200 15% 18%;
|
||||||
|
|
||||||
--primary: 40 20% 90%;
|
/* Pétrol clair — visible sur fond sombre */
|
||||||
--primary-foreground: 220 40% 6%;
|
--primary: 185 55% 58%;
|
||||||
|
--primary-foreground: 200 40% 8%;
|
||||||
|
|
||||||
--secondary: 220 30% 15%;
|
--secondary: 200 15% 16%;
|
||||||
--secondary-foreground: 40 20% 90%;
|
--secondary-foreground: 40 15% 85%;
|
||||||
|
|
||||||
--muted: 220 30% 12%;
|
--muted: 200 15% 13%;
|
||||||
--muted-foreground: 220 15% 60%;
|
--muted-foreground: 200 12% 58%;
|
||||||
|
|
||||||
--accent: 220 30% 15%;
|
--accent: 200 15% 16%;
|
||||||
--accent-foreground: 40 20% 90%;
|
--accent-foreground: 40 15% 85%;
|
||||||
|
|
||||||
--destructive: 350 60% 45%;
|
--destructive: 12 60% 48%;
|
||||||
--destructive-foreground: 0 0% 100%;
|
--destructive-foreground: 0 0% 100%;
|
||||||
|
|
||||||
--sidebar: 220 40% 6%;
|
--sidebar: 200 20% 8%;
|
||||||
--sidebar-foreground: 40 20% 90%;
|
--sidebar-foreground: 40 15% 90%;
|
||||||
--sidebar-border: 220 30% 20%;
|
--sidebar-border: 200 15% 22%;
|
||||||
--sidebar-primary: 40 20% 90%;
|
--sidebar-primary: 185 55% 58%;
|
||||||
--sidebar-primary-foreground: 220 40% 6%;
|
--sidebar-primary-foreground: 200 40% 8%;
|
||||||
--sidebar-accent: 220 30% 15%;
|
--sidebar-accent: 200 15% 16%;
|
||||||
--sidebar-accent-foreground: 40 20% 90%;
|
--sidebar-accent-foreground: 40 15% 85%;
|
||||||
--sidebar-ring: 40 20% 90%;
|
--sidebar-ring: 185 55% 58%;
|
||||||
|
|
||||||
--chart-1: 40 20% 90%;
|
--chart-1: 185 55% 58%;
|
||||||
--chart-2: 350 60% 45%;
|
--chart-2: 30 65% 55%;
|
||||||
--chart-3: 220 15% 60%;
|
--chart-3: 155 40% 50%;
|
||||||
--chart-4: 200 50% 50%;
|
--chart-4: 210 40% 55%;
|
||||||
--chart-5: 25 70% 50%;
|
--chart-5: 50 60% 52%;
|
||||||
|
|
||||||
--shadow-2xs: 0px 1px 0px 0px rgba(0,0,0,0.3);
|
--shadow-2xs: 0px 1px 0px 0px rgba(0,0,0,0.3);
|
||||||
--shadow-xs: 0px 1px 2px 0px rgba(0,0,0,0.3);
|
--shadow-xs: 0px 1px 2px 0px rgba(0,0,0,0.3);
|
||||||
@@ -303,6 +304,99 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── Accessibilité ────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/* Anneau de focus renforcé pour la navigation clavier */
|
||||||
|
:focus-visible {
|
||||||
|
outline: 3px solid hsl(var(--primary));
|
||||||
|
outline-offset: 3px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mode dyslexie — police et espacement optimisés */
|
||||||
|
.dyslexia,
|
||||||
|
.dyslexia body {
|
||||||
|
font-family: 'Arial', 'Verdana', 'Trebuchet MS', sans-serif !important;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
word-spacing: 0.14em;
|
||||||
|
line-height: 2;
|
||||||
|
}
|
||||||
|
.dyslexia p,
|
||||||
|
.dyslexia li,
|
||||||
|
.dyslexia blockquote {
|
||||||
|
max-width: 70ch;
|
||||||
|
word-break: keep-all;
|
||||||
|
hyphenation-character: none;
|
||||||
|
}
|
||||||
|
.dyslexia h1, .dyslexia h2, .dyslexia h3 {
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
word-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Texte agrandi (+20 %) */
|
||||||
|
.large-text {
|
||||||
|
font-size: 120%;
|
||||||
|
}
|
||||||
|
.large-text .text-xs { font-size: 0.9rem; }
|
||||||
|
.large-text .text-sm { font-size: 1rem; }
|
||||||
|
.large-text .text-base { font-size: 1.2rem; }
|
||||||
|
.large-text .text-lg { font-size: 1.35rem; }
|
||||||
|
.large-text .text-xl { font-size: 1.5rem; }
|
||||||
|
.large-text .text-2xl { font-size: 1.7rem; }
|
||||||
|
.large-text .text-3xl { font-size: 2rem; }
|
||||||
|
|
||||||
|
/* Contraste élevé — mode clair */
|
||||||
|
.high-contrast:not(.dark) {
|
||||||
|
--background: 0 0% 100%;
|
||||||
|
--foreground: 0 0% 0%;
|
||||||
|
--primary: 185 80% 18%;
|
||||||
|
--primary-foreground: 0 0% 100%;
|
||||||
|
--card: 0 0% 100%;
|
||||||
|
--card-foreground: 0 0% 0%;
|
||||||
|
--border: 0 0% 25%;
|
||||||
|
--input: 0 0% 25%;
|
||||||
|
--muted: 0 0% 93%;
|
||||||
|
--muted-foreground: 0 0% 15%;
|
||||||
|
--secondary: 0 0% 90%;
|
||||||
|
--secondary-foreground: 0 0% 0%;
|
||||||
|
--accent: 185 80% 14%;
|
||||||
|
--accent-foreground: 0 0% 100%;
|
||||||
|
--popover: 0 0% 100%;
|
||||||
|
--popover-foreground: 0 0% 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Contraste élevé — mode sombre */
|
||||||
|
.high-contrast.dark {
|
||||||
|
--background: 0 0% 0%;
|
||||||
|
--foreground: 0 0% 100%;
|
||||||
|
--primary: 185 90% 68%;
|
||||||
|
--primary-foreground: 0 0% 0%;
|
||||||
|
--card: 0 0% 5%;
|
||||||
|
--card-foreground: 0 0% 100%;
|
||||||
|
--border: 0 0% 65%;
|
||||||
|
--input: 0 0% 65%;
|
||||||
|
--muted: 0 0% 10%;
|
||||||
|
--muted-foreground: 0 0% 80%;
|
||||||
|
--secondary: 0 0% 15%;
|
||||||
|
--secondary-foreground: 0 0% 100%;
|
||||||
|
--accent: 0 0% 15%;
|
||||||
|
--accent-foreground: 0 0% 100%;
|
||||||
|
--popover: 0 0% 5%;
|
||||||
|
--popover-foreground: 0 0% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Contraste élevé — renforcement général */
|
||||||
|
.high-contrast * {
|
||||||
|
text-shadow: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
.high-contrast a:not([class]) {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.high-contrast img {
|
||||||
|
filter: contrast(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
/* ─── Impression / PDF ─────────────────────────────────────── */
|
/* ─── Impression / PDF ─────────────────────────────────────── */
|
||||||
@media print {
|
@media print {
|
||||||
/* Masquer tout sauf le flyer */
|
/* Masquer tout sauf le flyer */
|
||||||
|
|||||||
+16
-1
@@ -1,6 +1,6 @@
|
|||||||
# Document d'Architecture Technique — La Voix du Peuple
|
# Document d'Architecture Technique — La Voix du Peuple
|
||||||
|
|
||||||
**Version** : 1.2
|
**Version** : 1.3
|
||||||
**Date** : Avril 2026
|
**Date** : Avril 2026
|
||||||
**Statut** : En production (Replit), prêt pour auto-hébergement
|
**Statut** : En production (Replit), prêt pour auto-hébergement
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
| 1.0 | Avril 2026 | Version initiale |
|
| 1.0 | Avril 2026 | Version initiale |
|
||||||
| 1.1 | Avril 2026 | Ajout page Flyer QR, boutons Partager / PDF, `qrcode.react` |
|
| 1.1 | Avril 2026 | Ajout page Flyer QR, boutons Partager / PDF, `qrcode.react` |
|
||||||
| 1.2 | Avril 2026 | Palette pétrol neutre, textes de posture (expression vs. vérité) |
|
| 1.2 | Avril 2026 | Palette pétrol neutre, textes de posture (expression vs. vérité) |
|
||||||
|
| 1.3 | Avril 2026 | Dark mode pétrol, panneau d'accessibilité (dyslexie, contraste, zoom) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -87,6 +88,20 @@
|
|||||||
| `/transparence` | Fonctionnement de l'IA, données collectées, limites, posture éditoriale |
|
| `/transparence` | Fonctionnement de l'IA, données collectées, limites, posture éditoriale |
|
||||||
| `/flyer` | Flyer imprimable avec QR code configurable pour diffusion physique |
|
| `/flyer` | Flyer imprimable avec QR code configurable pour diffusion physique |
|
||||||
|
|
||||||
|
**Accessibilité** :
|
||||||
|
|
||||||
|
| Fonctionnalité | Mécanisme |
|
||||||
|
|----------------|-----------|
|
||||||
|
| Dark mode | Classes CSS `.dark` sur `<html>`, variables pétrol clair (`hsl(185 55% 58%)`) |
|
||||||
|
| Police dyslexie | Classe `.dyslexia` : Arial/Verdana, `letter-spacing 0.06em`, `line-height 2` |
|
||||||
|
| Contraste élevé | Classe `.high-contrast` : fond blanc/noir pur, ratio WCAG AA+ |
|
||||||
|
| Texte agrandi | Classe `.large-text` : `font-size 120%` global |
|
||||||
|
| Navigation clavier | `skip-link` "Aller au contenu principal" + anneau `:focus-visible` 3 px |
|
||||||
|
| ARIA | `aria-pressed` sur les toggles, `aria-label` sur tous les boutons d'action |
|
||||||
|
| Persistance | Préférences stockées dans `localStorage`, relues au chargement |
|
||||||
|
| Panneau | Composant `AccessibilityPanel` — icône dans la barre de navigation |
|
||||||
|
| Fournisseur d'état | `AccessibilityProvider` + `useAccessibility` hook (React Context) |
|
||||||
|
|
||||||
**Textes de posture** (infusés dans plusieurs sections) :
|
**Textes de posture** (infusés dans plusieurs sections) :
|
||||||
- Bandeau d'intro : "espace d'expression citoyenne, pas un sondage ni une vérité établie"
|
- Bandeau d'intro : "espace d'expression citoyenne, pas un sondage ni une vérité établie"
|
||||||
- Pied de synthèse : note italique discrète rappelant l'ancrage dans l'expertise de l'auteur
|
- Pied de synthèse : note italique discrète rappelant l'ancrage dans l'expertise de l'auteur
|
||||||
|
|||||||
+28
-4
@@ -1,6 +1,6 @@
|
|||||||
# Document d'Exploitation — La Voix du Peuple
|
# Document d'Exploitation — La Voix du Peuple
|
||||||
|
|
||||||
**Version** : 1.2
|
**Version** : 1.3
|
||||||
**Date** : Avril 2026
|
**Date** : Avril 2026
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
| 1.0 | Avril 2026 | Version initiale |
|
| 1.0 | Avril 2026 | Version initiale |
|
||||||
| 1.1 | Avril 2026 | Ajout section flyer QR, export PDF, partage horodaté |
|
| 1.1 | Avril 2026 | Ajout section flyer QR, export PDF, partage horodaté |
|
||||||
| 1.2 | Avril 2026 | Palette pétrol neutre, textes de posture sur l'expression vs. vérité |
|
| 1.2 | Avril 2026 | Palette pétrol neutre, textes de posture sur l'expression vs. vérité |
|
||||||
|
| 1.3 | Avril 2026 | Dark mode pétrol, panneau d'accessibilité (dyslexie, contraste, zoom) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -292,7 +293,30 @@ Après une purge, la synthèse se régénère automatiquement à la prochaine co
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 12. Modifier les textes de posture
|
## 12. Accessibilité — fonctionnement et personnalisation
|
||||||
|
|
||||||
|
Le panneau d'accessibilité est accessible via l'icône dans la barre de navigation (à gauche du drapeau). Quatre options sont disponibles :
|
||||||
|
|
||||||
|
| Option | Effet | Classe CSS sur `<html>` | Clé `localStorage` |
|
||||||
|
|--------|-------|------------------------|---------------------|
|
||||||
|
| Mode sombre | Fond sombre, pétrol clair | `.dark` | `a11y-dark` |
|
||||||
|
| Police dyslexie | Arial/Verdana, espacement élargi, interligne 2 | `.dyslexia` | `a11y-dyslexia` |
|
||||||
|
| Contraste élevé | Fond blanc/noir pur, contrastes WCAG AA+ | `.high-contrast` | `a11y-contrast` |
|
||||||
|
| Texte agrandi | +20 % sur tous les textes | `.large-text` | `a11y-large` |
|
||||||
|
|
||||||
|
Les préférences sont stockées dans le navigateur (localStorage) et relues automatiquement à chaque visite.
|
||||||
|
|
||||||
|
**Pour désactiver une option par code** (au déploiement, si souhaité), supprimer le `ToggleRow` correspondant dans `src/components/accessibility-panel.tsx`.
|
||||||
|
|
||||||
|
**Fichiers impliqués** :
|
||||||
|
- `src/hooks/use-accessibility.tsx` — contexte React, état, persistance
|
||||||
|
- `src/components/accessibility-panel.tsx` — interface utilisateur
|
||||||
|
- `src/index.css` — section `/* ─── Accessibilité */` — toutes les classes CSS
|
||||||
|
- `src/App.tsx` — `<AccessibilityProvider>`, skip-link, `id="main-content"`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Modifier les textes de posture
|
||||||
|
|
||||||
Les phrases de positionnement éditorial ("expression citoyenne, pas vérité établie", "auteur attaché à l'expertise") sont définies directement dans le code des composants React. Pour les modifier :
|
Les phrases de positionnement éditorial ("expression citoyenne, pas vérité établie", "auteur attaché à l'expertise") sont définies directement dans le code des composants React. Pour les modifier :
|
||||||
|
|
||||||
@@ -307,7 +331,7 @@ Après modification, reconstruire le frontend si en production (`pnpm build`), o
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 13. Modifier la palette de couleurs
|
## 14. Modifier la palette de couleurs
|
||||||
|
|
||||||
La couleur principale est définie dans `artifacts/voix-du-peuple/src/index.css`, ligne `--primary`. La valeur actuelle est `185 42% 28%` (pétrol foncé, politiquement neutre).
|
La couleur principale est définie dans `artifacts/voix-du-peuple/src/index.css`, ligne `--primary`. La valeur actuelle est `185 42% 28%` (pétrol foncé, politiquement neutre).
|
||||||
|
|
||||||
@@ -325,7 +349,7 @@ Toutes les occurrences de `--primary` dans le fichier CSS s'appliquent automatiq
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 14. Contacts et ressources
|
## 15. Contacts et ressources
|
||||||
|
|
||||||
- Documentation Mistral : https://docs.mistral.ai
|
- Documentation Mistral : https://docs.mistral.ai
|
||||||
- PostgreSQL : https://www.postgresql.org/docs/
|
- PostgreSQL : https://www.postgresql.org/docs/
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
**Hébergement** : Replit (dev) / Auto-hébergeable (RockyLinux, Debian)
|
**Hébergement** : Replit (dev) / Auto-hébergeable (RockyLinux, Debian)
|
||||||
**Dépôt** : `voix-du-peuple` (Gitea)
|
**Dépôt** : `voix-du-peuple` (Gitea)
|
||||||
**Statut** : Actif — avril 2026
|
**Statut** : Actif — avril 2026
|
||||||
**Version doc** : 1.2
|
**Version doc** : 1.3
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user