correction des themes

This commit is contained in:
2026-02-28 00:15:54 +01:00
parent 521e529ab0
commit 82c4d1cfe1
67 changed files with 1861 additions and 742 deletions

View File

@@ -1,10 +1,49 @@
@import "tailwindcss";
/* Custom Theme */
@theme {
--font-sans: 'Inter', sans-serif;
--font-serif: 'Merriweather', serif;
--color-paper: #fcfbf7;
/* Global Theme Colors */
--color-theme-bg: var(--theme-bg);
--color-theme-panel: var(--theme-panel);
--color-theme-text: var(--theme-text);
--color-theme-muted: var(--theme-muted);
--color-theme-border: var(--theme-border);
--color-theme-editor-bg: var(--theme-editor-bg);
--color-theme-editor-text: var(--theme-editor-text);
}
:root,
.theme-light {
--theme-bg: #eef2ff;
--theme-panel: #ffffff;
--theme-text: #0f172a;
--theme-muted: #64748b;
--theme-border: #e2e8f0;
--theme-editor-bg: #ffffff;
--theme-editor-text: #0f172a;
}
.theme-dark {
--theme-bg: #0f172a;
--theme-panel: #1e293b;
--theme-text: #f8fafc;
--theme-muted: #94a3b8;
--theme-border: #334155;
--theme-editor-bg: #1e293b;
--theme-editor-text: #e2e8f0;
}
.theme-sepia {
--theme-bg: #eaddc4;
--theme-panel: #fbf8f1;
--theme-text: #332616;
--theme-muted: #735e44;
--theme-border: #dfcdae;
--theme-editor-bg: #fbf8f1;
--theme-editor-text: #332616;
}
/* Editor placeholder */
@@ -19,13 +58,16 @@
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
@@ -37,7 +79,8 @@
size: auto;
}
html, body {
html,
body {
height: auto !important;
overflow: visible !important;
margin: 0 !important;
@@ -53,11 +96,23 @@
position: relative !important;
}
.no-print { display: none !important; }
.print-only { display: block !important; }
.no-print {
display: none !important;
}
.break-before-page { page-break-before: always; break-before: page; }
.break-after-page { page-break-after: always; break-after: page; }
.print-only {
display: block !important;
}
.break-before-page {
page-break-before: always;
break-before: page;
}
.break-after-page {
page-break-after: always;
break-after: page;
}
p {
text-align: justify;
@@ -66,10 +121,16 @@
color: black !important;
}
h1, h2, h3, h4 {
h1,
h2,
h3,
h4 {
color: black !important;
page-break-after: avoid;
}
a { text-decoration: none; color: black !important; }
}
a {
text-decoration: none;
color: black !important;
}
}

View File

