Refonte complète du thème : éditorial clair, inspiré de studiocart.co
À 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:
parent
ecdaf19583
commit
4cf882144c
9 changed files with 127 additions and 77 deletions
|
|
@ -5,7 +5,7 @@ export function AuthBackground() {
|
|||
const activeIndex = useBackgroundCrossfade();
|
||||
|
||||
return (
|
||||
<div className="absolute inset-0 overflow-hidden bg-[#120f24]">
|
||||
<div className="absolute inset-0 overflow-hidden bg-[#f4efe6]">
|
||||
{BACKGROUND_SCENES.map((scene, i) => (
|
||||
<div
|
||||
key={scene.id}
|
||||
|
|
@ -15,7 +15,7 @@ export function AuthBackground() {
|
|||
{scene.blobs.map((blob, bi) => (
|
||||
<div
|
||||
key={bi}
|
||||
className="tc-drift absolute rounded-full blur-[80px]"
|
||||
className="tc-drift absolute rounded-full blur-[90px]"
|
||||
style={{
|
||||
top: blob.top,
|
||||
left: blob.left,
|
||||
|
|
@ -31,16 +31,16 @@ export function AuthBackground() {
|
|||
|
||||
{/* Grain léger : casse l'effet "dégradé plat", donne une texture
|
||||
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">
|
||||
<feTurbulence type="fractalNoise" baseFrequency="0.85" numOctaves="2" stitchTiles="stitch" />
|
||||
</filter>
|
||||
<rect width="100%" height="100%" filter="url(#tc-grain)" />
|
||||
</svg>
|
||||
|
||||
{/* Assombrit seulement côté texte (gauche) - le reste du fond reste
|
||||
vif et visible, pas noyé sous un voile uniforme. */}
|
||||
<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%)]" />
|
||||
{/* Léger éclaircissement côté texte (gauche) pour garantir le
|
||||
contraste de l'encre noire, sans jamais assombrir le fond. */}
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,43 @@
|
|||
import { AuthBackground } from './AuthBackground';
|
||||
import { AuthPanel } from './AuthPanel';
|
||||
import { DoodleArrow, DoodleSquiggle } from './Doodle';
|
||||
import { TripoliLogo, TripoliWordmark } from '../layout/TripoliLogo';
|
||||
|
||||
export function AuthLayout() {
|
||||
return (
|
||||
<div className="relative min-h-screen overflow-hidden bg-[#120f24]">
|
||||
<div className="relative min-h-screen overflow-hidden bg-[#f4efe6]">
|
||||
<AuthBackground />
|
||||
|
||||
<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" />
|
||||
<TripoliWordmark className="text-base" />
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<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">
|
||||
<div className="relative min-w-0 max-w-[560px] flex-1 basis-[340px] text-ink">
|
||||
<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" />
|
||||
Coffre-fort en ligne
|
||||
</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,
|
||||
<br />
|
||||
sous clé.
|
||||
<span className="italic">
|
||||
sous <span className="tc-highlight">clé</span>.
|
||||
</span>
|
||||
</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.
|
||||
</p>
|
||||
<DoodleArrow className="mt-6 hidden h-11 w-14 text-ink/60 sm:block" />
|
||||
</div>
|
||||
|
||||
<AuthPanel />
|
||||
</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>Hébergé sur votre propre serveur</span>
|
||||
<span>Sans publicité, sans traçage</span>
|
||||
|
|
|
|||
|
|
@ -22,23 +22,20 @@ export function AuthPanel() {
|
|||
const isSignup = mode === 'signup';
|
||||
|
||||
return (
|
||||
<div
|
||||
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]"
|
||||
style={{ colorScheme: 'dark' }}
|
||||
>
|
||||
<h2 className="m-0 mb-1.5 text-[23px] font-bold tracking-tight text-white">
|
||||
<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]">
|
||||
<h2 className="m-0 mb-1.5 font-display text-[24px] font-semibold tracking-tight text-ink">
|
||||
{isSignup ? 'Créer votre espace' : 'Bon retour'}
|
||||
</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.'}
|
||||
</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
|
||||
type="button"
|
||||
onClick={() => setMode('login')}
|
||||
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
|
||||
|
|
@ -47,7 +44,7 @@ export function AuthPanel() {
|
|||
type="button"
|
||||
onClick={() => setMode('signup')}
|
||||
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
|
||||
|
|
@ -64,7 +61,7 @@ export function AuthPanel() {
|
|||
|
||||
{isSignup && (
|
||||
<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
|
||||
</label>
|
||||
<input
|
||||
|
|
@ -75,17 +72,17 @@ export function AuthPanel() {
|
|||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
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 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
|
||||
</label>
|
||||
<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
|
||||
id="tc-email"
|
||||
name="email"
|
||||
|
|
@ -96,8 +93,8 @@ export function AuthPanel() {
|
|||
aria-invalid={emailError}
|
||||
aria-describedby="tc-email-err"
|
||||
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 ${
|
||||
emailError ? 'border-danger' : 'border-white/14'
|
||||
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-ink/14'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -111,15 +108,15 @@ export function AuthPanel() {
|
|||
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<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
|
||||
</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é ?
|
||||
</a>
|
||||
</div>
|
||||
<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
|
||||
id="tc-pass"
|
||||
name="password"
|
||||
|
|
@ -130,8 +127,8 @@ export function AuthPanel() {
|
|||
aria-invalid={passwordError}
|
||||
aria-describedby="tc-pass-err"
|
||||
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 ${
|
||||
passwordError ? 'border-danger' : 'border-white/14'
|
||||
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-ink/14'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -155,13 +152,13 @@ export function AuthPanel() {
|
|||
<button
|
||||
type="button"
|
||||
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} />
|
||||
Visiter en tant qu'invité
|
||||
</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} />
|
||||
Connexion chiffrée (HTTPS), mots de passe hachés (Argon2) — jamais stockés en clair.
|
||||
</div>
|
||||
|
|
|
|||
35
app/src/components/auth/Doodle.tsx
Normal file
35
app/src/components/auth/Doodle.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -5,8 +5,8 @@ interface TripoliLogoProps {
|
|||
|
||||
/** Minimalist inline SVG mark — no external asset, easy to swap later. */
|
||||
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 badgeStroke = variant === 'hero' ? 'rgba(255,255,255,0.22)' : 'rgba(91,140,255,0.36)';
|
||||
const badgeFill = variant === 'hero' ? 'rgba(23,20,14,0.05)' : 'rgba(91,140,255,0.14)';
|
||||
const badgeStroke = variant === 'hero' ? 'rgba(23,20,14,0.16)' : 'rgba(91,140,255,0.36)';
|
||||
|
||||
return (
|
||||
<svg width={size} height={size} viewBox="0 0 32 32" fill="none" aria-hidden="true">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue