feat: design system overhaul — sidebar, AI chats, settings, brainstorm, color cleanup
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 12s

- Sidebar: dynamic brand-accent colors, brainstorm section restyled
- AI chat general: popup panel with expand/collapse, hides when contextual AI open
- AI chat contextual: tabs reordered (Actions first), X close button, height fix
- Settings: all tabs restyled, 6 new color presets (sage, terracotta, iron, etc.)
- Global color cleanup: emerald/orange hardcoded → brand-accent dynamic
- Brainstorm page: orange → brand-accent throughout
- PageEntry animation component added to key pages
- Floating AI button: bg-brand-accent instead of hardcoded black
- i18n: all 15 locales updated with new AI/billing keys
- Billing: freemium quota tracking, BYOK, stripe subscription scaffolding
- Admin: integrated into new design
- AGENTS.md + CLAUDE.md project rules added
This commit is contained in:
Antigravity
2026-05-16 12:59:30 +00:00
parent 1fcea6ed7d
commit bd495be965
2284 changed files with 395285 additions and 2327 deletions

View File

@@ -60,7 +60,7 @@ function CursorTrackerEffect({ containerRef, moveCursor }: { containerRef: React
const WAVE_COLORS: Record<number, { border: string; bg: string; text: string }> = {
1: { border: 'border-orange-200', bg: 'bg-orange-50', text: 'text-orange-600' },
2: { border: 'border-memento-blue', bg: 'bg-memento-blue', text: 'text-memento-blue' },
2: { border: 'border-brand-accent', bg: 'bg-brand-accent', text: 'text-brand-accent' },
3: { border: 'border-violet-200', bg: 'bg-violet-50', text: 'text-violet-600' },
}
@@ -224,8 +224,8 @@ export function BrainstormPage() {
try {
const result = await exportBrainstorm.mutateAsync()
if (result?.id) {
const notebookName = result._notebookName || 'Brainstorm'
setExportToast({ noteTitle: result.title || 'Synthèse', notebookName })
const notebookName = result._notebookName || t('brainstorm.exportDefaultNotebookName')
setExportToast({ noteTitle: result.title || t('brainstorm.exportDefaultNoteTitle'), notebookName })
setTimeout(() => {
setExportToast(null)
router.push(`/?openNote=${result.id}`)
@@ -238,7 +238,7 @@ export function BrainstormPage() {
setTimeout(() => setImpactToast(null), 5000)
}
} catch (err: any) {
setExportError(err?.message || 'Export failed')
setExportError(err?.message || t('brainstorm.exportFailedMessage'))
setTimeout(() => setExportError(null), 4000)
}
}
@@ -283,7 +283,7 @@ export function BrainstormPage() {
duration: 20,
ease: 'linear',
}}
className="w-14 h-14 rounded-2xl bg-orange-500 shadow-[0_0_20px_rgba(249,115,22,0.2)] flex items-center justify-center text-white"
className="w-14 h-14 rounded-2xl bg-brand-accent shadow-[0_0_20px_rgba(164,113,72,0.2)] flex items-center justify-center text-white"
>
<Wind size={28} />
</motion.div>
@@ -292,7 +292,7 @@ export function BrainstormPage() {
{t('brainstorm.title') || 'Waves of Thought'}
</h1>
<div className="flex items-center gap-2 mt-1">
<span className="w-8 h-px bg-orange-400/40" />
<span className="w-8 h-px bg-brand-accent/40" />
<p className="text-[10px] text-muted-foreground tracking-[0.3em] uppercase font-bold">
{t('brainstorm.subtitle') || 'Unfold dimensions of potentiality'}
</p>
@@ -304,7 +304,7 @@ export function BrainstormPage() {
<button
onClick={handleExport}
disabled={exportBrainstorm.isPending}
className="flex items-center gap-2 px-4 py-2 bg-white dark:bg-white/5 border border-border rounded-xl text-xs font-bold uppercase tracking-widest text-muted-foreground hover:text-orange-500 transition-all shadow-sm disabled:opacity-50"
className="flex items-center gap-2 px-4 py-2 bg-white dark:bg-white/5 border border-border rounded-xl text-xs font-bold uppercase tracking-widest text-muted-foreground hover:text-brand-accent transition-all shadow-sm disabled:opacity-50"
title={t('brainstorm.export') || 'Export'}
>
<Download size={14} />
@@ -325,18 +325,18 @@ export function BrainstormPage() {
<span className="hidden sm:inline">{t('brainstorm.invite') || 'Invite'}</span>
</button>
<div className="flex items-center px-3 py-1.5 bg-white dark:bg-white/5 border border-border rounded-xl shadow-sm transition-all hover:border-emerald-500/30">
<div className="flex items-center gap-2 mr-3" title="Live Collaboration">
<div className="flex items-center gap-2 mr-3" title={t('brainstorm.liveCollaborationTitle')}>
<div className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-500 shadow-[0_0_8px_rgba(16,185,129,0.6)]"></span>
</div>
<span className="text-[10px] font-bold uppercase tracking-widest text-emerald-600 dark:text-emerald-400 hidden sm:inline-block">Live</span>
<span className="text-[10px] font-bold uppercase tracking-widest text-emerald-600 dark:text-emerald-400 hidden sm:inline-block">{t('brainstorm.liveStatus')}</span>
</div>
<div className="flex items-center group/avatars">
{authSession?.user?.name && (
<div
className="w-6 h-6 rounded-full flex items-center justify-center text-[10px] font-bold text-white ring-2 ring-white dark:ring-[#1A1A1A] shadow-sm bg-memento-blue z-10 transition-transform hover:scale-110 hover:z-20"
className="w-6 h-6 rounded-full flex items-center justify-center text-[10px] font-bold text-white ring-2 ring-white dark:ring-[#1A1A1A] shadow-sm bg-brand-accent z-10 transition-transform hover:scale-110 hover:z-20"
title={`${authSession.user.name} (You)`}
>
{authSession.user.name.charAt(0).toUpperCase()}
@@ -368,19 +368,19 @@ export function BrainstormPage() {
</div>
<div className="relative group">
<div className="absolute -inset-1 bg-gradient-to-r from-orange-500/20 to-memento-blue/20 rounded-[28px] blur-xl opacity-0 group-focus-within:opacity-100 transition-opacity duration-700" />
<div className="absolute -inset-1 bg-gradient-to-r from-brand-accent/20 to-brand-accent/10 rounded-[28px] blur-xl opacity-0 group-focus-within:opacity-100 transition-opacity duration-700" />
<input
type="text"
value={seedInput}
onChange={(e) => setSeedInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleStartBrainstorm()}
placeholder={t('brainstorm.placeholder') || 'Enter a concept to unfold...'}
className="w-full relative bg-white dark:bg-[#1A1A1A] border-2 rounded-2xl px-8 py-7 pr-20 outline-none transition-all text-2xl font-serif italic text-foreground shadow-sm group-hover:shadow-md border-border/40 focus:border-orange-400/40 focus:ring-4 focus:ring-orange-500/5"
className="w-full relative bg-white dark:bg-[#1A1A1A] border-2 rounded-2xl px-8 py-7 pr-20 outline-none transition-all text-2xl font-serif italic text-foreground shadow-sm group-hover:shadow-md border-border/40 focus:border-brand-accent/40 focus:ring-4 focus:ring-brand-accent/5"
/>
<button
onClick={() => handleStartBrainstorm()}
disabled={isGenerating || !seedInput.trim()}
className="absolute right-4 top-4 bottom-4 px-6 bg-foreground dark:bg-orange-500 text-background rounded-xl disabled:opacity-50 transition-all hover:scale-[1.02] active:scale-[0.98] flex items-center justify-center gap-2 min-w-[70px] shadow-lg"
className="absolute right-4 top-4 bottom-4 px-6 bg-foreground dark:bg-brand-accent text-background rounded-xl disabled:opacity-50 transition-all hover:scale-[1.02] active:scale-[0.98] flex items-center justify-center gap-2 min-w-[70px] shadow-lg"
>
{isGenerating ? (
<div className="w-6 h-6 border-3 border-white/30 border-t-white rounded-full animate-spin" />
@@ -395,7 +395,7 @@ export function BrainstormPage() {
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="mt-6 flex items-center gap-4 text-orange-500/80 italic font-serif"
className="mt-6 flex items-center gap-4 text-brand-accent/80 italic font-serif"
>
<div className="flex gap-1.5">
{[0.2, 0.4, 0.6].map((d, i) => (
@@ -403,7 +403,7 @@ export function BrainstormPage() {
key={i}
animate={{ scale: [1, 1.5, 1], opacity: [0.3, 1, 0.3] }}
transition={{ duration: 1.5, repeat: Infinity, delay: d }}
className="w-1.5 h-1.5 rounded-full bg-orange-500"
className="w-1.5 h-1.5 rounded-full bg-brand-accent"
/>
))}
</div>
@@ -444,7 +444,7 @@ export function BrainstormPage() {
<div className="absolute inset-0 flex items-center justify-center pointer-events-none opacity-20 flex-col gap-6">
<Wind size={120} strokeWidth={1} className="text-muted-foreground animate-pulse" />
<p className="text-xl font-serif italic text-muted-foreground">
The canvas is waiting for your spark...
{t('brainstorm.canvasWaitingHint')}
</p>
</div>
)}
@@ -467,7 +467,7 @@ export function BrainstormPage() {
}}
/>
<span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground">
Wave {w}
{t('brainstorm.waveBadge', { wave: w })}
</span>
</div>
))}
@@ -479,7 +479,7 @@ export function BrainstormPage() {
className="px-6 py-3 bg-[#F4F1EA] dark:bg-black/60 backdrop-blur-xl border border-border shadow-xl rounded-full flex items-center gap-2 text-[10px] font-bold uppercase tracking-widest text-muted-foreground hover:bg-foreground hover:text-background transition-all"
>
<Plus size={14} />
{t('brainstorm.addManualIdea') || 'Add Manual Idea'}
{t('brainstorm.addIdea')}
</button>
)}
</motion.div>
@@ -519,7 +519,7 @@ export function BrainstormPage() {
selectedIdea.waveNumber === 1
? 'border-orange-300 dark:border-orange-700 bg-orange-50 dark:bg-orange-500/15 text-orange-600 dark:text-orange-400'
: selectedIdea.waveNumber === 2
? 'border-memento-blue dark:border-blue-700 bg-memento-blue dark:bg-memento-blue/15 text-memento-blue dark:text-memento-blue'
? 'border-brand-accent dark:border-blue-700 bg-brand-accent dark:bg-brand-accent/15 text-brand-accent dark:text-brand-accent'
: 'border-violet-300 dark:border-violet-700 bg-violet-50 dark:bg-violet-500/15 text-violet-600 dark:text-violet-400'
}`}
>
@@ -546,18 +546,18 @@ export function BrainstormPage() {
<div className="flex items-center gap-4 mb-8">
<div className="flex items-center gap-1">
<Zap size={14} className="text-orange-500" />
<Zap size={14} className="text-brand-accent" />
<span className="text-xs font-bold text-muted-foreground">
{t('brainstorm.novelty') || 'Novelty'}: {selectedIdea.noveltyScore || 'N/A'}/10
{t('brainstorm.novelty')}: {selectedIdea.noveltyScore ?? t('common.notAvailable')}/10
</span>
</div>
<div className="flex items-center gap-1.5">
{selectedIdea.createdByType === 'human' ? (
<>
<div className="w-4 h-4 rounded-full bg-memento-blue flex items-center justify-center text-[8px] font-bold text-white">
<div className="w-4 h-4 rounded-full bg-brand-accent flex items-center justify-center text-[8px] font-bold text-white">
{(selectedIdea as any).creator?.name?.charAt(0)?.toUpperCase() || 'U'}
</div>
<span className="text-[10px] font-bold uppercase tracking-widest text-memento-blue">
<span className="text-[10px] font-bold uppercase tracking-widest text-brand-accent">
{t('brainstorm.humanIdea') || 'Human'}
</span>
</>
@@ -615,13 +615,13 @@ export function BrainstormPage() {
</span>
{isRestricted && !isGuest && (
<span className="shrink-0 px-1.5 py-0.5 rounded-full text-[8px] font-bold uppercase tracking-wider border border-amber-200 dark:border-amber-700 bg-amber-500/10 text-amber-600 dark:text-amber-400 flex items-center gap-0.5">
<Lock size={8} /> Owner
<Lock size={8} /> {t('brainstorm.ownerBadge')}
</span>
)}
<div className="flex-1 min-w-0">
{ref.note ? (
<p className="text-sm font-medium text-foreground truncate">
{ref.note.title || 'Untitled'}
{ref.note.title || t('notes.untitled')}
</p>
) : (
<p className="text-sm italic text-muted-foreground">
@@ -653,9 +653,9 @@ export function BrainstormPage() {
<button
onClick={() => handleDeepen(selectedIdea)}
disabled={expandIdea.isPending}
className="flex flex-col items-center justify-center p-6 border-2 border-dashed border-border rounded-2xl hover:border-orange-400/40 hover:bg-orange-500/5 transition-all group disabled:opacity-50"
className="flex flex-col items-center justify-center p-6 border-2 border-dashed border-border rounded-2xl hover:border-brand-accent/40 hover:bg-brand-accent/5 transition-all group disabled:opacity-50"
>
<Wind size={24} className="text-muted-foreground group-hover:text-orange-500 mb-2" />
<Wind size={24} className="text-muted-foreground group-hover:text-brand-accent mb-2" />
<span className="text-[11px] font-bold uppercase tracking-widest text-muted-foreground group-hover:text-foreground">
{expandIdea.isPending ? (t('brainstorm.deepening') || 'Generating...') : (t('brainstorm.deepen') || 'Deepen')}
</span>
@@ -682,10 +682,10 @@ export function BrainstormPage() {
)}
{isGuest && (
<div className="mt-6 px-4 py-3 bg-orange-500/5 border border-orange-500/10 rounded-xl flex items-center gap-2">
<Globe size={14} className="text-orange-500 shrink-0" />
<span className="text-[11px] text-orange-600 dark:text-orange-400">
Vous consultez ce brainstorm en tant qu'invité. Connectez-vous pour modifier.
<div className="mt-6 px-4 py-3 bg-brand-accent/5 border border-brand-accent/10 rounded-xl flex items-center gap-2">
<Globe size={14} className="text-brand-accent shrink-0" />
<span className="text-[11px] text-brand-accent">
{t('brainstorm.guestReadOnlyNotice')}
</span>
</div>
)}
@@ -706,7 +706,7 @@ export function BrainstormPage() {
activeSessionId === s.id
? 'bg-foreground text-background scale-110 shadow-lg'
: (s as any)._owned === false
? 'bg-memento-blue dark:bg-memento-blue/10 text-memento-blue hover:bg-blue-100 hover:text-memento-blue'
? 'bg-brand-accent dark:bg-brand-accent/10 text-brand-accent hover:bg-blue-100 hover:text-brand-accent'
: 'bg-white dark:bg-white/10 text-muted-foreground hover:bg-foreground/5 hover:text-foreground'
}`}
title={s.seedIdea}
@@ -740,11 +740,11 @@ export function BrainstormPage() {
>
<span>
{impactToast.notesEnriched === 0 && impactToast.notesMarkedDry === 0
? (t('brainstorm.linkCopied') || 'Invite link copied!')
? t('brainstorm.linkCopied')
: <>
{impactToast.notesEnriched > 0 && `${impactToast.notesEnriched} note(s) enriched`}
{impactToast.notesEnriched > 0 && impactToast.notesMarkedDry > 0 && ' · '}
{impactToast.notesMarkedDry > 0 && `${impactToast.notesMarkedDry} note(s) marked dry`}
{impactToast.notesEnriched > 0 && t('brainstorm.impactNotesEnriched', { count: impactToast.notesEnriched })}
{impactToast.notesEnriched > 0 && impactToast.notesMarkedDry > 0 && t('brainstorm.impactSeparator')}
{impactToast.notesMarkedDry > 0 && t('brainstorm.impactNotesMarkedDry', { count: impactToast.notesMarkedDry })}
</>
}
</span>
@@ -782,11 +782,13 @@ export function BrainstormPage() {
<Wind size={18} />
<div className="flex flex-col">
<span className="font-bold">{exportToast.noteTitle}</span>
<span className="text-[11px] text-emerald-100">Carnet : {exportToast.notebookName}</span>
<span className="text-[11px] text-emerald-100">
{t('brainstorm.exportNotebookPrefix')} {exportToast.notebookName}
</span>
</div>
<div className="ml-3 flex items-center gap-1.5 text-emerald-200 text-[10px]">
<div className="w-1.5 h-1.5 rounded-full bg-emerald-300 animate-pulse" />
Ouverture…
{t('brainstorm.exportOpening')}
</div>
</motion.div>
)}
@@ -807,7 +809,7 @@ export function BrainstormPage() {
</div>
<div className="ml-3 flex items-center gap-1.5 text-emerald-200 text-[10px]">
<div className="w-1.5 h-1.5 rounded-full bg-emerald-300 animate-pulse" />
Ouverture
{t('brainstorm.exportOpening')}
</div>
</motion.div>
)}