Refonte page de connexion : vraies photos Unsplash, thème unique
- Fond de connexion : diaporama de photos Unsplash hotlinkées (jamais réhébergées) avec attribution photographe + Unsplash toujours visible, conforme aux règles d'usage de l'API (download tracking déclenché). - Suppression complète du switch thème clair/sombre : plus qu'un seul thème assumé (ThemeContext, darkMode Tailwind et .dark CSS retirés). - Nouveau favicon minimaliste (l'ancien logo violet dégradé remplacé par un mark plat noir/citron vert cohérent avec la nouvelle DA). - Suppression des doodles décoratifs, devenus superflus sur une vraie photo, pour un rendu plus sobre. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
4cf882144c
commit
ddd0fbc0cb
13 changed files with 103 additions and 202 deletions
|
|
@ -1,46 +1,55 @@
|
|||
import { BACKGROUND_SCENES } from '../../lib/backgrounds';
|
||||
import { BACKGROUND_PHOTOS } from '../../lib/backgrounds';
|
||||
import { useBackgroundCrossfade } from '../../hooks/useBackgroundCrossfade';
|
||||
|
||||
/**
|
||||
* Photos hotlinkées depuis Unsplash (jamais copiées sur notre serveur) —
|
||||
* requis par leur charte développeur. L'attribution ci-dessous (nom du
|
||||
* photographe + Unsplash, tous deux liés) est également requise, pas
|
||||
* décorative : elle doit rester visible en permanence, pas seulement
|
||||
* au survol.
|
||||
*/
|
||||
export function AuthBackground() {
|
||||
const activeIndex = useBackgroundCrossfade();
|
||||
const active = BACKGROUND_PHOTOS[activeIndex];
|
||||
|
||||
return (
|
||||
<div className="absolute inset-0 overflow-hidden bg-[#f4efe6]">
|
||||
{BACKGROUND_SCENES.map((scene, i) => (
|
||||
<div
|
||||
key={scene.id}
|
||||
className="absolute inset-0 transition-opacity duration-[2400ms] ease-in-out"
|
||||
style={{ opacity: i === activeIndex ? 1 : 0, background: scene.base }}
|
||||
>
|
||||
{scene.blobs.map((blob, bi) => (
|
||||
<div
|
||||
key={bi}
|
||||
className="tc-drift absolute rounded-full blur-[90px]"
|
||||
style={{
|
||||
top: blob.top,
|
||||
left: blob.left,
|
||||
width: blob.size,
|
||||
height: blob.size,
|
||||
background: blob.color,
|
||||
animationDelay: `${bi * -7}s`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="absolute inset-0 overflow-hidden bg-[#171410]">
|
||||
{BACKGROUND_PHOTOS.map((photo, i) => (
|
||||
<img
|
||||
key={photo.id}
|
||||
src={photo.url}
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
className="absolute inset-0 h-full w-full object-cover transition-opacity duration-[2400ms] ease-in-out"
|
||||
style={{ opacity: i === activeIndex ? 1 : 0 }}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* 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.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>
|
||||
{/* Voile sombre pour garantir la lisibilité du texte clair par-dessus
|
||||
une vraie photo, plus prononcé à gauche où vit le contenu. */}
|
||||
<div className="absolute inset-0 bg-[linear-gradient(100deg,rgba(15,13,9,0.78)_0%,rgba(15,13,9,0.5)_46%,rgba(15,13,9,0.22)_75%)]" />
|
||||
<div className="absolute inset-0 bg-[linear-gradient(0deg,rgba(15,13,9,0.35)_0%,transparent_30%)]" />
|
||||
|
||||
{/* 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 className="absolute bottom-5 right-5 z-10 rounded-full bg-black/35 px-3.5 py-1.5 text-[11.5px] font-medium text-white/80 backdrop-blur-md sm:bottom-7 sm:right-7">
|
||||
Photo par{' '}
|
||||
<a
|
||||
href={active.photographerUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline decoration-white/40 underline-offset-2 hover:text-white hover:decoration-white"
|
||||
>
|
||||
{active.photographerName}
|
||||
</a>{' '}
|
||||
sur{' '}
|
||||
<a
|
||||
href="https://unsplash.com/?utm_source=tripoli_cloud&utm_medium=referral"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline decoration-white/40 underline-offset-2 hover:text-white hover:decoration-white"
|
||||
>
|
||||
Unsplash
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,43 +1,38 @@
|
|||
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-[#f4efe6]">
|
||||
<div className="relative min-h-screen overflow-hidden bg-[#171410]">
|
||||
<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-ink">
|
||||
<div className="flex items-center gap-2.5 text-white">
|
||||
<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="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">
|
||||
<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/20 bg-white/10 px-[13px] py-1.5 text-xs font-semibold tracking-wide text-white/85 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 font-display text-[38px] font-semibold leading-[1.05] tracking-tight text-balance text-ink sm:text-[52px] lg:text-[66px]">
|
||||
<h1 className="m-0 my-4 font-display text-[38px] font-semibold leading-[1.05] tracking-tight text-balance text-white sm:text-[52px] lg:text-[66px]">
|
||||
Vos documents,
|
||||
<br />
|
||||
<span className="italic">
|
||||
sous <span className="tc-highlight">clé</span>.
|
||||
</span>
|
||||
<span className="italic">sous clé.</span>
|
||||
</h1>
|
||||
<p className="m-0 max-w-[44ch] text-[15px] leading-relaxed text-ink-soft sm:text-[17.5px]">
|
||||
<p className="m-0 max-w-[44ch] text-[15px] leading-relaxed text-white/70 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-ink-soft">
|
||||
<div className="flex flex-wrap gap-5 text-xs text-white/60">
|
||||
<span>Tripoli Cloud — 2026</span>
|
||||
<span>Hébergé sur votre propre serveur</span>
|
||||
<span>Sans publicité, sans traçage</span>
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
/** 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,11 +1,9 @@
|
|||
import { Menu, Moon, Search, Sun } from 'lucide-react';
|
||||
import { useTheme } from '../../contexts/ThemeContext';
|
||||
import { Menu, Search } from 'lucide-react';
|
||||
import { useNavigation } from '../../contexts/NavigationContext';
|
||||
import { useIsMobile } from '../../hooks/useIsMobile';
|
||||
import { NotificationCenter } from '../notifications/NotificationCenter';
|
||||
|
||||
export function TopBar() {
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
const { openDrawer } = useNavigation();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
|
|
@ -32,14 +30,6 @@ export function TopBar() {
|
|||
</div>
|
||||
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleTheme}
|
||||
aria-label="Basculer clair / sombre"
|
||||
className="flex h-[38px] w-[38px] items-center justify-center rounded-[11px] border border-line bg-field text-ink"
|
||||
>
|
||||
{theme === 'dark' ? <Moon size={17} strokeWidth={1.8} /> : <Sun size={17} strokeWidth={1.8} />}
|
||||
</button>
|
||||
<NotificationCenter />
|
||||
</div>
|
||||
</header>
|
||||
|
|
|
|||
|
|
@ -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(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)';
|
||||
const badgeFill = variant === 'hero' ? 'rgba(255,255,255,0.12)' : 'rgba(91,140,255,0.14)';
|
||||
const badgeStroke = variant === 'hero' ? 'rgba(255,255,255,0.28)' : '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