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:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user