Refonte complète du thème : éditorial clair, inspiré de studiocart.co
All checks were successful
CI / build-and-test (push) Successful in 2m14s
CI / deploy (push) Successful in 3m16s

À la demande explicite de l'utilisateur (site jugé "trop moche"),
inspiration visuelle prise sur https://www.studiocart.co/ : grands
titres serif (Fraunces) avec italique, fond papier clair plutôt que le
coffre-fort sombre d'origine, un seul accent vif (citron vert) utilisé
avec parcimonie pour les surlignages, cartes blanches nettes à bordure
fine, petites touches dessinées à la main (squiggle, flèche) sur l'écran
de connexion.

- Jetons de thème (index.css, tailwind.config.js) : le thème clair
  devient la palette par défaut. Le thème sombre existant reste
  disponible via le bouton de bascule, inchangé.
- ThemeContext : thème par défaut passé à 'light'.
- Écran de connexion entièrement repensé (fond génératif pastel qui
  dérive lentement, carte blanche, surlignage citron vert via la
  nouvelle classe utilitaire .tc-highlight, doodles SVG).
- Reste de l'application : cascade automatique via les tokens CSS déjà
  utilisés partout (bg-panel, text-ink, ...), sans avoir à toucher
  chaque composant individuellement.

Inspiration de studiocart.co pour le ton visuel uniquement (typographie,
palette, motifs) - aucun contenu, logo ni image de ce site n'est repris.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Adam Belkacemi 2026-09-22 14:37:16 +02:00
parent ecdaf19583
commit 4cf882144c
9 changed files with 127 additions and 77 deletions

View file

