feat(frontend): add design tokens, dark mode hook, and i18n scaffold

Part of the Ombrora Convert redesign (Task 1/5).
This commit is contained in:
2026-07-31 14:49:19 +02:00
parent f499196dae
commit ee18cc2d90
8 changed files with 359 additions and 90 deletions
+10
View File
@@ -0,0 +1,10 @@
import { Moon, Sun } from '@phosphor-icons/react';
export function ThemeToggle({ theme, onToggle, label }) {
const isDark = theme === 'dark';
return (
<button type="button" className="theme-toggle" onClick={onToggle} aria-label={label} aria-pressed={isDark}>
{isDark ? <Sun size={20} weight="regular" /> : <Moon size={20} weight="regular" />}
</button>
);
}
+27
View File
@@ -0,0 +1,27 @@
import { useEffect, useState } from 'react';
const STORAGE_KEY = 'theme';
function getInitialTheme() {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored === 'light' || stored === 'dark') return stored;
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
export function useTheme() {
const [theme, setTheme] = useState(getInitialTheme);
useEffect(() => {
document.documentElement.setAttribute('data-theme', theme);
}, [theme]);
function toggleTheme() {
setTheme((current) => {
const next = current === 'dark' ? 'light' : 'dark';
localStorage.setItem(STORAGE_KEY, next);
return next;
});
}
return { theme, toggleTheme };
}
+16
View File
@@ -0,0 +1,16 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import fr from './locales/fr.json';
import en from './locales/en.json';
i18n.use(initReactI18next).init({
resources: {
fr: { translation: fr },
en: { translation: en },
},
lng: 'fr',
fallbackLng: 'fr',
interpolation: { escapeValue: false },
});
export default i18n;
+50 -88
View File
@@ -1,111 +1,73 @@
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
:root {
--text: #6b6375;
--text-h: #08060d;
--bg: #fff;
--border: #e5e4e7;
--code-bg: #f4f3ec;
--accent: #aa3bff;
--accent-bg: rgba(170, 59, 255, 0.1);
--accent-border: rgba(170, 59, 255, 0.5);
--social-bg: rgba(244, 243, 236, 0.5);
--shadow:
rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px;
--color-primary: #2563eb;
--color-on-primary: #ffffff;
--color-secondary: #3b82f6;
--color-accent: #d97706;
--color-background: #f8fafc;
--color-foreground: #0f172a;
--color-muted: #f1f5fd;
--color-border: #e4ecfc;
--color-destructive: #dc2626;
--color-ring: #2563eb;
--sans: system-ui, 'Segoe UI', Roboto, sans-serif;
--heading: system-ui, 'Segoe UI', Roboto, sans-serif;
--mono: ui-monospace, Consolas, monospace;
--font-sans: 'Plus Jakarta Sans', system-ui, 'Segoe UI', Roboto, sans-serif;
font: 18px/145% var(--sans);
letter-spacing: 0.18px;
color-scheme: light dark;
color: var(--text);
background: var(--bg);
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@media (max-width: 1024px) {
font-size: 16px;
}
font: 16px/1.5 var(--font-sans);
}
@media (prefers-color-scheme: dark) {
:root {
--text: #9ca3af;
--text-h: #f3f4f6;
--bg: #16171d;
--border: #2e303a;
--code-bg: #1f2028;
--accent: #c084fc;
--accent-bg: rgba(192, 132, 252, 0.15);
--accent-border: rgba(192, 132, 252, 0.5);
--social-bg: rgba(47, 48, 58, 0.5);
--shadow:
rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px;
}
:root[data-theme='dark'] {
--color-primary: #3b82f6;
--color-on-primary: #0f172a;
--color-secondary: #60a5fa;
--color-accent: #f59e0b;
--color-background: #0f172a;
--color-foreground: #f1f5f9;
--color-muted: #1e293b;
--color-border: #334155;
--color-destructive: #f87171;
--color-ring: #3b82f6;
}
#social .button-icon {
filter: invert(1) brightness(2);
}
*, *::before, *::after {
box-sizing: border-box;
}
body {
margin: 0;
background: var(--color-background);
color: var(--color-foreground);
transition: background-color 200ms ease, color 200ms ease;
}
#root {
width: 1126px;
max-width: 100%;
margin: 0 auto;
text-align: center;
border-inline: 1px solid var(--border);
min-height: 100svh;
display: flex;
flex-direction: column;
box-sizing: border-box;
}
h1,
h2 {
font-family: var(--heading);
font-weight: 500;
color: var(--text-h);
a {
color: var(--color-primary);
}
h1 {
font-size: 56px;
letter-spacing: -1.68px;
margin: 32px 0;
@media (max-width: 1024px) {
font-size: 36px;
margin: 20px 0;
button, input, select {
font-family: inherit;
}
button {
cursor: pointer;
}
:focus-visible {
outline: 2px solid var(--color-ring);
outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
transition: none !important;
animation: none !important;
}
}
h2 {
font-size: 24px;
line-height: 118%;
letter-spacing: -0.24px;
margin: 0 0 8px;
@media (max-width: 1024px) {
font-size: 20px;
}
}
p {
margin: 0;
}
code,
.counter {
font-family: var(--mono);
display: inline-flex;
border-radius: 4px;
color: var(--text-h);
}
code {
font-size: 15px;
line-height: 135%;
padding: 4px 8px;
background: var(--code-bg);
}
+47
View File
@@ -0,0 +1,47 @@
{
"brand": "Ombrora Convert",
"nav": {
"switchToEnglish": "English",
"switchToFrench": "Français",
"themeToLight": "Switch to light mode",
"themeToDark": "Switch to dark mode"
},
"hero": {
"title": "Convert your files online, for free",
"subtitle": "Drag and drop your files, pick the output format, download the result. No install, no account.",
"dropzoneLabel": "Drag your files here or click to browse",
"convert": "Convert",
"unsupportedFormat": "Unsupported format"
},
"quality": {
"label": "Quality ({{value}})",
"compression": "Compression ({{value}})",
"iconSize": "Icon size",
"compressPdf": "Compress as JPEG"
},
"job": {
"converting": "Converting...",
"download": "Download"
},
"reassurance": {
"title": "Why you can trust us",
"autoDelete": "Files deleted automatically after 1h",
"noAccount": "No account required",
"online": "100% online — nothing to install",
"maxSize": "Up to 100MB per file"
},
"formats": {
"title": "Supported formats",
"images": "Images",
"documents": "Documents",
"fonts": "Fonts",
"ebooks": "Ebooks"
},
"footer": {
"rights": "Ombrora Convert — Online file conversion"
},
"notFound": {
"title": "Page not found",
"backHome": "Back to home"
}
}
+47
View File
@@ -0,0 +1,47 @@
{
"brand": "Ombrora Convert",
"nav": {
"switchToEnglish": "English",
"switchToFrench": "Français",
"themeToLight": "Activer le mode clair",
"themeToDark": "Activer le mode sombre"
},
"hero": {
"title": "Convertissez vos fichiers en ligne, gratuitement",
"subtitle": "Glissez-déposez vos fichiers, choisissez le format de sortie, téléchargez le résultat. Aucune installation, aucun compte.",
"dropzoneLabel": "Glissez vos fichiers ici ou cliquez pour parcourir",
"convert": "Convertir",
"unsupportedFormat": "Format non supporté"
},
"quality": {
"label": "Qualité ({{value}})",
"compression": "Compression ({{value}})",
"iconSize": "Taille de l'icône",
"compressPdf": "Compresser en JPEG"
},
"job": {
"converting": "Conversion en cours...",
"download": "Télécharger"
},
"reassurance": {
"title": "Pourquoi nous faire confiance",
"autoDelete": "Fichiers supprimés automatiquement après 1h",
"noAccount": "Aucun compte requis",
"online": "100% en ligne — rien à installer",
"maxSize": "Jusqu'à 100 Mo par fichier"
},
"formats": {
"title": "Formats pris en charge",
"images": "Images",
"documents": "Documents",
"fonts": "Polices",
"ebooks": "Ebooks"
},
"footer": {
"rights": "Ombrora Convert — Conversion de fichiers en ligne"
},
"notFound": {
"title": "Page introuvable",
"backHome": "Retour à l'accueil"
}
}