@@ -26,7 +26,7 @@ export default function RootLayout({
}) {
return (
<html lang="fr">
<body className={`${inter.variable} ${merriweather.variable} font-sans bg-gray-100 text-slate-800 h-screen overflow-hidden antialiased`}>
<body className={`${inter.variable} ${merriweather.variable} font-sans h-screen overflow-hidden antialiased bg-theme-bg text-theme-text transition-colors duration-300`}>
<AuthProvider>
{children}
</AuthProvider>

View File

@@ -32,17 +32,17 @@ const AIPanel: React.FC<AIPanelProps> = ({ chatHistory, onSendMessage, onInsertT
const isLimitReached = usage ? usage.aiActionsCurrent >= usage.aiActionsLimit : false;
return (
<div className="flex flex-col h-full bg-white border-l border-slate-200 shadow-xl w-80 lg:w-96">
<div className="flex flex-col h-full bg-theme-panel border-l border-theme-border shadow-xl w-80 lg:w-96 transition-colors duration-300">
{/* Header with Usage Counter */}
<div className="p-4 bg-indigo-600 text-white flex items-center justify-between shadow-md">
<div className="flex items-center gap-2">
<Sparkles size={20} className="animate-pulse" />
<h3 className="font-bold tracking-tight">Assistant IA</h3>
<Sparkles size={20} className="animate-pulse" />
<h3 className="font-bold tracking-tight">Assistant IA</h3>
</div>
{usage && (
<div className="bg-indigo-900/50 px-2 py-1 rounded text-[10px] font-black flex items-center gap-1">
<Zap size={10} fill="currentColor" /> {usage.aiActionsCurrent} / {usage.aiActionsLimit === 999999 ? '∞' : usage.aiActionsLimit}
</div>
<div className="bg-indigo-900/50 px-2 py-1 rounded text-[10px] font-black flex items-center gap-1">
<Zap size={10} fill="currentColor" /> {usage.aiActionsCurrent} / {usage.aiActionsLimit === 999999 ? '∞' : usage.aiActionsLimit}
</div>
)}
</div>
@@ -53,33 +53,33 @@ const AIPanel: React.FC<AIPanelProps> = ({ chatHistory, onSendMessage, onInsertT
</div>
)}
<div className="flex-1 overflow-y-auto p-4 space-y-4 bg-slate-50">
<div className="flex-1 overflow-y-auto p-4 space-y-4 bg-theme-bg transition-colors duration-300">
{chatHistory.length === 0 && (
<div className="text-center text-slate-400 mt-10">
<div className="text-center text-theme-muted mt-10">
<Bot size={48} className="mx-auto mb-2 opacity-50" />
<p className="text-sm">Bonjour ! Comment puis-je vous aider aujourd'hui ?</p>
{isLimitReached && (
<div className="mt-4 p-4 bg-red-50 border border-red-100 rounded-xl text-red-600 text-xs font-bold uppercase animate-pulse">
Limite atteinte ! Améliorez votre plan.
</div>
<div className="mt-4 p-4 bg-red-50 border border-red-100 rounded-xl text-red-600 text-xs font-bold uppercase animate-pulse">
Limite atteinte ! Améliorez votre plan.
</div>
)}
</div>
)}
{chatHistory.map((msg) => (
<div key={msg.id} className={`flex flex-col ${msg.role === 'user' ? 'items-end' : 'items-start'}`}>
<div className={`max-w-[85%] rounded-2xl p-4 text-sm shadow-sm ${msg.role === 'user' ? 'bg-indigo-600 text-white rounded-br-none' : 'bg-white text-slate-700 border border-slate-100 rounded-bl-none'}`}>
<div className={`max-w-[85%] rounded-2xl p-4 text-sm shadow-sm transition-colors duration-300 ${msg.role === 'user' ? 'bg-indigo-600 text-white rounded-br-none' : 'bg-theme-panel text-theme-text border border-theme-border rounded-bl-none'}`}>
{msg.role === 'model' && msg.responseType === 'reflection' && (
<div className="flex items-center gap-1.5 text-[10px] font-black text-amber-600 mb-1.5 uppercase tracking-wide"><BrainCircuit size={12} /> Réflexion</div>
<div className="flex items-center gap-1.5 text-[10px] font-black text-amber-600 mb-1.5 uppercase tracking-wide"><BrainCircuit size={12} /> Réflexion</div>
)}
<div className="whitespace-pre-wrap leading-relaxed">{msg.text}</div>
</div>
</div>
))}
{isGenerating && (
<div className="flex justify-start">
<div className="bg-white p-3 rounded-2xl rounded-bl-none shadow-sm border border-slate-100 flex items-center gap-2 text-xs text-slate-500">
<div className="bg-theme-panel p-3 rounded-2xl rounded-bl-none shadow-sm border border-theme-border flex items-center gap-2 text-xs text-theme-muted transition-colors duration-300">
<RefreshCw size={14} className="animate-spin" /> L'IA travaille...
</div>
</div>
@@ -87,17 +87,17 @@ const AIPanel: React.FC<AIPanelProps> = ({ chatHistory, onSendMessage, onInsertT
<div ref={messagesEndRef} />
</div>
<div className="p-4 bg-white border-t border-slate-200">
<div className="p-4 bg-theme-panel border-t border-theme-border transition-colors duration-300">
<form onSubmit={handleSubmit} className="relative">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder={isLimitReached ? "Limite atteinte..." : "Votre message..."}
className="w-full pl-4 pr-12 py-3 bg-slate-100 rounded-2xl text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-shadow disabled:opacity-50"
className="w-full pl-4 pr-12 py-3 bg-theme-bg text-theme-text border border-theme-border rounded-2xl text-sm focus:outline-none focus:border-indigo-500 transition-all disabled:opacity-50"
disabled={isGenerating || isLimitReached}
/>
<button
<button
type="submit"
disabled={!input.trim() || isGenerating || isLimitReached}
className="absolute right-1.5 top-1.5 p-2 bg-indigo-600 text-white rounded-xl hover:bg-indigo-700 disabled:opacity-50 transition-colors shadow-md"

View File

@@ -43,8 +43,8 @@ const BookSettingsComponent: React.FC<BookSettingsProps> = ({ project, onUpdate,
};
return (
<div className="h-full bg-[#eef2ff] p-8 overflow-y-auto">
<div className="max-w-4xl mx-auto bg-white rounded-xl shadow-lg border border-slate-200 overflow-hidden">
<div className="h-full bg-theme-bg p-8 overflow-y-auto transition-colors duration-300">
<div className="max-w-4xl mx-auto bg-theme-panel rounded-xl shadow-lg border border-theme-border overflow-hidden transition-colors duration-300">
<div className="bg-slate-900 text-white p-6 border-b border-slate-800 flex items-center gap-4">
<div className="bg-blue-600 p-3 rounded-lg">
@@ -58,53 +58,53 @@ const BookSettingsComponent: React.FC<BookSettingsProps> = ({ project, onUpdate,
<div className="p-8 space-y-8">
<section className="space-y-4">
<h3 className="text-lg font-bold text-slate-800 flex items-center gap-2 border-b border-slate-100 pb-2">
<h3 className="text-lg font-bold text-theme-text flex items-center gap-2 border-b border-theme-border pb-2">
<Book size={18} className="text-blue-600" /> Informations de Base
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-semibold text-slate-600 mb-1">Titre du Roman</label>
<label className="block text-sm font-semibold text-theme-muted mb-1">Titre du Roman</label>
<input
type="text"
value={project.title}
onChange={(e) => onUpdate({ ...project, title: e.target.value })}
className="w-full p-2.5 bg-[#eef2ff] border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none font-serif font-bold text-lg"
className="w-full p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none font-serif font-bold text-lg transition-colors duration-300"
/>
</div>
<div>
<label className="block text-sm font-semibold text-slate-600 mb-1">Nom d'Auteur</label>
<label className="block text-sm font-semibold text-theme-muted mb-1">Nom d'Auteur</label>
<input
type="text"
value={project.author}
onChange={(e) => onUpdate({ ...project, author: e.target.value })}
className="w-full p-2.5 bg-[#eef2ff] border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none"
className="w-full p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-colors duration-300"
/>
</div>
</div>
<div>
<label className="block text-sm font-semibold text-slate-600 mb-1">Synopsis Global</label>
<label className="block text-sm font-semibold text-theme-muted mb-1">Synopsis Global</label>
<textarea
value={settings.synopsis}
onChange={(e) => handleChange('synopsis', e.target.value)}
className="w-full p-3 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none h-24 text-sm bg-[#eef2ff]"
className="w-full p-3 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none h-24 text-sm transition-colors duration-300"
placeholder="De quoi parle votre histoire dans les grandes lignes ?"
/>
</div>
</section>
<section className="space-y-4">
<h3 className="text-lg font-bold text-slate-800 flex items-center gap-2 border-b border-slate-100 pb-2">
<h3 className="text-lg font-bold text-theme-text flex items-center gap-2 border-b border-theme-border pb-2">
<Target size={18} className="text-red-500" /> Genre & Public
</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div>
<label className="block text-sm font-semibold text-slate-600 mb-1">Genre Principal</label>
<label className="block text-sm font-semibold text-theme-muted mb-1">Genre Principal</label>
<input
type="text"
list="genre-suggestions"
value={settings.genre}
onChange={(e) => handleChange('genre', e.target.value)}
className="w-full p-2.5 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none bg-[#eef2ff]"
className="w-full p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-colors duration-300"
placeholder="Ex: Fantasy"
/>
<datalist id="genre-suggestions">
@@ -112,35 +112,35 @@ const BookSettingsComponent: React.FC<BookSettingsProps> = ({ project, onUpdate,
</datalist>
</div>
<div>
<label className="block text-sm font-semibold text-slate-600 mb-1">Sous-Genre</label>
<label className="block text-sm font-semibold text-theme-muted mb-1">Sous-Genre</label>
<input
type="text"
value={settings.subGenre || ''}
onChange={(e) => handleChange('subGenre', e.target.value)}
className="w-full p-2.5 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none bg-[#eef2ff]"
className="w-full p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-colors duration-300"
placeholder="Ex: Dark Fantasy"
/>
</div>
<div>
<label className="block text-sm font-semibold text-slate-600 mb-1">Public Cible</label>
<label className="block text-sm font-semibold text-theme-muted mb-1">Public Cible</label>
<input
type="text"
value={settings.targetAudience}
onChange={(e) => handleChange('targetAudience', e.target.value)}
className="w-full p-2.5 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none bg-[#eef2ff]"
className="w-full p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-colors duration-300"
placeholder="Ex: Jeune Adulte, Adulte..."
/>
</div>
</div>
<div>
<label className="block text-sm font-semibold text-slate-600 mb-1">Thèmes Clés</label>
<label className="block text-sm font-semibold text-theme-muted mb-1">Thèmes Clés</label>
<div className="relative">
<Hash size={14} className="absolute left-3 top-3 text-slate-400" />
<Hash size={14} className="absolute left-3 top-3 text-theme-muted" />
<input
type="text"
value={settings.themes}
onChange={(e) => handleChange('themes', e.target.value)}
className="w-full pl-9 p-2.5 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none bg-[#eef2ff]"
className="w-full pl-9 p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-colors duration-300"
placeholder="Ex: Vengeance, Rédemption, Voyage initiatique..."
/>
</div>
@@ -148,44 +148,44 @@ const BookSettingsComponent: React.FC<BookSettingsProps> = ({ project, onUpdate,
</section>
<section className="space-y-4">
<h3 className="text-lg font-bold text-slate-800 flex items-center gap-2 border-b border-slate-100 pb-2">
<h3 className="text-lg font-bold text-theme-text flex items-center gap-2 border-b border-theme-border pb-2">
<Feather size={18} className="text-purple-600" /> Narration & Style
</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div>
<label className="block text-sm font-semibold text-slate-600 mb-1 flex items-center gap-1">
<label className="block text-sm font-semibold text-theme-muted mb-1 flex items-center gap-1">
<Users size={14} /> Point de Vue (POV)
</label>
<select
value={settings.pov}
onChange={(e) => handleChange('pov', e.target.value)}
className="w-full p-2.5 bg-[#eef2ff] border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none"
className="w-full p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-colors duration-300"
>
<option value="">Sélectionner...</option>
{POV_OPTIONS.map(o => <option key={o} value={o}>{o}</option>)}
</select>
</div>
<div>
<label className="block text-sm font-semibold text-slate-600 mb-1 flex items-center gap-1">
<label className="block text-sm font-semibold text-theme-muted mb-1 flex items-center gap-1">
<Clock size={14} /> Temps du récit
</label>
<select
value={settings.tense}
onChange={(e) => handleChange('tense', e.target.value)}
className="w-full p-2.5 bg-[#eef2ff] border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none"
className="w-full p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-colors duration-300"
>
<option value="">Sélectionner...</option>
{TENSE_OPTIONS.map(o => <option key={o} value={o}>{o}</option>)}
</select>
</div>
<div>
<label className="block text-sm font-semibold text-slate-600 mb-1">Ton Général</label>
<label className="block text-sm font-semibold text-theme-muted mb-1">Ton Général</label>
<input
type="text"
list="tone-suggestions"
value={settings.tone}
onChange={(e) => handleChange('tone', e.target.value)}
className="w-full p-2.5 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none bg-[#eef2ff]"
className="w-full p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-colors duration-300"
placeholder="Ex: Sombre, Ironique..."
/>
<datalist id="tone-suggestions">
@@ -195,16 +195,16 @@ const BookSettingsComponent: React.FC<BookSettingsProps> = ({ project, onUpdate,
</div>
<div className="mt-4">
<label className="block text-sm font-semibold text-slate-600 mb-1">
<label className="block text-sm font-semibold text-theme-muted mb-1">
Guide de Style & Instructions IA (Prompt Système)
</label>
<p className="text-xs text-slate-400 mb-2">
<p className="text-xs text-theme-muted mb-2">
Ces instructions seront envoyées à l'IA à chaque génération. Décrivez ici le style d'écriture désiré (ex: "phrases courtes", "vocabulaire soutenu", "beaucoup de métaphores").
</p>
<textarea
value={project.styleGuide || ''}
onChange={(e) => handleStyleGuideChange(e.target.value)}
className="w-full p-3 border border-indigo-100 bg-[#eef2ff] rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none h-32 text-sm font-mono text-slate-700"
className="w-full p-3 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none h-32 text-sm font-mono transition-colors duration-300"
placeholder="Ex: Utilise un style descriptif et sensoriel. Évite les adverbes. Le narrateur est cynique."
/>
</div>
@@ -220,17 +220,17 @@ const BookSettingsComponent: React.FC<BookSettingsProps> = ({ project, onUpdate,
Cette action est irréversible. Toutes les données associées à ce projet (chapitres, entités, idées) seront définitivement effacées.
</p>
{showDeleteConfirm ? (
<div className="flex items-center gap-4 bg-white p-4 rounded border border-red-200">
<span className="text-sm font-bold text-slate-700">Êtes-vous sûr ?</span>
<div className="flex items-center gap-4 bg-theme-panel p-4 rounded border border-red-200">
<span className="text-sm font-bold text-theme-text">Êtes-vous sûr ?</span>
<button
onClick={onDeleteProject}
className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 text-sm font-bold"
className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 text-sm font-bold opacity-90 transition-opacity"
>
Oui, supprimer définitivement
</button>
<button
onClick={() => setShowDeleteConfirm(false)}
className="px-4 py-2 bg-slate-200 text-slate-700 rounded hover:bg-slate-300 text-sm"
className="px-4 py-2 bg-theme-bg text-theme-text border border-theme-border rounded hover:opacity-80 text-sm transition-opacity"
>
Annuler
</button>
@@ -238,7 +238,7 @@ const BookSettingsComponent: React.FC<BookSettingsProps> = ({ project, onUpdate,
) : (
<button
onClick={() => setShowDeleteConfirm(true)}
className="px-4 py-2 bg-white border border-red-300 text-red-600 rounded hover:bg-red-50 text-sm font-bold"
className="px-4 py-2 bg-theme-panel border border-red-300 text-red-600 rounded hover:bg-red-50 text-sm font-bold transition-colors duration-300"
>
Supprimer ce projet
</button>

View File

@@ -17,53 +17,53 @@ interface DashboardProps {
const Dashboard: React.FC<DashboardProps> = ({ user, projects, onSelect, onCreate, onLogout, onPricing, onProfile }) => {
return (
<div className="min-h-screen bg-[#eef2ff] p-8 font-sans">
<div className="min-h-screen bg-theme-bg p-8 font-sans transition-colors duration-300">
<div className="max-w-6xl mx-auto space-y-8">
{/* User Card */}
<div className="flex flex-col md:flex-row justify-between items-center bg-white p-8 rounded-[2rem] shadow-sm border border-indigo-100 gap-6">
<div className="flex flex-col md:flex-row justify-between items-center bg-theme-panel p-8 rounded-[2rem] shadow-sm border border-theme-border gap-6">
<div className="flex items-center gap-6">
<div className="relative">
<img src={user.avatar} className="w-20 h-20 rounded-full border-4 border-slate-50 shadow-lg object-cover" alt="Avatar" />
<div className="absolute -bottom-1 -right-1 bg-green-500 w-5 h-5 rounded-full border-4 border-white" />
</div>
<div>
<h2 className="text-3xl font-black text-slate-900">Bonjour, {user.name} 👋</h2>
<h2 className="text-3xl font-black text-theme-text">Bonjour, {user.name} 👋</h2>
<div className="flex items-center gap-3 mt-1">
<span className="px-3 py-1 rounded-full bg-indigo-100 text-indigo-700 text-[10px] uppercase font-black tracking-widest">{user.subscription.planDetails?.displayName || user.subscription.plan}</span>
<span className="text-slate-400 text-xs font-medium">Membre depuis le 24 janv.</span>
<span className="text-theme-muted text-xs font-medium">Membre depuis le 24 janv.</span>
</div>
</div>
</div>
<div className="flex items-center gap-3">
<button onClick={onProfile} className="bg-slate-50 text-slate-700 px-5 py-2.5 rounded-xl text-sm font-bold hover:bg-slate-100 transition-all flex items-center gap-2 border border-slate-200">
<button onClick={onProfile} className="bg-theme-bg text-theme-text px-5 py-2.5 rounded-xl text-sm font-bold hover:opacity-80 transition-all flex items-center gap-2 border border-theme-border">
<User size={18} /> Mon Profil
</button>
<button onClick={onLogout} className="p-3 text-slate-400 hover:text-red-500 rounded-full hover:bg-red-50 transition-colors"><LogOut size={20} /></button>
<button onClick={onLogout} className="p-3 text-theme-muted hover:text-red-500 rounded-full hover:bg-red-500/10 transition-colors"><LogOut size={20} /></button>
</div>
</div>
{/* Stats Section */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="bg-white p-6 rounded-3xl shadow-sm border border-indigo-50 flex items-center gap-4">
<div className="bg-theme-panel p-6 rounded-3xl shadow-sm border border-theme-border flex items-center gap-4">
<div className="bg-orange-100 p-3 rounded-2xl text-orange-600"><Flame size={24} /></div>
<div>
<p className="text-xs font-bold text-slate-400 uppercase tracking-wider">Série actuelle</p>
<p className="text-2xl font-black text-slate-900">{user.stats.writingStreak} Jours</p>
<p className="text-xs font-bold text-theme-muted uppercase tracking-wider">Série actuelle</p>
<p className="text-2xl font-black text-theme-text">{user.stats.writingStreak} Jours</p>
</div>
</div>
<div className="bg-white p-6 rounded-3xl shadow-sm border border-indigo-50 flex items-center gap-4">
<div className="bg-theme-panel p-6 rounded-3xl shadow-sm border border-theme-border flex items-center gap-4">
<div className="bg-blue-100 p-3 rounded-2xl text-blue-600"><Edit3 size={24} /></div>
<div>
<p className="text-xs font-bold text-slate-400 uppercase tracking-wider">Mots écrits</p>
<p className="text-2xl font-black text-slate-900">{user.stats.totalWordsWritten.toLocaleString()}</p>
<p className="text-xs font-bold text-theme-muted uppercase tracking-wider">Mots écrits</p>
<p className="text-2xl font-black text-theme-text">{user.stats.totalWordsWritten.toLocaleString()}</p>
</div>
</div>
<div className="bg-white p-6 rounded-3xl shadow-sm border border-indigo-50 flex items-center gap-4">
<div className="bg-theme-panel p-6 rounded-3xl shadow-sm border border-theme-border flex items-center gap-4">
<div className="bg-indigo-100 p-3 rounded-2xl text-indigo-600"><Target size={24} /></div>
<div>
<p className="text-xs font-bold text-slate-400 uppercase tracking-wider">Objectif du jour</p>
<p className="text-2xl font-black text-slate-900">{user.preferences.dailyWordGoal} Mots</p>
<p className="text-xs font-bold text-theme-muted uppercase tracking-wider">Objectif du jour</p>
<p className="text-2xl font-black text-theme-text">{user.preferences.dailyWordGoal} Mots</p>
</div>
</div>
</div>
@@ -72,7 +72,7 @@ const Dashboard: React.FC<DashboardProps> = ({ user, projects, onSelect, onCreat
{/* Project List */}
<div className="lg:col-span-2 space-y-4">
<div className="flex justify-between items-center mb-6">
<h3 className="text-2xl font-black text-slate-900">Mes Romans</h3>
<h3 className="text-2xl font-black text-theme-text">Mes Romans</h3>
<button
onClick={onCreate}
className="flex items-center gap-2 bg-blue-600 text-white px-6 py-3 rounded-2xl font-bold hover:bg-blue-700 transition-all shadow-xl shadow-blue-200"
@@ -86,23 +86,23 @@ const Dashboard: React.FC<DashboardProps> = ({ user, projects, onSelect, onCreat
<div
key={p.id}
onClick={() => onSelect(p.id)}
className="bg-white p-8 rounded-[2.5rem] border border-indigo-50 shadow-sm hover:shadow-2xl hover:scale-[1.02] transition-all cursor-pointer group flex flex-col justify-between h-64"
className="bg-theme-panel p-8 rounded-[2.5rem] border border-theme-border shadow-sm hover:shadow-2xl hover:scale-[1.02] transition-all cursor-pointer group flex flex-col justify-between h-64"
>
<div>
<div className="bg-blue-50 w-12 h-12 rounded-2xl flex items-center justify-center text-blue-600 mb-6 group-hover:bg-blue-600 group-hover:text-white transition-colors">
<div className="bg-blue-500/10 w-12 h-12 rounded-2xl flex items-center justify-center text-blue-500 mb-6 group-hover:bg-blue-600 group-hover:text-white transition-colors">
<Book size={24} />
</div>
<h4 className="font-black text-slate-900 text-xl truncate mb-1">{p.title}</h4>
<p className="text-slate-400 text-sm">Dernière modification : {new Date(p.lastModified).toLocaleDateString()}</p>
<h4 className="font-black text-theme-text text-xl truncate mb-1">{p.title}</h4>
<p className="text-theme-muted text-sm">Dernière modification : {new Date(p.lastModified).toLocaleDateString()}</p>
</div>
<div className="flex justify-between items-center text-[10px] text-slate-400 font-black uppercase tracking-widest border-t border-slate-50 pt-4">
<div className="flex justify-between items-center text-[10px] text-theme-muted font-black uppercase tracking-widest border-t border-theme-border pt-4 mt-auto">
<span>{p.chapters.length} Chapitres</span>
<ChevronRight size={20} className="group-hover:text-blue-600 transition-transform group-hover:translate-x-1 duration-300" />
</div>
</div>
))}
{projects.length === 0 && (
<div className="col-span-2 py-24 bg-white rounded-[3rem] border-2 border-dashed border-indigo-100 flex flex-col items-center justify-center text-indigo-300">
<div className="col-span-2 py-24 bg-theme-panel rounded-[3rem] border-2 border-dashed border-theme-border flex flex-col items-center justify-center text-theme-muted">
<Book size={64} className="mb-6 opacity-20" />
<p className="font-bold text-lg">Prêt à commencer votre premier roman ?</p>
<button onClick={onCreate} className="mt-4 text-blue-600 font-bold hover:underline">Créer un projet maintenant</button>

View File

@@ -5,8 +5,8 @@ import { Idea, IdeaStatus, IdeaCategory } from '@/lib/types';
import { Plus, X, GripVertical, CheckCircle, Circle, Clock, Lightbulb, Search, Trash2, Edit3, Save } from 'lucide-react';
interface IdeaBoardProps {
ideas: Idea[];
onUpdate: (ideas: Idea[]) => void;
ideas: Idea[];
onUpdate: (ideas: Idea[]) => void;
}
const CATEGORIES: Record<IdeaCategory, { label: string, color: string, icon: any }> = {
@@ -25,349 +25,347 @@ const STATUS_LABELS: Record<IdeaStatus, string> = {
const MAX_DESCRIPTION_LENGTH = 500;
const IdeaBoard: React.FC<IdeaBoardProps> = ({ ideas, onUpdate }) => {
const [newIdeaTitle, setNewIdeaTitle] = useState('');
const [newIdeaCategory, setNewIdeaCategory] = useState<IdeaCategory>('plot');
const [newIdeaTitle, setNewIdeaTitle] = useState('');
const [newIdeaCategory, setNewIdeaCategory] = useState<IdeaCategory>('plot');
// Drag and Drop State
const [draggedIdeaId, setDraggedIdeaId] = useState<string | null>(null);
// Drag and Drop State
const [draggedIdeaId, setDraggedIdeaId] = useState<string | null>(null);
// Modal State for Edit/Quick Add
const [editingItem, setEditingItem] = useState<Partial<Idea> | null>(null);
// Modal State for Edit/Quick Add
const [editingItem, setEditingItem] = useState<Partial<Idea> | null>(null);
// --- ACTIONS ---
// --- ACTIONS ---
const handleAddIdea = (e: React.FormEvent) => {
e.preventDefault();
if (!newIdeaTitle.trim()) return;
const handleAddIdea = (e: React.FormEvent) => {
e.preventDefault();
if (!newIdeaTitle.trim()) return;
const newIdea: Idea = {
id: `idea-${Date.now()}`,
title: newIdeaTitle,
description: '',
category: newIdeaCategory,
status: 'todo',
createdAt: Date.now()
const newIdea: Idea = {
id: `idea-${Date.now()}`,
title: newIdeaTitle,
description: '',
category: newIdeaCategory,
status: 'todo',
createdAt: Date.now()
};
onUpdate([...ideas, newIdea]);
setNewIdeaTitle('');
};
onUpdate([...ideas, newIdea]);
setNewIdeaTitle('');
};
const handleDelete = (id: string) => {
if (confirm("Supprimer cette carte ?")) {
onUpdate(ideas.filter(i => i.id !== id));
if (editingItem?.id === id) setEditingItem(null);
}
};
const handleDelete = (id: string) => {
if(confirm("Supprimer cette carte ?")) {
onUpdate(ideas.filter(i => i.id !== id));
if (editingItem?.id === id) setEditingItem(null);
}
};
const handleSaveEdit = () => {
if (!editingItem || !editingItem.title?.trim()) return;
const handleSaveEdit = () => {
if (!editingItem || !editingItem.title?.trim()) return;
if (editingItem.id) {
// Update existing
onUpdate(ideas.map(i => i.id === editingItem.id ? { ...i, ...editingItem } as Idea : i));
} else {
// Create new from modal
const newIdea: Idea = {
id: `idea-${Date.now()}`,
title: editingItem.title || '',
description: editingItem.description || '',
category: editingItem.category || 'plot',
status: editingItem.status || 'todo',
createdAt: Date.now()
if (editingItem.id) {
// Update existing
onUpdate(ideas.map(i => i.id === editingItem.id ? { ...i, ...editingItem } as Idea : i));
} else {
// Create new from modal
const newIdea: Idea = {
id: `idea-${Date.now()}`,
title: editingItem.title || '',
description: editingItem.description || '',
category: editingItem.category || 'plot',
status: editingItem.status || 'todo',
createdAt: Date.now()
};
onUpdate([...ideas, newIdea]);
}
setEditingItem(null);
};
}
setEditingItem(null);
};
const openQuickAdd = (status: IdeaStatus) => {
setEditingItem({
title: '',
description: '',
category: 'plot',
status: status
});
};
const openQuickAdd = (status: IdeaStatus) => {
setEditingItem({
title: '',
description: '',
category: 'plot',
status: status
});
};
const openEdit = (idea: Idea) => {
setEditingItem({ ...idea });
};
const openEdit = (idea: Idea) => {
setEditingItem({ ...idea });
};
// --- DRAG HANDLERS ---
// --- DRAG HANDLERS ---
const handleDragStart = (e: React.DragEvent, id: string) => {
setDraggedIdeaId(id);
e.dataTransfer.effectAllowed = 'move';
};
const handleDragStart = (e: React.DragEvent, id: string) => {
setDraggedIdeaId(id);
e.dataTransfer.effectAllowed = 'move';
};
const handleDrop = (e: React.DragEvent, status: IdeaStatus) => {
e.preventDefault();
if (draggedIdeaId) {
const updatedIdeas = ideas.map(idea =>
idea.id === draggedIdeaId ? { ...idea, status } : idea
);
onUpdate(updatedIdeas);
setDraggedIdeaId(null);
}
};
const handleDrop = (e: React.DragEvent, status: IdeaStatus) => {
e.preventDefault();
if (draggedIdeaId) {
const updatedIdeas = ideas.map(idea =>
idea.id === draggedIdeaId ? { ...idea, status } : idea
);
onUpdate(updatedIdeas);
setDraggedIdeaId(null);
}
};
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
};
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
};
// --- RENDERERS ---
// --- RENDERERS ---
const Column = ({ title, status, icon: Icon }: { title: string, status: IdeaStatus, icon: any }) => {
const columnIdeas = ideas.filter(i => i.status === status);
const Column = ({ title, status, icon: Icon }: { title: string, status: IdeaStatus, icon: any }) => {
const columnIdeas = ideas.filter(i => i.status === status);
return (
<div
className="flex-1 bg-theme-bg/50 rounded-xl border border-theme-border flex flex-col h-full overflow-hidden transition-colors hover:border-blue-300"
onDragOver={handleDragOver}
onDrop={(e) => handleDrop(e, status)}
onDoubleClick={() => openQuickAdd(status)}
title="Double-cliquez dans le vide pour ajouter une carte ici"
>
{/* Column Header */}
<div className={`p-4 border-b border-theme-border flex justify-between items-center transition-colors duration-300 ${status === 'todo' ? 'bg-theme-bg' :
status === 'progress' ? 'bg-indigo-500/10' :
'bg-green-500/10'
}`}>
<div className="flex items-center gap-2 font-bold text-theme-text">
<Icon size={18} />
{title}
</div>
<div className="flex items-center gap-2">
<button
onClick={(e) => { e.stopPropagation(); openQuickAdd(status); }}
className="p-1 hover:bg-theme-panel rounded-full text-theme-muted hover:text-blue-600 transition-colors"
>
<Plus size={16} />
</button>
<span className="text-xs font-semibold bg-theme-panel px-2 py-1 rounded-full border border-theme-border text-theme-muted">
{columnIdeas.length}
</span>
</div>
</div>
{/* Column Body */}
<div className="flex-1 overflow-y-auto p-3 space-y-3 custom-scrollbar">
{columnIdeas.map(idea => {
const truncatedDesc = idea.description.length > 300
? idea.description.substring(0, 300) + '...'
: idea.description;
return (
<div
key={idea.id}
draggable
onDragStart={(e) => handleDragStart(e, idea.id)}
onDoubleClick={(e) => {
e.stopPropagation(); // Prevent column double-click
openEdit(idea);
}}
className="bg-theme-panel p-3 rounded-lg shadow-sm border border-theme-border cursor-grab active:cursor-grabbing hover:shadow-md hover:border-blue-300 transition-all group relative animate-in zoom-in-95 duration-200"
>
<div className="flex justify-between items-start mb-2">
<span className={`text-[10px] uppercase font-bold px-2 py-0.5 rounded-full flex items-center gap-1 ${CATEGORIES[idea.category].color}`}>
{CATEGORIES[idea.category].label}
</span>
<div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
<button
onClick={(e) => { e.stopPropagation(); openEdit(idea); }}
className="text-slate-300 hover:text-blue-500"
>
<Edit3 size={14} />
</button>
<button
onClick={(e) => { e.stopPropagation(); handleDelete(idea.id); }}
className="text-slate-300 hover:text-red-500"
>
<Trash2 size={14} />
</button>
</div>
</div>
{/* CARD CONTENT */}
<div className="mb-2">
<h4 className="font-bold text-theme-text text-sm mb-1 leading-tight">{idea.title}</h4>
{idea.description && (
<p className="text-xs text-theme-muted line-clamp-3 leading-relaxed" title={idea.description.length > 300 ? "Description tronquée (voir détail)" : undefined}>
{truncatedDesc}
</p>
)}
</div>
<div className="flex justify-between items-center text-xs text-theme-muted border-t border-theme-border pt-2 mt-2 transition-colors duration-300">
<span className="flex items-center gap-1">
<Clock size={10} /> {new Date(idea.createdAt).toLocaleDateString()}
</span>
<GripVertical size={14} className="opacity-20" />
</div>
</div>
);
})}
{columnIdeas.length === 0 && (
<div className="h-full flex flex-col items-center justify-center text-slate-300 text-sm italic border-2 border-dashed border-indigo-200 rounded-lg m-1">
<span className="mb-2">Vide</span>
<span className="text-xs opacity-70">Double-cliquez pour ajouter</span>
</div>
)}
</div>
</div>
);
};
return (
<div
className="flex-1 bg-[#eef2ff] rounded-xl border border-indigo-100 flex flex-col h-full overflow-hidden transition-colors hover:border-blue-200"
onDragOver={handleDragOver}
onDrop={(e) => handleDrop(e, status)}
onDoubleClick={() => openQuickAdd(status)}
title="Double-cliquez dans le vide pour ajouter une carte ici"
>
{/* Column Header */}
<div className={`p-4 border-b border-indigo-200 flex justify-between items-center ${
status === 'todo' ? 'bg-[#eef2ff]' :
status === 'progress' ? 'bg-indigo-50' :
'bg-green-50'
}`}>
<div className="flex items-center gap-2 font-bold text-slate-700">
<Icon size={18} />
{title}
</div>
<div className="flex items-center gap-2">
<button
onClick={(e) => { e.stopPropagation(); openQuickAdd(status); }}
className="p-1 hover:bg-white rounded-full text-slate-400 hover:text-blue-600 transition-colors"
>
<Plus size={16} />
</button>
<span className="text-xs font-semibold bg-white px-2 py-1 rounded-full border border-indigo-100 text-slate-500">
{columnIdeas.length}
</span>
</div>
</div>
<div className="flex flex-col h-full bg-theme-bg p-6 gap-6 relative transition-colors duration-300 -m-8">
{/* Column Body */}
<div className="flex-1 overflow-y-auto p-3 space-y-3 custom-scrollbar">
{columnIdeas.map(idea => {
const truncatedDesc = idea.description.length > 300
? idea.description.substring(0, 300) + '...'
: idea.description;
{/* Header & Add Form (Top Bar) */}
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 bg-theme-panel p-4 rounded-xl border border-theme-border shadow-sm shrink-0 transition-colors duration-300">
<div>
<h2 className="text-2xl font-bold text-theme-text flex items-center gap-2">
<Lightbulb className="text-yellow-500" /> Boîte à Idées
</h2>
<p className="text-theme-muted text-sm">Organisez vos tâches, idées de scènes et recherches.</p>
</div>
return (
<div
key={idea.id}
draggable
onDragStart={(e) => handleDragStart(e, idea.id)}
onDoubleClick={(e) => {
e.stopPropagation(); // Prevent column double-click
openEdit(idea);
}}
className="bg-white p-3 rounded-lg shadow-sm border border-slate-200 cursor-grab active:cursor-grabbing hover:shadow-md hover:border-blue-300 transition-all group relative animate-in zoom-in-95 duration-200"
>
<div className="flex justify-between items-start mb-2">
<span className={`text-[10px] uppercase font-bold px-2 py-0.5 rounded-full flex items-center gap-1 ${CATEGORIES[idea.category].color}`}>
{CATEGORIES[idea.category].label}
</span>
<div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
<button
onClick={(e) => { e.stopPropagation(); openEdit(idea); }}
className="text-slate-300 hover:text-blue-500"
>
<Edit3 size={14} />
<form onSubmit={handleAddIdea} className="flex-1 w-full md:w-auto max-w-2xl flex gap-2">
<select
value={newIdeaCategory}
onChange={(e) => setNewIdeaCategory(e.target.value as IdeaCategory)}
className="bg-theme-bg border border-theme-border text-theme-text text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 p-2.5 outline-none transition-colors duration-300"
>
{Object.entries(CATEGORIES).map(([key, val]) => (
<option key={key} value={key}>{val.label}</option>
))}
</select>
<input
type="text"
value={newIdeaTitle}
onChange={(e) => setNewIdeaTitle(e.target.value)}
placeholder="Titre de la nouvelle idée..."
className="flex-1 bg-theme-bg border border-theme-border text-theme-text text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 p-2.5 outline-none font-medium transition-colors duration-300"
/>
<button
type="submit"
disabled={!newIdeaTitle.trim()}
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 disabled:opacity-50 transition-colors flex items-center gap-2"
>
<Plus size={18} />
</button>
</form>
</div>
{/* Kanban Board */}
<div className="flex-1 grid grid-cols-1 md:grid-cols-3 gap-6 min-h-0">
<Column title="Idées / À faire" status="todo" icon={Circle} />
<Column title="En cours" status="progress" icon={Clock} />
<Column title="Terminé" status="done" icon={CheckCircle} />
</div>
{/* EDIT / QUICK ADD MODAL */}
{editingItem && (
<div className="absolute inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm p-4 animate-in fade-in duration-200">
<div className="bg-theme-panel rounded-xl shadow-2xl w-full max-w-lg overflow-hidden flex flex-col max-h-[90%] transition-colors duration-300 border border-theme-border">
<div className="bg-theme-bg border-b border-theme-border p-4 flex justify-between items-center">
<h3 className="font-bold text-theme-text flex items-center gap-2">
{editingItem.id ? <Edit3 size={18} /> : <Plus size={18} />}
{editingItem.id ? 'Éditer la carte' : 'Ajouter une carte'}
</h3>
<button onClick={() => setEditingItem(null)} className="text-theme-muted hover:text-theme-text">
<X size={20} />
</button>
<button
onClick={(e) => { e.stopPropagation(); handleDelete(idea.id); }}
className="text-slate-300 hover:text-red-500"
</div>
<div className="p-6 space-y-4 overflow-y-auto">
<div>
<label className="block text-xs font-bold text-theme-muted uppercase mb-1">Titre</label>
<input
type="text"
value={editingItem.title}
onChange={(e) => setEditingItem({ ...editingItem, title: e.target.value })}
className="w-full p-3 bg-theme-bg border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none font-bold text-theme-text transition-colors duration-300"
placeholder="Titre de la tâche ou de l'idée..."
autoFocus
/>
</div>
<div>
<label className="block text-xs font-bold text-theme-muted uppercase mb-1">Description</label>
<textarea
value={editingItem.description}
onChange={(e) => setEditingItem({ ...editingItem, description: e.target.value })}
maxLength={MAX_DESCRIPTION_LENGTH}
className="w-full p-3 bg-theme-bg border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none min-h-[120px] text-sm text-theme-text leading-relaxed resize-none transition-colors duration-300"
placeholder="Détails, notes, liens..."
/>
<div className={`text-right text-xs mt-1 transition-colors ${(editingItem.description?.length || 0) >= MAX_DESCRIPTION_LENGTH ? 'text-red-500 font-bold' :
(editingItem.description?.length || 0) > MAX_DESCRIPTION_LENGTH * 0.9 ? 'text-orange-500' : 'text-slate-400'
}`}>
{editingItem.description?.length || 0} / {MAX_DESCRIPTION_LENGTH} caractères
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-xs font-bold text-theme-muted uppercase mb-1">Catégorie</label>
<select
value={editingItem.category}
onChange={(e) => setEditingItem({ ...editingItem, category: e.target.value as IdeaCategory })}
className="w-full p-2 bg-theme-bg border border-theme-border rounded-lg text-theme-text text-sm outline-none focus:border-blue-500 transition-colors duration-300"
>
{Object.entries(CATEGORIES).map(([key, val]) => (
<option key={key} value={key}>{val.label}</option>
))}
</select>
</div>
<div>
<label className="block text-xs font-bold text-theme-muted uppercase mb-1">Statut</label>
<select
value={editingItem.status}
onChange={(e) => setEditingItem({ ...editingItem, status: e.target.value as IdeaStatus })}
className="w-full p-2 bg-theme-bg border border-theme-border rounded-lg text-theme-text text-sm outline-none focus:border-blue-500 transition-colors duration-300"
>
{Object.entries(STATUS_LABELS).map(([key, val]) => (
<option key={key} value={key}>{val}</option>
))}
</select>
</div>
</div>
</div>
<div className="p-4 border-t border-theme-border bg-theme-bg flex justify-end gap-2 shrink-0 transition-colors duration-300">
{editingItem.id && (
<button
onClick={() => handleDelete(editingItem.id!)}
className="mr-auto text-red-500 hover:text-red-700 text-sm font-medium px-3 py-2"
>
Supprimer
</button>
)}
<button
onClick={() => setEditingItem(null)}
className="px-4 py-2 text-theme-text hover:bg-theme-panel border border-transparent rounded-lg text-sm font-medium transition-colors duration-300"
>
<Trash2 size={14} />
Annuler
</button>
<button
onClick={handleSaveEdit}
disabled={!editingItem.title?.trim()}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm font-medium shadow-sm disabled:opacity-50 flex items-center gap-2"
>
<Save size={16} /> Enregistrer
</button>
</div>
</div>
{/* CARD CONTENT */}
<div className="mb-2">
<h4 className="font-bold text-slate-800 text-sm mb-1 leading-tight">{idea.title}</h4>
{idea.description && (
<p className="text-xs text-slate-500 line-clamp-3 leading-relaxed" title={idea.description.length > 300 ? "Description tronquée (voir détail)" : undefined}>
{truncatedDesc}
</p>
)}
</div>
<div className="flex justify-between items-center text-xs text-slate-400 border-t border-slate-50 pt-2 mt-2">
<span className="flex items-center gap-1">
<Clock size={10} /> {new Date(idea.createdAt).toLocaleDateString()}
</span>
<GripVertical size={14} className="opacity-20" />
</div>
</div>
);
})}
{columnIdeas.length === 0 && (
<div className="h-full flex flex-col items-center justify-center text-slate-300 text-sm italic border-2 border-dashed border-indigo-200 rounded-lg m-1">
<span className="mb-2">Vide</span>
<span className="text-xs opacity-70">Double-cliquez pour ajouter</span>
</div>
)}
)}
</div>
</div>
);
};
return (
<div className="flex flex-col h-full bg-white p-6 gap-6 relative">
{/* Header & Add Form (Top Bar) */}
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 bg-white p-4 rounded-xl border border-slate-200 shadow-sm shrink-0">
<div>
<h2 className="text-2xl font-bold text-slate-800 flex items-center gap-2">
<Lightbulb className="text-yellow-500" /> Boîte à Idées
</h2>
<p className="text-slate-500 text-sm">Organisez vos tâches, idées de scènes et recherches.</p>
</div>
<form onSubmit={handleAddIdea} className="flex-1 w-full md:w-auto max-w-2xl flex gap-2">
<select
value={newIdeaCategory}
onChange={(e) => setNewIdeaCategory(e.target.value as IdeaCategory)}
className="bg-[#eef2ff] border border-indigo-200 text-slate-700 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 p-2.5 outline-none"
>
{Object.entries(CATEGORIES).map(([key, val]) => (
<option key={key} value={key}>{val.label}</option>
))}
</select>
<input
type="text"
value={newIdeaTitle}
onChange={(e) => setNewIdeaTitle(e.target.value)}
placeholder="Titre de la nouvelle idée..."
className="flex-1 bg-[#eef2ff] border border-indigo-200 text-slate-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 p-2.5 outline-none font-medium"
/>
<button
type="submit"
disabled={!newIdeaTitle.trim()}
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 disabled:opacity-50 transition-colors flex items-center gap-2"
>
<Plus size={18} />
</button>
</form>
</div>
{/* Kanban Board */}
<div className="flex-1 grid grid-cols-1 md:grid-cols-3 gap-6 min-h-0">
<Column title="Idées / À faire" status="todo" icon={Circle} />
<Column title="En cours" status="progress" icon={Clock} />
<Column title="Terminé" status="done" icon={CheckCircle} />
</div>
{/* EDIT / QUICK ADD MODAL */}
{editingItem && (
<div className="absolute inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm p-4 animate-in fade-in duration-200">
<div className="bg-white rounded-xl shadow-2xl w-full max-w-lg overflow-hidden flex flex-col max-h-[90%]">
<div className="bg-[#eef2ff] border-b border-indigo-100 p-4 flex justify-between items-center">
<h3 className="font-bold text-slate-700 flex items-center gap-2">
{editingItem.id ? <Edit3 size={18}/> : <Plus size={18}/>}
{editingItem.id ? 'Éditer la carte' : 'Ajouter une carte'}
</h3>
<button onClick={() => setEditingItem(null)} className="text-slate-400 hover:text-slate-600">
<X size={20} />
</button>
</div>
<div className="p-6 space-y-4 overflow-y-auto">
<div>
<label className="block text-xs font-bold text-slate-500 uppercase mb-1">Titre</label>
<input
type="text"
value={editingItem.title}
onChange={(e) => setEditingItem({...editingItem, title: e.target.value})}
className="w-full p-3 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none font-bold text-slate-800"
placeholder="Titre de la tâche ou de l'idée..."
autoFocus
/>
</div>
<div>
<label className="block text-xs font-bold text-slate-500 uppercase mb-1">Description</label>
<textarea
value={editingItem.description}
onChange={(e) => setEditingItem({...editingItem, description: e.target.value})}
maxLength={MAX_DESCRIPTION_LENGTH}
className="w-full p-3 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none min-h-[120px] text-sm text-slate-600 leading-relaxed resize-none"
placeholder="Détails, notes, liens..."
/>
<div className={`text-right text-xs mt-1 transition-colors ${
(editingItem.description?.length || 0) >= MAX_DESCRIPTION_LENGTH ? 'text-red-500 font-bold' :
(editingItem.description?.length || 0) > MAX_DESCRIPTION_LENGTH * 0.9 ? 'text-orange-500' : 'text-slate-400'
}`}>
{editingItem.description?.length || 0} / {MAX_DESCRIPTION_LENGTH} caractères
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-xs font-bold text-slate-500 uppercase mb-1">Catégorie</label>
<select
value={editingItem.category}
onChange={(e) => setEditingItem({...editingItem, category: e.target.value as IdeaCategory})}
className="w-full p-2 bg-white border border-slate-300 rounded-lg text-sm outline-none focus:border-blue-500"
>
{Object.entries(CATEGORIES).map(([key, val]) => (
<option key={key} value={key}>{val.label}</option>
))}
</select>
</div>
<div>
<label className="block text-xs font-bold text-slate-500 uppercase mb-1">Statut</label>
<select
value={editingItem.status}
onChange={(e) => setEditingItem({...editingItem, status: e.target.value as IdeaStatus})}
className="w-full p-2 bg-white border border-slate-300 rounded-lg text-sm outline-none focus:border-blue-500"
>
{Object.entries(STATUS_LABELS).map(([key, val]) => (
<option key={key} value={key}>{val}</option>
))}
</select>
</div>
</div>
</div>
<div className="p-4 border-t border-slate-200 bg-[#eef2ff] flex justify-end gap-2 shrink-0">
{editingItem.id && (
<button
onClick={() => handleDelete(editingItem.id!)}
className="mr-auto text-red-500 hover:text-red-700 text-sm font-medium px-3 py-2"
>
Supprimer
</button>
)}
<button
onClick={() => setEditingItem(null)}
className="px-4 py-2 text-slate-600 hover:bg-slate-200 rounded-lg text-sm font-medium"
>
Annuler
</button>
<button
onClick={handleSaveEdit}
disabled={!editingItem.title?.trim()}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm font-medium shadow-sm disabled:opacity-50 flex items-center gap-2"
>
<Save size={16} /> Enregistrer
</button>
</div>
</div>
</div>
)}
</div>
);
};
export default IdeaBoard;

View File

@@ -315,7 +315,7 @@ const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorProps>(({
const hasSelection = savedRange.current && !savedRange.current.collapsed;
return (
<div className="flex flex-col h-full bg-white rounded-lg shadow-sm border border-slate-200 overflow-hidden relative">
<div className="flex flex-col h-full bg-theme-panel rounded-lg shadow-sm border border-theme-border overflow-hidden relative transition-colors duration-300">
<style>{`
.editor-content:empty::before {
content: attr(data-placeholder);
@@ -326,7 +326,7 @@ const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorProps>(({
`}</style>
{/* Toolbar */}
<div className="flex items-center gap-1 p-2 bg-slate-50 border-b border-slate-200 flex-wrap relative z-20 shadow-sm">
<div className="flex items-center gap-1 p-2 bg-theme-bg border-b border-theme-border flex-wrap relative z-20 shadow-sm transition-colors duration-300">
<ToolbarButton icon={Bold} cmd="bold" label="Gras" />
<ToolbarButton icon={Italic} cmd="italic" label="Italique" />
<ToolbarButton icon={Underline} cmd="underline" label="Souligné" />
@@ -360,7 +360,7 @@ const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorProps>(({
{/* Main Container - Scrollable Area */}
<div
className="flex-1 overflow-y-auto relative bg-slate-100"
className="flex-1 overflow-y-auto relative bg-theme-bg/50 transition-colors duration-300"
ref={scrollContainerRef}
>
<div className="flex justify-center relative min-h-full py-8">
@@ -370,7 +370,7 @@ const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorProps>(({
ref={contentRef}
contentEditable
suppressContentEditableWarning
className="bg-white shadow-sm w-[800px] min-h-[1000px] p-12 outline-none font-serif text-lg leading-relaxed text-slate-900 editor-content"
className="bg-theme-editor-bg shadow-sm w-[800px] min-h-[1000px] p-12 outline-none font-serif text-lg leading-relaxed text-theme-editor-text editor-content transition-colors duration-300"
onInput={handleInput}
onBlur={() => { setIsFocused(false); saveSelection(); }}
onFocus={() => setIsFocused(true)}
@@ -422,8 +422,8 @@ const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorProps>(({
<Layers size={14} className="text-indigo-500" />
)}
<span className={`text-[10px] font-bold px-1.5 py-0.5 rounded uppercase tracking-wide ${latest.type.includes('Correction') ? 'bg-green-100 text-green-700' :
latest.type.includes('Insertion') ? 'bg-blue-100 text-blue-700' :
'bg-purple-100 text-purple-700'
latest.type.includes('Insertion') ? 'bg-blue-100 text-blue-700' :
'bg-purple-100 text-purple-700'
}`}>
{latest.type}
</span>

View File

@@ -609,19 +609,19 @@ const StoryWorkflow: React.FC<StoryWorkflowProps> = ({ data, onUpdate, entities,
};
return (
<div className="h-full flex flex-col overflow-hidden bg-[#eef2ff] relative">
<div className="h-12 bg-white border-b border-indigo-100 flex items-center justify-between px-4 z-10 shadow-sm shrink-0">
<div className="h-full flex flex-col overflow-hidden bg-theme-bg relative transition-colors duration-300">
<div className="h-12 bg-theme-panel border-b border-theme-border flex items-center justify-between px-4 z-10 shadow-sm shrink-0 transition-colors duration-300">
<div className="flex items-center gap-2">
<button onClick={handleAddNodeCenter} className="flex items-center gap-1.5 px-3 py-1.5 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 text-xs font-bold transition-all shadow-md shadow-indigo-100">
<Plus size={14} /> AJOUTER NŒUD
</button>
<div className="w-px h-6 bg-slate-100 mx-2" />
<div className="text-[10px] uppercase font-bold text-slate-400 tracking-wider">
<div className="w-px h-6 bg-theme-border mx-2" />
<div className="text-[10px] uppercase font-bold text-theme-muted tracking-wider">
{selectedNodeIds.size > 0 ? `${selectedNodeIds.size} SÉLECTIONNÉ(S)` : 'Double-cliquez sur le canvas pour créer'}
</div>
</div>
<div className="flex items-center gap-2">
<button onClick={handleDeleteSelected} disabled={selectedNodeIds.size === 0} className="p-2 text-red-500 hover:bg-red-50 rounded-lg disabled:opacity-30 transition-colors" title="Supprimer">
<button onClick={handleDeleteSelected} disabled={selectedNodeIds.size === 0} className="p-2 text-red-500 hover:bg-red-500/10 rounded-lg disabled:opacity-30 transition-colors" title="Supprimer">
<Trash2 size={16} />
</button>
</div>
@@ -629,13 +629,13 @@ const StoryWorkflow: React.FC<StoryWorkflowProps> = ({ data, onUpdate, entities,
<div
ref={containerRef}
className="flex-1 overflow-auto relative cursor-grab active:cursor-grabbing bg-[#eef2ff]"
className="flex-1 overflow-auto relative cursor-grab active:cursor-grabbing bg-theme-bg canvas-grid transition-colors duration-300"
onMouseDown={handleCanvasMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onDoubleClick={handleCanvasDoubleClick}
style={{
backgroundImage: 'radial-gradient(#d1d5db 1px, transparent 1px)',
backgroundImage: 'radial-gradient(var(--theme-border) 1px, transparent 1px)',
backgroundSize: '24px 24px'
}}
>
@@ -649,7 +649,7 @@ const StoryWorkflow: React.FC<StoryWorkflowProps> = ({ data, onUpdate, entities,
const endX = target.x + CARD_WIDTH / 2;
const endY = target.y + CARD_HEIGHT / 2;
return (
<line key={conn.id} x1={startX} y1={startY} x2={endX} y2={endY} stroke="#cbd5e1" strokeWidth="2" markerEnd="url(#arrowhead)" />
<line key={conn.id} x1={startX} y1={startY} x2={endX} y2={endY} stroke="var(--theme-border)" strokeWidth="2" markerEnd="url(#arrowhead)" />
);
})}
{connectingNodeId && (
@@ -662,7 +662,7 @@ const StoryWorkflow: React.FC<StoryWorkflowProps> = ({ data, onUpdate, entities,
)}
<defs>
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="28" refY="3.5" orient="auto">
<path d="M0,0 L0,7 L10,3.5 Z" fill="#cbd5e1" />
<path d="M0,0 L0,7 L10,3.5 Z" fill="var(--theme-border)" />
</marker>
<marker id="arrowhead-blue" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
<path d="M0,0 L0,7 L10,3.5 Z" fill="#6366f1" />

View File

@@ -86,15 +86,26 @@ const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ user, onUpdat
alert("Profil mis à jour !");
};
const isDark = formData.theme === 'dark';
const isSepia = formData.theme === 'sepia';
const themeOuterClass = isDark ? 'bg-slate-900 text-white' : isSepia ? 'bg-[#eaddc4] text-[#433422]' : 'bg-slate-50 text-slate-900';
const themeInnerClass = isDark ? 'bg-slate-800 border-slate-700' : isSepia ? 'bg-[#f4ecd8] border-[#dfcdae]' : 'bg-white border-slate-200';
const themeTextHeading = isDark ? 'text-white' : isSepia ? 'text-[#332616]' : 'text-slate-900';
const themeTextMuted = isDark ? 'text-slate-400' : isSepia ? 'text-[#735e44]' : 'text-slate-500';
const themeInputBg = isDark ? 'bg-slate-900 border-slate-700 text-white' : isSepia ? 'bg-[#fbf8f1] border-[#eaddc4] text-[#433422]' : 'bg-slate-50 border-slate-200 text-slate-900';
const themeTabActive = isDark ? 'bg-white text-slate-900 shadow-lg' : isSepia ? 'bg-[#5c4731] text-white shadow-lg' : 'bg-slate-900 text-white shadow-lg';
const themeTabInactive = isDark ? 'text-slate-400 hover:bg-slate-800 hover:text-white' : isSepia ? 'text-[#735e44] hover:bg-[#eaddc4] hover:text-[#332616]' : 'text-slate-500 hover:bg-white hover:text-slate-900';
return (
<div className="h-full bg-slate-50 overflow-y-auto p-8 font-sans">
<div className={`h-screen overflow-y-auto p-8 font-sans ${themeOuterClass}`}>
<div className="max-w-4xl mx-auto">
<div className="flex justify-between items-center mb-10">
<div>
<h1 className="text-3xl font-black text-slate-900">Mon Compte</h1>
<p className="text-slate-500">Gérez vos informations personnelles et préférences d'écriture.</p>
<h1 className={`text-3xl font-black ${themeTextHeading}`}>Mon Compte</h1>
<p className={themeTextMuted}>Gérez vos informations personnelles et préférences d'écriture.</p>
</div>
<button onClick={onBack} className="bg-white border border-slate-200 px-4 py-2 rounded-lg text-sm font-bold hover:bg-slate-50 transition-colors">Fermer</button>
<button onClick={onBack} className={`${themeInnerClass} px-4 py-2 rounded-lg text-sm font-bold opacity-80 hover:opacity-100 transition-opacity`}>Fermer</button>
</div>
<div className="flex flex-col md:flex-row gap-8">
@@ -102,29 +113,29 @@ const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ user, onUpdat
<div className="w-full md:w-64 space-y-1">
<button
onClick={() => setActiveTab('profile')}
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-bold transition-all ${activeTab === 'profile' ? 'bg-slate-900 text-white shadow-lg' : 'text-slate-500 hover:bg-white hover:text-slate-900'}`}
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-bold transition-all ${activeTab === 'profile' ? themeTabActive : themeTabInactive}`}
>
<User size={18} /> Profil Public
</button>
<button
onClick={() => setActiveTab('preferences')}
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-bold transition-all ${activeTab === 'preferences' ? 'bg-slate-900 text-white shadow-lg' : 'text-slate-500 hover:bg-white hover:text-slate-900'}`}
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-bold transition-all ${activeTab === 'preferences' ? themeTabActive : themeTabInactive}`}
>
<Layout size={18} /> Interface & Écriture
</button>
<button
onClick={() => setActiveTab('account')}
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-bold transition-all ${activeTab === 'account' ? 'bg-slate-900 text-white shadow-lg' : 'text-slate-500 hover:bg-white hover:text-slate-900'}`}
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-bold transition-all ${activeTab === 'account' ? themeTabActive : themeTabInactive}`}
>
<Shield size={18} /> Sécurité & Plan
</button>
</div>
{/* Main Content Pane */}
<div className="flex-1 bg-white rounded-2xl shadow-sm border border-slate-200 p-8">
<div className={`flex-1 rounded-2xl shadow-sm border p-8 ${themeInnerClass}`}>
{activeTab === 'profile' && (
<div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-300">
<div className="flex items-center gap-6 pb-8 border-b border-slate-100">
<div className={`flex items-center gap-6 pb-8 border-b ${isDark ? 'border-slate-700' : isSepia ? 'border-[#dfcdae]' : 'border-slate-100'}`}>
<div className="relative group cursor-pointer" onClick={() => fileInputRef.current?.click()}>
<input
type="file"
@@ -133,14 +144,14 @@ const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ user, onUpdat
accept="image/*"
className="hidden"
/>
<img src={formData.avatar || 'https://via.placeholder.com/150'} className="w-24 h-24 rounded-full object-cover border-4 border-slate-50 shadow-md" alt="Avatar" />
<img src={formData.avatar || 'https://via.placeholder.com/150'} className={`w-24 h-24 rounded-full object-cover border-4 shadow-md ${isDark ? 'border-slate-800' : isSepia ? 'border-[#f4ecd8]' : 'border-slate-50'}`} alt="Avatar" />
<div className="absolute inset-0 bg-black/40 text-white rounded-full opacity-0 group-hover:opacity-100 flex items-center justify-center transition-opacity" title="Changer d'avatar">
<Camera size={20} />
</div>
</div>
<div>
<h3 className="font-bold text-slate-900 text-lg">{user.name}</h3>
<p className="text-slate-400 text-sm">Membre depuis Janvier 2024</p>
<h3 className={`font-bold text-lg ${themeTextHeading}`}>{user.name}</h3>
<p className={`text-sm ${themeTextMuted}`}>Membre depuis {new Date(user.subscription.startDate).toLocaleDateString('fr-FR', { month: 'long', year: 'numeric' })}</p>
<div className="mt-2 flex gap-4">
<div className="flex items-center gap-1.5 text-xs font-bold text-orange-500">
<Flame size={14} fill="currentColor" /> {user.stats.writingStreak} jours de streak
@@ -151,21 +162,21 @@ const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ user, onUpdat
<div className="grid grid-cols-1 gap-6">
<div className="space-y-1">
<label className="text-xs font-black text-slate-400 uppercase tracking-widest">Nom affiché</label>
<label className={`text-xs font-black uppercase tracking-widest ${themeTextMuted}`}>Nom affiché</label>
<input
type="text"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className="w-full p-3 bg-slate-50 border border-slate-200 rounded-xl outline-none focus:ring-2 focus:ring-blue-500"
className={`w-full p-3 rounded-xl outline-none focus:ring-2 focus:ring-blue-500 ${themeInputBg}`}
/>
</div>
<div className="space-y-1">
<label className="text-xs font-black text-slate-400 uppercase tracking-widest">Bio / Citation inspirante</label>
<label className={`text-xs font-black uppercase tracking-widest ${themeTextMuted}`}>Bio / Citation inspirante</label>
<textarea
value={formData.bio}
onChange={(e) => setFormData({ ...formData, bio: e.target.value })}
className="w-full p-3 bg-slate-50 border border-slate-200 rounded-xl outline-none focus:ring-2 focus:ring-blue-500 h-24 resize-none"
className={`w-full p-3 rounded-xl outline-none focus:ring-2 focus:ring-blue-500 h-24 resize-none ${themeInputBg}`}
placeholder="Partagez quelques mots sur votre style..."
/>
</div>
@@ -177,7 +188,7 @@ const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ user, onUpdat
<div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-300">
<div className="grid grid-cols-1 gap-8">
<div className="space-y-3">
<label className="text-xs font-black text-slate-400 uppercase tracking-widest flex items-center gap-2">
<label className={`text-xs font-black uppercase tracking-widest flex items-center gap-2 ${themeTextMuted}`}>
<Target size={14} /> Objectif quotidien de mots
</label>
<div className="flex items-center gap-4">
@@ -192,7 +203,7 @@ const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ user, onUpdat
</div>
<div className="space-y-3">
<label className="text-xs font-black text-slate-400 uppercase tracking-widest flex items-center gap-2">
<label className={`text-xs font-black uppercase tracking-widest flex items-center gap-2 ${themeTextMuted}`}>
Thème de l'éditeur
</label>
<div className="grid grid-cols-3 gap-3">
@@ -200,10 +211,10 @@ const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ user, onUpdat
<button
key={t}
onClick={() => setFormData({ ...formData, theme: t as any })}
className={`p-4 rounded-xl border-2 transition-all flex flex-col items-center gap-2 ${formData.theme === t ? 'border-blue-500 bg-blue-50 text-blue-700' : 'border-slate-100 hover:border-slate-200 text-slate-500'}`}
className={`p-4 rounded-xl border-2 transition-all flex flex-col items-center gap-2 ${formData.theme === t ? 'border-blue-500 bg-blue-50 text-blue-700' : isDark ? 'border-slate-700 hover:border-slate-600' : isSepia ? 'border-[#dfcdae] hover:border-[#cfbd9e]' : 'border-slate-100 hover:border-slate-200'}`}
>
<div className={`w-8 h-8 rounded-full border border-slate-200 ${t === 'light' ? 'bg-white' : t === 'sepia' ? 'bg-[#f4ecd8]' : 'bg-slate-900'}`} />
<span className="text-[10px] font-bold uppercase">{t}</span>
<span className={`text-[10px] font-bold uppercase ${formData.theme !== t ? themeTextMuted : ''}`}>{t}</span>
</button>
))}
</div>
@@ -217,18 +228,18 @@ const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ user, onUpdat
<div className="p-4 bg-blue-50 border border-blue-100 rounded-xl flex justify-between items-center">
<div>
<h4 className="font-bold text-blue-900">Plan {(user.subscription.planDetails?.displayName || user.subscription.plan).toUpperCase()}</h4>
<p className="text-xs text-blue-700">Prochaine facturation le 15 Mars 2024</p>
<p className="text-xs text-blue-700">Abonnement actif</p>
</div>
<button className="bg-blue-600 text-white px-4 py-2 rounded-lg text-xs font-bold hover:bg-blue-700 shadow-md shadow-blue-200">Gérer</button>
</div>
<div className="space-y-1">
<label className="text-xs font-black text-slate-400 uppercase tracking-widest">Email du compte</label>
<label className={`text-xs font-black uppercase tracking-widest ${themeTextMuted}`}>Email du compte</label>
<input
type="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
className="w-full p-3 bg-slate-50 border border-slate-200 rounded-xl outline-none focus:ring-2 focus:ring-blue-500"
className={`w-full p-3 rounded-xl outline-none focus:ring-2 focus:ring-blue-500 ${themeInputBg}`}
/>
</div>
@@ -238,10 +249,10 @@ const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ user, onUpdat
</div>
)}
<div className="mt-12 pt-8 border-t border-slate-100 flex justify-end">
<div className={`mt-12 pt-8 border-t flex justify-end ${isDark ? 'border-slate-700' : isSepia ? 'border-[#dfcdae]' : 'border-slate-100'}`}>
<button
onClick={handleSave}
className="bg-slate-900 text-white px-8 py-3 rounded-xl font-bold flex items-center gap-2 hover:bg-blue-600 transition-all shadow-xl hover:shadow-blue-200"
className={`px-8 py-3 rounded-xl font-bold flex items-center gap-2 transition-all shadow-xl hover:shadow-blue-200 ${isDark ? 'bg-white text-slate-900 hover:bg-blue-500 hover:text-white' : isSepia ? 'bg-[#5c4731] text-white hover:bg-blue-600' : 'bg-slate-900 text-white hover:bg-blue-600'}`}
>
<Save size={18} /> Sauvegarder les modifications
</button>

View File

@@ -204,22 +204,22 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
const attrs = tempEntity.attributes;
return (
<div className="space-y-8 border-t border-slate-100 pt-6 mt-4">
<div className="space-y-8 border-t border-theme-border pt-6 mt-4">
{/* SECTION 1: ROLE & ARCHETYPE */}
<div className="bg-[#eef2ff] p-4 rounded-lg border border-indigo-100">
<h3 className="text-sm font-bold text-slate-700 uppercase mb-4 flex items-center gap-2">
<div className="bg-theme-bg p-4 rounded-lg border border-theme-border">
<h3 className="text-sm font-bold text-theme-text uppercase mb-4 flex items-center gap-2">
<User size={16} /> Identité Narrative
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-xs font-semibold text-slate-500 mb-2">Archétype</label>
<label className="block text-xs font-semibold text-theme-muted mb-2">Archétype</label>
<input
type="text"
list="archetype-suggestions"
value={attrs.archetype}
onChange={(e) => updateAttribute('archetype', e.target.value)}
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded text-sm outline-none focus:border-blue-500"
className="w-full p-2 bg-theme-bg border border-theme-border rounded text-sm outline-none focus:border-blue-500"
placeholder="Ex: Le Héros, Le Sage..."
/>
<datalist id="archetype-suggestions">
@@ -227,7 +227,7 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
</datalist>
</div>
<div>
<label className="block text-xs font-semibold text-slate-500 mb-2">Rôle dans l'histoire</label>
<label className="block text-xs font-semibold text-theme-muted mb-2">Rôle dans l'histoire</label>
<div className="flex gap-2 flex-wrap">
{[
{ val: 'protagonist', label: 'Protagoniste' },
@@ -235,7 +235,7 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
{ val: 'support', label: 'Secondaire' },
{ val: 'extra', label: 'Figurant' }
].map(opt => (
<label key={opt.val} className={`cursor-pointer px-3 py-1.5 rounded text-xs border transition-colors ${attrs.role === opt.val ? 'bg-indigo-100 border-indigo-300 text-indigo-700 font-bold' : 'bg-[#eef2ff] border-slate-200 text-slate-600 hover:bg-slate-100'}`}>
<label key={opt.val} className={`cursor-pointer px-3 py-1.5 rounded text-xs border transition-colors ${attrs.role === opt.val ? 'bg-indigo-100 border-indigo-300 text-indigo-700 font-bold' : 'bg-theme-bg border-theme-border text-theme-muted hover:bg-theme-border'}`}>
<input
type="radio"
name="role"
@@ -253,15 +253,15 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
</div>
{/* SECTION 2: PHYSIQUE */}
<div className="bg-[#eef2ff] p-4 rounded-lg border border-indigo-100">
<h3 className="text-sm font-bold text-slate-700 uppercase mb-4 flex items-center gap-2">
<div className="bg-theme-bg p-4 rounded-lg border border-theme-border">
<h3 className="text-sm font-bold text-theme-text uppercase mb-4 flex items-center gap-2">
<Ruler size={16} /> Apparence Physique
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-6">
<div>
<div className="flex justify-between text-xs mb-1">
<label className="font-semibold text-slate-600">Âge (ans)</label>
<label className="font-semibold text-theme-muted">Âge (ans)</label>
</div>
<div className="flex items-center gap-3">
<input
@@ -274,14 +274,14 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
type="number"
value={attrs.age}
onChange={(e) => updateAttribute('age', parseInt(e.target.value))}
className="w-20 p-1 text-right text-sm border border-slate-300 rounded font-mono text-indigo-700 bg-[#eef2ff] focus:border-indigo-500 outline-none"
className="w-20 p-1 text-right text-sm border border-theme-border rounded font-mono text-indigo-700 bg-theme-bg focus:border-indigo-500 outline-none"
/>
</div>
</div>
<div>
<div className="flex justify-between text-xs mb-1">
<label className="font-semibold text-slate-600">Taille (cm)</label>
<label className="font-semibold text-theme-muted">Taille (cm)</label>
</div>
<div className="flex items-center gap-3">
<input
@@ -294,7 +294,7 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
type="number"
value={attrs.height}
onChange={(e) => updateAttribute('height', parseInt(e.target.value))}
className="w-20 p-1 text-right text-sm border border-slate-300 rounded font-mono text-indigo-700 bg-[#eef2ff] focus:border-indigo-500 outline-none"
className="w-20 p-1 text-right text-sm border border-theme-border rounded font-mono text-indigo-700 bg-theme-bg focus:border-indigo-500 outline-none"
/>
</div>
</div>
@@ -303,34 +303,34 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-xs font-semibold text-slate-500 mb-1">Cheveux</label>
<label className="block text-xs font-semibold text-theme-muted mb-1">Cheveux</label>
<select
value={attrs.hair}
onChange={(e) => updateAttribute('hair', e.target.value)}
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded text-sm"
className="w-full p-2 bg-theme-bg border border-theme-border rounded text-sm"
>
{HAIR_COLORS.map(c => <option key={c} value={c}>{c}</option>)}
</select>
</div>
<div>
<label className="block text-xs font-semibold text-slate-500 mb-1">Yeux</label>
<label className="block text-xs font-semibold text-theme-muted mb-1">Yeux</label>
<select
value={attrs.eyes}
onChange={(e) => updateAttribute('eyes', e.target.value)}
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded text-sm"
className="w-full p-2 bg-theme-bg border border-theme-border rounded text-sm"
>
{EYE_COLORS.map(c => <option key={c} value={c}>{c}</option>)}
</select>
</div>
</div>
<div>
<label className="block text-xs font-semibold text-slate-500 mb-1">Signe distinctif</label>
<label className="block text-xs font-semibold text-theme-muted mb-1">Signe distinctif</label>
<input
type="text"
value={attrs.physicalQuirk}
onChange={(e) => updateAttribute('physicalQuirk', e.target.value)}
placeholder="Cicatrice, tatouage..."
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded text-sm outline-none focus:border-indigo-400"
className="w-full p-2 bg-theme-bg border border-theme-border rounded text-sm outline-none focus:border-indigo-400"
/>
</div>
</div>
@@ -338,15 +338,15 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
</div>
{/* SECTION 3: PSYCHOLOGIE */}
<div className="bg-[#eef2ff] p-4 rounded-lg border border-indigo-100">
<h3 className="text-sm font-bold text-slate-700 uppercase mb-4 flex items-center gap-2">
<div className="bg-theme-bg p-4 rounded-lg border border-theme-border">
<h3 className="text-sm font-bold text-theme-text uppercase mb-4 flex items-center gap-2">
<Brain size={16} /> Psychologie & Comportement
</h3>
<div className="space-y-6">
<div className="space-y-4 px-2">
<div className="relative pt-1">
<div className="flex justify-between text-[10px] uppercase font-bold text-slate-500 mb-1">
<div className="flex justify-between text-[10px] uppercase font-bold text-theme-muted mb-1">
<span>Introverti</span>
<span>Extraverti</span>
</div>
@@ -358,7 +358,7 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
/>
</div>
<div className="relative pt-1">
<div className="flex justify-between text-[10px] uppercase font-bold text-slate-500 mb-1">
<div className="flex justify-between text-[10px] uppercase font-bold text-theme-muted mb-1">
<span>Émotionnel</span>
<span>Rationnel</span>
</div>
@@ -370,7 +370,7 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
/>
</div>
<div className="relative pt-1">
<div className="flex justify-between text-[10px] uppercase font-bold text-slate-500 mb-1">
<div className="flex justify-between text-[10px] uppercase font-bold text-theme-muted mb-1">
<span>Chaotique</span>
<span>Loyal</span>
</div>
@@ -383,14 +383,14 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
</div>
</div>
<div className="border-t border-slate-200 pt-4">
<label className="block text-xs font-semibold text-slate-500 mb-1">Toc ou habitude comportementale</label>
<div className="border-t border-theme-border pt-4">
<label className="block text-xs font-semibold text-theme-muted mb-1">Toc ou habitude comportementale</label>
<input
type="text"
value={attrs.behavioralQuirk}
onChange={(e) => updateAttribute('behavioralQuirk', e.target.value)}
placeholder="Joue avec sa bague, bégaie quand il ment..."
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded text-sm outline-none focus:border-indigo-400"
className="w-full p-2 bg-theme-bg border border-theme-border rounded text-sm outline-none focus:border-indigo-400"
/>
</div>
</div>
@@ -404,8 +404,8 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
if (!currentTemplate || currentTemplate.fields.length === 0) return null;
return (
<div className="bg-[#eef2ff] p-4 rounded-lg border border-indigo-100 mt-6">
<h3 className="text-sm font-bold text-slate-700 uppercase mb-4 flex items-center gap-2">
<div className="bg-theme-bg p-4 rounded-lg border border-theme-border mt-6">
<h3 className="text-sm font-bold text-theme-text uppercase mb-4 flex items-center gap-2">
<List size={16} /> Champs Personnalisés
</h3>
<div className="grid grid-cols-1 gap-4">
@@ -414,20 +414,20 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
return (
<div key={field.id}>
<label className="block text-xs font-semibold text-slate-500 mb-1">{field.label}</label>
<label className="block text-xs font-semibold text-theme-muted mb-1">{field.label}</label>
{field.type === 'textarea' ? (
<textarea
value={value}
onChange={(e) => updateCustomValue(field.id, e.target.value)}
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded text-sm outline-none focus:border-indigo-400"
className="w-full p-2 bg-theme-bg border border-theme-border rounded text-sm outline-none focus:border-indigo-400"
placeholder={field.placeholder}
/>
) : field.type === 'select' ? (
<select
value={value}
onChange={(e) => updateCustomValue(field.id, e.target.value)}
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded text-sm outline-none focus:border-indigo-400"
className="w-full p-2 bg-theme-bg border border-theme-border rounded text-sm outline-none focus:border-indigo-400"
>
<option value="">Sélectionner...</option>
{field.options?.map(opt => (
@@ -440,16 +440,16 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
type="checkbox"
checked={!!value}
onChange={(e) => updateCustomValue(field.id, e.target.checked)}
className="w-4 h-4 text-indigo-600 rounded border-slate-300 focus:ring-indigo-500"
className="w-4 h-4 text-indigo-600 rounded border-theme-border focus:ring-indigo-500"
/>
<span className="text-sm text-slate-700">Activé / Oui</span>
<span className="text-sm text-theme-text">Activé / Oui</span>
</label>
) : (
<input
type={field.type === 'number' ? 'number' : 'text'}
value={value}
onChange={(e) => updateCustomValue(field.id, e.target.value)}
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded text-sm outline-none focus:border-indigo-400"
className="w-full p-2 bg-theme-bg border border-theme-border rounded text-sm outline-none focus:border-indigo-400"
placeholder={field.placeholder}
/>
)}
@@ -465,29 +465,29 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
const template = templates.find(t => t.entityType === activeTemplateType) || { entityType: activeTemplateType, fields: [] };
return (
<div className="flex-1 bg-white rounded-xl shadow-lg border border-slate-200 p-8 overflow-y-auto">
<div className="flex-1 bg-theme-panel rounded-xl shadow-lg border border-theme-border p-8 overflow-y-auto">
<div className="flex justify-between items-start mb-6">
<div>
<h2 className="text-2xl font-bold text-slate-800 flex items-center gap-2">
<h2 className="text-2xl font-bold text-theme-text flex items-center gap-2">
<Layout size={24} className="text-indigo-600" /> Éditeur de Modèles
</h2>
<p className="text-slate-500 text-sm mt-1">
<p className="text-theme-muted text-sm mt-1">
Configurez les champs personnalisés pour chaque type de fiche.
</p>
</div>
<button onClick={() => setMode('entities')} className="p-2 text-slate-500 hover:bg-slate-100 rounded-full">
<button onClick={() => setMode('entities')} className="p-2 text-theme-muted hover:bg-theme-border rounded-full">
<X size={20} />
</button>
</div>
<div className="flex gap-2 mb-8 border-b border-slate-200 pb-1">
<div className="flex gap-2 mb-8 border-b border-theme-border pb-1">
{Object.values(EntityType).map(type => (
<button
key={type}
onClick={() => setActiveTemplateType(type)}
className={`px-4 py-2 text-sm font-medium rounded-t-lg transition-colors ${activeTemplateType === type
? 'bg-indigo-50 text-indigo-700 border-b-2 border-indigo-600'
: 'text-slate-500 hover:text-slate-800 hover:bg-slate-50'
? 'bg-indigo-500/10 text-indigo-700 border-b-2 border-indigo-600'
: 'text-theme-muted hover:text-theme-text hover:bg-theme-panel/50'
}`}
>
{type}
@@ -497,23 +497,23 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
<div className="space-y-4">
{template.fields.map((field, idx) => (
<div key={field.id} className="bg-[#eef2ff] border border-indigo-100 rounded-lg p-4 flex gap-4 items-start group">
<div key={field.id} className="bg-theme-bg border border-theme-border rounded-lg p-4 flex gap-4 items-start group">
<div className="flex-1 grid grid-cols-2 gap-4">
<div>
<label className="block text-xs font-semibold text-slate-500 mb-1">Nom du champ</label>
<label className="block text-xs font-semibold text-theme-muted mb-1">Nom du champ</label>
<input
type="text"
value={field.label}
onChange={(e) => updateCustomField(activeTemplateType, field.id, { label: e.target.value })}
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded text-sm"
className="w-full p-2 bg-theme-bg border border-theme-border rounded text-sm"
/>
</div>
<div>
<label className="block text-xs font-semibold text-slate-500 mb-1">Type</label>
<label className="block text-xs font-semibold text-theme-muted mb-1">Type</label>
<select
value={field.type}
onChange={(e) => updateCustomField(activeTemplateType, field.id, { type: e.target.value as CustomFieldType })}
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded text-sm"
className="w-full p-2 bg-theme-bg border border-theme-border rounded text-sm"
>
<option value="text">Texte court</option>
<option value="textarea">Texte long</option>
@@ -524,12 +524,12 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
</div>
{field.type === 'select' && (
<div className="col-span-2">
<label className="block text-xs font-semibold text-slate-500 mb-1">Options (séparées par des virgules)</label>
<label className="block text-xs font-semibold text-theme-muted mb-1">Options (séparées par des virgules)</label>
<input
type="text"
value={field.options?.join(',') || ''}
onChange={(e) => updateCustomField(activeTemplateType, field.id, { options: e.target.value.split(',').map(s => s.trim()) })}
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded text-sm"
className="w-full p-2 bg-theme-bg border border-theme-border rounded text-sm"
placeholder="Option A, Option B, Option C"
/>
</div>
@@ -537,7 +537,7 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
</div>
<button
onClick={() => deleteCustomField(activeTemplateType, field.id)}
className="p-2 text-slate-400 hover:text-red-600 hover:bg-red-50 rounded mt-5"
className="p-2 text-theme-muted hover:text-red-600 hover:bg-red-50 rounded mt-5"
>
<Trash2 size={16} />
</button>
@@ -546,7 +546,7 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
<button
onClick={() => addCustomField(activeTemplateType)}
className="w-full py-3 border-2 border-dashed border-slate-300 rounded-lg text-slate-500 hover:border-indigo-400 hover:text-indigo-600 hover:bg-indigo-50 transition-all flex items-center justify-center gap-2"
className="w-full py-3 border-2 border-dashed border-theme-border rounded-lg text-theme-muted hover:border-indigo-400 hover:text-indigo-600 hover:bg-indigo-500/10 transition-all flex items-center justify-center gap-2"
>
<Plus size={20} /> Ajouter un champ
</button>
@@ -557,14 +557,14 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
if (mode === 'templates') {
return (
<div className="flex h-full gap-6 p-6 bg-[#eef2ff]">
<div className="flex h-full gap-6 p-6 bg-theme-bg">
<div className="w-1/3 opacity-50 pointer-events-none filter blur-[1px]">
<div className="bg-white rounded-lg p-6 shadow-sm border border-slate-200">
<h3 className="font-bold text-slate-700 mb-4">Aperçu Fiches</h3>
<div className="bg-theme-panel rounded-lg p-6 shadow-sm border border-theme-border">
<h3 className="font-bold text-theme-text mb-4">Aperçu Fiches</h3>
<div className="space-y-2">
<div className="h-10 bg-indigo-50 rounded"></div>
<div className="h-10 bg-indigo-50 rounded"></div>
<div className="h-10 bg-indigo-50 rounded"></div>
<div className="h-10 bg-indigo-500/10 rounded"></div>
<div className="h-10 bg-indigo-500/10 rounded"></div>
<div className="h-10 bg-indigo-500/10 rounded"></div>
</div>
</div>
</div>
@@ -574,10 +574,10 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
}
return (
<div className="flex h-full gap-6 p-6 bg-[#eef2ff]">
<div className="flex h-full gap-6 p-6 bg-theme-bg">
<div className="w-1/3 flex flex-col gap-4">
<div className="flex justify-between items-center px-1">
<h2 className="text-lg font-bold text-slate-700">Explorateur</h2>
<h2 className="text-lg font-bold text-theme-text">Explorateur</h2>
<button
onClick={() => setMode('templates')}
className="flex items-center gap-1.5 px-3 py-1.5 bg-indigo-100 text-indigo-700 hover:bg-indigo-200 rounded text-xs font-medium transition-colors"
@@ -589,9 +589,9 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
<div className="space-y-6 overflow-y-auto pr-2 pb-4 flex-1">
{Object.values(EntityType).map(type => (
<div key={type} className="bg-white rounded-lg shadow-sm border border-slate-200 overflow-hidden">
<div className="bg-indigo-50 p-3 border-b border-indigo-100 flex justify-between items-center">
<h3 className="font-semibold text-slate-700 flex items-center gap-2">
<div key={type} className="bg-theme-panel rounded-lg shadow-sm border border-theme-border overflow-hidden">
<div className="bg-indigo-500/10 p-3 border-b border-theme-border flex justify-between items-center">
<h3 className="font-semibold text-theme-text flex items-center gap-2">
<span>{ENTITY_ICONS[type]}</span> {type}s
</h3>
<button
@@ -603,17 +603,17 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
</div>
<div className="divide-y divide-slate-100">
{filterByType(type).length === 0 && (
<p className="p-4 text-sm text-slate-400 italic text-center">Aucun élément</p>
<p className="p-4 text-sm text-theme-muted italic text-center">Aucun élément</p>
)}
{filterByType(type).map(entity => (
<div
key={entity.id}
onClick={() => handleEdit(entity)}
className={`p-3 cursor-pointer hover:bg-blue-50 transition-colors flex justify-between group ${editingId === entity.id ? 'bg-blue-50 border-l-4 border-blue-500' : ''}`}
className={`p-3 cursor-pointer hover:bg-blue-500/10 transition-colors flex justify-between group ${editingId === entity.id ? 'bg-blue-500/10 border-l-4 border-blue-500' : ''}`}
>
<div>
<div className="font-medium text-slate-800">{entity.name}</div>
<div className="text-xs text-slate-500 truncate">{entity.description}</div>
<div className="font-medium text-theme-text">{entity.name}</div>
<div className="text-xs text-theme-muted truncate">{entity.description}</div>
</div>
<button
onClick={(e) => { e.stopPropagation(); handleDelete(entity.id); }}
@@ -629,7 +629,7 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
</div>
</div>
<div className="flex-1 bg-white rounded-xl shadow-lg border border-slate-200 p-8 overflow-y-auto">
<div className="flex-1 bg-theme-panel rounded-xl shadow-lg border border-theme-border p-8 overflow-y-auto">
{editingId && tempEntity ? (
<div className="space-y-6 animate-in fade-in duration-200">
<div className="flex justify-between items-start">
@@ -637,12 +637,12 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
<span className={`inline-block px-2 py-1 rounded text-xs font-bold uppercase tracking-wider ${ENTITY_COLORS[tempEntity.type]}`}>
{tempEntity.type}
</span>
<h2 className="text-2xl font-bold text-slate-800">
<h2 className="text-2xl font-bold text-theme-text">
{tempEntity.type === EntityType.CHARACTER ? 'Fiche Personnage' : 'Édition de la fiche'}
</h2>
</div>
<div className="flex gap-2">
<button onClick={() => setEditingId(null)} className="p-2 text-slate-500 hover:bg-slate-100 rounded-full">
<button onClick={() => setEditingId(null)} className="p-2 text-theme-muted hover:bg-theme-border rounded-full">
<X size={20} />
</button>
</div>
@@ -650,22 +650,22 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">Nom</label>
<label className="block text-sm font-medium text-theme-text mb-1">Nom</label>
<input
type="text"
value={tempEntity.name}
onChange={e => setTempEntity({ ...tempEntity, name: e.target.value })}
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded focus:ring-2 focus:ring-blue-500 outline-none font-serif text-lg"
className="w-full p-2 bg-theme-bg border border-theme-border rounded focus:ring-2 focus:ring-blue-500 outline-none font-serif text-lg"
placeholder="Ex: Gandalf le Gris"
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">Description Courte (pour l'IA)</label>
<label className="block text-sm font-medium text-theme-text mb-1">Description Courte (pour l'IA)</label>
<textarea
value={tempEntity.description}
onChange={e => setTempEntity({ ...tempEntity, description: e.target.value })}
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded focus:ring-2 focus:ring-blue-500 outline-none text-sm h-20"
className="w-full p-2 bg-theme-bg border border-theme-border rounded focus:ring-2 focus:ring-blue-500 outline-none text-sm h-20"
placeholder="Un magicien puissant qui guide la communauté..."
/>
</div>
@@ -674,7 +674,7 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
{renderCustomFieldsEditor()}
<div className="mt-6 border-t border-slate-100 pt-6">
<div className="mt-6 border-t border-theme-border pt-6">
<div>
<label className="block text-sm font-medium text-indigo-700 mb-1 flex items-center gap-2">
<Sparkles size={14} /> Contexte Narratif (Auto-généré)
@@ -682,17 +682,17 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
<textarea
value={tempEntity.storyContext || ''}
onChange={e => setTempEntity({ ...tempEntity, storyContext: e.target.value })}
className="w-full p-2 border border-indigo-200 bg-indigo-50 rounded focus:ring-2 focus:ring-blue-500 outline-none text-sm h-24 italic text-slate-600"
className="w-full p-2 border border-indigo-200 bg-indigo-500/10 rounded focus:ring-2 focus:ring-blue-500 outline-none text-sm h-24 italic text-theme-muted"
placeholder="Les événements vécus par ce personnage apparaîtront ici..."
/>
</div>
<div className="mt-4">
<label className="block text-sm font-medium text-slate-700 mb-1">Notes & Biographie Complète</label>
<label className="block text-sm font-medium text-theme-text mb-1">Notes & Biographie Complète</label>
<textarea
value={tempEntity.details}
onChange={e => setTempEntity({ ...tempEntity, details: e.target.value })}
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded focus:ring-2 focus:ring-blue-500 outline-none h-48 font-serif"
className="w-full p-2 bg-theme-bg border border-theme-border rounded focus:ring-2 focus:ring-blue-500 outline-none h-48 font-serif"
placeholder="Histoire détaillée, secrets, origines..."
/>
</div>
@@ -710,7 +710,7 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
</div>
</div>
) : (
<div className="h-full flex flex-col items-center justify-center text-slate-400">
<div className="h-full flex flex-col items-center justify-center text-theme-muted">
<div className="text-6xl mb-4 opacity-20">🌍</div>
<p className="text-lg">Sélectionnez ou créez une fiche pour commencer.</p>
<p className="text-sm">Ces informations aideront l'IA à rester cohérente.</p>

View File

@@ -34,7 +34,7 @@ const EditorShell: React.FC<EditorShellProps> = (props) => {
const currentChapter = project.chapters.find(c => c.id === currentChapterId);
return (
<div className={`flex h-screen overflow-hidden no-print ${user.preferences.theme === 'dark' ? 'bg-slate-900 text-white' : user.preferences.theme === 'sepia' ? 'bg-[#f4ecd8]' : 'bg-[#eef2ff]'}`}>
<div className="flex h-screen overflow-hidden no-print bg-theme-bg text-theme-text transition-colors duration-300">
{/* SIDEBAR */}
<aside className={`${isSidebarOpen ? 'w-64' : 'w-0'} bg-slate-900 text-slate-300 flex-shrink-0 transition-all duration-300 overflow-hidden flex flex-col border-r border-slate-800`}>
@@ -95,9 +95,9 @@ const EditorShell: React.FC<EditorShellProps> = (props) => {
{/* MAIN CONTENT */}
<div className="flex-1 flex flex-col h-full overflow-hidden">
<header className="h-14 bg-white border-b border-slate-200 flex items-center justify-between px-4 shadow-sm z-10 text-slate-800">
<header className="h-14 bg-theme-panel border-b border-theme-border flex items-center justify-between px-4 shadow-sm z-10 text-theme-text transition-colors duration-300">
<div className="flex items-center gap-4">
<button onClick={() => setIsSidebarOpen(!isSidebarOpen)} className="text-slate-500 hover:text-slate-800"><Menu size={20} /></button>
<button onClick={() => setIsSidebarOpen(!isSidebarOpen)} className="text-theme-muted hover:text-theme-text"><Menu size={20} /></button>
{viewMode === 'write' ? (
<input
type="text"
@@ -124,7 +124,7 @@ const EditorShell: React.FC<EditorShellProps> = (props) => {
</div>
{/* AI PANEL */}
<div className={`${isAiPanelOpen ? 'w-80 lg:w-96' : 'w-0'} transition-all duration-300 flex-shrink-0 h-full border-l border-slate-200 relative`}>
<div className={`${isAiPanelOpen ? 'w-80 lg:w-96' : 'w-0'} transition-all duration-300 flex-shrink-0 h-full border-l border-theme-border relative bg-theme-panel`}>
{isAiPanelOpen && <AIPanel chatHistory={props.chatHistory} onSendMessage={props.onSendMessage} onInsertText={props.onInsertText} selectedText="" isGenerating={props.isGenerating} usage={user.usage} />}
</div>
</div>

View File

@@ -3,12 +3,17 @@
import React, { createContext, useContext } from 'react';
import { SessionProvider } from 'next-auth/react';
import { useAuth } from '@/hooks/useAuth';
import { ThemeProvider } from './ThemeProvider';
const AuthContext = createContext<any>(null);
function AuthInner({ children }: { children: React.ReactNode }) {
const auth = useAuth();
return <AuthContext.Provider value={auth}>{children}</AuthContext.Provider>;
return (
<AuthContext.Provider value={auth}>
<ThemeProvider>{children}</ThemeProvider>
</AuthContext.Provider>
);
}
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {

View File

@@ -0,0 +1,20 @@
'use client';
import React, { useEffect } from 'react';
import { useAuthContext } from './AuthProvider';
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const { user } = useAuthContext();
useEffect(() => {
if (!user) return;
const theme = user.preferences?.theme || 'light';
const root = document.documentElement;
root.classList.remove('theme-light', 'theme-dark', 'theme-sepia');
root.classList.add(`theme-${theme}`);
}, [user?.preferences?.theme]);
return <>{children}</>;
}