@ -5,7 +5,7 @@ export function AuthBackground() {
const activeIndex = useBackgroundCrossfade(); const activeIndex = useBackgroundCrossfade();
return ( return (
<div className="absolute inset-0 overflow-hidden bg-[#120f24]"> <div className="absolute inset-0 overflow-hidden bg-[#f4efe6]">
{BACKGROUND_SCENES.map((scene, i) => ( {BACKGROUND_SCENES.map((scene, i) => (
<div <div
key={scene.id} key={scene.id}
@ -15,7 +15,7 @@ export function AuthBackground() {
{scene.blobs.map((blob, bi) => ( {scene.blobs.map((blob, bi) => (
<div <div
key={bi} key={bi}
className="tc-drift absolute rounded-full blur-[80px]" className="tc-drift absolute rounded-full blur-[90px]"
style={{ style={{
top: blob.top, top: blob.top,
left: blob.left, left: blob.left,
@ -31,16 +31,16 @@ export function AuthBackground() {
{/* Grain léger : casse l'effet "dégradé plat", donne une texture {/* Grain léger : casse l'effet "dégradé plat", donne une texture
plus organique - technique courante sur les fonds génératifs. */} plus organique - technique courante sur les fonds génératifs. */}
<svg className="absolute inset-0 h-full w-full opacity-[0.06] mix-blend-overlay" aria-hidden="true"> <svg className="absolute inset-0 h-full w-full opacity-[0.05] mix-blend-multiply" aria-hidden="true">
<filter id="tc-grain"> <filter id="tc-grain">
<feTurbulence type="fractalNoise" baseFrequency="0.85" numOctaves="2" stitchTiles="stitch" /> <feTurbulence type="fractalNoise" baseFrequency="0.85" numOctaves="2" stitchTiles="stitch" />
</filter> </filter>
<rect width="100%" height="100%" filter="url(#tc-grain)" /> <rect width="100%" height="100%" filter="url(#tc-grain)" />
</svg> </svg>
{/* Assombrit seulement côté texte (gauche) - le reste du fond reste {/* Léger éclaircissement côté texte (gauche) pour garantir le
vif et visible, pas noyé sous un voile uniforme. */} contraste de l'encre noire, sans jamais assombrir le fond. */}
<div className="absolute inset-0 bg-[linear-gradient(100deg,rgba(9,7,18,0.66)_0%,rgba(9,7,18,0.28)_38%,transparent_66%)]" /> <div className="absolute inset-0 bg-[linear-gradient(100deg,rgba(250,247,241,0.55)_0%,rgba(250,247,241,0.2)_42%,transparent_68%)]" />
</div> </div>
); );
} }

View file

@ -1,38 +1,43 @@
import { AuthBackground } from './AuthBackground'; import { AuthBackground } from './AuthBackground';
import { AuthPanel } from './AuthPanel'; import { AuthPanel } from './AuthPanel';
import { DoodleArrow, DoodleSquiggle } from './Doodle';
import { TripoliLogo, TripoliWordmark } from '../layout/TripoliLogo'; import { TripoliLogo, TripoliWordmark } from '../layout/TripoliLogo';
export function AuthLayout() { export function AuthLayout() {
return ( return (
<div className="relative min-h-screen overflow-hidden bg-[#120f24]"> <div className="relative min-h-screen overflow-hidden bg-[#f4efe6]">
<AuthBackground /> <AuthBackground />
<div className="relative flex min-h-screen flex-col justify-between gap-11 p-6 sm:p-10 lg:p-[60px]"> <div className="relative flex min-h-screen flex-col justify-between gap-11 p-6 sm:p-10 lg:p-[60px]">
<div className="flex items-center gap-2.5 text-white"> <div className="flex items-center gap-2.5 text-ink">
<TripoliLogo size={30} variant="hero" /> <TripoliLogo size={30} variant="hero" />
<TripoliWordmark className="text-base" /> <TripoliWordmark className="text-base" />
</div> </div>
<div className="flex flex-wrap items-center justify-between gap-8 sm:gap-14 lg:gap-[72px]"> <div className="flex flex-wrap items-center justify-between gap-8 sm:gap-14 lg:gap-[72px]">
<div className="min-w-0 max-w-[560px] flex-1 basis-[340px] text-white"> <div className="relative min-w-0 max-w-[560px] flex-1 basis-[340px] text-ink">
<span className="inline-flex items-center gap-2 rounded-full border border-white/14 bg-white/8 px-[13px] py-1.5 text-xs font-semibold tracking-wide text-white/80 backdrop-blur-md"> <DoodleSquiggle className="absolute -left-2 -top-9 h-9 w-14 text-ink/70 sm:-top-11" />
<span className="inline-flex items-center gap-2 rounded-full border border-ink/16 bg-panel/70 px-[13px] py-1.5 text-xs font-semibold tracking-wide text-ink-soft backdrop-blur-md">
<span className="h-1.5 w-1.5 animate-tc-pulse rounded-full bg-success" /> <span className="h-1.5 w-1.5 animate-tc-pulse rounded-full bg-success" />
Coffre-fort en ligne Coffre-fort en ligne
</span> </span>
<h1 className="m-0 my-4 text-[36px] font-extrabold leading-[1.04] tracking-tight text-balance sm:text-[48px] lg:text-[62px]"> <h1 className="m-0 my-4 font-display text-[38px] font-semibold leading-[1.05] tracking-tight text-balance text-ink sm:text-[52px] lg:text-[66px]">
Vos documents, Vos documents,
<br /> <br />
sous clé. <span className="italic">
sous <span className="tc-highlight">clé</span>.
</span>
</h1> </h1>
<p className="m-0 max-w-[44ch] text-[15px] leading-relaxed text-white/62 sm:text-[17.5px]"> <p className="m-0 max-w-[44ch] text-[15px] leading-relaxed text-ink-soft sm:text-[17.5px]">
Documents, photos et sauvegardes sur un serveur que vous contrôlez. Documents, photos et sauvegardes sur un serveur que vous contrôlez.
</p> </p>
<DoodleArrow className="mt-6 hidden h-11 w-14 text-ink/60 sm:block" />
</div> </div>
<AuthPanel /> <AuthPanel />
</div> </div>
<div className="flex flex-wrap gap-5 text-xs text-white/34"> <div className="flex flex-wrap gap-5 text-xs text-ink-soft">
<span>Tripoli Cloud — 2026</span> <span>Tripoli Cloud — 2026</span>
<span>Hébergé sur votre propre serveur</span> <span>Hébergé sur votre propre serveur</span>
<span>Sans publicité, sans traçage</span> <span>Sans publicité, sans traçage</span>

View file

@ -22,23 +22,20 @@ export function AuthPanel() {
const isSignup = mode === 'signup'; const isSignup = mode === 'signup';
return ( return (
<div <div className="w-full max-w-[430px] rounded-[26px] border border-ink/12 bg-white p-6 shadow-[0_28px_64px_rgba(23,20,14,0.14)] sm:p-[34px]">
className="w-full max-w-[430px] rounded-[22px] border border-white/16 bg-white/[0.075] p-6 shadow-[0_32px_80px_rgba(0,0,0,0.5),inset_0_1px_0_rgba(255,255,255,0.18)] backdrop-blur-2xl backdrop-saturate-150 sm:p-[34px]" <h2 className="m-0 mb-1.5 font-display text-[24px] font-semibold tracking-tight text-ink">
style={{ colorScheme: 'dark' }}
>
<h2 className="m-0 mb-1.5 text-[23px] font-bold tracking-tight text-white">
{isSignup ? 'Créer votre espace' : 'Bon retour'} {isSignup ? 'Créer votre espace' : 'Bon retour'}
</h2> </h2>
<p className="m-0 mb-6 text-[13.5px] text-white/55"> <p className="m-0 mb-6 text-[13.5px] text-ink-soft">
{isSignup ? 'Créez votre compte en quelques secondes.' : 'Déverrouillez votre coffre-fort.'} {isSignup ? 'Créez votre compte en quelques secondes.' : 'Déverrouillez votre coffre-fort.'}
</p> </p>
<div className="mb-6 flex gap-1 rounded-xl border border-white/8 bg-black/28 p-1"> <div className="mb-6 flex gap-1 rounded-xl border border-ink/10 bg-field p-1">
<button <button
type="button" type="button"
onClick={() => setMode('login')} onClick={() => setMode('login')}
className={`flex-1 rounded-[9px] py-2.5 text-[13px] font-semibold transition-colors ${ className={`flex-1 rounded-[9px] py-2.5 text-[13px] font-semibold transition-colors ${
mode === 'login' ? 'bg-white/14 text-white' : 'bg-transparent text-white/55' mode === 'login' ? 'bg-white text-ink shadow-sm' : 'bg-transparent text-ink-soft'
}`} }`}
> >
Connexion Connexion
@ -47,7 +44,7 @@ export function AuthPanel() {
type="button" type="button"
onClick={() => setMode('signup')} onClick={() => setMode('signup')}
className={`flex-1 rounded-[9px] py-2.5 text-[13px] font-semibold transition-colors ${ className={`flex-1 rounded-[9px] py-2.5 text-[13px] font-semibold transition-colors ${
mode === 'signup' ? 'bg-white/14 text-white' : 'bg-transparent text-white/55' mode === 'signup' ? 'bg-white text-ink shadow-sm' : 'bg-transparent text-ink-soft'
}`} }`}
> >
Créer un compte Créer un compte
@ -64,7 +61,7 @@ export function AuthPanel() {
{isSignup && ( {isSignup && (
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-1.5">
<label htmlFor="tc-name" className="text-[12.5px] font-semibold text-white/72"> <label htmlFor="tc-name" className="text-[12.5px] font-semibold text-ink-soft">
Nom complet Nom complet
</label> </label>
<input <input
@ -75,17 +72,17 @@ export function AuthPanel() {
value={name} value={name}
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
placeholder="Leïla Barghouti" placeholder="Leïla Barghouti"
className="w-full rounded-xl border border-white/14 bg-black/26 px-[15px] py-3.5 text-[14.5px] text-white outline-none placeholder:text-white/34" className="w-full rounded-xl border border-ink/14 bg-field px-[15px] py-3.5 text-[14.5px] text-ink outline-none placeholder:text-ink-soft/70 focus:border-ink/30"
/> />
</div> </div>
)} )}
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-1.5">
<label htmlFor="tc-email" className="text-[12.5px] font-semibold text-white/72"> <label htmlFor="tc-email" className="text-[12.5px] font-semibold text-ink-soft">
Adresse e-mail Adresse e-mail
</label> </label>
<div className="relative flex items-center"> <div className="relative flex items-center">
<Mail size={16} className="absolute left-3.5 text-white/40" strokeWidth={1.8} /> <Mail size={16} className="absolute left-3.5 text-ink-soft/70" strokeWidth={1.8} />
<input <input
id="tc-email" id="tc-email"
name="email" name="email"
@ -96,8 +93,8 @@ export function AuthPanel() {
aria-invalid={emailError} aria-invalid={emailError}
aria-describedby="tc-email-err" aria-describedby="tc-email-err"
placeholder="vous@domaine.tn" placeholder="vous@domaine.tn"
className={`w-full rounded-xl border bg-black/26 py-3.5 pl-10 pr-[15px] text-[14.5px] text-white outline-none placeholder:text-white/34 ${ className={`w-full rounded-xl border bg-field py-3.5 pl-10 pr-[15px] text-[14.5px] text-ink outline-none placeholder:text-ink-soft/70 focus:border-ink/30 ${
emailError ? 'border-danger' : 'border-white/14' emailError ? 'border-danger' : 'border-ink/14'
}`} }`}
/> />
</div> </div>
@ -111,15 +108,15 @@ export function AuthPanel() {
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-1.5">
<div className="flex items-baseline justify-between gap-3"> <div className="flex items-baseline justify-between gap-3">
<label htmlFor="tc-pass" className="text-[12.5px] font-semibold text-white/72"> <label htmlFor="tc-pass" className="text-[12.5px] font-semibold text-ink-soft">
Mot de passe Mot de passe
</label> </label>
<a href="#tc-pass" className="text-xs text-brand-soft hover:text-[#a8c2ff]"> <a href="#tc-pass" className="text-xs font-semibold text-brand hover:text-brand-soft">
Oublié ? Oublié ?
</a> </a>
</div> </div>
<div className="relative flex items-center"> <div className="relative flex items-center">
<Lock size={16} className="absolute left-3.5 text-white/40" strokeWidth={1.8} /> <Lock size={16} className="absolute left-3.5 text-ink-soft/70" strokeWidth={1.8} />
<input <input
id="tc-pass" id="tc-pass"
name="password" name="password"
@ -130,8 +127,8 @@ export function AuthPanel() {
aria-invalid={passwordError} aria-invalid={passwordError}
aria-describedby="tc-pass-err" aria-describedby="tc-pass-err"
placeholder="••••••••••" placeholder="••••••••••"
className={`w-full rounded-xl border bg-black/26 py-3.5 pl-10 pr-[15px] text-[14.5px] text-white outline-none placeholder:text-white/34 ${ className={`w-full rounded-xl border bg-field py-3.5 pl-10 pr-[15px] text-[14.5px] text-ink outline-none placeholder:text-ink-soft/70 focus:border-ink/30 ${
passwordError ? 'border-danger' : 'border-white/14' passwordError ? 'border-danger' : 'border-ink/14'
}`} }`}
/> />
</div> </div>
@ -155,13 +152,13 @@ export function AuthPanel() {
<button <button
type="button" type="button"
onClick={() => void continueAsGuest()} onClick={() => void continueAsGuest()}
className="flex w-full items-center justify-center gap-2 rounded-xl border border-white/18 bg-white/6 px-[18px] py-[13px] text-[13.5px] font-semibold text-white transition-colors hover:bg-white/12" className="flex w-full items-center justify-center gap-2 rounded-xl border border-ink/16 bg-white px-[18px] py-[13px] text-[13.5px] font-semibold text-ink transition-colors hover:bg-field"
> >
<Eye size={16} strokeWidth={1.8} /> <Eye size={16} strokeWidth={1.8} />
Visiter en tant qu'invité Visiter en tant qu'invité
</button> </button>
<div className="mt-1.5 flex items-center gap-2.5 border-t border-white/10 pt-4 text-xs text-white/48"> <div className="mt-1.5 flex items-center gap-2.5 border-t border-ink/10 pt-4 text-xs text-ink-soft">
<ShieldCheck size={14} className="text-success" strokeWidth={2} /> <ShieldCheck size={14} className="text-success" strokeWidth={2} />
Connexion chiffrée (HTTPS), mots de passe hachés (Argon2) — jamais stockés en clair. Connexion chiffrée (HTTPS), mots de passe hachés (Argon2) — jamais stockés en clair.
</div> </div>

View file

@ -0,0 +1,35 @@
/** Petits traits dessinés à la main, en accent décoratif - inspirés du
* ton "studiocart.co" : jamais informatifs, purement une touche humaine
* dans une interface par ailleurs très structurée. */
export function DoodleSquiggle({ className }: { className?: string }) {
return (
<svg viewBox="0 0 60 40" fill="none" className={className} aria-hidden="true">
<path
d="M4 30C10 6 22 4 26 16C29 25 40 27 44 14C46 8 50 6 56 8"
stroke="currentColor"
strokeWidth="3"
strokeLinecap="round"
/>
</svg>
);
}
export function DoodleArrow({ className }: { className?: string }) {
return (
<svg viewBox="0 0 54 46" fill="none" className={className} aria-hidden="true">
<path
d="M6 6C22 10 40 18 46 36"
stroke="currentColor"
strokeWidth="3"
strokeLinecap="round"
/>
<path
d="M32 34C38 36 43 37 47 37C46 33 45 28 45 23"
stroke="currentColor"
strokeWidth="3"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View file

@ -5,8 +5,8 @@ interface TripoliLogoProps {
/** Minimalist inline SVG mark — no external asset, easy to swap later. */ /** Minimalist inline SVG mark — no external asset, easy to swap later. */
export function TripoliLogo({ size = 28, variant = 'sidebar' }: TripoliLogoProps) { export function TripoliLogo({ size = 28, variant = 'sidebar' }: TripoliLogoProps) {
const badgeFill = variant === 'hero' ? 'rgba(255,255,255,0.08)' : 'rgba(91,140,255,0.14)'; const badgeFill = variant === 'hero' ? 'rgba(23,20,14,0.05)' : 'rgba(91,140,255,0.14)';
const badgeStroke = variant === 'hero' ? 'rgba(255,255,255,0.22)' : 'rgba(91,140,255,0.36)'; const badgeStroke = variant === 'hero' ? 'rgba(23,20,14,0.16)' : 'rgba(91,140,255,0.36)';
return ( return (
<svg width={size} height={size} viewBox="0 0 32 32" fill="none" aria-hidden="true"> <svg width={size} height={size} viewBox="0 0 32 32" fill="none" aria-hidden="true">

View file

@ -14,7 +14,7 @@ const ThemeContext = createContext<ThemeContextValue | null>(null);
* `dark:` utilities and the CSS-variable tokens in index.css move together. * `dark:` utilities and the CSS-variable tokens in index.css move together.
*/ */
export function ThemeProvider({ children }: { children: ReactNode }) { export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme>('dark'); const [theme, setTheme] = useState<Theme>('light');
useEffect(() => { useEffect(() => {
document.documentElement.classList.toggle('dark', theme === 'dark'); document.documentElement.classList.toggle('dark', theme === 'dark');

View file

@ -1,4 +1,4 @@
@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Fraunces:ital,opsz,wght@0,9..144,500;0,9..144,600;0,9..144,700;1,9..144,500;1,9..144,600&display=swap');
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@ -8,17 +8,22 @@
* Theme tokens consumed by tailwind.config.js (colors.bg, colors.panel, ...). * Theme tokens consumed by tailwind.config.js (colors.bg, colors.panel, ...).
* `.dark` is toggled on <html> by ThemeContext — this is what makes * `.dark` is toggled on <html> by ThemeContext — this is what makes
* Tailwind's `dark:` strategy and these variables move together. * Tailwind's `dark:` strategy and these variables move together.
*
* Thème clair "éditorial" par défaut (papier + encre + citron vert en
* accent) plutôt que le coffre-fort sombre d'origine - inspiré du ton
* studiocart.co : gros titres serif, cartes blanches nettes, un seul
* accent vif utilisé avec parcimonie plutôt que des néons partout.
*/ */
:root { :root {
--tc-bg: #f4f6fb; --tc-bg: #faf7f1;
--tc-panel: rgba(255, 255, 255, 0.72); --tc-panel: #ffffff;
--tc-panel-2: rgba(255, 255, 255, 0.9); --tc-panel-2: #fffefb;
--tc-line: rgba(16, 24, 40, 0.1); --tc-line: rgba(23, 20, 14, 0.12);
--tc-field: rgba(16, 24, 40, 0.04); --tc-field: rgba(23, 20, 14, 0.045);
--tc-selected: rgba(91, 140, 255, 0.12); --tc-selected: rgba(196, 255, 82, 0.38);
--tc-ink: #101828; --tc-ink: #171410;
--tc-ink-2: rgba(16, 24, 40, 0.58); --tc-ink-2: rgba(23, 20, 14, 0.58);
--tc-shadow: 0 24px 54px rgba(16, 24, 40, 0.14); --tc-shadow: 0 20px 48px rgba(23, 20, 14, 0.09);
} }
:root.dark { :root.dark {
@ -46,6 +51,17 @@ body {
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
} }
/* Surlignage façon marqueur derrière un fragment de texte - un
dégradé plat plutôt qu'un pseudo-élément, pour rester sur une seule
classe utilitaire réutilisable n'importe où dans un titre. */
.tc-highlight {
background-image: linear-gradient(transparent 62%, var(--tc-accent, #c8ff52) 62%);
background-repeat: no-repeat;
padding: 0 0.06em;
box-decoration-break: clone;
-webkit-box-decoration-break: clone;
}
input::placeholder { input::placeholder {
color: var(--tc-ink-2); color: var(--tc-ink-2);
opacity: 0.65; opacity: 0.65;

View file

@ -1,9 +1,9 @@
/** /**
* Scènes du diaporama de fond de l'écran de connexion : art génératif * Scènes du diaporama de fond de l'écran de connexion : art génératif
* (pas de photo tierce, pour rester libre de droits) - de larges taches de * (pas de photo tierce, pour rester libre de droits) - de larges taches de
* couleur saturées, floutées, qui dérivent lentement (voir `.tc-drift` dans * couleur pastel, floutées, qui dérivent lentement (voir `.tc-drift` dans
* index.css) et s'enchaînent en fondu. Volontairement lumineux et coloré, * index.css) et s'enchaînent en fondu. Palette "papier + pastel", assortie
* pas "noir avec une pointe de couleur" - le fond DOIT se voir. * au thème éditorial clair du reste de l'interface.
*/ */
export interface BackgroundBlob { export interface BackgroundBlob {
color: string; color: string;
@ -14,46 +14,36 @@ export interface BackgroundBlob {
export interface BackgroundScene { export interface BackgroundScene {
id: string; id: string;
/** Couleur de base de la scène - un dégradé de fond, pas du noir plat. */
base: string; base: string;
blobs: BackgroundBlob[]; blobs: BackgroundBlob[];
} }
export const BACKGROUND_SCENES: BackgroundScene[] = [ export const BACKGROUND_SCENES: BackgroundScene[] = [
{ {
id: 'aube', id: 'lavande',
base: 'linear-gradient(160deg, #241a3d 0%, #171029 60%, #0d0a18 100%)', base: 'linear-gradient(160deg, #f4efe6 0%, #efe9de 55%, #eae3d6 100%)',
blobs: [ blobs: [
{ color: 'rgba(140,163,255,0.62)', top: '-18%', left: '52%', size: '62vw' }, { color: 'rgba(196,181,253,0.55)', top: '-16%', left: '55%', size: '58vw' },
{ color: 'rgba(255,140,163,0.42)', top: '46%', left: '-6%', size: '52vw' }, { color: 'rgba(253,186,203,0.42)', top: '48%', left: '-8%', size: '50vw' },
{ color: 'rgba(255,196,120,0.28)', top: '62%', left: '62%', size: '40vw' }, { color: 'rgba(200,255,82,0.28)', top: '64%', left: '62%', size: '36vw' },
], ],
}, },
{ {
id: 'nuit', id: 'peche',
base: 'linear-gradient(160deg, #1c1740 0%, #130f2c 60%, #0b0918 100%)', base: 'linear-gradient(160deg, #f6efe4 0%, #f1e9db 55%, #ece1d0 100%)',
blobs: [ blobs: [
{ color: 'rgba(110,120,255,0.58)', top: '-20%', left: '66%', size: '66vw' }, { color: 'rgba(255,196,158,0.5)', top: '-12%', left: '64%', size: '56vw' },
{ color: 'rgba(163,110,255,0.46)', top: '54%', left: '4%', size: '54vw' }, { color: 'rgba(255,150,164,0.34)', top: '52%', left: '6%', size: '48vw' },
{ color: 'rgba(94,214,255,0.22)', top: '4%', left: '2%', size: '36vw' }, { color: 'rgba(196,181,253,0.26)', top: '6%', left: '4%', size: '34vw' },
], ],
}, },
{ {
id: 'foret', id: 'menthe',
base: 'linear-gradient(160deg, #0f2a2e 0%, #0e1f26 60%, #0a1418 100%)', base: 'linear-gradient(160deg, #eef2e6 0%, #eaeee0 55%, #e4e8d8 100%)',
blobs: [ blobs: [
{ color: 'rgba(94,224,180,0.48)', top: '2%', left: '70%', size: '54vw' }, { color: 'rgba(167,229,180,0.5)', top: '4%', left: '68%', size: '52vw' },
{ color: 'rgba(53,150,166,0.5)', top: '58%', left: '-4%', size: '58vw' }, { color: 'rgba(140,214,214,0.4)', top: '58%', left: '-6%', size: '54vw' },
{ color: 'rgba(210,255,150,0.2)', top: '70%', left: '68%', size: '38vw' }, { color: 'rgba(200,255,82,0.24)', top: '70%', left: '66%', size: '34vw' },
],
},
{
id: 'braise',
base: 'linear-gradient(160deg, #351a2c 0%, #23122a 60%, #130a1a 100%)',
blobs: [
{ color: 'rgba(255,176,110,0.5)', top: '-10%', left: '10%', size: '54vw' },
{ color: 'rgba(210,120,255,0.44)', top: '48%', left: '64%', size: '58vw' },
{ color: 'rgba(255,120,150,0.26)', top: '68%', left: '10%', size: '40vw' },
], ],
}, },
]; ];

View file

@ -16,6 +16,10 @@ export default {
extend: { extend: {
fontFamily: { fontFamily: {
sans: ['Manrope', 'system-ui', 'sans-serif'], sans: ['Manrope', 'system-ui', 'sans-serif'],
// Titres uniquement (voir `.font-display`) - jamais le corps de
// texte ni l'UI, pour garder l'interface lisible et rapide à
// scanner ; le serif est réservé à l'effet éditorial des titres.
display: ['Fraunces', 'Georgia', 'serif'],
}, },
colors: { colors: {
brand: { brand: {
@ -24,6 +28,9 @@ export default {
DEFAULT: '#5b8cff', DEFAULT: '#5b8cff',
soft: '#7ba4ff', soft: '#7ba4ff',
}, },
// Citron vert - l'unique accent vif, utilisé avec parcimonie
// (surlignage, badge, détail) plutôt que comme couleur dominante.
accent: '#c8ff52',
success: '#4ade80', success: '#4ade80',
danger: '#ff8f7a', danger: '#ff8f7a',
violet: '#b39dff', violet: '#b39dff',