docs: add minimalist UI design spec
Covers Tailwind CSS setup, dark mode via next-themes, flag image language switcher, SEO metadata + JSON-LD, and full layout for home and status pages. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,225 @@
|
|||||||
|
# Ombrora -- Refonte UI minimaliste
|
||||||
|
|
||||||
|
**Date :** 2026-08-10
|
||||||
|
**Statut :** Approuve
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
L'application est fonctionnelle mais entierement non stylee (inline styles uniquement). Objectifs : interface minimaliste, SEO-optimisee, light/dark mode, language switcher avec drapeaux images.
|
||||||
|
|
||||||
|
## Stack technique ajoutee
|
||||||
|
|
||||||
|
| Package | Role | Compatibilite o2switch |
|
||||||
|
|---|---|---|
|
||||||
|
| `tailwindcss` + `@tailwindcss/postcss` | Styles utility-first, compile en CSS statique | OK (build-time) |
|
||||||
|
| `next-themes` | Dark/light mode via class sur `<html>` | OK (pure JS) |
|
||||||
|
| Flag SVGs locaux dans `/public/flags/` | Drapeaux langue | OK (assets statiques) |
|
||||||
|
|
||||||
|
Aucun package avec bindings natifs.
|
||||||
|
|
||||||
|
## Fichiers touches
|
||||||
|
|
||||||
|
### Nouveaux
|
||||||
|
- `tailwind.config.ts`
|
||||||
|
- `src/app/globals.css`
|
||||||
|
- `src/components/Header.tsx`
|
||||||
|
- `src/components/ThemeToggle.tsx`
|
||||||
|
- `src/components/ReassuranceSection.tsx`
|
||||||
|
- `public/flags/en.svg`
|
||||||
|
- `public/flags/fr.svg`
|
||||||
|
- `public/flags/es.svg`
|
||||||
|
- `public/flags/it.svg`
|
||||||
|
|
||||||
|
### Modifies
|
||||||
|
- `src/app/[locale]/layout.tsx` -- ThemeProvider, Inter font, JSON-LD, metadata complete
|
||||||
|
- `src/app/[locale]/page.tsx` -- hero + form + reassurance
|
||||||
|
- `src/app/[locale]/status/[uuid]/page.tsx` -- layout status restylee
|
||||||
|
- `src/components/SubmitForm.tsx` -- Tailwind, UI refaite
|
||||||
|
- `src/components/StatusView.tsx` -- Tailwind, UI refaite
|
||||||
|
- `src/components/LanguageSwitcher.tsx` -- `<img>` drapeaux SVG
|
||||||
|
- `messages/en.json`, `fr.json`, `es.json`, `it.json` -- cles SEO + reassurance
|
||||||
|
|
||||||
|
## Design
|
||||||
|
|
||||||
|
### Palette
|
||||||
|
|
||||||
|
```
|
||||||
|
Light : bg-white / text-gray-900 / accent violet-600
|
||||||
|
Dark : bg-slate-950 / text-gray-50 / accent violet-400
|
||||||
|
Input : border-gray-200 / dark:border-slate-700
|
||||||
|
Card : bg-gray-50 / dark:bg-slate-900
|
||||||
|
```
|
||||||
|
|
||||||
|
Tailwind strategy : `darkMode: 'class'` dans `tailwind.config.ts`.
|
||||||
|
|
||||||
|
### Typographie
|
||||||
|
|
||||||
|
`next/font/google` avec **Inter** (subsets `latin`, `latin-ext`). Latin-ext couvre les accents FR/ES/IT. Preload automatique par Next.js, zero CDN runtime.
|
||||||
|
|
||||||
|
### Layout -- Page home
|
||||||
|
|
||||||
|
```
|
||||||
|
<header> Logo | ThemeToggle | LanguageSwitcher </header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section> <!-- hero -->
|
||||||
|
<h1>Telechargez vos videos gratuitement</h1>
|
||||||
|
<p>Sous-titre</p>
|
||||||
|
|
||||||
|
<!-- glow violet radial derriere l'input -->
|
||||||
|
<SubmitForm /> <!-- URL + bouton inline, options compactes dessous -->
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section> <!-- reassurance -->
|
||||||
|
<ReassuranceSection /> <!-- 4 cards -->
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer> Copyright Ombrora </footer>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Layout -- Page status
|
||||||
|
|
||||||
|
```
|
||||||
|
<header> (identique) </header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section>
|
||||||
|
<h1>Statut du telechargement</h1>
|
||||||
|
<StatusView /> <!-- card centree -->
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Composant SubmitForm
|
||||||
|
|
||||||
|
- Input URL : pleine largeur, `rounded-xl`, bouton violet inline a droite (`rounded-r-xl`)
|
||||||
|
- Glow : `div` absolue violet `blur-3xl opacity-15` positionnee derriere l'input, visible en both modes
|
||||||
|
- Options : ligne flex sous l'input -- Format (select), Qualite (select), Sous-titres (checkbox styled), Options avancees (collapse/details)
|
||||||
|
- Bouton submit : `bg-violet-600 hover:bg-violet-700 dark:bg-violet-500` + spinner animate-spin quand loading
|
||||||
|
- Erreur : texte rouge sous les options
|
||||||
|
|
||||||
|
### Composant StatusView
|
||||||
|
|
||||||
|
- Card `max-w-md mx-auto rounded-2xl border bg-gray-50 dark:bg-slate-900 p-6`
|
||||||
|
- PENDING/PROCESSING : spinner `animate-spin` violet + texte statut
|
||||||
|
- DONE : bouton telechargement violet, nom fichier, taille, expiration en `text-sm text-gray-500`
|
||||||
|
- FAILED : bloc `bg-red-50 dark:bg-red-950 border-red-200` avec `<pre>` pour le message d'erreur
|
||||||
|
- FILE_DELETED : message informatif gris
|
||||||
|
|
||||||
|
### Composant Header
|
||||||
|
|
||||||
|
- `<header class="sticky top-0 z-50 border-b bg-white/80 dark:bg-slate-950/80 backdrop-blur">`
|
||||||
|
- Logo : "Ombrora" texte, "O" en `text-violet-600`
|
||||||
|
- Droite : ThemeToggle + LanguageSwitcher
|
||||||
|
- Padding `px-6 py-3`, flex justify-between
|
||||||
|
|
||||||
|
### Composant ThemeToggle
|
||||||
|
|
||||||
|
- Bouton icon-only, SVG lune (dark) ou soleil (light) inline
|
||||||
|
- Pas de librairie d'icones -- SVG paths hardcodes (moon : `M 12 3 a 6 6 0 0 0 9 9 ...`, sun : rayons simples)
|
||||||
|
- `aria-label="Basculer le theme"`
|
||||||
|
|
||||||
|
### Composant LanguageSwitcher
|
||||||
|
|
||||||
|
- Flex row, gap-2
|
||||||
|
- Chaque locale : `<button>` avec `<img src="/flags/{loc}.svg" alt="{nom_langue}" width="28" height="20" />`
|
||||||
|
- Actif : `opacity-100 ring-2 ring-violet-500 rounded-sm`
|
||||||
|
- Inactif : `opacity-40 hover:opacity-70`
|
||||||
|
- SVG sources : telecharger depuis `https://flagcdn.com/w40/{code}.png` equivalents SVG ou utiliser `flag-icons` open-source (MIT)
|
||||||
|
|
||||||
|
### Composant ReassuranceSection
|
||||||
|
|
||||||
|
4 cards en grille `grid grid-cols-2 md:grid-cols-4 gap-4` :
|
||||||
|
|
||||||
|
| Icone SVG | Titre | Sous-titre |
|
||||||
|
|---|---|---|
|
||||||
|
| Cadeau | Gratuit | Sans compte, sans inscription |
|
||||||
|
| Eclair | Rapide | Pret en quelques secondes |
|
||||||
|
| Verrou | Prive | Aucune donnee conservee |
|
||||||
|
| Film | Multi-format | mp4, mp3, webm, mkv -- jusqu'en 1080p |
|
||||||
|
|
||||||
|
Chaque card : `rounded-xl border p-4 bg-gray-50 dark:bg-slate-900 text-center`.
|
||||||
|
|
||||||
|
## SEO
|
||||||
|
|
||||||
|
### Metadata (par locale, dans generateMetadata du layout)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
title: t('meta.title'), // "Ombrora -- Telechargeur de videos gratuit"
|
||||||
|
description: t('meta.description'), // ~155 chars, locale-specific
|
||||||
|
robots: { index: true, follow: true },
|
||||||
|
openGraph: {
|
||||||
|
title, description,
|
||||||
|
url: `${process.env.NEXT_PUBLIC_BASE_URL}/${locale}`,
|
||||||
|
siteName: 'Ombrora',
|
||||||
|
locale,
|
||||||
|
},
|
||||||
|
alternates: {
|
||||||
|
canonical: `${process.env.NEXT_PUBLIC_BASE_URL}/${locale}`,
|
||||||
|
languages: { en: '/en', fr: '/fr', es: '/es', it: '/it' },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### HTML semantique
|
||||||
|
|
||||||
|
- `<html lang={locale}>` : deja en place
|
||||||
|
- `<header>`, `<main>`, `<section aria-labelledby="hero-heading">`, `<footer>`
|
||||||
|
- `<h1>` unique par page (traduit)
|
||||||
|
- `<h2>` pour titres reassurance
|
||||||
|
- `<h1>` sur status page : "Statut du telechargement"
|
||||||
|
|
||||||
|
### JSON-LD (WebApplication)
|
||||||
|
|
||||||
|
Injecte dans `<head>` via `<script type="application/ld+json">` dans le layout :
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "WebApplication",
|
||||||
|
"name": "Ombrora",
|
||||||
|
"url": "${process.env.NEXT_PUBLIC_BASE_URL}",
|
||||||
|
"description": "Telechargeur de videos gratuit, rapide et prive.",
|
||||||
|
"applicationCategory": "MultimediaApplication",
|
||||||
|
"operatingSystem": "Web",
|
||||||
|
"offers": {
|
||||||
|
"@type": "Offer",
|
||||||
|
"price": "0",
|
||||||
|
"priceCurrency": "EUR"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Traductions -- nouvelles cles
|
||||||
|
|
||||||
|
A ajouter dans chaque `messages/*.json` :
|
||||||
|
|
||||||
|
```json
|
||||||
|
"meta": {
|
||||||
|
"title": "Ombrora -- Telechargeur de videos gratuit",
|
||||||
|
"description": "Telechargez vos videos YouTube, Vimeo et plus en mp4, mp3, webm -- gratuit, rapide, sans compte."
|
||||||
|
},
|
||||||
|
"reassurance": {
|
||||||
|
"free": "Gratuit",
|
||||||
|
"freeDesc": "Sans compte, sans inscription",
|
||||||
|
"fast": "Rapide",
|
||||||
|
"fastDesc": "Pret en quelques secondes",
|
||||||
|
"private": "Prive",
|
||||||
|
"privateDesc": "Aucune donnee conservee",
|
||||||
|
"formats": "Multi-format",
|
||||||
|
"formatsDesc": "mp4, mp3, webm, mkv -- jusqu'en 1080p"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"copyright": "© 2026 Ombrora"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contraintes
|
||||||
|
|
||||||
|
- Pas de CSS framework avec runtime JS (Tailwind compile en statique)
|
||||||
|
- Pas de librairie d'icones externe -- SVG inline pour ThemeToggle, SVG fichiers pour drapeaux
|
||||||
|
- Flag SVGs stockes dans `/public/flags/` (pas de CDN runtime)
|
||||||
|
- `next/font/google` pour Inter (preload auto, pas de FOUT)
|
||||||
|
- `next-themes` : ajouter `suppressHydrationWarning` sur `<html>` pour eviter hydration mismatch
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { SubmitForm } from '@/components/SubmitForm'
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<main style={{ padding: '2rem' }}>
|
||||||
|
<h1>Ombrora</h1>
|
||||||
|
<SubmitForm />
|
||||||
|
</main>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { StatusView } from '@/components/StatusView'
|
||||||
|
|
||||||
|
export default async function StatusPage({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ uuid: string }>
|
||||||
|
}) {
|
||||||
|
const { uuid } = await params
|
||||||
|
return (
|
||||||
|
<main style={{ padding: '2rem' }}>
|
||||||
|
<h1>Statut du telechargement</h1>
|
||||||
|
<StatusView uuid={uuid} />
|
||||||
|
</main>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user