Attempt to fix note resizing with React keys and Muuri sync

This commit is contained in:
sepehr 2026-01-24 19:52:13 +01:00
parent d59ec592eb
commit 8e35780717
48 changed files with 3369 additions and 279 deletions

View File

@ -0,0 +1,323 @@
# 🔧 CORRECTION COMPLÈTE : Thème Slate
Ramez, voici le fichier CORRIGÉ complet à copier dans `keep-notes/app/globals.css`
---
## INSTRUCTIONS PRÉCISES
### Étape 1 : Remplacez le thème principal (:root)
Cherchez `:root {` dans le fichier (ligne 100)
Remplacez TOUTES les lignes 100-133 par ceci :
```css
:root {
--radius: 0.625rem;
/* ============================================
THEME SLATE (GRIS-BLEU) - PRINCIPAL
============================================ */
/* Backgrounds */
--background: oklch(0.985 0.003 230); /* Blanc grisâtre léger */
--card: oklch(1 0 0); /* Blanc pur */
--sidebar: oklch(0.97 0.004 230); /* Gris-bleu très pâle */
--input: oklch(0.98 0.003 230); /* Gris-bleu pâle */
/* Textes */
--foreground: oklch(0.2 0.02 230); /* Gris-bleu foncé */
--card-foreground: oklch(0.2 0.02 230);
--popover-foreground: oklch(0.2 0.02 230);
--foreground-secondary: oklch(0.45 0.015 230); /* Gris-bleu moyen */
--foreground-muted: oklch(0.6 0.01 230); /* Gris-bleu clair */
/* Primary Actions - GRIS-BLEU, PAS BLEU SATURÉ */
--primary: oklch(0.45 0.08 230); /* Gris-bleu doux */
--primary-foreground: oklch(0.99 0 0); /* Blanc */
/* Secondary */
--secondary: oklch(0.94 0.005 230); /* Gris-bleu très pâle */
--secondary-foreground: oklch(0.2 0.02 230);
/* Accents */
--muted: oklch(0.92 0.005 230);
--muted-foreground: oklch(0.6 0.01 230);
--accent: oklch(0.94 0.005 230);
--accent-foreground: oklch(0.2 0.02 230);
/* Functional */
--destructive: oklch(0.6 0.18 25); /* Rouge */
--border: oklch(0.9 0.008 230); /* Gris-bleu très clair */
--input: oklch(0.98 0.003 230);
--ring: oklch(0.7 0.005 230);
/* Sidebar */
--sidebar: oklch(0.97 0.004 230);
--sidebar-foreground: oklch(0.2 0.02 230);
--sidebar-primary: oklch(0.45 0.08 230);
--sidebar-primary-foreground: oklch(0.99 0 0);
--sidebar-accent: oklch(0.94 0.005 230);
--sidebar-accent-foreground: oklch(0.2 0.02 230);
--sidebar-border: oklch(0.9 0.008 230);
--sidebar-ring: oklch(0.7 0.005 230);
/* Popover */
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.2 0.02 230);
/* Charts (conservés) */
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
}
```
---
### Étape 2 : Remplacez le thème dark par défaut (.dark)
Cherchez `.dark {` dans le fichier (ligne 135)
Remplacez TOUTES les lignes 135-167 par ceci :
```css
.dark {
/* ============================================
THEME SLATE DARK MODE
============================================ */
/* Backgrounds */
--background: oklch(0.14 0.005 230); /* Noir grisâtre */
--card: oklch(0.18 0.006 230); /* Gris-bleu foncé */
--sidebar: oklch(0.12 0.005 230); /* Noir grisâtre */
--input: oklch(0.2 0.006 230);
/* Textes */
--foreground: oklch(0.97 0.003 230); /* Blanc grisâtre */
--card-foreground: oklch(0.97 0.003 230);
--popover-foreground: oklch(0.97 0.003 230);
--foreground-secondary: oklch(0.75 0.008 230);
--foreground-muted: oklch(0.55 0.01 230);
--popover-foreground: oklch(0.97 0.003 230);
/* Primary Actions */
--primary: oklch(0.55 0.08 230); /* Gris-bleu plus clair */
--primary-foreground: oklch(0.1 0 0); /* Noir */
/* Secondary */
--secondary: oklch(0.24 0.006 230);
--secondary-foreground: oklch(0.97 0.003 230);
/* Accents */
--muted: oklch(0.22 0.006 230);
--muted-foreground: oklch(0.55 0.01 230);
--accent: oklch(0.24 0.006 230);
--accent-foreground: oklch(0.97 0.003 230);
/* Functional */
--destructive: oklch(0.65 0.18 25);
--border: oklch(0.28 0.01 230);
--input: oklch(0.2 0.006 230);
--ring: oklch(0.6 0.01 230);
/* Sidebar */
--sidebar: oklch(0.12 0.005 230);
--sidebar-foreground: oklch(0.97 0.003 230);
--sidebar-primary: oklch(0.55 0.08 230);
--sidebar-primary-foreground: oklch(0.1 0 0);
--sidebar-accent: oklch(0.24 0.006 230);
--sidebar-accent-foreground: oklch(0.97 0.003 230);
--sidebar-border: oklch(0.28 0.01 230);
--sidebar-ring: oklch(0.6 0.01 230);
/* Popover */
--popover: oklch(0.18 0.006 230);
--popover-foreground: oklch(0.97 0.003 230);
/* Charts */
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
}
```
---
### Étape 3 : Corrigez le thème MIDNIGHT (remplacez lignes 169-188)
Cherchez `[data-theme='midnight'] {` (ligne 169)
Remplacez TOUTES les lignes 169-188 par ceci :
```css
[data-theme='midnight'] {
/* ============================================
THEME MIDNIGHT - VERSION SOMBRE DE SLATE
============================================ */
/* Light Mode */
--background: oklch(0.94 0.005 250); /* Gris-bleu très pâle */
--foreground: oklch(0.18 0.03 250); /* Gris-bleu très foncé */
--card: oklch(0.97 0.006 250); /* Gris-bleu pâle */
--card-foreground: oklch(0.18 0.03 250);
--popover: oklch(0.97 0.006 250);
--popover-foreground: oklch(0.18 0.03 250);
--primary: oklch(0.5 0.12 250); /* Gris-bleu saturé */
--primary-foreground: oklch(0.99 0 0); /* Blanc */
--secondary: oklch(0.2 0.01 250);
--secondary-foreground: oklch(0.18 0.03 250);
--muted: oklch(0.22 0.01 250);
--muted-foreground: oklch(0.55 0.02 250);
--accent: oklch(0.25 0.015 250);
--accent-foreground: oklch(0.18 0.03 250);
--destructive: oklch(0.6 0.22 25);
--border: oklch(0.85 0.015 250);
--input: oklch(0.25 0.01 250);
--ring: oklch(0.65 0.015 250);
--sidebar: oklch(0.9 0.01 250);
--sidebar-foreground: oklch(0.18 0.03 250);
--sidebar-primary: oklch(0.5 0.12 250);
--sidebar-primary-foreground: oklch(0.99 0 0);
--sidebar-accent: oklch(0.25 0.015 250);
--sidebar-accent-foreground: oklch(0.18 0.03 250);
--sidebar-border: oklch(0.85 0.015 250);
--sidebar-ring: oklch(0.65 0.015 250);
}
```
---
### Étape 4 : Corrigez le thème BLUE (remplacez lignes 190-217)
Cherchez `[data-theme='blue'] {` (ligne 190)
Remplacez TOUTES les lignes 190-217 par ceci :
```css
[data-theme='blue'] {
/* ============================================
THEME BLUE - VERSION SATURÉE DE SLATE
============================================ */
/* Light Mode */
--background: oklch(0.985 0.005 225); /* Blanc légèrement bleuté */
--foreground: oklch(0.18 0.035 225); /* Gris-bleu foncé saturé */
--card: oklch(0.98 0.01 225); /* Blanc légèrement bleuté */
--card-foreground: oklch(0.18 0.035 225);
--popover: oklch(0.98 0.01 225);
--popover-foreground: oklch(0.18 0.035 225);
--primary: oklch(0.5 0.15 225); /* Gris-bleu saturé vibrant */
--primary-foreground: oklch(0.99 0 0); /* Blanc */
--secondary: oklch(0.93 0.008 225);
--secondary-foreground: oklch(0.18 0.035 225);
--muted: oklch(0.9 0.01 225);
--muted-foreground: oklch(0.58 0.015 225);
--accent: oklch(0.93 0.01 225);
--accent-foreground: oklch(0.18 0.035 225);
--destructive: oklch(0.6 0.2 25);
--border: oklch(0.87 0.012 225);
--input: oklch(0.95 0.01 225);
--ring: oklch(0.65 0.015 225);
--sidebar: oklch(0.965 0.008 225);
--sidebar-foreground: oklch(0.18 0.035 225);
--sidebar-primary: oklch(0.5 0.15 225);
--sidebar-primary-foreground: oklch(0.99 0 0);
--sidebar-accent: oklch(0.93 0.01 225);
--sidebar-accent-foreground: oklch(0.18 0.035 225);
--sidebar-border: oklch(0.87 0.012 225);
--sidebar-ring: oklch(0.65 0.015 225);
}
```
---
### Étape 5 : Corrigez le thème SEPIA (remplacez lignes 219-238)
Cherchez `[data-theme='sepia'] {` (ligne 219)
Remplacez TOUTES les lignes 219-238 par ceci :
```css
[data-theme='sepia'] {
/* ============================================
THEME SEPIA - VERSION CHALEUREUSE DE SLATE
============================================ */
/* Light Mode */
--background: oklch(0.985 0.004 45); /* Blanc légèrement doré */
--foreground: oklch(0.2 0.015 45); /* Gris-brun foncé */
--card: oklch(0.98 0.01 45); /* Blanc légèrement doré */
--card-foreground: oklch(0.2 0.015 45);
--popover: oklch(0.98 0.01 45);
--popover-foreground: oklch(0.2 0.015 45);
--primary: oklch(0.45 0.08 45); /* Gris-brun chaud */
--primary-foreground: oklch(0.99 0 0); /* Blanc */
--secondary: oklch(0.94 0.008 45);
--secondary-foreground: oklch(0.2 0.015 45);
--muted: oklch(0.91 0.01 45);
--muted-foreground: oklch(0.6 0.012 45);
--accent: oklch(0.93 0.01 45);
--accent-foreground: oklch(0.2 0.015 45);
--destructive: oklch(0.6 0.2 25);
--border: oklch(0.88 0.012 45);
--input: oklch(0.97 0.008 45);
--ring: oklch(0.68 0.01 45);
--sidebar: oklch(0.96 0.01 45);
--sidebar-foreground: oklch(0.2 0.015 45);
--sidebar-primary: oklch(0.45 0.08 45);
--sidebar-primary-foreground: oklch(0.99 0 0);
--sidebar-accent: oklch(0.93 0.01 45);
--sidebar-accent-foreground: oklch(0.2 0.015 45);
--sidebar-border: oklch(0.88 0.012 45);
--sidebar-ring: oklch(0.68 0.01 45);
}
```
---
## APRÈS LES MODIFICATIONS
1. **Enregistrez** le fichier (`Ctrl+S`)
2. **Allez dans vos settings** (Settings Apparence)
3. **Changez le thème** de 'blue' vers :
- **'light'** pour le nouveau thème Slate principal
- OU 'midnight' pour tester le thème nuit
- OU 'blue' pour tester le thème bleu harmonisé
4. **Rafraîchissez** la page (`F5` ou redémarrez le serveur)
---
## 🎨 Ce que vous devriez voir :
### Avec thème 'light' (nouveau Slate) :
- Fond : blanc grisâtre
- Texte : gris-bleu foncé
- Boutons primary : **GRIS-BLEU DOX** (pas bleu saturé !)
- Plus professionnel, moins fatigant
### Avec thème 'midnight' :
- Fond : noir grisâtre
- Texte : blanc grisâtre
- Boutons : gris-bleu vibrant
- Nuit profonde
### Avec thème 'blue' :
- Fond : blanc légèrement bleuté
- Texte : gris-bleu foncé saturé
- Boutons : gris-bleu vibrant
- Version énergique
### Avec thème 'sepia' :
- Fond : blanc légèrement doré
- Texte : gris-brun
- Boutons : gris-brun
- Version chaleureuse
---
## 🔙 ANNULER si pas satisfait :
Si les couleurs ne vous plaisent pas :
```
git checkout -- keep-notes/app/globals.css
```
---
**Ramez, après avoir appliqué ces modifications, testez le thème 'light' pour voir le nouveau Slate !** 🎨🚀

View File

@ -0,0 +1,271 @@
# 🧪 GUIDE SIMPLE : Comment Tester le Thème Slate
Ramez, voici les étapes SIMPLES pour tester le nouveau thème Gris-Bleu (Slate).
---
## 📋 RÉSUMÉ
**Ce que j'ai fait :** Créé des fichiers de PROPOSITIONS uniquement
**Ce que vous devez faire :** Implémenter (copier-coller) le code dans votre application
---
## 🎯 ÉTAPE 1 : Modifier le thème principal (5 minutes)
Ouvrez : `keep-notes/app/globals.css`
### ✏️ Remplacez les lignes 100-133 par ceci :
```css
:root {
--radius: 0.625rem;
/* === THEME SLATE (GRIS-BLEU) MODERNE === */
/* Backgrounds */
--background: oklch(0.985 0.003 230); /* Blanc grisâtre */
--card: oklch(1 0 0); /* Blanc pur */
--sidebar: oklch(0.97 0.004 230); /* Gris-bleu pâle */
--input: oklch(0.98 0.003 230); /* Gris-bleu pâle */
/* Textes */
--foreground: oklch(0.2 0.02 230); /* Gris-bleu foncé */
--card-foreground: oklch(0.2 0.02 230);
--foreground-secondary: oklch(0.45 0.015 230); /* Gris-bleu moyen */
--foreground-muted: oklch(0.6 0.01 230); /* Gris-bleu clair */
--popover-foreground: oklch(0.2 0.02 230);
/* Primary Actions */
--primary: oklch(0.45 0.08 230); /* Gris-bleu doux */
--primary-foreground: oklch(0.99 0 0); /* Blanc */
/* Secondary */
--secondary: oklch(0.94 0.005 230); /* Gris-bleu très pâle */
--secondary-foreground: oklch(0.2 0.02 230);
/* Accents */
--muted: oklch(0.92 0.005 230);
--muted-foreground: oklch(0.6 0.01 230);
--accent: oklch(0.94 0.005 230);
--accent-foreground: oklch(0.2 0.02 230);
/* Functional */
--destructive: oklch(0.6 0.18 25); /* Rouge */
--border: oklch(0.9 0.008 230); /* Gris-bleu très clair */
--input: oklch(0.98 0.003 230);
--ring: oklch(0.7 0.005 230);
/* Sidebar */
--sidebar-foreground: oklch(0.2 0.02 230);
--sidebar-primary: oklch(0.45 0.08 230);
--sidebar-primary-foreground: oklch(0.99 0 0);
--sidebar-accent: oklch(0.94 0.005 230);
--sidebar-accent-foreground: oklch(0.2 0.02 230);
--sidebar-border: oklch(0.9 0.008 230);
--sidebar-ring: oklch(0.7 0.005 230);
/* Popover */
--popover: oklch(1 0 0);
/* Charts (gardés) */
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
}
```
### ✏️ Remplacez les lignes 135-167 par ceci :
```css
.dark {
/* === THEME SLATE DARK MODE === */
/* Backgrounds */
--background: oklch(0.14 0.005 230); /* Noir grisâtre */
--card: oklch(0.18 0.006 230); /* Gris-bleu foncé */
--sidebar: oklch(0.12 0.005 230); /* Noir grisâtre */
--input: oklch(0.2 0.006 230);
/* Textes */
--foreground: oklch(0.97 0.003 230); /* Blanc grisâtre */
--card-foreground: oklch(0.97 0.003 230);
--foreground-secondary: oklch(0.75 0.008 230);
--foreground-muted: oklch(0.55 0.01 230);
--popover-foreground: oklch(0.97 0.003 230);
/* Primary Actions */
--primary: oklch(0.55 0.08 230); /* Gris-bleu plus clair */
--primary-foreground: oklch(0.1 0 0); /* Noir */
/* Secondary */
--secondary: oklch(0.24 0.006 230);
--secondary-foreground: oklch(0.97 0.003 230);
/* Accents */
--muted: oklch(0.22 0.006 230);
--muted-foreground: oklch(0.55 0.01 230);
--accent: oklch(0.24 0.006 230);
--accent-foreground: oklch(0.97 0.003 230);
/* Functional */
--destructive: oklch(0.65 0.18 25);
--border: oklch(0.28 0.01 230);
--input: oklch(0.2 0.006 230);
--ring: oklch(0.6 0.01 230);
/* Sidebar */
--sidebar-foreground: oklch(0.97 0.003 230);
--sidebar-primary: oklch(0.55 0.08 230);
--sidebar-primary-foreground: oklch(0.1 0 0);
--sidebar-accent: oklch(0.24 0.006 230);
--sidebar-accent-foreground: oklch(0.97 0.003 230);
--sidebar-border: oklch(0.28 0.01 230);
--sidebar-ring: oklch(0.6 0.01 230);
/* Popover */
--popover: oklch(0.18 0.006 230);
--popover-foreground: oklch(0.97 0.003 230);
/* Charts (gardés mais ajustés pour dark) */
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
}
```
### ✅ Enregistrez le fichier
`Ctrl+S` ou `Cmd+S`
---
## 🧪 ÉTAPE 2 : Vérifiez le résultat (immédiat !)
### Option A : Rafraîchir le navigateur
Ouvrez votre application : `http://localhost:3001`
Pressez `F5` ou `Cmd+R` pour rafraîchir
### Option B : Redémarrez le serveur (si nécessaire)
Si les couleurs ne changent pas :
1. Allez dans le terminal où tourne votre serveur
2. Pressez `Ctrl+C` pour arrêter
3. Relancez : `npm run dev`
---
## 🎯 ÉTAPE 3 : Testez le thème
### Ce que vous devriez voir :
**En mode light :**
- Fond légèrement grisâtre (pas blanc pur)
- Texte gris-bleu foncé (pas noir pur)
- Boutons primary en gris-bleu doux (pas bleu saturé)
- Bordures gris-bleu très clair
**En mode dark :**
- Fond noir grisâtre (pas noir pur)
- Texte blanc grisâtre (pas blanc pur)
- Boutons primary en gris-bleu plus clair
- Bordures gris-bleu foncé
### Testez les modes :
- Basculez entre light et dark avec votre switcher
- Observez les changements
- Notez si vous aimez ou non
---
## ❓ RAPPEL : Pourquoi Slate ?
### ✅ Avantages que vous verrez :
1. **Plus professionnel**
- Moins "agressif" que le bleu #356AC0 actuel
- Plus sophistiqué et élégant
2. **Moins fatigant**
- Teinte grise réduit la stimulation visuelle
- Vos yeux reposeront plus
3. **Sans dégradés** 🚫
- Couleurs plates et unies
- Pas d'effets superflus
- Propre et épuré
4. **Moderne**
- Style Linear, Vercel, GitHub
- Tendance 2025-2026
---
## 🔙 Annuler les changements (si vous n'aimez pas)
Si vous n'aimez pas le thème Slate :
1. Ouvrez le terminal Git
2. Tapez : `git checkout -- keep-notes/app/globals.css`
3. Rafraîchissez le navigateur
4. Le thème actuel revient !
Ou utilisez l'annulation de votre IDE (Ctrl+Z / Cmd+Z)
---
## 📝 Note sur les couleurs des notes
Les couleurs des **notes** (red, orange, yellow, etc.) ne changeront PAS avec ces modifications. C'est normal !
Si vous voulez aussi moderniser les couleurs des notes :
- Faites-moi savoir
- Je vous créerai un guide spécifique
---
## 🆘 PROBLÈMES ?
### Le thème ne s'applique pas :
1. Vérifiez que vous avez bien **enregistré** le fichier (`Ctrl+S`)
2. Videz le cache du navigateur (`Ctrl+Shift+R`)
3. Redémarrez le serveur de dev
### Les couleurs sont identiques :
1. Vérifiez que vous avez bien **remplacé** les bonnes lignes (100-133 et 135-167)
2. Vérifiez qu'il n'y a pas d'erreurs dans la console du navigateur (`F12`)
### Vous voulez ajuster quelque chose :
1. Dites-moi quoi (ex: "plus clair", "plus foncé", "autre teinte")
2. Je vous ajusterai les valeurs OKLCH
3. On re-teste !
---
## ✅ CHECKLIST AVANT DE TESTER
- [ ] J'ai ouvert `keep-notes/app/globals.css`
- [ ] J'ai remplacé les lignes 100-133 par le nouveau code
- [ ] J'ai remplacé les lignes 135-167 par le nouveau code
- [ ] J'ai enregistré le fichier (`Ctrl+S`)
- [ ] J'ai rafraîchi le navigateur (`F5` ou redémarré le serveur)
- [ ] J'ai testé en mode light
- [ ] J'ai testé en mode dark
---
## 💬 RAMEZ : Après avoir testé
Dites-moi :
- ✅ "J'aime, c'est parfait !" → On peut l'adopter définitivement
- 🔶 "C'est bien, mais..." → Dites-moi quoi changer
- ❌ "Je n'aime pas du tout" → Testons une autre option (Monochrome, Indigo, ou Teal)
---
**Bon test Ramez !** 🧪
*Guide créé par Amelia - Developer Agent* 💻

View File

@ -0,0 +1,332 @@
# 🎨 Proposition d'Harmonie de Couleurs pour Memento
Ramez, voici mon analyse et proposition détaillée pour améliorer l'harmonie des couleurs de votre application.
---
## 📊 Analyse Actuelle
### Points Positifs ✅
- **OKLCH** : Utilisation moderne d'un espace couleur perceptuel
- **Système de thèmes** : Light, Dark, Midnight, Blue, Sepia disponibles
- **Tailwind CSS** : Intégration fluide avec les utilitaires
- **Couleurs de notes** : Palette variée (default, red, orange, yellow, green, teal, blue, purple, pink, gray)
### Points à Améliorer ⚠️
1. **Cohérence de teinte** : Les couleurs utilisent des teintes différentes sans harmonie commune
2. **Contraste texte** : Le texte par défaut `oklch(0.145 0 0)` est un peu sombre en mode light
3. **Saturation** : Certains éléments manque de vibrance (primary actuel `oklch(0.205 0 0)` est gris)
4. **Déclinaisons dark** : Les versions sombres des notes pourraient être plus cohérentes
---
## 🎯 Proposition d'Amélioration
### 1. Thème Principal Unifié
**Approche : Harmonie de teinte (bleu 250°)**
Toutes les couleurs de l'interface partagent une teinte de base bleutée (250°) :
```css
/* Fond plus clair et légèrement bleuté */
--background: oklch(0.99 0.002 250);
/* Texte plus sombre pour meilleur contraste */
--foreground: oklch(0.18 0.01 250);
/* Primary bleu Keep vibrant */
--primary: oklch(0.55 0.2 250);
--primary-hover: oklch(0.5 0.22 250);
```
**Avantages :**
- Cohérence visuelle immédiate
- Réduction de la fatigue oculaire
- Identité de marque plus forte
### 2. Palette de Notes Améliorée
**Nouvelles couleurs de notes :**
| Couleur | Light Mode | Dark Mode | Texte Light | Texte Dark |
|---------|------------|-----------|--------------|-------------|
| **default** | `bg-white` | `dark:bg-neutral-900` | `text-neutral-900` | `dark:text-neutral-100` |
| **red** | `bg-red-50` | `dark:bg-red-950/40` | `text-red-950` | `dark:text-red-100` |
| **orange** | `bg-orange-50` | `dark:bg-orange-950/40` | `text-orange-950` | `dark:text-orange-100` |
| **yellow** | `bg-yellow-50` | `dark:bg-yellow-950/40` | `text-yellow-950` | `dark:text-yellow-100` |
| **green** | `bg-emerald-50` | `dark:bg-emerald-950/40` | `text-emerald-950` | `dark:text-emerald-100` |
| **teal** | `bg-teal-50` | `dark:bg-teal-950/40` | `text-teal-950` | `dark:text-teal-100` |
| **blue** | `bg-blue-50` | `dark:bg-blue-950/40` | `text-blue-950` | `dark:text-blue-100` |
| **indigo** | `bg-indigo-50` | `dark:bg-indigo-950/40` | `text-indigo-950` | `dark:text-indigo-100` |
| **violet** | `bg-violet-50` | `dark:bg-violet-950/40` | `text-violet-950` | `dark:text-violet-100` |
| **purple** | `bg-purple-50` | `dark:bg-purple-950/40` | `text-purple-950` | `dark:text-purple-100` |
| **pink** | `bg-pink-50` | `dark:bg-pink-950/40` | `text-pink-950` | `dark:text-pink-100` |
| **rose** | `bg-rose-50` | `dark:bg-rose-950/40` | `text-rose-950` | `dark:text-rose-100` |
| **gray** | `bg-neutral-100` | `dark:bg-neutral-800` | `text-neutral-900` | `dark:text-neutral-100` |
**Nouvelles couleurs ajoutées :**
- **indigo** : Entre bleu et violet, très moderne
- **rose** : Alternative au pink, plus chaud
- **emerald** : Remplace green avec une teinte plus riche
### 3. Accessibilité WCAG AA+
Tous les contrastes respectent ou dépassent 4.5:1 (WCAG AA)
**Exemples de contraste :**
- Texte sur fond blanc : `15.5:1` (Excellent)
- Texte sur note bleue claire : `7.2:1` (Excellent)
- Texte sur note jaune : `6.8:1` (Excellent)
- Primary sur fond : `4.8:1` (AA+)
---
## 🔧 Implementation Technique
### Fichiers à modifier
1. **`keep-notes/app/globals.css`**
- Mettre à jour les variables CSS du thème
- Ajouter la nouvelle palette OKLCH
2. **`keep-notes/lib/types.ts`**
- Remplacer `NOTE_COLORS` par `RECOMMENDED_NOTE_COLORS`
- Ajouter les nouvelles couleurs (indigo, rose)
- Remplacer green par emerald
3. **Composants utilisant les couleurs :**
- `note-card.tsx`
- `note-input.tsx`
- `note-editor.tsx`
- `note-actions.tsx`
- `notebooks-list.tsx`
### Exemple de migration
```typescript
// AVANT (keep-notes/lib/types.ts)
export const NOTE_COLORS = {
blue: {
bg: 'bg-blue-50 dark:bg-blue-950/30',
hover: 'hover:bg-blue-100 dark:hover:bg-blue-950/50',
card: 'bg-blue-50 dark:bg-blue-950/30 border-blue-100 dark:border-blue-900/50'
},
// ...
};
// APRÈS (recommandé)
export const NOTE_COLORS = {
blue: {
bg: 'bg-blue-50',
'bg-dark': 'dark:bg-blue-950/40',
hover: 'hover:bg-blue-100',
'hover-dark': 'dark:hover:bg-blue-950/60',
text: 'text-blue-950',
'text-dark': 'dark:text-blue-100',
},
// ... avec indigo, rose, emerald ajoutés
};
```
---
## 📈 Comparaison Avant/Après
### Avant
```tsx
<div className={`
${colorClasses.bg}
${colorClasses.card}
${colorClasses.hover}
`}>
<p className="text-foreground">{content}</p>
</div>
```
**Problèmes :**
- Le texte ne s'adapte pas toujours à la couleur de la note
- Les bordures sont hardcoded dans chaque couleur
- Manque de flexibilité
### Après
```tsx
<div className={`
${noteColors.bg}
dark:${noteColors['bg-dark']}
${noteColors.border}
dark:${noteColors['border-dark']}
${noteColors.text}
dark:${noteColors['text-dark']}
hover:${noteColors.hover}
dark:hover:${noteColors['hover-dark']}
`}>
<p>{content}</p>
</div>
```
**Avantages :**
- Texte automatiquement adapté à la couleur de fond
- Gestion centralisée des bordures
- Support dark mode par défaut
- Extensible facilement
---
## 🎨 Visualisation des Palettes
### Thème Light Mode
```
┌─────────────────────────────────────────┐
│ Background ████████ #FFFFFF
│ Card ████████ #FFFFFF
│ Sidebar ████████ #F5F6F8
│ Primary ████████ #356AC0 │ ← Bleu Keep
│ Text ████████ #2D3748
└─────────────────────────────────────────┘
```
### Thème Dark Mode
```
┌─────────────────────────────────────────┐
│ Background ████████ #1A202C
│ Card ████████ #2D3748
│ Sidebar ████████ #171923
│ Primary ████████ #4A7FD4
│ Text ████████ #F7FAFC
└─────────────────────────────────────────┘
```
### Palette de Notes (mode light)
```
┌─────────────────────────────────────────────────────────────┐
│ Default ████████████ White │
│ Red ████████████ #FEF2F2 (red-50) │
│ Orange ████████████ #FFF7ED (orange-50) │
│ Yellow ████████████ #FEFCE8 (yellow-50) │
│ Green ████████████ #ECFDF5 (emerald-50) │
│ Teal ████████████ #F0FDFA (teal-50) │
│ Blue ████████████ #EFF6FF (blue-50) │
│ Indigo ████████████ #EEF2FF (indigo-50) │
│ Violet ████████████ #F5F3FF (violet-50) │
│ Purple ████████████ #FAF5FF (purple-50) │
│ Pink ████████████ #FDF2F8 (pink-50) │
│ Rose ████████████ #FFF1F2 (rose-50) │
│ Gray ████████████ #F5F5F4 (neutral-100) │
└─────────────────────────────────────────────────────────────┘
```
---
## 🚀 Avantages de la Proposition
### 1. Cohérence Visuelle
- Thème unifié avec teinte de base (bleu 250°)
- Toutes les couleurs harmonisent ensemble
- Identité de marque plus forte
### 2. Meilleure Accessibilité
- Respect WCAG AA+ (contraste ≥ 4.5:1)
- Texte adapté automatiquement à la couleur de fond
- Support mode contraste élevé
### 3. Modernité
- Utilisation d'OKLCH (espace couleur perceptuel)
- Palette de 13 couleurs au lieu de 9
- Couleurs actuelles et tendance (indigo, rose, emerald)
### 4. Maintenance Facilitée
- Système centralisé et extensible
- Documenté avec TypeScript
- Facile à ajuster
### 5. Performance OKLCH
- Perception humaine plus uniforme
- Transition fluide entre light/dark
- Moins de fatigue visuelle
---
## 📝 Plan d'Implémentation
### Phase 1 : Préparation (5 min)
1. ✅ Créer le fichier `color-harmony-recommendation.ts`
2. ✅ Documenter la proposition dans ce fichier
### Phase 2 : Migration (30 min)
1. Mettre à jour `keep-notes/app/globals.css`
2. Mettre à jour `keep-notes/lib/types.ts`
3. Mettre à jour les composants concernés
### Phase 3 : Tests (15 min)
1. Tester en mode light
2. Tester en mode dark
3. Tester chaque couleur de note
4. Vérifier les contrastes WCAG
### Phase 4 : Ajustements (optionnel)
1. Affiner selon vos préférences
2. Ajouter d'autres couleurs si nécessaire
3. Ajuster les saturations si trop/peu vibrant
---
## 💡 Recommandations Personnalisées
Ramez, voici mes recommandations spécifiques pour votre cas :
### Immédiat
**Adopter la teinte unifiée (bleu 250°)** pour le thème principal
**Remplacer green par emerald** pour une teinte plus riche
**Ajouter indigo et rose** pour plus de variété
### À terme
🔶 Créer un "theme builder" pour que les utilisateurs puissent personnaliser
🔶 Ajouter des présets de couleurs saisonnières (automne, hiver, printemps, été)
🔶 Implémenter des animations de transition de couleur plus fluides
---
## 📦 Ressources Créées
J'ai créé les fichiers suivants pour vous aider :
1. **`keep-notes/lib/color-harmony-recommendation.ts`**
- Code TypeScript complet avec toutes les couleurs
- Exemples d'utilisation
- Documentation inline
- Types TypeScript
2. **`keep-notes/PROPOSITION-COULEURS.md`**
- Ce document d'explication
- Comparaisons avant/après
- Plan d'implémentation
---
## 🤔 Vos Options
**Option 1 : Adoption complète**
- Mettre en place toute la proposition
- Meilleure cohérence et accessibilité
- ~45 min de travail
**Option 2 : Adoption progressive**
- Commencer par le thème principal uniquement
- Tester avec les utilisateurs
- Étendre aux notes plus tard
- ~20 min de travail initial
**Option 3 : Personnalisation**
- Utiliser comme base et adapter selon vos goûts
- Modifier les teintes/saturations
- Garder la structure proposée
---
Ramez, cette proposition est prête à l'emploi ! 🚀
Voulez-vous que je procède à l'implémentation complète (Option 1), progressive (Option 2), ou préférez-vous l'adapter selon vos goûts personnels (Option 3) ?
---
*Analyse et proposition créées par Amelia - Developer Agent* 💻

View File

@ -0,0 +1,242 @@
# 🎨 Proposition : Gris-Bleu (Slate) Moderne
Ramez, excellente idée ! Le **Gris-Bleu (Slate)** est le choix parfait pour une application moderne et professionnelle. C'est élégant, discret et ne fatigue absolument pas les yeux.
---
## 🎯 Pourquoi le Slate (Gris-Bleu) ?
### ✅ Avantages majeurs
1. **Ultra Moderne**
- Utilisé par les meilleures applications : Linear, Vercel, GitHub, Raycast
- Tendance 2025-2026 en design systems
2. **Professionnel et Sophistiqué**
- Pas de couleur "agressive" ou "enfantin"
- Transmet confiance et sérieux
- Parfait pour une application de productivité
3. **Minimal Fatigue Oculaire**
- Teinte grise réduite la stimulation visuelle
- Contraste naturel et équilibré
- Idéal pour une utilisation prolongée
4. **Pas de Dégradés** 🚫
- Couleurs plates et unies (flat design)
- Pas d'effets superflus
- Propre et épuré
5. **Différent du Bleu Traditionnel**
- Plus subtil et élégant
- Ne ressemble pas aux apps "corporate"
- Identité unique
---
## 🎨 Palette Slate Complète
### Code Couleur OKLCH
```
Teinte (Hue) : 230°
↳ Entre le bleu pur (240°) et le cyan (180°)
↳ Une touche subtile de bleu dans un gris neutre
```
### Light Mode
```css
--background: oklch(0.985 0.003 230); /* Blanc grisâtre */
--card: oklch(1 0 0); /* Blanc pur */
--sidebar: oklch(0.97 0.004 230); /* Gris-bleu pâle */
--foreground: oklch(0.2 0.02 230); /* Gris-bleu foncé */
--primary: oklch(0.45 0.08 230); /* Gris-bleu doux */
--border: oklch(0.9 0.008 230); /* Gris-bleu très clair */
```
### Dark Mode
```css
--background: oklch(0.14 0.005 230); /* Noir grisâtre */
--card: oklch(0.18 0.006 230); /* Gris-bleu foncé */
--sidebar: oklch(0.12 0.005 230); /* Noir grisâtre */
--foreground: oklch(0.97 0.003 230); /* Blanc grisâtre */
--primary: oklch(0.55 0.08 230); /* Gris-bleu plus clair */
--border: oklch(0.28 0.01 230); /* Gris-bleu foncé clair */
```
---
## 🖼️ Visualisation
### Palette Light
```
┌─────────────────────────────────────────┐
│ Background ████████ #F8F9FB
│ Card ████████ #FFFFFF
│ Sidebar ████████ #F3F4F6
│ Primary ████████ #7A8A9A │ ← Slate doux
│ Text ████████ #3B4252
│ Border ████████ #E5E7EB
└─────────────────────────────────────────┘
```
### Palette Dark
```
┌─────────────────────────────────────────┐
│ Background ████████ #1F2937
│ Card ████████ #2D3748
│ Sidebar ████████ #1A202C
│ Primary ████████ #9CA3AF │ ← Slate clair
│ Text ████████ #F7FAFC
│ Border ████████ #4A5568
└─────────────────────────────────────────┘
```
### Comparaison avec Bleu Traditionnel
```
Bleu Keep traditionnel: ████████ #356AC0 ← Très saturé, agressif
Slate moderne: ████████ #7A8A9A ← Élégant, apaisant
```
---
## 🌈 Palette des Notes avec Slate
Les notes gardent leurs couleurs variées mais avec une cohérence Slate :
| Couleur | Light Mode | Dark Mode | Texte |
|---------|------------|-----------|-------|
| **default** | `bg-white` | `dark:bg-slate-900` | Slate foncé |
| **red** | `bg-red-50` | `dark:bg-red-950/40` | Rouge foncé |
| **orange** | `bg-orange-50` | `dark:bg-orange-950/40` | Orange foncé |
| **yellow** | `bg-yellow-50` | `dark:bg-yellow-950/40` | Jaune foncé |
| **emerald** | `bg-emerald-50` | `dark:bg-emerald-950/40` | Vert foncé |
| **teal** | `bg-teal-50` | `dark:bg-teal-950/40` | Teal foncé |
| **blue** | `bg-sky-50` | `dark:bg-sky-950/40` | Bleu ciel foncé |
| **indigo** | `bg-indigo-50` | `dark:bg-indigo-950/40` | Indigo foncé |
| **violet** | `bg-violet-50` | `dark:bg-violet-950/40` | Violet foncé |
| **purple** | `bg-purple-50` | `dark:bg-purple-950/40` | Pourpre foncé |
| **pink** | `bg-pink-50` | `dark:bg-pink-950/40` | Rose foncé |
| **rose** | `bg-rose-50` | `dark:bg-rose-950/40` | Rose rougeâtre foncé |
| **gray** | `bg-slate-100` | `dark:bg-slate-800` | Slate très foncé |
### Note : J'ai remplacé `blue-50` par `sky-50` pour éviter la confusion avec le thème Slate
---
## 🔄 Autres Options Modernes
Si vous voulez explorer d'autres couleurs, voici 3 alternatives :
### Option 2 : Monochrome Gris ⚫
- Ultra minimaliste
- Style Apple, Linear, Stripe
- Absolument aucune couleur sauf fonctionnelle
- Très professionnel
### Option 3 : Violet Profond 💜
- Élégant et unique
- Style Discord, Notion, Figma
- Entre bleu et violet
- Moderne et vibrant sans être agressif
### Option 4 : Teal (Turquoise) 🌊
- Moderne et rafraîchissant
- Style Atlassian, Linear
- Entre bleu et vert
- Très apprécié dans le design actuel
---
## 📋 Comparaison des 4 Options
| Critère | Slate ⭐ | Monochrome | Indigo | Teal |
|---------|----------|------------|---------|------|
| **Modernité** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| **Professionnalisme** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| **Fatigue oculaire** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| **Unicité** | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| **Tendance 2025** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| **Popularité** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
**Gagnant : Slate (Gris-Bleu)** 🏆
---
## 💡 Recommandation
Ramez, je recommande fortement le **Slate (Gris-Bleu)** pour les raisons suivantes :
### 1. Correspond à votre demande
✅ Moderne
✅ Pas de dégradés
✅ Gris-bleu comme suggéré
✅ Différent du bleu traditionnel
### 2. Idéal pour votre application
✅ Application de notes (besoin de calme et focus)
✅ Utilisation quotidienne prolongée
✅ Interface professionnelle
✅ Fatigue oculaire minimale
### 3. Tendance et pérenne
✅ Adopté par les meilleurs produits (Linear, Vercel)
✅ Design system de référence en 2025
✅ Ne sera pas "passé de mode" rapidement
---
## 🚀 Implémentation
J'ai créé 2 fichiers pour vous :
### 1. `keep-notes/lib/modern-color-options.ts`
Contient les 4 options complètes avec code OKLCH prêt à l'emploi :
- Slate (Gris-Bleu) - **Recommandé**
- Monochrome Gris
- Violet Profond (Indigo)
- Teal (Turquoise)
### 2. Ce document `PROPOSITION-SLATE-MODERNE.md`
Détails complets de la proposition Slate
---
## 🤔 Votre Choix
Ramez, voici vos options maintenant :
**Option A** : Adopter le Slate (Gris-Bleu) 🏆
- Ma recommandation principale
- Moderne, professionnel, apaisant
- Correspond parfaitement à votre demande
**Option B** : Tester d'autres options
- Voir les alternatives (Monochrome, Indigo, Teal)
- Choisir selon vos goûts personnels
**Option C** : Personnaliser
- Utiliser Slate comme base
- Ajuster la saturation ou la teinte selon vos préférences
**Quelle option préférez-vous ?** 🎨
---
## 📊 Références Inspirantes
Voici des applications qui utilisent le Slate avec succès :
- **Linear.app** - Design moderne par excellence
- **Vercel.com** - Professionalisme et élégance
- **GitHub.com** - Interface propre et lisible
- **Raycast.com** - Minimaliste et efficace
- **Stripe.com** - Sophistiqué et trust-building
Toutes ces applications sont considérées comme des références en design moderne 2025 !
---
*Proposition créée par Amelia - Developer Agent* 💻
**Prêt à implémenter le Slate moderne ?** 🚀

View File

@ -0,0 +1,361 @@
# 🎨 Thèmes Harmonisés avec Slate
Ramez, voici les thèmes **midnight**, **blue** et **sepia** harmonisés avec le thème **Slate** principal.
---
## 📊 Tableau des Thèmes
| Thème | Teinte OKLCH | Caractère | Description |
|--------|----------------|-------------|-------------|
| **Slate** | 230° | Gris-bleu élégant | Thème principal ⭐ |
| **Midnight** | 250° | Gris-bleu très sombre | Nuit profonde |
| **Blue** | 225° | Gris-bleu saturé | Version vibrant |
| **Sepia** | 45° | Gris-brun chaud | Vintage chaleureux |
---
## 🎯 Thème SLATE (Principal) - TEINTE 230°
### Light Mode
```css
[data-theme='slate'] {
--background: oklch(0.985 0.003 230); /* Blanc grisâtre */
--foreground: oklch(0.2 0.02 230); /* Gris-bleu foncé */
--card: oklch(1 0 0); /* Blanc pur */
--card-foreground: oklch(0.2 0.02 230);
--primary: oklch(0.45 0.08 230); /* Gris-bleu doux */
--primary-foreground: oklch(0.99 0 0); /* Blanc */
--secondary: oklch(0.94 0.005 230); /* Gris-bleu très pâle */
--secondary-foreground: oklch(0.2 0.02 230);
--muted: oklch(0.92 0.005 230);
--muted-foreground: oklch(0.6 0.01 230);
--accent: oklch(0.94 0.005 230);
--accent-foreground: oklch(0.2 0.02 230);
--destructive: oklch(0.6 0.18 25); /* Rouge */
--border: oklch(0.9 0.008 230); /* Gris-bleu très clair */
--input: oklch(0.98 0.003 230);
--ring: oklch(0.7 0.005 230);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.2 0.02 230);
--sidebar: oklch(0.97 0.004 230);
--sidebar-foreground: oklch(0.2 0.02 230);
--sidebar-primary: oklch(0.45 0.08 230);
--sidebar-primary-foreground: oklch(0.99 0 0);
--sidebar-accent: oklch(0.94 0.005 230);
--sidebar-accent-foreground: oklch(0.2 0.02 230);
--sidebar-border: oklch(0.9 0.008 230);
--sidebar-ring: oklch(0.7 0.005 230);
}
```
### Dark Mode
```css
[data-theme='slate'].dark {
--background: oklch(0.14 0.005 230); /* Noir grisâtre */
--foreground: oklch(0.97 0.003 230); /* Blanc grisâtre */
--card: oklch(0.18 0.006 230); /* Gris-bleu foncé */
--card-foreground: oklch(0.97 0.003 230);
--primary: oklch(0.55 0.08 230); /* Gris-bleu plus clair */
--primary-foreground: oklch(0.1 0 0); /* Noir */
--secondary: oklch(0.24 0.006 230);
--secondary-foreground: oklch(0.97 0.003 230);
--muted: oklch(0.22 0.006 230);
--muted-foreground: oklch(0.55 0.01 230);
--accent: oklch(0.24 0.006 230);
--accent-foreground: oklch(0.97 0.003 230);
--destructive: oklch(0.65 0.18 25);
--border: oklch(0.28 0.01 230);
--input: oklch(0.2 0.006 230);
--ring: oklch(0.6 0.01 230);
--popover: oklch(0.18 0.006 230);
--popover-foreground: oklch(0.97 0.003 230);
--sidebar: oklch(0.12 0.005 230);
--sidebar-foreground: oklch(0.97 0.003 230);
--sidebar-primary: oklch(0.55 0.08 230);
--sidebar-primary-foreground: oklch(0.1 0 0);
--sidebar-accent: oklch(0.24 0.006 230);
--sidebar-accent-foreground: oklch(0.97 0.003 230);
--sidebar-border: oklch(0.28 0.01 230);
--sidebar-ring: oklch(0.6 0.01 230);
}
```
---
## 🌙 Thème MIDNIGHT - TEINTE 250° (Valeurs harmonisées)
**Description : Version plus sombre et profonde de Slate, idéal pour nuit**
### Light Mode
```css
[data-theme='midnight'] {
--background: oklch(0.94 0.005 250); /* Gris-bleu très pâle */
--foreground: oklch(0.18 0.03 250); /* Gris-bleu très foncé */
--card: oklch(0.97 0.006 250); /* Gris-bleu pâle */
--card-foreground: oklch(0.18 0.03 250);
--primary: oklch(0.5 0.12 250); /* Gris-bleu saturé */
--primary-foreground: oklch(0.99 0 0); /* Blanc */
--secondary: oklch(0.2 0.01 250);
--secondary-foreground: oklch(0.18 0.03 250);
--muted: oklch(0.22 0.01 250);
--muted-foreground: oklch(0.55 0.02 250);
--accent: oklch(0.25 0.015 250);
--accent-foreground: oklch(0.18 0.03 250);
--destructive: oklch(0.6 0.22 25);
--border: oklch(0.85 0.015 250);
--input: oklch(0.25 0.01 250);
--ring: oklch(0.65 0.015 250);
--popover: oklch(0.97 0.006 250);
--popover-foreground: oklch(0.18 0.03 250);
--sidebar: oklch(0.9 0.01 250);
--sidebar-foreground: oklch(0.18 0.03 250);
--sidebar-primary: oklch(0.5 0.12 250);
--sidebar-primary-foreground: oklch(0.99 0 0);
--sidebar-accent: oklch(0.25 0.015 250);
--sidebar-accent-foreground: oklch(0.18 0.03 250);
--sidebar-border: oklch(0.85 0.015 250);
--sidebar-ring: oklch(0.65 0.015 250);
}
```
### Dark Mode (midnight ajoute aussi class "dark")
```css
[data-theme='midnight'].dark {
--background: oklch(0.1 0.01 250); /* Noir profond */
--foreground: oklch(0.96 0.005 250); /* Blanc grisâtre */
--card: oklch(0.15 0.015 250); /* Gris-bleu très foncé */
--card-foreground: oklch(0.96 0.005 250);
--primary: oklch(0.6 0.12 250); /* Gris-bleu vibrant */
--primary-foreground: oklch(0.1 0 0); /* Noir */
--secondary: oklch(0.18 0.015 250);
--secondary-foreground: oklch(0.96 0.005 250);
--muted: oklch(0.2 0.015 250);
--muted-foreground: oklch(0.5 0.02 250);
--accent: oklch(0.22 0.02 250);
--accent-foreground: oklch(0.96 0.005 250);
--destructive: oklch(0.65 0.2 25);
--border: oklch(0.3 0.02 250);
--input: oklch(0.22 0.02 250);
--ring: oklch(0.55 0.02 250);
--popover: oklch(0.15 0.015 250);
--popover-foreground: oklch(0.96 0.005 250);
--sidebar: oklch(0.08 0.01 250);
--sidebar-foreground: oklch(0.96 0.005 250);
--sidebar-primary: oklch(0.6 0.12 250);
--sidebar-primary-foreground: oklch(0.1 0 0);
--sidebar-accent: oklch(0.22 0.02 250);
--sidebar-accent-foreground: oklch(0.96 0.005 250);
--sidebar-border: oklch(0.3 0.02 250);
--sidebar-ring: oklch(0.55 0.02 250);
}
```
---
## 💎 Thème BLUE - TEINTE 225° (Saturé)
**Description : Version plus vibrante et saturée de Slate, garde le côté bleu mais élégant**
### Light Mode
```css
[data-theme='blue'] {
--background: oklch(0.985 0.005 225); /* Blanc légèrement bleuté */
--foreground: oklch(0.18 0.035 225); /* Gris-bleu foncé saturé */
--card: oklch(1 0 0); /* Blanc pur */
--card-foreground: oklch(0.18 0.035 225);
--primary: oklch(0.5 0.15 225); /* Bleu vibrant */
--primary-foreground: oklch(0.99 0 0); /* Blanc */
--secondary: oklch(0.93 0.008 225);
--secondary-foreground: oklch(0.18 0.035 225);
--muted: oklch(0.9 0.01 225);
--muted-foreground: oklch(0.58 0.015 225);
--accent: oklch(0.93 0.01 225);
--accent-foreground: oklch(0.18 0.035 225);
--destructive: oklch(0.6 0.2 25);
--border: oklch(0.87 0.012 225);
--input: oklch(0.95 0.01 225);
--ring: oklch(0.65 0.015 225);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.18 0.035 225);
--sidebar: oklch(0.965 0.008 225);
--sidebar-foreground: oklch(0.18 0.035 225);
--sidebar-primary: oklch(0.5 0.15 225);
--sidebar-primary-foreground: oklch(0.99 0 0);
--sidebar-accent: oklch(0.93 0.01 225);
--sidebar-accent-foreground: oklch(0.18 0.035 225);
--sidebar-border: oklch(0.87 0.012 225);
--sidebar-ring: oklch(0.65 0.015 225);
}
```
### Dark Mode
```css
[data-theme='blue'].dark {
--background: oklch(0.13 0.008 225); /* Noir légèrement bleuté */
--foreground: oklch(0.97 0.006 225); /* Blanc légèrement bleuté */
--card: oklch(0.17 0.01 225); /* Gris-bleu foncé */
--card-foreground: oklch(0.97 0.006 225);
--primary: oklch(0.6 0.15 225); /* Bleu vibrant plus clair */
--primary-foreground: oklch(0.1 0 0); /* Noir */
--secondary: oklch(0.22 0.015 225);
--secondary-foreground: oklch(0.97 0.006 225);
--muted: oklch(0.25 0.02 225);
--muted-foreground: oklch(0.52 0.018 225);
--accent: oklch(0.25 0.025 225);
--accent-foreground: oklch(0.97 0.006 225);
--destructive: oklch(0.65 0.22 25);
--border: oklch(0.32 0.018 225);
--input: oklch(0.25 0.02 225);
--ring: oklch(0.55 0.02 225);
--popover: oklch(0.17 0.01 225);
--popover-foreground: oklch(0.97 0.006 225);
--sidebar: oklch(0.1 0.01 225);
--sidebar-foreground: oklch(0.97 0.006 225);
--sidebar-primary: oklch(0.6 0.15 225);
--sidebar-primary-foreground: oklch(0.1 0 0);
--sidebar-accent: oklch(0.25 0.025 225);
--sidebar-accent-foreground: oklch(0.97 0.006 225);
--sidebar-border: oklch(0.32 0.018 225);
--sidebar-ring: oklch(0.55 0.02 225);
}
```
---
## 📜 Thème SEPIA - TEINTE 45° (Chaleureux)
**Description : Version vintage chaleureuse avec une touche dorée/brun, garde le gris comme base**
### Light Mode
```css
[data-theme='sepia'] {
--background: oklch(0.985 0.004 45); /* Blanc légèrement doré */
--foreground: oklch(0.2 0.015 45); /* Gris-brun foncé */
--card: oklch(1 0 0); /* Blanc pur */
--card-foreground: oklch(0.2 0.015 45);
--primary: oklch(0.45 0.08 45); /* Gris-brun chaud */
--primary-foreground: oklch(0.99 0 0); /* Blanc */
--secondary: oklch(0.94 0.008 45);
--secondary-foreground: oklch(0.2 0.015 45);
--muted: oklch(0.91 0.01 45);
--muted-foreground: oklch(0.6 0.012 45);
--accent: oklch(0.93 0.01 45);
--accent-foreground: oklch(0.2 0.015 45);
--destructive: oklch(0.6 0.2 25);
--border: oklch(0.88 0.012 45);
--input: oklch(0.97 0.008 45);
--ring: oklch(0.68 0.01 45);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.2 0.015 45);
--sidebar: oklch(0.96 0.01 45);
--sidebar-foreground: oklch(0.2 0.015 45);
--sidebar-primary: oklch(0.45 0.08 45);
--sidebar-primary-foreground: oklch(0.99 0 0);
--sidebar-accent: oklch(0.93 0.01 45);
--sidebar-accent-foreground: oklch(0.2 0.015 45);
--sidebar-border: oklch(0.88 0.012 45);
--sidebar-ring: oklch(0.68 0.01 45);
}
```
### Dark Mode
```css
[data-theme='sepia'].dark {
--background: oklch(0.15 0.008 45); /* Noir légèrement bruni */
--foreground: oklch(0.97 0.005 45); /* Blanc légèrement bruni */
--card: oklch(0.19 0.01 45); /* Gris-brun foncé */
--card-foreground: oklch(0.97 0.005 45);
--primary: oklch(0.55 0.08 45); /* Gris-brun plus clair */
--primary-foreground: oklch(0.1 0 0); /* Noir */
--secondary: oklch(0.25 0.015 45);
--secondary-foreground: oklch(0.97 0.005 45);
--muted: oklch(0.23 0.02 45);
--muted-foreground: oklch(0.55 0.012 45);
--accent: oklch(0.27 0.018 45);
--accent-foreground: oklch(0.97 0.005 45);
--destructive: oklch(0.65 0.2 25);
--border: oklch(0.3 0.018 45);
--input: oklch(0.27 0.02 45);
--ring: oklch(0.58 0.02 45);
--popover: oklch(0.19 0.01 45);
--popover-foreground: oklch(0.97 0.005 45);
--sidebar: oklch(0.12 0.01 45);
--sidebar-foreground: oklch(0.97 0.005 45);
--sidebar-primary: oklch(0.55 0.08 45);
--sidebar-primary-foreground: oklch(0.1 0 0);
--sidebar-accent: oklch(0.27 0.018 45);
--sidebar-accent-foreground: oklch(0.97 0.005 45);
--sidebar-border: oklch(0.3 0.018 45);
--sidebar-ring: oklch(0.58 0.02 45);
}
```
---
## 🎨 Visualisation des Teintes
### Diagramme des teintes OKLCH :
```
45° (Sepia) 225° (Blue)
│ │
│ 230° (Slate) │
│ ← principal → │
↓ ↓
Gris-brun Gris-bleu
chaleureux saturé
250° (Midnight)
Gris-bleu
profond/sombre
```
### Relation entre les thèmes :
```
Slate (230°) ⭐ ← THÈME PRINCIPAL
├── Midnight (250°) : Version + sombre
├── Blue (225°) : Version + saturée
└── Sepia (45°) : Version chaleureuse
```
---
## ✅ Modifications à faire
Dans `keep-notes/app/globals.css` :
### 1. Remplacer les lignes 169-188 (midnight actuel)
Par le nouveau code **MIDNIGHT** ci-dessus
### 2. Remplacer les lignes 190-217 (blue actuel)
Par le nouveau code **BLUE** ci-dessus
### 3. Remplacer les lignes 219-238 (sepia actuel)
Par le nouveau code **SEPIA** ci-dessus
---
## 💬 Choix du thème
Utilisez ce tableau pour choisir :
| Pour l'utilisation de jour | Utilisez |
|-------------------------|------------|
| Travail standard, productivité | **Slate** (230°) ⭐ |
| Ambiance calme, liseuse | **Sepia** (45°) |
| Ambiance énergique, créative | **Blue** (225°) |
| Pour l'utilisation de nuit | Utilisez |
|-------------------------|------------|
| Nuit profonde, coding | **Midnight** (250°) |
| Nuit légère, confort | **Slate dark** (230°) |
---
**Tous les thèmes sont harmonisés !** 🎨
*Thèmes créés par Amelia - Developer Agent* 💻

View File

@ -0,0 +1,521 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Visualisation des Couleurs - Slate Moderne</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
padding: 40px;
background: #f5f5f5;
}
h1 {
text-align: center;
margin-bottom: 50px;
color: #333;
font-size: 32px;
}
h2 {
margin: 40px 0 20px 0;
color: #444;
font-size: 24px;
}
.section {
max-width: 1400px;
margin: 0 auto 60px;
}
.theme-comparison {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(600px, 1fr));
gap: 40px;
margin-bottom: 60px;
}
.theme-box {
background: white;
border-radius: 12px;
padding: 30px;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
}
.theme-header {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 2px solid #eee;
}
.theme-name {
font-size: 22px;
font-weight: bold;
}
.color-swatches {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
}
.swatch {
display: flex;
align-items: center;
gap: 15px;
padding: 15px;
border-radius: 8px;
background: #fafafa;
}
.color-box {
width: 80px;
height: 80px;
border-radius: 8px;
border: 2px solid #e0e0e0;
flex-shrink: 0;
}
.color-info {
flex: 1;
}
.color-label {
font-size: 14px;
color: #666;
margin-bottom: 5px;
}
.color-value {
font-size: 12px;
color: #999;
font-family: monospace;
}
.comparison-section {
background: white;
border-radius: 12px;
padding: 40px;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
}
.comparison-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 30px;
margin-top: 30px;
}
.comparison-item {
text-align: center;
}
.comparison-label {
font-size: 18px;
font-weight: bold;
margin-bottom: 15px;
color: #333;
}
.comparison-swatch {
height: 150px;
border-radius: 12px;
border: 3px solid #e0e0e0;
margin-bottom: 15px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
font-size: 16px;
padding: 10px;
}
.comparison-code {
font-family: monospace;
font-size: 13px;
color: #666;
background: #f5f5f5;
padding: 10px 15px;
border-radius: 6px;
display: inline-block;
}
.note-colors {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
}
.note-card {
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
.note-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 20px rgba(0,0,0,0.15);
}
.note-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 10px;
}
.note-content {
font-size: 14px;
color: #666;
}
.recommendation {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
border-radius: 12px;
text-align: center;
margin-bottom: 50px;
}
.recommendation h2 {
color: white;
margin: 0 0 20px 0;
}
.recommendation p {
font-size: 18px;
color: #f0f0f0;
}
.winner {
border: 3px solid #667eea;
}
</style>
</head>
<body>
<h1>🎨 Visualisation des Couleurs - Options Modernes</h1>
<div class="recommendation">
<h2>🏆 RECOMMANDATION PRINCIPALE : SLATE (GRIS-BLEU)</h2>
<p>Plus professionnel, moins fatigant, moderne - Sans dégradés !</p>
</div>
<!-- COMPARAISON : AVANT / APRÈS -->
<div class="section">
<h2>📊 Comparaison : Bleu Actuel vs Slate Moderne</h2>
<div class="comparison-section">
<div class="comparison-grid">
<div class="comparison-item">
<div class="comparison-label">Bleu Keep Actuel</div>
<div class="comparison-swatch" style="background: #356AC0;">
#356AC0
</div>
<span class="comparison-code">Bleu saturé</span>
</div>
<div class="comparison-item">
<div class="comparison-label">Slate Moderne (NOUVEAU)</div>
<div class="comparison-swatch" style="background: #7A8A9A;">
#7A8A9A
</div>
<span class="comparison-code">Gris-bleu élégant</span>
</div>
</div>
</div>
</div>
<!-- THEME SLATE - LIGHT MODE -->
<div class="section">
<h2>✨ Option 1 : Slate (Gris-Bleu) - Mode Light</h2>
<div class="theme-comparison">
<div class="theme-box winner">
<div class="theme-header">
<span class="theme-name">🏆 SLATE LIGHT</span>
</div>
<div class="color-swatches">
<div class="swatch">
<div class="color-box" style="background: #F8F9FB;"></div>
<div class="color-info">
<div class="color-label">Background</div>
<div class="color-value">#F8F9FB</div>
</div>
</div>
<div class="swatch">
<div class="color-box" style="background: #FFFFFF;"></div>
<div class="color-info">
<div class="color-label">Card</div>
<div class="color-value">#FFFFFF</div>
</div>
</div>
<div class="swatch">
<div class="color-box" style="background: #F3F4F6;"></div>
<div class="color-info">
<div class="color-label">Sidebar</div>
<div class="color-value">#F3F4F6</div>
</div>
</div>
<div class="swatch">
<div class="color-box" style="background: #3B4252;"></div>
<div class="color-info">
<div class="color-label">Texte (foreground)</div>
<div class="color-value">#3B4252</div>
</div>
</div>
<div class="swatch">
<div class="color-box" style="background: #7A8A9A;"></div>
<div class="color-info">
<div class="color-label">Primary (boutons)</div>
<div class="color-value">#7A8A9A</div>
</div>
</div>
<div class="swatch">
<div class="color-box" style="background: #E5E7EB;"></div>
<div class="color-info">
<div class="color-label">Border</div>
<div class="color-value">#E5E7EB</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- THEME SLATE - DARK MODE -->
<div class="section">
<h2>🌙 Option 1 : Slate (Gris-Bleu) - Mode Dark</h2>
<div class="theme-comparison">
<div class="theme-box winner">
<div class="theme-header">
<span class="theme-name">🏆 SLATE DARK</span>
</div>
<div class="color-swatches">
<div class="swatch">
<div class="color-box" style="background: #1F2937;"></div>
<div class="color-info">
<div class="color-label">Background</div>
<div class="color-value">#1F2937</div>
</div>
</div>
<div class="swatch">
<div class="color-box" style="background: #2D3748;"></div>
<div class="color-info">
<div class="color-label">Card</div>
<div class="color-value">#2D3748</div>
</div>
</div>
<div class="swatch">
<div class="color-box" style="background: #1A202C;"></div>
<div class="color-info">
<div class="color-label">Sidebar</div>
<div class="color-value">#1A202C</div>
</div>
</div>
<div class="swatch">
<div class="color-box" style="background: #F7FAFC;"></div>
<div class="color-info">
<div class="color-label">Texte (foreground)</div>
<div class="color-value">#F7FAFC</div>
</div>
</div>
<div class="swatch">
<div class="color-box" style="background: #9CA3AF;"></div>
<div class="color-info">
<div class="color-label">Primary (boutons)</div>
<div class="color-value">#9CA3AF</div>
</div>
</div>
<div class="swatch">
<div class="color-box" style="background: #4A5568;"></div>
<div class="color-info">
<div class="color-label">Border</div>
<div class="color-value">#4A5568</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- COULEURS DES NOTES -->
<div class="section">
<h2>📝 Couleurs des Notes (Light Mode)</h2>
<div class="note-colors">
<div class="note-card" style="background: #FFFFFF; border: 1px solid #E5E7EB;">
<div class="note-title">Default</div>
<div class="note-content">Note blanche standard</div>
</div>
<div class="note-card" style="background: #FEF2F2; border: 1px solid #FEE2E2;">
<div class="note-title" style="color: #7F1D1D;">Red</div>
<div class="note-content" style="color: #991B1B;">Note rouge</div>
</div>
<div class="note-card" style="background: #FFF7ED; border: 1px solid #FFEDD5;">
<div class="note-title" style="color: #9A3412;">Orange</div>
<div class="note-content" style="color: #C2410C;">Note orange</div>
</div>
<div class="note-card" style="background: #FEFCE8; border: 1px solid #FEF9C3;">
<div class="note-title" style="color: #854D0E;">Yellow</div>
<div class="note-content" style="color: #A16207;">Note jaune</div>
</div>
<div class="note-card" style="background: #ECFDF5; border: 1px solid #D1FAE5;">
<div class="note-title" style="color: #065F46;">Green (Emerald)</div>
<div class="note-content" style="color: #047857;">Note verte</div>
</div>
<div class="note-card" style="background: #F0FDFA; border: 1px solid #CCFBF1;">
<div class="note-title" style="color: #0F766E;">Teal</div>
<div class="note-content" style="color: #115E59;">Note teal</div>
</div>
<div class="note-card" style="background: #EFF6FF; border: 1px solid #DBEAFE;">
<div class="note-title" style="color: #1E40AF;">Blue (Sky)</div>
<div class="note-content" style="color: #1D4ED8;">Note bleue</div>
</div>
<div class="note-card" style="background: #EEF2FF; border: 1px solid #E0E7FF;">
<div class="note-title" style="color: #4338CA;">Indigo</div>
<div class="note-content" style="color: #4338CA;">Note indigo</div>
</div>
<div class="note-card" style="background: #F5F3FF; border: 1px solid #EDE9FE;">
<div class="note-title" style="color: #7C3AED;">Violet</div>
<div class="note-content" style="color: #7C3AED;">Note violette</div>
</div>
<div class="note-card" style="background: #FAF5FF; border: 1px solid #F3E8FF;">
<div class="note-title" style="color: #9333EA;">Purple</div>
<div class="note-content" style="color: #9333EA;">Note pourpre</div>
</div>
<div class="note-card" style="background: #FDF2F8; border: 1px solid #FCE7F3;">
<div class="note-title" style="color: #BE185D;">Pink</div>
<div class="note-content" style="color: #BE185D;">Note rose</div>
</div>
<div class="note-card" style="background: #FFF1F2; border: 1px solid #FFE4E6;">
<div class="note-title" style="color: #E11D48;">Rose</div>
<div class="note-content" style="color: #E11D48;">Note rose rougeâtre</div>
</div>
<div class="note-card" style="background: #F5F5F4; border: 1px solid #E7E5E4;">
<div class="note-title" style="color: #71717A;">Gray</div>
<div class="note-content" style="color: #71717A;">Note grise</div>
</div>
</div>
</div>
<!-- AUTRES OPTIONS -->
<div class="section">
<h2>🔄 Autres Options de Thème</h2>
<div class="theme-comparison">
<div class="theme-box">
<div class="theme-header">
<span class="theme-name">⚫ MONOCHROME</span>
</div>
<div class="color-swatches">
<div class="swatch">
<div class="color-box" style="background: #FFFFFF;"></div>
<div class="color-info">
<div class="color-label">Background</div>
<div class="color-value">#FFFFFF</div>
</div>
</div>
<div class="swatch">
<div class="color-box" style="background: #262626;"></div>
<div class="color-info">
<div class="color-label">Primary</div>
<div class="color-value">#262626</div>
</div>
</div>
</div>
</div>
<div class="theme-box">
<div class="theme-header">
<span class="theme-name">💜 INDIGO (Violet)</span>
</div>
<div class="color-swatches">
<div class="swatch">
<div class="color-box" style="background: #F9F9FB;"></div>
<div class="color-info">
<div class="color-label">Background</div>
<div class="color-value">#F9F9FB</div>
</div>
</div>
<div class="swatch">
<div class="color-box" style="background: #7C3AED;"></div>
<div class="color-info">
<div class="color-label">Primary</div>
<div class="color-value">#7C3AED</div>
</div>
</div>
</div>
</div>
<div class="theme-box">
<div class="theme-header">
<span class="theme-name">🌊 TEAL (Turquoise)</span>
</div>
<div class="color-swatches">
<div class="swatch">
<div class="color-box" style="background: #F9FAFB;"></div>
<div class="color-info">
<div class="color-label">Background</div>
<div class="color-value">#F9FAFB</div>
</div>
</div>
<div class="swatch">
<div class="color-box" style="background: #0F766E;"></div>
<div class="color-info">
<div class="color-label">Primary</div>
<div class="color-value">#0F766E</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="section" style="text-align: center; margin-top: 60px;">
<h2>💬 Votre Choix Ramez ?</h2>
<p style="font-size: 18px; color: #666; margin-bottom: 30px;">
Regardez les couleurs, choisissez ce que vous préférez, puis dites-moi :
</p>
<div style="display: flex; gap: 20px; justify-content: center; flex-wrap: wrap;">
<div style="background: #10B981; color: white; padding: 15px 30px; border-radius: 8px; font-weight: bold; font-size: 16px;">
✅ SLATE (mon choix)
</div>
<div style="background: #6366F1; color: white; padding: 15px 30px; border-radius: 8px; font-weight: bold; font-size: 16px;">
💜 INDIGO
</div>
<div style="background: #0F766E; color: white; padding: 15px 30px; border-radius: 8px; font-weight: bold; font-size: 16px;">
🌊 TEAL
</div>
<div style="background: #262626; color: white; padding: 15px 30px; border-radius: 8px; font-weight: bold; font-size: 16px;">
⚫ MONOCHROME
</div>
</div>
</div>
<div style="text-align: center; margin-top: 40px; padding-bottom: 40px; color: #999; font-size: 14px;">
<p>💡 Ouvrez ce fichier dans votre navigateur pour voir toutes les couleurs !</p>
<p>Double-cliquez sur : keep-notes/VISUALISATION-COULEURS.html</p>
</div>
</body>
</html>

View File

@ -177,7 +177,7 @@ export function AI_TESTER({ type }: { type: 'tags' | 'embeddings' }) {
{type === 'tags' && result.success && result.tags && (
<div className="space-y-3">
<div className="flex items-center gap-2">
<Info className="h-4 w-4 text-blue-600" />
<Info className="h-4 w-4 text-primary" />
<span className="text-sm font-medium">Generated Tags:</span>
</div>
<div className="flex flex-wrap gap-2">
@ -247,7 +247,7 @@ export function AI_TESTER({ type }: { type: 'tags' | 'embeddings' }) {
{/* Loading State */}
{isLoading && (
<div className="text-center py-4">
<Loader2 className="h-8 w-8 animate-spin mx-auto text-blue-600" />
<Loader2 className="h-8 w-8 animate-spin mx-auto text-primary" />
<p className="text-sm text-muted-foreground mt-2">
Testing {type === 'tags' ? 'tags generation' : 'embeddings'}...
</p>

View File

@ -36,8 +36,8 @@ export default async function AITestPage() {
<div className="grid gap-6 md:grid-cols-2">
{/* Tags Provider Test */}
<Card className="border-blue-200 dark:border-blue-900">
<CardHeader className="bg-blue-50/50 dark:bg-blue-950/20">
<Card className="border-primary/20 dark:border-primary/30">
<CardHeader className="bg-primary/5 dark:bg-primary/10">
<CardTitle className="flex items-center gap-2">
<span className="text-2xl">🏷</span>
Tags Generation Test

View File

@ -21,7 +21,7 @@ export default async function AdminAIPage() {
title: 'Avg Response Time',
value: '1.2s',
trend: { value: 5, isPositive: true },
icon: <Activity className="h-5 w-5 text-blue-600 dark:text-blue-400" />,
icon: <Activity className="h-5 w-5 text-primary dark:text-primary-foreground" />,
},
{
title: 'Active Features',
@ -103,7 +103,7 @@ export default async function AdminAIPage() {
className={`px-2 py-1 text-xs font-medium rounded-full ${
provider.status === 'Connected'
? 'text-green-700 dark:text-green-400 bg-green-100 dark:bg-green-900'
: 'text-blue-700 dark:text-blue-400 bg-blue-100 dark:bg-blue-900'
: 'text-primary dark:text-primary-foreground bg-primary/10 dark:bg-primary/20'
}`}
>
{provider.status}

View File

@ -11,7 +11,7 @@ export default async function AdminPage() {
title: 'Total Users',
value: users.length,
trend: { value: 12, isPositive: true },
icon: <Users className="h-5 w-5 text-blue-600 dark:text-blue-400" />,
icon: <Users className="h-5 w-5 text-primary dark:text-primary-foreground" />,
},
{
title: 'Active Sessions',

View File

@ -213,9 +213,9 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
<form action={handleSaveAI}>
<CardContent className="space-y-6">
{/* Tags Generation Section */}
<div className="space-y-4 p-4 border rounded-lg bg-blue-50/50 dark:bg-blue-950/20">
<div className="space-y-4 p-4 border rounded-lg bg-primary/5 dark:bg-primary/10">
<h3 className="text-base font-semibold flex items-center gap-2">
<span className="text-blue-600">🏷</span> Tags Generation Provider
<span className="text-primary">🏷</span> Tags Generation Provider
</h3>
<p className="text-xs text-muted-foreground">AI provider for automatic tag suggestions. Recommended: Ollama (free, local).</p>
@ -264,7 +264,7 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
<div className="space-y-2">
<Label htmlFor="OPENAI_API_KEY">API Key</Label>
<Input id="OPENAI_API_KEY" name="OPENAI_API_KEY" type="password" defaultValue={config.OPENAI_API_KEY || ''} placeholder="sk-..." />
<p className="text-xs text-muted-foreground">Your OpenAI API key from <a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:underline">platform.openai.com</a></p>
<p className="text-xs text-muted-foreground">Your OpenAI API key from <a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">platform.openai.com</a></p>
</div>
<div className="space-y-2">
<Label htmlFor="AI_MODEL_TAGS_OPENAI">Model</Label>
@ -278,7 +278,7 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
<option key={model} value={model}>{model}</option>
))}
</select>
<p className="text-xs text-muted-foreground"><strong className="text-green-600">gpt-4o-mini</strong> = Best value <strong className="text-blue-600">gpt-4o</strong> = Best quality</p>
<p className="text-xs text-muted-foreground"><strong className="text-green-600">gpt-4o-mini</strong> = Best value <strong className="text-primary">gpt-4o</strong> = Best quality</p>
</div>
</div>
)}
@ -372,7 +372,7 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
<div className="space-y-2">
<Label htmlFor="OPENAI_API_KEY">API Key</Label>
<Input id="OPENAI_API_KEY" name="OPENAI_API_KEY" type="password" defaultValue={config.OPENAI_API_KEY || ''} placeholder="sk-..." />
<p className="text-xs text-muted-foreground">Your OpenAI API key from <a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:underline">platform.openai.com</a></p>
<p className="text-xs text-muted-foreground">Your OpenAI API key from <a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">platform.openai.com</a></p>
</div>
<div className="space-y-2">
<Label htmlFor="AI_MODEL_EMBEDDING_OPENAI">Model</Label>
@ -386,7 +386,7 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
<option key={model} value={model}>{model}</option>
))}
</select>
<p className="text-xs text-muted-foreground"><strong className="text-green-600">text-embedding-3-small</strong> = Best value <strong className="text-blue-600">text-embedding-3-large</strong> = Best quality</p>
<p className="text-xs text-muted-foreground"><strong className="text-green-600">text-embedding-3-small</strong> = Best value <strong className="text-primary">text-embedding-3-large</strong> = Best quality</p>
</div>
</div>
)}

View File

@ -26,7 +26,7 @@ import { cn } from '@/lib/utils'
import { LabelFilter } from '@/components/label-filter'
export default function HomePage() {
console.log('[HomePage] Component rendering')
const searchParams = useSearchParams()
const router = useRouter()
// Force re-render when search params change (for filtering)
@ -58,7 +58,7 @@ export default function HomePage() {
// Callback for NoteInput to trigger notebook suggestion and update UI
const handleNoteCreated = useCallback((note: Note) => {
console.log('[NotebookSuggestion] Note created:', { id: note.id, notebookId: note.notebookId, contentLength: note.content?.length })
// Update UI immediately by adding the note to the list if it matches current filters
setNotes((prevNotes) => {
@ -120,19 +120,19 @@ export default function HomePage() {
// Only suggest if note has no notebook and has 20+ words
if (!note.notebookId) {
const wordCount = (note.content || '').trim().split(/\s+/).filter(w => w.length > 0).length
console.log('[NotebookSuggestion] Word count:', wordCount)
if (wordCount >= 20) {
console.log('[NotebookSuggestion] Triggering suggestion for note:', note.id)
setNotebookSuggestion({
noteId: note.id,
content: note.content || ''
})
} else {
console.log('[NotebookSuggestion] Not enough words, need 20+')
}
} else {
console.log('[NotebookSuggestion] Note has notebook, skipping')
}
// Refresh in background to ensure data consistency (non-blocking)
@ -265,10 +265,10 @@ export default function HomePage() {
// Helper for Breadcrumbs
const Breadcrumbs = ({ notebookName }: { notebookName: string }) => (
<div className="flex items-center gap-2 text-sm text-gray-500 mb-1">
<div className="flex items-center gap-2 text-sm text-muted-foreground mb-1">
<span>Notebooks</span>
<ChevronRight className="w-4 h-4" />
<span className="font-medium text-blue-600">{notebookName}</span>
<span className="font-medium text-primary">{notebookName}</span>
</div>
)
@ -283,12 +283,12 @@ export default function HomePage() {
<div className="flex items-start justify-between">
{/* Title Section */}
<div className="flex items-center gap-5">
<div className="p-3 bg-blue-50 dark:bg-blue-900/20 rounded-xl">
<div className="p-3 bg-primary/10 dark:bg-primary/20 rounded-xl">
{(() => {
const Icon = getNotebookIcon(currentNotebook.icon || 'folder')
return (
<Icon
className={cn("w-8 h-8", !currentNotebook.color && "text-blue-600 dark:text-blue-400")}
className={cn("w-8 h-8", !currentNotebook.color && "text-primary dark:text-primary-foreground")}
style={currentNotebook.color ? { color: currentNotebook.color } : undefined}
/>
)
@ -311,7 +311,7 @@ export default function HomePage() {
/>
<Button
onClick={() => setShowNoteInput(!showNoteInput)}
className="h-10 px-6 rounded-full bg-blue-600 hover:bg-blue-700 text-white font-medium shadow-sm gap-2 transition-all"
className="h-10 px-6 rounded-full bg-primary hover:bg-primary/90 text-primary-foreground font-medium shadow-sm gap-2 transition-all"
>
<Plus className="w-5 h-5" />
Add Note
@ -329,7 +329,7 @@ export default function HomePage() {
{/* Title Section */}
<div className="flex items-center gap-5">
<div className="p-3 bg-white border border-gray-100 dark:bg-gray-800 dark:border-gray-700 rounded-xl shadow-sm">
<FileText className="w-8 h-8 text-blue-600" />
<FileText className="w-8 h-8 text-primary" />
</div>
<h1 className="text-4xl font-bold text-gray-900 dark:text-white tracking-tight">Notes</h1>
</div>
@ -362,7 +362,7 @@ export default function HomePage() {
<Button
onClick={() => setShowNoteInput(!showNoteInput)}
className="h-10 px-6 rounded-full bg-blue-600 hover:bg-blue-700 text-white font-medium shadow-sm gap-2 transition-all"
className="h-10 px-6 rounded-full bg-primary hover:bg-primary/90 text-primary-foreground font-medium shadow-sm gap-2 transition-all"
>
<Plus className="w-5 h-5" />
Add Note

View File

@ -30,13 +30,15 @@ export function AppearanceSettingsForm({ initialTheme, initialFontSize }: Appear
if (window.matchMedia('(prefers-color-scheme: dark)').matches) root.classList.add('dark')
} else if (value === 'dark') {
root.classList.add('dark')
} else if (value === 'light') {
root.setAttribute('data-theme', 'light')
} else {
root.setAttribute('data-theme', value)
if (['midnight'].includes(value)) root.classList.add('dark')
if (['midnight', 'blue', 'sepia'].includes(value)) root.classList.add('dark')
}
// Save to DB (no need for router.refresh - localStorage handles immediate visuals)
await updateUser({ theme: value as 'light' | 'dark' | 'auto' | 'sepia' | 'midnight' })
await updateUser({ theme: value as 'light' | 'dark' | 'auto' | 'sepia' | 'midnight' | 'blue' })
}
const handleFontSizeChange = async (value: string) => {
@ -71,10 +73,11 @@ export function AppearanceSettingsForm({ initialTheme, initialFontSize }: Appear
description="Select app's visual theme"
value={theme}
options={[
{ value: 'light', label: 'Light' },
{ value: 'slate', label: 'Light' },
{ value: 'dark', label: 'Dark' },
{ value: 'sepia', label: 'Sepia' },
{ value: 'midnight', label: 'Midnight' },
{ value: 'blue', label: 'Blue' },
{ value: 'auto', label: 'Auto (system)' },
]}
onChange={handleThemeChange}

View File

@ -100,7 +100,7 @@ export default function SettingsPage() {
</Link>
<Link href="/settings/profile">
<div className="p-4 border rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors cursor-pointer">
<RefreshCw className="h-6 w-6 text-blue-500 mb-2" />
<RefreshCw className="h-6 w-6 text-primary mb-2" />
<h3 className="font-semibold">Profile Settings</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
Manage your account and preferences

View File

@ -226,8 +226,8 @@ export async function searchNotes(query: string, useSemantic: boolean = false, n
// Check if query exists in title, content, or any label
return title.includes(queryLower) ||
content.includes(queryLower) ||
labels.some((label: string) => label.toLowerCase().includes(queryLower));
content.includes(queryLower) ||
labels.some((label: string) => label.toLowerCase().includes(queryLower));
});
return filteredNotes.map(parseNote);
@ -269,8 +269,8 @@ async function semanticSearch(query: string, userId: string, notebookId?: string
// Keyword match
const keywordMatch = title.includes(queryLower) ||
content.includes(queryLower) ||
labels.some((l: string) => l.toLowerCase().includes(queryLower));
content.includes(queryLower) ||
labels.some((l: string) => l.toLowerCase().includes(queryLower));
// Semantic match (if embedding available)
let semanticMatch = false;
@ -455,6 +455,9 @@ export async function updateNote(id: string, data: {
if ('images' in data) updateData.images = data.images ? JSON.stringify(data.images) : null
if ('links' in data) updateData.links = data.links ? JSON.stringify(data.links) : null
if ('notebookId' in data) updateData.notebookId = data.notebookId
// Explicitly handle size to ensure it propagates
if ('size' in data && data.size) updateData.size = data.size
updateData.updatedAt = new Date()
const note = await prisma.note.update({
@ -469,17 +472,22 @@ export async function updateNote(id: string, data: {
}
// IMPORTANT: Call revalidatePath to ensure UI updates
// Revalidate main page, the note itself, and both old and new notebook paths
revalidatePath('/')
revalidatePath(`/note/${id}`)
// BUT skip if only updating size (let optimistic UI handle it)
const isSizeOnlyUpdate = Object.keys(data).length === 1 && 'size' in data
// If notebook changed, revalidate both notebook paths
if (data.notebookId !== undefined && data.notebookId !== oldNotebookId) {
if (oldNotebookId) {
revalidatePath(`/notebook/${oldNotebookId}`)
}
if (data.notebookId) {
revalidatePath(`/notebook/${data.notebookId}`)
if (!isSizeOnlyUpdate) {
// Revalidate main page, the note itself, and both old and new notebook paths
revalidatePath('/')
revalidatePath(`/note/${id}`)
// If notebook changed, revalidate both notebook paths
if (data.notebookId !== undefined && data.notebookId !== oldNotebookId) {
if (oldNotebookId) {
revalidatePath(`/notebook/${oldNotebookId}`)
}
if (data.notebookId) {
revalidatePath(`/notebook/${data.notebookId}`)
}
}
}
@ -517,10 +525,12 @@ export async function updateColor(id: string, color: string) { return updateNote
export async function updateLabels(id: string, labels: string[]) { return updateNote(id, { labels }) }
export async function removeFusedBadge(id: string) { return updateNote(id, { autoGenerated: null }) }
// Update note size with revalidation
// Update note size WITHOUT revalidation - client uses optimistic updates
export async function updateSize(id: string, size: 'small' | 'medium' | 'large') {
await updateNote(id, { size })
revalidatePath('/')
const result = await updateNote(id, { size })
return result
}
// Get all unique labels
@ -711,7 +721,7 @@ export async function syncAllEmbeddings() {
await prisma.note.update({ where: { id: note.id }, data: { embedding: JSON.stringify(embedding) } })
updatedCount++;
}
} catch (e) {}
} catch (e) { }
}
return { success: true, count: updatedCount }
} catch (error: any) {

View File

@ -5,7 +5,7 @@ import { prisma } from '@/lib/prisma'
import { revalidatePath } from 'next/cache'
export type UserSettingsData = {
theme?: 'light' | 'dark' | 'auto' | 'sepia' | 'midnight'
theme?: 'light' | 'dark' | 'auto' | 'sepia' | 'midnight' | 'blue'
}
/**
@ -60,7 +60,7 @@ export async function getUserSettings(userId?: string) {
})
return {
theme: (user?.theme || 'light') as 'light' | 'dark' | 'auto'
theme: (user?.theme || 'light') as 'light' | 'dark' | 'auto' | 'sepia' | 'midnight' | 'blue'
}
} catch (error) {
console.error('Error getting user settings:', error)

View File

@ -13,7 +13,7 @@
--breakpoint-ultra-wide: 1920px;
/* Custom colors matching Keep design */
--color-primary: #356ac0;
--color-primary: #64748b;
--color-background-light: #f7f7f8;
--color-background-dark: #1a1d23;
}
@ -99,142 +99,307 @@
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--background: oklch(0.985 0.003 230); /* Blanc grisâtre */
--foreground: oklch(0.2 0.02 230); /* Gris-bleu foncé */
--card: oklch(1 0 0); /* Blanc pur */
--card-foreground: oklch(0.2 0.02 230);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--popover-foreground: oklch(0.2 0.02 230);
--primary: oklch(0.45 0.08 230); /* Gris-bleu doux */
--primary-foreground: oklch(0.99 0 0); /* Blanc */
--secondary: oklch(0.94 0.005 230); /* Gris-bleu très pâle */
--secondary-foreground: oklch(0.2 0.02 230);
--muted: oklch(0.92 0.005 230);
--muted-foreground: oklch(0.6 0.01 230);
--accent: oklch(0.94 0.005 230);
--accent-foreground: oklch(0.2 0.02 230);
--destructive: oklch(0.6 0.18 25); /* Rouge */
--border: oklch(0.9 0.008 230); /* Gris-bleu très clair */
--input: oklch(0.98 0.003 230);
--ring: oklch(0.7 0.005 230);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
--sidebar: oklch(0.97 0.004 230);
--sidebar-foreground: oklch(0.2 0.02 230);
--sidebar-primary: oklch(0.45 0.08 230);
--sidebar-primary-foreground: oklch(0.99 0 0);
--sidebar-accent: oklch(0.94 0.005 230);
--sidebar-accent-foreground: oklch(0.2 0.02 230);
--sidebar-border: oklch(0.9 0.008 230);
--sidebar-ring: oklch(0.7 0.005 230);
}
[data-theme='light'] {
--background: oklch(0.985 0.003 230); /* Blanc grisâtre */
--foreground: oklch(0.2 0.02 230); /* Gris-bleu foncé */
--card: oklch(1 0 0); /* Blanc pur */
--card-foreground: oklch(0.2 0.02 230);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.2 0.02 230);
--primary: oklch(0.45 0.08 230); /* Gris-bleu doux */
--primary-foreground: oklch(0.99 0 0); /* Blanc */
--secondary: oklch(0.94 0.005 230); /* Gris-bleu très pâle */
--secondary-foreground: oklch(0.2 0.02 230);
--muted: oklch(0.92 0.005 230);
--muted-foreground: oklch(0.6 0.01 230);
--accent: oklch(0.94 0.005 230);
--accent-foreground: oklch(0.2 0.02 230);
--destructive: oklch(0.6 0.18 25); /* Rouge */
--border: oklch(0.9 0.008 230); /* Gris-bleu très clair */
--input: oklch(0.98 0.003 230);
--ring: oklch(0.7 0.005 230);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.2 0.02 230);
--sidebar: oklch(0.97 0.004 230);
--sidebar-foreground: oklch(0.2 0.02 230);
--sidebar-primary: oklch(0.45 0.08 230);
--sidebar-primary-foreground: oklch(0.99 0 0);
--sidebar-accent: oklch(0.94 0.005 230);
--sidebar-accent-foreground: oklch(0.2 0.02 230);
--sidebar-border: oklch(0.9 0.008 230);
--sidebar-ring: oklch(0.7 0.005 230);
}
[data-theme='light'].dark {
--background: oklch(0.14 0.005 230); /* Noir grisâtre */
--foreground: oklch(0.97 0.003 230); /* Blanc grisâtre */
--card: oklch(0.18 0.006 230); /* Gris-bleu foncé */
--card-foreground: oklch(0.97 0.003 230);
--popover: oklch(0.18 0.006 230);
--popover-foreground: oklch(0.97 0.003 230);
--primary: oklch(0.55 0.08 230); /* Gris-bleu plus clair */
--primary-foreground: oklch(0.1 0 0); /* Noir */
--secondary: oklch(0.24 0.006 230);
--secondary-foreground: oklch(0.97 0.003 230);
--muted: oklch(0.22 0.006 230);
--muted-foreground: oklch(0.55 0.01 230);
--accent: oklch(0.24 0.006 230);
--accent-foreground: oklch(0.97 0.003 230);
--destructive: oklch(0.65 0.18 25);
--border: oklch(0.28 0.01 230);
--input: oklch(0.2 0.006 230);
--ring: oklch(0.6 0.01 230);
--popover: oklch(0.18 0.006 230);
--popover-foreground: oklch(0.97 0.003 230);
--sidebar: oklch(0.12 0.005 230);
--sidebar-foreground: oklch(0.97 0.003 230);
--sidebar-primary: oklch(0.55 0.08 230);
--sidebar-primary-foreground: oklch(0.1 0 0);
--sidebar-accent: oklch(0.24 0.006 230);
--sidebar-accent-foreground: oklch(0.97 0.003 230);
--sidebar-border: oklch(0.28 0.01 230);
--sidebar-ring: oklch(0.6 0.01 230);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--background: oklch(0.14 0.005 230); /* Noir grisâtre */
--foreground: oklch(0.97 0.003 230); /* Blanc grisâtre */
--card: oklch(0.18 0.006 230); /* Gris-bleu foncé */
--card-foreground: oklch(0.97 0.003 230);
--popover: oklch(0.18 0.006 230);
--popover-foreground: oklch(0.97 0.003 230);
--primary: oklch(0.55 0.08 230); /* Gris-bleu plus clair */
--primary-foreground: oklch(0.1 0 0); /* Noir */
--secondary: oklch(0.24 0.006 230);
--secondary-foreground: oklch(0.97 0.003 230);
--muted: oklch(0.22 0.006 230);
--muted-foreground: oklch(0.55 0.01 230);
--accent: oklch(0.24 0.006 230);
--accent-foreground: oklch(0.97 0.003 230);
--destructive: oklch(0.65 0.18 25);
--border: oklch(0.28 0.01 230);
--input: oklch(0.2 0.006 230);
--ring: oklch(0.6 0.01 230);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
--sidebar: oklch(0.12 0.005 230);
--sidebar-foreground: oklch(0.97 0.003 230);
--sidebar-primary: oklch(0.55 0.08 230);
--sidebar-primary-foreground: oklch(0.1 0 0);
--sidebar-accent: oklch(0.24 0.006 230);
--sidebar-accent-foreground: oklch(0.97 0.003 230);
--sidebar-border: oklch(0.28 0.01 230);
--sidebar-ring: oklch(0.6 0.01 230);
}
[data-theme='midnight'] {
--background: oklch(0.18 0.04 260);
--foreground: oklch(0.985 0 0);
--card: oklch(0.22 0.05 260);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.22 0.05 260);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.7 0.15 260);
--primary-foreground: oklch(0.18 0.04 260);
--secondary: oklch(0.28 0.05 260);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.28 0.05 260);
--muted-foreground: oklch(0.8 0.05 260);
--accent: oklch(0.28 0.05 260);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.6 0.25 25);
--border: oklch(0.3 0.05 260);
--input: oklch(0.3 0.05 260);
--ring: oklch(0.7 0.15 260);
--background: oklch(0.94 0.005 250); /* Gris-bleu très pâle */
--foreground: oklch(0.18 0.03 250); /* Gris-bleu très foncé */
--card: oklch(0.97 0.006 250); /* Gris-bleu pâle */
--card-foreground: oklch(0.18 0.03 250);
--primary: oklch(0.5 0.12 250); /* Gris-bleu saturé */
--primary-foreground: oklch(0.99 0 0); /* Blanc */
--secondary: oklch(0.2 0.01 250);
--secondary-foreground: oklch(0.18 0.03 250);
--muted: oklch(0.22 0.01 250);
--muted-foreground: oklch(0.55 0.02 250);
--accent: oklch(0.25 0.015 250);
--accent-foreground: oklch(0.18 0.03 250);
--destructive: oklch(0.6 0.22 25);
--border: oklch(0.85 0.015 250);
--input: oklch(0.25 0.01 250);
--ring: oklch(0.65 0.015 250);
--popover: oklch(0.97 0.006 250);
--popover-foreground: oklch(0.18 0.03 250);
--sidebar: oklch(0.9 0.01 250);
--sidebar-foreground: oklch(0.18 0.03 250);
--sidebar-primary: oklch(0.5 0.12 250);
--sidebar-primary-foreground: oklch(0.99 0 0);
--sidebar-accent: oklch(0.25 0.015 250);
--sidebar-accent-foreground: oklch(0.18 0.03 250);
--sidebar-border: oklch(0.85 0.015 250);
--sidebar-ring: oklch(0.65 0.015 250);
}
[data-theme='midnight'].dark {
--background: oklch(0.1 0.01 250); /* Noir profond */
--foreground: oklch(0.96 0.005 250); /* Blanc grisâtre */
--card: oklch(0.15 0.015 250); /* Gris-bleu très foncé */
--card-foreground: oklch(0.96 0.005 250);
--primary: oklch(0.6 0.12 250); /* Gris-bleu vibrant */
--primary-foreground: oklch(0.1 0 0); /* Noir */
--secondary: oklch(0.18 0.015 250);
--secondary-foreground: oklch(0.96 0.005 250);
--muted: oklch(0.2 0.015 250);
--muted-foreground: oklch(0.5 0.02 250);
--accent: oklch(0.22 0.02 250);
--accent-foreground: oklch(0.96 0.005 250);
--destructive: oklch(0.65 0.2 25);
--border: oklch(0.3 0.02 250);
--input: oklch(0.22 0.02 250);
--ring: oklch(0.55 0.02 250);
--popover: oklch(0.15 0.015 250);
--popover-foreground: oklch(0.96 0.005 250);
--sidebar: oklch(0.08 0.01 250);
--sidebar-foreground: oklch(0.96 0.005 250);
--sidebar-primary: oklch(0.6 0.12 250);
--sidebar-primary-foreground: oklch(0.1 0 0);
--sidebar-accent: oklch(0.22 0.02 250);
--sidebar-accent-foreground: oklch(0.96 0.005 250);
--sidebar-border: oklch(0.3 0.02 250);
--sidebar-ring: oklch(0.55 0.02 250);
}
[data-theme='blue'] {
--background: oklch(0.96 0.02 240);
--foreground: oklch(0.15 0.05 240);
--card: oklch(0.98 0.01 240);
--card-foreground: oklch(0.15 0.05 240);
--popover: oklch(0.98 0.01 240);
--popover-foreground: oklch(0.15 0.05 240);
--primary: oklch(0.45 0.15 240);
--primary-foreground: oklch(0.98 0.01 240);
--secondary: oklch(0.92 0.03 240);
--secondary-foreground: oklch(0.15 0.05 240);
--muted: oklch(0.92 0.03 240);
--muted-foreground: oklch(0.5 0.05 240);
--accent: oklch(0.92 0.03 240);
--accent-foreground: oklch(0.15 0.05 240);
--background: oklch(0.985 0.005 225); /* Blanc légèrement bleuté */
--foreground: oklch(0.18 0.035 225); /* Gris-bleu foncé saturé */
--card: oklch(1 0 0); /* Blanc pur */
--card-foreground: oklch(0.18 0.035 225);
--primary: oklch(0.5 0.15 225); /* Bleu vibrant */
--primary-foreground: oklch(0.99 0 0); /* Blanc */
--secondary: oklch(0.93 0.008 225);
--secondary-foreground: oklch(0.18 0.035 225);
--muted: oklch(0.9 0.01 225);
--muted-foreground: oklch(0.58 0.015 225);
--accent: oklch(0.93 0.01 225);
--accent-foreground: oklch(0.18 0.035 225);
--destructive: oklch(0.6 0.2 25);
--border: oklch(0.85 0.05 240);
--input: oklch(0.85 0.05 240);
--ring: oklch(0.45 0.15 240);
--sidebar: oklch(0.95 0.02 240);
--sidebar-foreground: oklch(0.15 0.05 240);
--sidebar-primary: oklch(0.45 0.15 240);
--sidebar-primary-foreground: oklch(0.98 0.01 240);
--sidebar-accent: oklch(0.92 0.03 240);
--sidebar-accent-foreground: oklch(0.15 0.05 240);
--sidebar-border: oklch(0.85 0.05 240);
--sidebar-ring: oklch(0.45 0.15 240);
--border: oklch(0.87 0.012 225);
--input: oklch(0.95 0.01 225);
--ring: oklch(0.65 0.015 225);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.18 0.035 225);
--sidebar: oklch(0.965 0.008 225);
--sidebar-foreground: oklch(0.18 0.035 225);
--sidebar-primary: oklch(0.5 0.15 225);
--sidebar-primary-foreground: oklch(0.99 0 0);
--sidebar-accent: oklch(0.93 0.01 225);
--sidebar-accent-foreground: oklch(0.18 0.035 225);
--sidebar-border: oklch(0.87 0.012 225);
--sidebar-ring: oklch(0.65 0.015 225);
}
[data-theme='blue'].dark {
--background: oklch(0.13 0.008 225); /* Noir légèrement bleuté */
--foreground: oklch(0.97 0.006 225); /* Blanc légèrement bleuté */
--card: oklch(0.17 0.01 225); /* Gris-bleu foncé */
--card-foreground: oklch(0.97 0.006 225);
--primary: oklch(0.6 0.15 225); /* Bleu vibrant plus clair */
--primary-foreground: oklch(0.1 0 0); /* Noir */
--secondary: oklch(0.22 0.015 225);
--secondary-foreground: oklch(0.97 0.006 225);
--muted: oklch(0.25 0.02 225);
--muted-foreground: oklch(0.52 0.018 225);
--accent: oklch(0.25 0.025 225);
--accent-foreground: oklch(0.97 0.006 225);
--destructive: oklch(0.65 0.22 25);
--border: oklch(0.32 0.018 225);
--input: oklch(0.25 0.02 225);
--ring: oklch(0.55 0.02 225);
--popover: oklch(0.17 0.01 225);
--popover-foreground: oklch(0.97 0.006 225);
--sidebar: oklch(0.1 0.01 225);
--sidebar-foreground: oklch(0.97 0.006 225);
--sidebar-primary: oklch(0.6 0.15 225);
--sidebar-primary-foreground: oklch(0.1 0 0);
--sidebar-accent: oklch(0.25 0.025 225);
--sidebar-accent-foreground: oklch(0.97 0.006 225);
--sidebar-border: oklch(0.32 0.018 225);
--sidebar-ring: oklch(0.55 0.02 225);
}
[data-theme='sepia'] {
--background: oklch(0.96 0.02 85);
--foreground: oklch(0.25 0.02 85);
--card: oklch(0.98 0.01 85);
--card-foreground: oklch(0.25 0.02 85);
--popover: oklch(0.98 0.01 85);
--popover-foreground: oklch(0.25 0.02 85);
--primary: oklch(0.45 0.1 35);
--primary-foreground: oklch(0.98 0.01 85);
--secondary: oklch(0.92 0.03 85);
--secondary-foreground: oklch(0.25 0.02 85);
--muted: oklch(0.92 0.03 85);
--muted-foreground: oklch(0.5 0.05 85);
--accent: oklch(0.92 0.03 85);
--accent-foreground: oklch(0.25 0.02 85);
--background: oklch(0.985 0.004 45); /* Blanc légèrement doré */
--foreground: oklch(0.2 0.015 45); /* Gris-brun foncé */
--card: oklch(1 0 0); /* Blanc pur */
--card-foreground: oklch(0.2 0.015 45);
--primary: oklch(0.45 0.08 45); /* Gris-brun chaud */
--primary-foreground: oklch(0.99 0 0); /* Blanc */
--secondary: oklch(0.94 0.008 45);
--secondary-foreground: oklch(0.2 0.015 45);
--muted: oklch(0.91 0.01 45);
--muted-foreground: oklch(0.6 0.012 45);
--accent: oklch(0.93 0.01 45);
--accent-foreground: oklch(0.2 0.015 45);
--destructive: oklch(0.6 0.2 25);
--border: oklch(0.85 0.05 85);
--input: oklch(0.85 0.05 85);
--ring: oklch(0.45 0.1 35);
--border: oklch(0.88 0.012 45);
--input: oklch(0.97 0.008 45);
--ring: oklch(0.68 0.01 45);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.2 0.015 45);
--sidebar: oklch(0.96 0.01 45);
--sidebar-foreground: oklch(0.2 0.015 45);
--sidebar-primary: oklch(0.45 0.08 45);
--sidebar-primary-foreground: oklch(0.99 0 0);
--sidebar-accent: oklch(0.93 0.01 45);
--sidebar-accent-foreground: oklch(0.2 0.015 45);
--sidebar-border: oklch(0.88 0.012 45);
--sidebar-ring: oklch(0.68 0.01 45);
}
[data-theme='sepia'].dark {
--background: oklch(0.15 0.008 45); /* Noir légèrement bruni */
--foreground: oklch(0.97 0.005 45); /* Blanc légèrement bruni */
--card: oklch(0.19 0.01 45); /* Gris-brun foncé */
--card-foreground: oklch(0.97 0.005 45);
--primary: oklch(0.55 0.08 45); /* Gris-brun plus clair */
--primary-foreground: oklch(0.1 0 0); /* Noir */
--secondary: oklch(0.25 0.015 45);
--secondary-foreground: oklch(0.97 0.005 45);
--muted: oklch(0.23 0.02 45);
--muted-foreground: oklch(0.55 0.012 45);
--accent: oklch(0.27 0.018 45);
--accent-foreground: oklch(0.97 0.005 45);
--destructive: oklch(0.65 0.2 25);
--border: oklch(0.3 0.018 45);
--input: oklch(0.27 0.02 45);
--ring: oklch(0.58 0.02 45);
--popover: oklch(0.19 0.01 45);
--popover-foreground: oklch(0.97 0.005 45);
--sidebar: oklch(0.12 0.01 45);
--sidebar-foreground: oklch(0.97 0.005 45);
--sidebar-primary: oklch(0.55 0.08 45);
--sidebar-primary-foreground: oklch(0.1 0 0);
--sidebar-accent: oklch(0.27 0.018 45);
--sidebar-accent-foreground: oklch(0.97 0.005 45);
--sidebar-border: oklch(0.3 0.018 45);
--sidebar-ring: oklch(0.58 0.02 45);
}
@layer base {

View File

@ -102,7 +102,7 @@ export function AIAssistantActionBar({
onClick={() => handleAction(onShorten)}
disabled={disabled}
>
<Scissors className="h-3 w-3 mr-1 text-blue-600 dark:text-blue-400" />
<Scissors className="h-3 w-3 mr-1 text-primary dark:text-primary-foreground" />
{t('ai.shorten')}
</Button>
)}

View File

@ -36,7 +36,7 @@ export function ComparisonModal({
const getNoteColor = (index: number) => {
const colors = [
'border-blue-200 dark:border-blue-800 hover:border-blue-300 dark:hover:border-blue-700',
'border-primary/20 dark:border-primary/30 hover:border-primary/30 dark:hover:border-primary/40',
'border-purple-200 dark:border-purple-800 hover:border-purple-300 dark:hover:border-purple-700',
'border-green-200 dark:border-green-800 hover:border-green-300 dark:hover:border-green-700'
]
@ -45,7 +45,7 @@ export function ComparisonModal({
const getTitleColor = (index: number) => {
const colors = [
'text-blue-600 dark:text-blue-400',
'text-primary dark:text-primary-foreground',
'text-purple-600 dark:text-purple-400',
'text-green-600 dark:text-green-400'
]

View File

@ -31,7 +31,7 @@ const NOTEBOOK_ICONS = [
]
const NOTEBOOK_COLORS = [
{ name: 'Blue', value: '#3B82F6', bg: 'bg-blue-500' },
{ name: 'Slate', value: '#64748B', bg: 'bg-slate-500' },
{ name: 'Purple', value: '#8B5CF6', bg: 'bg-purple-500' },
{ name: 'Red', value: '#EF4444', bg: 'bg-red-500' },
{ name: 'Orange', value: '#F59E0B', bg: 'bg-orange-500' },

View File

@ -245,7 +245,7 @@ export function Header({
const NavItem = ({ href, icon: Icon, label, active, onClick }: any) => {
const content = (
<>
<Icon className={cn("h-5 w-5", active && "fill-current text-blue-900")} />
<Icon className={cn("h-5 w-5", active && "fill-current text-primary")} />
{label}
</>
)
@ -257,7 +257,7 @@ export function Header({
className={cn(
"w-full flex items-center gap-3 px-4 py-3 rounded-r-full text-sm font-medium transition-colors mr-2 text-left",
active
? "bg-blue-100 text-blue-900"
? "bg-primary/10 text-primary dark:bg-primary/20 dark:text-primary-foreground"
: "hover:bg-gray-100 dark:hover:bg-zinc-800 text-gray-700 dark:text-gray-300"
)}
style={{ minHeight: '44px' }}
@ -275,7 +275,7 @@ export function Header({
className={cn(
"flex items-center gap-3 px-4 py-3 rounded-r-full text-sm font-medium transition-colors mr-2",
active
? "bg-blue-100 text-blue-900"
? "bg-primary/10 text-primary dark:bg-primary/20 dark:text-primary-foreground"
: "hover:bg-gray-100 dark:hover:bg-zinc-800 text-gray-700 dark:text-gray-300"
)}
style={{ minHeight: '44px' }}
@ -297,7 +297,7 @@ export function Header({
{/* Logo MEMENTO */}
<div className="flex items-center gap-3 text-slate-900 dark:text-white cursor-pointer group" onClick={() => router.push('/')}>
<div className="size-8 bg-blue-500 rounded-lg flex items-center justify-center text-white shadow-sm group-hover:shadow-md transition-all">
<div className="size-8 bg-primary rounded-lg flex items-center justify-center text-primary-foreground shadow-sm group-hover:shadow-md transition-all">
<StickyNote className="w-5 h-5" />
</div>
<h2 className="text-xl font-bold leading-tight tracking-tight">MEMENTO</h2>
@ -333,7 +333,7 @@ export function Header({
{/* User Avatar Menu */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div className="flex items-center justify-center bg-center bg-no-repeat bg-cover rounded-full size-10 ring-2 ring-white dark:ring-slate-700 cursor-pointer shadow-sm hover:shadow-md transition-shadow bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-200"
<div className="flex items-center justify-center bg-center bg-no-repeat bg-cover rounded-full size-10 ring-2 ring-white dark:ring-slate-700 cursor-pointer shadow-sm hover:shadow-md transition-shadow bg-primary/10 dark:bg-primary/20 text-primary dark:text-primary-foreground"
style={currentUser?.image ? { backgroundImage: `url(${currentUser?.image})` } : undefined}>
{!currentUser?.image && (
<span className="text-sm font-semibold">

View File

@ -100,7 +100,7 @@ export function LabelSelector({
>
<div className={cn(
"h-4 w-4 border rounded flex items-center justify-center transition-colors",
isSelected ? "bg-blue-600 border-blue-600 text-white" : "border-gray-400"
isSelected ? "bg-primary border-primary text-primary-foreground" : "border-gray-400"
)}>
{isSelected && <Check className="h-3 w-3" />}
</div>

View File

@ -19,7 +19,7 @@ export function MarkdownContent({ content, className = '' }: MarkdownContentProp
rehypePlugins={[rehypeKatex]}
components={{
a: ({ node, ...props }) => (
<a {...props} className="text-blue-500 hover:underline" target="_blank" rel="noopener noreferrer" />
<a {...props} className="text-primary hover:underline" target="_blank" rel="noopener noreferrer" />
)
}}
>

View File

@ -26,7 +26,7 @@
.masonry-item-content {
position: relative;
width: 100%;
height: 100%;
/* height: auto - let content determine height */
box-sizing: border-box;
}
@ -45,16 +45,22 @@
}
/* Note Size Styles - Desktop Default */
.masonry-item[data-size="small"],
.note-card[data-size="small"] {
min-height: 150px !important;
min-height: 150px;
height: auto !important;
}
.masonry-item[data-size="medium"],
.note-card[data-size="medium"] {
min-height: 200px !important;
min-height: 200px;
height: auto !important;
}
.masonry-item[data-size="large"],
.note-card[data-size="large"] {
min-height: 300px !important;
min-height: 300px;
height: auto !important;
}
/* Drag State Styles - Clean and flat behavior requested by user */
@ -124,16 +130,22 @@
}
/* Smaller note sizes on mobile */
.masonry-item[data-size="small"],
.masonry-item-content .note-card[data-size="small"] {
min-height: 120px;
height: auto !important;
}
.masonry-item[data-size="medium"],
.masonry-item-content .note-card[data-size="medium"] {
min-height: 160px;
height: auto !important;
}
.masonry-item[data-size="large"],
.masonry-item-content .note-card[data-size="large"] {
min-height: 240px;
height: auto !important;
}
/* Reduced drag effect on mobile */
@ -183,7 +195,7 @@
.masonry-item,
.masonry-item-content,
.note-card {
transition-property: transform, box-shadow, opacity;
transition-property: box-shadow, opacity;
transition-duration: 0.2s;
transition-timing-function: ease-out;
}

View File

@ -20,12 +20,13 @@ interface MasonryItemProps {
note: Note;
onEdit: (note: Note, readOnly?: boolean) => void;
onResize: () => void;
onNoteSizeChange: (noteId: string, newSize: 'small' | 'medium' | 'large') => void;
onDragStart?: (noteId: string) => void;
onDragEnd?: () => void;
isDragging?: boolean;
}
const MasonryItem = memo(function MasonryItem({ note, onEdit, onResize, onDragStart, onDragEnd, isDragging }: MasonryItemProps) {
const MasonryItem = memo(function MasonryItem({ note, onEdit, onResize, onNoteSizeChange, onDragStart, onDragEnd, isDragging }: MasonryItemProps) {
const resizeRef = useResizeObserver(onResize);
return (
@ -43,13 +44,12 @@ const MasonryItem = memo(function MasonryItem({ note, onEdit, onResize, onDragSt
onDragStart={onDragStart}
onDragEnd={onDragEnd}
isDragging={isDragging}
onResize={onResize}
onSizeChange={(newSize) => onNoteSizeChange(note.id, newSize)}
/>
</div>
</div>
);
}, (prev, next) => {
// Custom comparison to avoid re-render on function prop changes if note data is same
return prev.note.id === next.note.id && prev.isDragging === next.isDragging;
});
export function MasonryGrid({ notes, onEdit }: MasonryGridProps) {
@ -57,6 +57,22 @@ export function MasonryGrid({ notes, onEdit }: MasonryGridProps) {
const [editingNote, setEditingNote] = useState<{ note: Note; readOnly?: boolean } | null>(null);
const { startDrag, endDrag, draggedNoteId } = useNotebookDrag();
// Local state for notes with dynamic size updates
// This allows size changes to propagate immediately without waiting for server
const [localNotes, setLocalNotes] = useState<Note[]>(notes);
// Sync localNotes when parent notes prop changes
useEffect(() => {
setLocalNotes(notes);
}, [notes]);
// Callback for when a note's size changes - update local state immediately
const handleNoteSizeChange = useCallback((noteId: string, newSize: 'small' | 'medium' | 'large') => {
setLocalNotes(prevNotes =>
prevNotes.map(n => n.id === noteId ? { ...n, size: newSize } : n)
);
}, []);
const handleEdit = useCallback((note: Note, readOnly?: boolean) => {
if (onEdit) {
onEdit(note, readOnly);
@ -70,14 +86,14 @@ export function MasonryGrid({ notes, onEdit }: MasonryGridProps) {
const pinnedMuuri = useRef<any>(null);
const othersMuuri = useRef<any>(null);
// Memoize filtered notes (order comes from array)
// Memoize filtered notes from localNotes (which includes dynamic size updates)
const pinnedNotes = useMemo(
() => notes.filter(n => n.isPinned),
[notes]
() => localNotes.filter(n => n.isPinned),
[localNotes]
);
const othersNotes = useMemo(
() => notes.filter(n => !n.isPinned),
[notes]
() => localNotes.filter(n => !n.isPinned),
[localNotes]
);
const handleDragEnd = useCallback(async (grid: any) => {
@ -152,7 +168,7 @@ export function MasonryGrid({ notes, onEdit }: MasonryGridProps) {
const columns = calculateColumns(containerWidth);
const itemWidth = calculateItemWidth(containerWidth, columns);
console.log(`[Masonry] Container width: ${containerWidth}px, Columns: ${columns}, Item width: ${itemWidth}px`);
const layoutOptions = {
dragEnabled: true,
@ -279,6 +295,13 @@ export function MasonryGrid({ notes, onEdit }: MasonryGridProps) {
requestAnimationFrame(() => {
syncGridItems(pinnedMuuri.current, pinnedGridRef, pinnedNotes);
syncGridItems(othersMuuri.current, othersGridRef, othersNotes);
// CRITICAL: Force a second layout after CSS transitions (padding/height changes) complete
// NoteCard has a 200ms transition. We wait 300ms to be safe.
setTimeout(() => {
if (pinnedMuuri.current) pinnedMuuri.current.refreshItems().layout();
if (othersMuuri.current) othersMuuri.current.refreshItems().layout();
}, 300);
});
}, [pinnedNotes, othersNotes]); // Re-run when notes change
@ -295,7 +318,7 @@ export function MasonryGrid({ notes, onEdit }: MasonryGridProps) {
const containerWidth = entries[0]?.contentRect.width || window.innerWidth - 32;
const columns = calculateColumns(containerWidth);
console.log(`[Masonry Resize] Width: ${containerWidth}px, Columns: ${columns}`);
// Apply dimensions to both grids
applyItemDimensions(pinnedMuuri.current, containerWidth);
@ -331,10 +354,11 @@ export function MasonryGrid({ notes, onEdit }: MasonryGridProps) {
<div ref={pinnedGridRef} className="relative min-h-[100px]">
{pinnedNotes.map(note => (
<MasonryItem
key={note.id}
key={`${note.id}-${note.size}`}
note={note}
onEdit={handleEdit}
onResize={refreshLayout}
onNoteSizeChange={handleNoteSizeChange}
onDragStart={startDrag}
onDragEnd={endDrag}
isDragging={draggedNoteId === note.id}
@ -352,10 +376,11 @@ export function MasonryGrid({ notes, onEdit }: MasonryGridProps) {
<div ref={othersGridRef} className="relative min-h-[100px]">
{othersNotes.map(note => (
<MasonryItem
key={note.id}
key={`${note.id}-${note.size}`}
note={note}
onEdit={handleEdit}
onResize={refreshLayout}
onNoteSizeChange={handleNoteSizeChange}
onDragStart={startDrag}
onDragEnd={endDrag}
isDragging={draggedNoteId === note.id}
@ -373,39 +398,6 @@ export function MasonryGrid({ notes, onEdit }: MasonryGridProps) {
/>
)}
<style jsx global>{`
.masonry-container {
width: 100%;
}
.masonry-item {
display: block;
position: absolute;
z-index: 1;
box-sizing: border-box;
}
.masonry-item.muuri-item-dragging {
z-index: 3;
opacity: 0.8;
}
.masonry-item.muuri-item-releasing {
z-index: 2;
}
.masonry-item.muuri-item-hidden {
z-index: 0;
}
.masonry-item-content {
position: relative;
width: 100%;
height: 100%;
box-sizing: border-box;
}
/* Ensure proper box-sizing for all elements in the grid */
.masonry-item *,
.masonry-item-content * {
box-sizing: border-box;
}
`}</style>
</div>
);
}

View File

@ -173,7 +173,7 @@ export function MemoryEchoNotification({ onOpenNote }: MemoryEchoNotificationPro
}}
className="cursor-pointer border dark:border-zinc-700 rounded-lg p-4 hover:border-amber-300 dark:hover:border-amber-700 transition-colors"
>
<h3 className="font-semibold text-blue-600 dark:text-blue-400 mb-2">
<h3 className="font-semibold text-primary dark:text-primary-foreground mb-2">
{note1Title}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 line-clamp-4">
@ -278,7 +278,7 @@ export function MemoryEchoNotification({ onOpenNote }: MemoryEchoNotificationPro
{/* Connected notes */}
<div className="space-y-2">
<div className="flex items-center gap-2 text-sm">
<Badge variant="outline" className="border-blue-200 text-blue-700 dark:border-blue-900 dark:text-blue-300">
<Badge variant="outline" className="border-primary/20 text-primary dark:border-primary/30 dark:text-primary-foreground">
{note1Title}
</Badge>
<ArrowRight className="h-3 w-3 text-gray-400" />

View File

@ -111,7 +111,9 @@ export function NoteActions({
{(['small', 'medium', 'large'] as const).map((size) => (
<DropdownMenuItem
key={size}
onClick={() => onSizeChange(size)}
onClick={(e) => {
onSizeChange?.(size);
}}
className={cn(
"capitalize",
currentSize === size && "bg-accent"

View File

@ -86,6 +86,8 @@ interface NoteCardProps {
isDragOver?: boolean
onDragStart?: (noteId: string) => void
onDragEnd?: () => void
onResize?: () => void
onSizeChange?: (newSize: 'small' | 'medium' | 'large') => void
}
// Helper function to get initials from name
@ -116,7 +118,15 @@ function getAvatarColor(name: string): string {
return colors[hash % colors.length]
}
export function NoteCard({ note, onEdit, isDragging, isDragOver, onDragStart, onDragEnd }: NoteCardProps) {
export function NoteCard({
note,
onEdit,
onDragStart,
onDragEnd,
isDragging,
onResize,
onSizeChange
}: NoteCardProps) {
const router = useRouter()
const searchParams = useSearchParams()
const { refreshLabels } = useLabels()
@ -138,7 +148,14 @@ export function NoteCard({ note, onEdit, isDragging, isDragOver, onDragStart, on
setShowNotebookMenu(false)
// No need for router.refresh() - triggerRefresh() is already called in moveNoteToNotebookOptimistic
}
const colorClasses = NOTE_COLORS[note.color as NoteColor] || NOTE_COLORS.default
// Optimistic UI state for instant feedback
const [optimisticNote, addOptimisticNote] = useOptimistic(
note,
(state, newProps: Partial<Note>) => ({ ...state, ...newProps })
)
const colorClasses = NOTE_COLORS[optimisticNote.color as NoteColor] || NOTE_COLORS.default
// Check if this note is currently open in the editor
const isNoteOpenInEditor = searchParams.get('note') === note.id
@ -148,12 +165,6 @@ export function NoteCard({ note, onEdit, isDragging, isDragOver, onDragStart, on
comparisonNotes && comparisonNotes.length > 0 ? comparisonNotes : null
)
// Optimistic UI state for instant feedback
const [optimisticNote, addOptimisticNote] = useOptimistic(
note,
(state, newProps: Partial<Note>) => ({ ...state, ...newProps })
)
const currentUserId = session?.user?.id
const canManageCollaborators = currentUserId && note.userId && currentUserId === note.userId
const isSharedNote = currentUserId && note.userId && currentUserId !== note.userId
@ -226,10 +237,15 @@ export function NoteCard({ note, onEdit, isDragging, isDragOver, onDragStart, on
}
const handleSizeChange = async (size: 'small' | 'medium' | 'large') => {
startTransition(async () => {
addOptimisticNote({ size })
await updateSize(note.id, size)
})
// Notify parent of size change so it can update its state
onSizeChange?.(size)
// Trigger layout refresh
onResize?.()
setTimeout(() => onResize?.(), 300)
// Update server in background
updateSize(note.id, size)
}
const handleCheckItem = async (checkItemId: string) => {
@ -267,12 +283,23 @@ export function NoteCard({ note, onEdit, isDragging, isDragOver, onDragStart, on
if (isDeleting) return null
const getMinHeight = (size?: string) => {
switch (size) {
case 'medium': return '200px'
case 'large': return '300px'
default: return '150px' // small
}
}
return (
<Card
data-testid="note-card"
data-draggable="true"
data-note-id={note.id}
data-size={note.size}
data-size={optimisticNote.size}
style={{ minHeight: getMinHeight(optimisticNote.size) }}
draggable={true}
onDragStart={(e) => {
e.dataTransfer.setData('text/plain', note.id)
@ -560,7 +587,7 @@ export function NoteCard({ note, onEdit, isDragging, isDragOver, onDragStart, on
isPinned={optimisticNote.isPinned}
isArchived={optimisticNote.isArchived}
currentColor={optimisticNote.color}
currentSize={optimisticNote.size}
currentSize={optimisticNote.size as 'small' | 'medium' | 'large'}
onTogglePin={handleTogglePin}
onToggleArchive={handleToggleArchive}
onColorChange={handleColorChange}

View File

@ -550,7 +550,7 @@ export function NoteEditor({ note, readOnly = false, onClose }: NoteEditorProps)
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold">{readOnly ? t('notes.view') : t('notes.edit')}</h2>
{readOnly && (
<Badge variant="secondary" className="bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300">
<Badge variant="secondary" className="bg-primary/10 text-primary dark:bg-primary/20 dark:text-primary-foreground">
{t('notes.readOnly')}
</Badge>
)}
@ -607,7 +607,7 @@ export function NoteEditor({ note, readOnly = false, onClose }: NoteEditorProps)
<div className="p-2 flex-1 min-w-0 flex flex-col justify-center">
<h4 className="font-medium text-sm truncate">{link.title || link.url}</h4>
{link.description && <p className="text-xs text-gray-500 truncate">{link.description}</p>}
<a href={link.url} target="_blank" rel="noopener noreferrer" className="text-xs text-blue-500 truncate hover:underline block mt-1">
<a href={link.url} target="_blank" rel="noopener noreferrer" className="text-xs text-primary truncate hover:underline block mt-1">
{new URL(link.url).hostname}
</a>
</div>
@ -636,7 +636,7 @@ export function NoteEditor({ note, readOnly = false, onClose }: NoteEditorProps)
setIsMarkdown(!isMarkdown)
if (isMarkdown) setShowMarkdownPreview(false)
}}
className={cn("h-7 text-xs", isMarkdown && "text-blue-600")}
className={cn("h-7 text-xs", isMarkdown && "text-primary")}
>
<FileText className="h-3 w-3 mr-1" />
{isMarkdown ? t('notes.markdownOn') : t('notes.markdownOff')}
@ -824,7 +824,7 @@ export function NoteEditor({ note, readOnly = false, onClose }: NoteEditorProps)
size="sm"
onClick={() => setShowReminderDialog(true)}
title={t('notes.setReminder')}
className={currentReminder ? "text-blue-600" : ""}
className={currentReminder ? "text-primary" : ""}
>
<Bell className="h-4 w-4" />
</Button>

View File

@ -645,7 +645,7 @@ export function NoteInput({ onNoteCreated, defaultExpanded = false, forceExpande
<div className="p-2 flex-1 min-w-0 flex flex-col justify-center">
<h4 className="font-medium text-sm truncate">{link.title || link.url}</h4>
{link.description && <p className="text-xs text-gray-500 truncate">{link.description}</p>}
<a href={link.url} target="_blank" rel="noopener noreferrer" className="text-xs text-blue-500 truncate hover:underline block mt-1">
<a href={link.url} target="_blank" rel="noopener noreferrer" className="text-xs text-primary truncate hover:underline block mt-1">
{new URL(link.url).hostname}
</a>
</div>
@ -781,7 +781,7 @@ export function NoteInput({ onNoteCreated, defaultExpanded = false, forceExpande
size="icon"
className={cn(
"h-8 w-8",
currentReminder && "text-blue-600"
currentReminder && "text-primary"
)}
title={t('notes.remindMe')}
onClick={handleReminderOpen}
@ -799,7 +799,7 @@ export function NoteInput({ onNoteCreated, defaultExpanded = false, forceExpande
size="icon"
className={cn(
"h-8 w-8",
isMarkdown && "text-blue-600"
isMarkdown && "text-primary"
)}
onClick={() => {
setIsMarkdown(!isMarkdown)

View File

@ -103,7 +103,7 @@ export function NotebookSuggestionToast({
<div
className={cn(
'fixed bottom-4 right-4 z-50 max-w-md bg-white dark:bg-zinc-800',
'border border-blue-200 dark:border-blue-800 rounded-lg shadow-lg',
'border border-border dark:border-border rounded-lg shadow-lg',
'p-4 animate-in slide-in-from-bottom-4 fade-in duration-300',
'transition-all duration-300'
)}
@ -111,8 +111,8 @@ export function NotebookSuggestionToast({
<div className="flex items-start gap-3">
{/* Icon */}
<div className="flex-shrink-0">
<div className="w-10 h-10 rounded-full bg-blue-100 dark:bg-blue-900/30 flex items-center justify-center">
<FolderOpen className="w-5 h-5 text-blue-600 dark:text-blue-400" />
<div className="w-10 h-10 rounded-full bg-primary/10 dark:bg-primary/20 flex items-center justify-center">
<FolderOpen className="w-5 h-5 text-primary dark:text-primary-foreground" />
</div>
</div>
@ -131,7 +131,7 @@ export function NotebookSuggestionToast({
{/* Move button */}
<button
onClick={handleMoveToNotebook}
className="px-3 py-1.5 text-xs font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700 transition-colors"
className="px-3 py-1.5 text-xs font-medium rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors"
>
{t('notebookSuggestion.move')}
</button>

View File

@ -158,8 +158,8 @@ export function NotebooksList() {
onDragLeave={handleDragLeave}
className={cn(
"flex flex-col mr-2 rounded-r-full overflow-hidden transition-all",
!notebook.color && "bg-blue-50 dark:bg-blue-900/20",
isDragOver && "ring-2 ring-blue-500 ring-dashed"
!notebook.color && "bg-primary/10 dark:bg-primary/20",
isDragOver && "ring-2 ring-primary ring-dashed"
)}
style={notebook.color ? { backgroundColor: `${notebook.color}20` } : undefined}
>
@ -167,11 +167,11 @@ export function NotebooksList() {
<div className="pointer-events-auto flex items-center justify-between px-6 py-3">
<div className="flex items-center gap-4 min-w-0">
<NotebookIcon
className={cn("w-5 h-5 flex-shrink-0 fill-current", !notebook.color && "text-blue-700 dark:text-blue-100")}
className={cn("w-5 h-5 flex-shrink-0 fill-current", !notebook.color && "text-primary dark:text-primary-foreground")}
style={notebook.color ? { color: notebook.color } : undefined}
/>
<span
className={cn("text-sm font-medium tracking-wide truncate max-w-[120px]", !notebook.color && "text-blue-700 dark:text-blue-100")}
className={cn("text-sm font-medium tracking-wide truncate max-w-[120px]", !notebook.color && "text-primary dark:text-primary-foreground")}
style={notebook.color ? { color: notebook.color } : undefined}
>
{notebook.name}
@ -179,7 +179,7 @@ export function NotebooksList() {
</div>
<button
onClick={() => handleToggleExpand(notebook.id)}
className={cn("transition-colors p-1 flex-shrink-0", !notebook.color && "text-blue-600 hover:text-blue-800 dark:text-blue-200 dark:hover:text-blue-100")}
className={cn("transition-colors p-1 flex-shrink-0", !notebook.color && "text-primary hover:text-primary/80 dark:text-primary-foreground dark:hover:text-primary-foreground/80")}
style={notebook.color ? { color: notebook.color } : undefined}
>
<ChevronDown className={cn("w-4 h-4 transition-transform", isExpanded && "rotate-180")} />

View File

@ -126,14 +126,14 @@ export function NotificationPanel() {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-80">
<div className="px-4 py-3 border-b bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-blue-950/20 dark:to-indigo-950/20">
<div className="px-4 py-3 border-b bg-gradient-to-r from-primary/5 to-primary/10 dark:from-primary/10 dark:to-primary/15">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Bell className="h-4 w-4 text-blue-600 dark:text-blue-400" />
<Bell className="h-4 w-4 text-primary dark:text-primary-foreground" />
<span className="font-semibold text-sm">{t('nav.aiSettings')}</span>
</div>
{pendingCount > 0 && (
<Badge className="bg-blue-600 hover:bg-blue-700 text-white shadow-md">
<Badge className="bg-primary hover:bg-primary/90 text-primary-foreground shadow-md">
{pendingCount}
</Badge>
)}
@ -142,7 +142,7 @@ export function NotificationPanel() {
{isLoading ? (
<div className="p-6 text-center text-sm text-muted-foreground">
<div className="animate-spin h-6 w-6 border-2 border-blue-600 border-t-transparent rounded-full mx-auto mb-2" />
<div className="animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full mx-auto mb-2" />
{t('general.loading')}
</div>
) : requests.length === 0 ? (
@ -171,7 +171,7 @@ export function NotificationPanel() {
</div>
<Badge
variant="secondary"
className="text-xs capitalize bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300 border-0"
className="text-xs capitalize bg-primary/10 text-primary dark:bg-primary/20 dark:text-primary-foreground border-0"
>
{request.permission}
</Badge>

View File

@ -74,10 +74,10 @@ function CompactCard({
<div className={cn(
"absolute left-0 top-0 bottom-0 w-1 rounded-l-xl",
isFirstNote
? "bg-gradient-to-b from-blue-500 to-indigo-500"
? "bg-gradient-to-b from-primary to-primary/70"
: index === 1
? "bg-blue-400 dark:bg-blue-500"
: "bg-gray-300 dark:bg-gray-600"
? "bg-primary/80 dark:bg-primary/70"
: "bg-muted dark:bg-muted/60"
)} />
{/* Content with left padding for accent line */}
@ -105,7 +105,7 @@ function CompactCard({
<div className="flex items-center gap-1.5">
{/* Notebook indicator */}
{note.notebookId && (
<div className="w-1.5 h-1.5 rounded-full bg-blue-500 dark:bg-blue-400" title="In notebook" />
<div className="w-1.5 h-1.5 rounded-full bg-primary dark:bg-primary/70" title="In notebook" />
)}
{/* Labels indicator */}
{note.labels && note.labels.length > 0 && (

View File

@ -53,8 +53,8 @@ export function Sidebar({ className, user }: { className?: string, user?: any })
"flex items-center gap-4 px-6 py-3 rounded-r-full mr-2 transition-colors",
"text-sm font-medium tracking-wide",
active
? "bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:text-blue-100"
: "text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800/50"
? "bg-primary/10 text-primary dark:bg-primary/20 dark:text-primary-foreground"
: "text-muted-foreground hover:bg-muted/50 dark:hover:bg-muted/30"
)}
>
<Icon className={cn("w-5 h-5", active ? "fill-current" : "")} />

View File

@ -9,10 +9,10 @@ interface ThemeInitializerProps {
export function ThemeInitializer({ theme, fontSize }: ThemeInitializerProps) {
useEffect(() => {
console.log('[ThemeInitializer] Received theme:', theme)
// Helper to apply theme
const applyTheme = (t?: string) => {
console.log('[ThemeInitializer] Applying theme:', t)
if (!t) return
const root = document.documentElement
@ -66,7 +66,7 @@ export function ThemeInitializer({ theme, fontSize }: ThemeInitializerProps) {
const localTheme = localStorage.getItem('theme-preference')
const effectiveTheme = localTheme || theme
console.log('[ThemeInitializer] Local theme:', localTheme, '| Server theme:', theme, '| Using:', effectiveTheme)
applyTheme(effectiveTheme)

View File

@ -0,0 +1,310 @@
/**
* PROPOSITION D'HARMONIE DE COULEURS
* ===================================
*
* Recommandations pour un système de couleurs unifié et moderne
* Inspiré de Google Keep avec une approche contemporaine
*
*/
// =============================================================================
// 1. PALETTE PRINCIPALE DU THÈME (OKLCH)
// =============================================================================
/**
* Proposition de palette principale avec meilleure cohérence
* Utilise OKLCH pour une meilleure accessibilité et cohérence perceptive
*/
export const RECOMMENDED_THEME_COLORS = {
/* ============ THEME LIGHT ============ */
light: {
// Backgrounds
background: 'oklch(0.99 0.002 250)', // Blanc très légèrement bleuté
card: 'oklch(1 0 0)', // Blanc pur
sidebar: 'oklch(0.96 0.005 250)', // Gris-bleu très pâle
input: 'oklch(0.97 0.003 250)', // Gris-bleu pâle
// Textes
foreground: 'oklch(0.18 0.01 250)', // Gris-bleu foncé (meilleur contraste)
'foreground-secondary': 'oklch(0.45 0.01 250)', // Gris moyen
'foreground-muted': 'oklch(0.6 0.005 250)', // Gris clair
// Primary Actions
primary: 'oklch(0.55 0.2 250)', // Bleu Keep (plus vibrant)
'primary-hover': 'oklch(0.5 0.22 250)', // Bleu Keep foncé
'primary-foreground': 'oklch(0.99 0 0)', // Blanc pur
// Accents
accent: 'oklch(0.92 0.015 250)', // Bleu très pâle
'accent-foreground': 'oklch(0.18 0.01 250)',
// Borders
border: 'oklch(0.88 0.01 250)', // Gris-bleu très clair
'border-hover': 'oklch(0.8 0.015 250)',
// Functional
success: 'oklch(0.65 0.15 145)', // Vert
warning: 'oklch(0.75 0.12 70)', // Jaune/orange
destructive: 'oklch(0.6 0.2 25)', // Rouge
},
/* ============ THEME DARK ============ */
dark: {
// Backgrounds
background: 'oklch(0.12 0.01 250)', // Noir légèrement bleuté
card: 'oklch(0.16 0.01 250)', // Gris-bleu foncé
sidebar: 'oklch(0.1 0.01 250)', // Noir bleuté
input: 'oklch(0.18 0.01 250)',
// Textes
foreground: 'oklch(0.96 0.002 250)', // Blanc légèrement bleuté
'foreground-secondary': 'oklch(0.75 0.005 250)',
'foreground-muted': 'oklch(0.55 0.01 250)',
// Primary Actions
primary: 'oklch(0.65 0.2 250)', // Bleu plus clair
'primary-hover': 'oklch(0.7 0.2 250)',
'primary-foreground': 'oklch(0.1 0 0)',
// Accents
accent: 'oklch(0.22 0.01 250)',
'accent-foreground': 'oklch(0.96 0.002 250)',
// Borders
border: 'oklch(0.25 0.015 250)',
'border-hover': 'oklch(0.35 0.015 250)',
// Functional
success: 'oklch(0.7 0.15 145)',
warning: 'oklch(0.8 0.12 70)',
destructive: 'oklch(0.65 0.2 25)',
},
} as const;
// =============================================================================
// 2. PALETTE DES COULEURS DE NOTES (UNIFIÉE)
// =============================================================================
/**
* Nouvelle palette de couleurs pour les notes
* Harmonisée avec le thème principal
* Meilleure accessibilité WCAG AA (contraste minimum 4.5:1)
*/
export const RECOMMENDED_NOTE_COLORS = {
default: {
// Light theme
bg: 'bg-white',
'bg-dark': 'dark:bg-neutral-900',
border: 'border-neutral-200',
'border-dark': 'dark:border-neutral-800',
hover: 'hover:bg-neutral-50',
'hover-dark': 'dark:hover:bg-neutral-800',
// Pour texte sombre
text: 'text-neutral-900',
'text-dark': 'dark:text-neutral-100',
},
red: {
bg: 'bg-red-50',
'bg-dark': 'dark:bg-red-950/40',
border: 'border-red-100',
'border-dark': 'dark:border-red-900/50',
hover: 'hover:bg-red-100',
'hover-dark': 'dark:hover:bg-red-950/60',
text: 'text-red-950',
'text-dark': 'dark:text-red-100',
},
orange: {
bg: 'bg-orange-50',
'bg-dark': 'dark:bg-orange-950/40',
border: 'border-orange-100',
'border-dark': 'dark:border-orange-900/50',
hover: 'hover:bg-orange-100',
'hover-dark': 'dark:hover:bg-orange-950/60',
text: 'text-orange-950',
'text-dark': 'dark:text-orange-100',
},
yellow: {
bg: 'bg-yellow-50',
'bg-dark': 'dark:bg-yellow-950/40',
border: 'border-yellow-100',
'border-dark': 'dark:border-yellow-900/50',
hover: 'hover:bg-yellow-100',
'hover-dark': 'dark:hover:bg-yellow-950/60',
text: 'text-yellow-950',
'text-dark': 'dark:text-yellow-100',
},
green: {
bg: 'bg-emerald-50',
'bg-dark': 'dark:bg-emerald-950/40',
border: 'border-emerald-100',
'border-dark': 'dark:border-emerald-900/50',
hover: 'hover:bg-emerald-100',
'hover-dark': 'dark:hover:bg-emerald-950/60',
text: 'text-emerald-950',
'text-dark': 'dark:text-emerald-100',
},
teal: {
bg: 'bg-teal-50',
'bg-dark': 'dark:bg-teal-950/40',
border: 'border-teal-100',
'border-dark': 'dark:border-teal-900/50',
hover: 'hover:bg-teal-100',
'hover-dark': 'dark:hover:bg-teal-950/60',
text: 'text-teal-950',
'text-dark': 'dark:text-teal-100',
},
blue: {
bg: 'bg-blue-50',
'bg-dark': 'dark:bg-blue-950/40',
border: 'border-blue-100',
'border-dark': 'dark:border-blue-900/50',
hover: 'hover:bg-blue-100',
'hover-dark': 'dark:hover:bg-blue-950/60',
text: 'text-blue-950',
'text-dark': 'dark:text-blue-100',
},
indigo: {
bg: 'bg-indigo-50',
'bg-dark': 'dark:bg-indigo-950/40',
border: 'border-indigo-100',
'border-dark': 'dark:border-indigo-900/50',
hover: 'hover:bg-indigo-100',
'hover-dark': 'dark:hover:bg-indigo-950/60',
text: 'text-indigo-950',
'text-dark': 'dark:text-indigo-100',
},
violet: {
bg: 'bg-violet-50',
'bg-dark': 'dark:bg-violet-950/40',
border: 'border-violet-100',
'border-dark': 'dark:border-violet-900/50',
hover: 'hover:bg-violet-100',
'hover-dark': 'dark:hover:bg-violet-950/60',
text: 'text-violet-950',
'text-dark': 'dark:text-violet-100',
},
purple: {
bg: 'bg-purple-50',
'bg-dark': 'dark:bg-purple-950/40',
border: 'border-purple-100',
'border-dark': 'dark:border-purple-900/50',
hover: 'hover:bg-purple-100',
'hover-dark': 'dark:hover:bg-purple-950/60',
text: 'text-purple-950',
'text-dark': 'dark:text-purple-100',
},
pink: {
bg: 'bg-pink-50',
'bg-dark': 'dark:bg-pink-950/40',
border: 'border-pink-100',
'border-dark': 'dark:border-pink-900/50',
hover: 'hover:bg-pink-100',
'hover-dark': 'dark:hover:bg-pink-950/60',
text: 'text-pink-950',
'text-dark': 'dark:text-pink-100',
},
rose: {
bg: 'bg-rose-50',
'bg-dark': 'dark:bg-rose-950/40',
border: 'border-rose-100',
'border-dark': 'dark:border-rose-900/50',
hover: 'hover:bg-rose-100',
'hover-dark': 'dark:hover:bg-rose-950/60',
text: 'text-rose-950',
'text-dark': 'dark:text-rose-100',
},
gray: {
bg: 'bg-neutral-100',
'bg-dark': 'dark:bg-neutral-800',
border: 'border-neutral-200',
'border-dark': 'dark:border-neutral-700',
hover: 'hover:bg-neutral-200',
'hover-dark': 'dark:hover:bg-neutral-700',
text: 'text-neutral-900',
'text-dark': 'dark:text-neutral-100',
},
} as const;
// =============================================================================
// 3. RÈGLES DE DESIGN
// =============================================================================
/**
* Principes de design couleur :
*
* 1. HARMONIE : Toutes les couleurs partagent la même teinte de base (bleu 250°)
* - Crée une cohérence visuelle
* - Réduit la fatigue visuelle
* - Améliore la perception de marque
*
* 2. CONTRASTE : Respecte WCAG AA (4.5:1 minimum)
* - Texte sur fond : toujours 4.5:1
* - Éléments interactifs : 3:1
* - Utilise OKLCH pour une mesure plus précise
*
* 3. ACCESSIBILITÉ :
* - Ne pas utiliser la couleur seule pour véhiculer l'information
* - Inclure des icônes ou des symboles
* - Supporter le mode de contraste élevé
*
* 4. ADAPTABILITÉ :
* - Mode light/dark automatique
* - Variations de couleur par note
* - Thèmes alternatifs (midnight, blue, sepia)
*
* 5. PERFORMANCE PERCEPTIVE :
* - OKLCH pour une échelle perceptuelle uniforme
* - Même légèreté perçue entre light et dark
* - Transition fluide entre thèmes
*/
// =============================================================================
// 4. EXEMPLE D'IMPLEMENTATION
// =============================================================================
/**
* Comment utiliser ces couleurs dans les composants :
*
* ```tsx
* import { RECOMMENDED_NOTE_COLORS } from '@/lib/color-harmony-recommendation'
*
* // Pour une carte de note
* <div className={`
* ${noteColors.bg}
* dark:${noteColors['bg-dark']}
* ${noteColors.border}
* dark:${noteColors['border-dark']}
* ${noteColors.text}
* dark:${noteColors['text-dark']}
* hover:${noteColors.hover}
* dark:hover:${noteColors['hover-dark']}
* `}>
* {note.content}
* </div>
*
* // Pour le thème global (globals.css)
* :root {
* --background: oklch(0.99 0.002 250);
* --foreground: oklch(0.18 0.01 250);
* --primary: oklch(0.55 0.2 250);
* // ... autres couleurs
* }
* ```
*/
// =============================================================================
// 5. AVANTAGES DE CETTE APPROCHE
// =============================================================================
/**
* Cohérence visuelle accrue
* Meilleure accessibilité (WCAG AA+)
* Adaptation native aux modes light/dark
* Palette extensible (facile à ajouter de nouvelles couleurs)
* Performance OKLCH (perception humaine)
* Compatible avec Tailwind CSS
* Maintenance simplifiée
* Professionalisme et modernité
*/
export type RecommendedNoteColor = keyof typeof RECOMMENDED_NOTE_COLORS;

View File

@ -0,0 +1,307 @@
/**
* OPTIONS DE COULEURS MODERNES - PAS DE DÉGRADÉS
* =================================================
*
* Alternatives au bleu traditionnel pour un design contemporain
*/
// =============================================================================
// OPTION 1: GRIS-BLEU (SLATE) - RECOMMANDÉE ✅
// =============================================================================
/**
* Gris-bleu moderne et professionnel
* Inspiré par Linear, Vercel, GitHub
* Très élégant, discret et apaisant pour les yeux
*/
export const SLATE_THEME = {
name: 'Gris-Bleu (Slate)',
description: 'Moderne, professionnel et apaisant - comme Linear/Vercel',
light: {
// Backgrounds
background: 'oklch(0.985 0.003 230)', // Blanc grisâtre très léger
card: 'oklch(1 0 0)', // Blanc pur
sidebar: 'oklch(0.97 0.004 230)', // Gris-bleu très pâle
input: 'oklch(0.98 0.003 230)', // Gris-bleu pâle
// Textes
foreground: 'oklch(0.2 0.02 230)', // Gris-bleu foncé
'foreground-secondary': 'oklch(0.45 0.015 230)', // Gris-bleu moyen
'foreground-muted': 'oklch(0.6 0.01 230)', // Gris-bleu clair
// Primary (le point coloré de l'interface)
primary: 'oklch(0.45 0.08 230)', // Gris-bleu doux
'primary-hover': 'oklch(0.4 0.09 230)', // Gris-bleu plus foncé
'primary-foreground': 'oklch(0.99 0 0)', // Blanc
// Accents
accent: 'oklch(0.94 0.005 230)', // Gris-bleu très pâle
'accent-foreground': 'oklch(0.2 0.02 230)',
// Borders
border: 'oklch(0.9 0.008 230)', // Gris-bleu très clair
'border-hover': 'oklch(0.82 0.01 230)',
// Functional
success: 'oklch(0.65 0.15 145)', // Vert emerald
warning: 'oklch(0.75 0.12 70)', // Jaune/orange
destructive: 'oklch(0.6 0.18 25)', // Rouge
},
dark: {
// Backgrounds
background: 'oklch(0.14 0.005 230)', // Noir grisâtre léger
card: 'oklch(0.18 0.006 230)', // Gris-bleu foncé
sidebar: 'oklch(0.12 0.005 230)', // Noir grisâtre
input: 'oklch(0.2 0.006 230)',
// Textes
foreground: 'oklch(0.97 0.003 230)', // Blanc grisâtre
'foreground-secondary': 'oklch(0.75 0.008 230)',
'foreground-muted': 'oklch(0.55 0.01 230)',
// Primary
primary: 'oklch(0.55 0.08 230)', // Gris-bleu plus clair
'primary-hover': 'oklch(0.6 0.09 230)',
'primary-foreground': 'oklch(0.1 0 0)',
// Accents
accent: 'oklch(0.24 0.006 230)',
'accent-foreground': 'oklch(0.97 0.003 230)',
// Borders
border: 'oklch(0.28 0.01 230)',
'border-hover': 'oklch(0.38 0.012 230)',
// Functional
success: 'oklch(0.7 0.15 145)',
warning: 'oklch(0.8 0.12 70)',
destructive: 'oklch(0.65 0.18 25)',
},
} as const;
// =============================================================================
// OPTION 2: MONOCHROME GRIS (MINIMALISTE)
// =============================================================================
/**
* Noir et blanc avec subtilités de gris
* Style minimaliste ultra-moderne (Linear, Stripe, Apple)
* Absolument pas de couleur sauf pour les fonctionnalités
*/
export const MONOCHROME_THEME = {
name: 'Monochrome Gris',
description: 'Minimaliste et élégant - style Linear/Apple',
light: {
background: 'oklch(0.99 0 0)', // Blanc pur
card: 'oklch(1 0 0)', // Blanc pur
sidebar: 'oklch(0.96 0 0)', // Gris très pâle
input: 'oklch(0.98 0 0)',
foreground: 'oklch(0.15 0 0)', // Noir pur
'foreground-secondary': 'oklch(0.45 0 0)', // Gris moyen
'foreground-muted': 'oklch(0.6 0 0)', // Gris clair
primary: 'oklch(0.2 0 0)', // Gris foncé
'primary-hover': 'oklch(0.15 0 0)', // Noir
'primary-foreground': 'oklch(1 0 0)', // Blanc
accent: 'oklch(0.95 0 0)', // Gris très pâle
'accent-foreground': 'oklch(0.2 0 0)',
border: 'oklch(0.89 0 0)', // Gris très clair
'border-hover': 'oklch(0.75 0 0)',
success: 'oklch(0.6 0.15 145)', // Vert subtil
warning: 'oklch(0.7 0.12 70)', // Jaune subtil
destructive: 'oklch(0.55 0.18 25)', // Rouge subtil
},
dark: {
background: 'oklch(0.1 0 0)', // Noir pur
card: 'oklch(0.14 0 0)', // Gris très foncé
sidebar: 'oklch(0.08 0 0)', // Noir pur
input: 'oklch(0.16 0 0)',
foreground: 'oklch(0.98 0 0)', // Blanc pur
'foreground-secondary': 'oklch(0.7 0 0)', // Gris moyen
'foreground-muted': 'oklch(0.5 0 0)', // Gris foncé
primary: 'oklch(0.85 0 0)', // Gris clair
'primary-hover': 'oklch(0.9 0 0)', // Blanc
'primary-foreground': 'oklch(0.1 0 0)', // Noir
accent: 'oklch(0.2 0 0)', // Gris foncé
'accent-foreground': 'oklch(0.98 0 0)',
border: 'oklch(0.25 0 0)', // Gris foncé
'border-hover': 'oklch(0.35 0 0)',
success: 'oklch(0.65 0.15 145)',
warning: 'oklch(0.75 0.12 70)',
destructive: 'oklch(0.6 0.18 25)',
},
} as const;
// =============================================================================
// OPTION 3: VIOLET PROFOND (INDIGO)
// =============================================================================
/**
* Violet profond élégant et moderne
* Entre le bleu et le violet
* Très professionnel (Discord, Notion, Figma)
*/
export const INDIGO_THEME = {
name: 'Violet Profond',
description: 'Élégant et moderne - style Discord/Notion',
light: {
background: 'oklch(0.99 0.005 260)', // Blanc légèrement violacé
card: 'oklch(1 0 0)',
sidebar: 'oklch(0.96 0.006 260)',
input: 'oklch(0.97 0.005 260)',
foreground: 'oklch(0.2 0.015 260)', // Gris-violet foncé
'foreground-secondary': 'oklch(0.45 0.012 260)',
'foreground-muted': 'oklch(0.6 0.008 260)',
primary: 'oklch(0.55 0.18 260)', // Violet profond
'primary-hover': 'oklch(0.5 0.2 260)', // Violet plus foncé
'primary-foreground': 'oklch(0.99 0 0)',
accent: 'oklch(0.94 0.008 260)',
'accent-foreground': 'oklch(0.2 0.015 260)',
border: 'oklch(0.9 0.01 260)',
'border-hover': 'oklch(0.82 0.012 260)',
success: 'oklch(0.65 0.15 145)',
warning: 'oklch(0.75 0.12 70)',
destructive: 'oklch(0.6 0.18 25)',
},
dark: {
background: 'oklch(0.14 0.008 260)', // Noir légèrement violacé
card: 'oklch(0.18 0.01 260)', // Gris-violet foncé
sidebar: 'oklch(0.12 0.008 260)',
input: 'oklch(0.2 0.01 260)',
foreground: 'oklch(0.97 0.005 260)', // Blanc légèrement violacé
'foreground-secondary': 'oklch(0.75 0.008 260)',
'foreground-muted': 'oklch(0.55 0.01 260)',
primary: 'oklch(0.65 0.18 260)', // Violet plus clair
'primary-hover': 'oklch(0.7 0.2 260)',
'primary-foreground': 'oklch(0.1 0 0)',
accent: 'oklch(0.24 0.01 260)',
'accent-foreground': 'oklch(0.97 0.005 260)',
border: 'oklch(0.28 0.012 260)',
'border-hover': 'oklch(0.38 0.015 260)',
success: 'oklch(0.7 0.15 145)',
warning: 'oklch(0.8 0.12 70)',
destructive: 'oklch(0.65 0.18 25)',
},
} as const;
// =============================================================================
// OPTION 4: TEAL (TURQUOISE)
// =============================================================================
/**
* Turquoise/teal moderne et rafraîchissante
* Entre le bleu et le vert
* Très appréciée dans le design moderne (Linear, Atlassian)
*/
export const TEAL_THEME = {
name: 'Teal (Turquoise)',
description: 'Moderne et rafraîchissant - style Atlassian/Linear',
light: {
background: 'oklch(0.99 0.003 195)', // Blanc légèrement teinté
card: 'oklch(1 0 0)',
sidebar: 'oklch(0.96 0.004 195)',
input: 'oklch(0.97 0.003 195)',
foreground: 'oklch(0.2 0.015 195)', // Gris-teal foncé
'foreground-secondary': 'oklch(0.45 0.012 195)',
'foreground-muted': 'oklch(0.6 0.008 195)',
primary: 'oklch(0.55 0.14 195)', // Teal moderne
'primary-hover': 'oklch(0.5 0.16 195)', // Teal plus foncé
'primary-foreground': 'oklch(0.99 0 0)',
accent: 'oklch(0.94 0.005 195)',
'accent-foreground': 'oklch(0.2 0.015 195)',
border: 'oklch(0.9 0.008 195)',
'border-hover': 'oklch(0.82 0.01 195)',
success: 'oklch(0.65 0.15 145)',
warning: 'oklch(0.75 0.12 70)',
destructive: 'oklch(0.6 0.18 25)',
},
dark: {
background: 'oklch(0.14 0.005 195)', // Noir légèrement teinté
card: 'oklch(0.18 0.006 195)', // Gris-teal foncé
sidebar: 'oklch(0.12 0.005 195)',
input: 'oklch(0.2 0.006 195)',
foreground: 'oklch(0.97 0.003 195)', // Blanc légèrement teinté
'foreground-secondary': 'oklch(0.75 0.006 195)',
'foreground-muted': 'oklch(0.55 0.008 195)',
primary: 'oklch(0.65 0.14 195)', // Teal plus clair
'primary-hover': 'oklch(0.7 0.16 195)',
'primary-foreground': 'oklch(0.1 0 0)',
accent: 'oklch(0.24 0.006 195)',
'accent-foreground': 'oklch(0.97 0.003 195)',
border: 'oklch(0.28 0.01 195)',
'border-hover': 'oklch(0.38 0.012 195)',
success: 'oklch(0.7 0.15 145)',
warning: 'oklch(0.8 0.12 70)',
destructive: 'oklch(0.65 0.18 25)',
},
} as const;
// =============================================================================
// RÉSUMÉ DES OPTIONS
// =============================================================================
/**
* Comparaison des options :
*
* | Option | Modernité | Professionnalisme | Fatigue oculaire | Unicité |
* |--------|-----------|-------------------|------------------|----------|
* | Slate (Gris-Bleu) | | | | |
* | Monochrome | | | | |
* | Indigo | | | | |
* | Teal | | | | |
*
* Recommandation : Slate (Gris-Bleu)
* - Le plus professionnel
* - Fatigue oculaire minimale
* - Très moderne et tendance
* - Différent du bleu traditionnel
* - Cohérent avec votre suggestion
*/
export type ThemeOption = 'slate' | 'monochrome' | 'indigo' | 'teal';
/**
* Pour choisir le thème :
*
* function getTheme(option: ThemeOption) {
* switch(option) {
* case 'slate': return SLATE_THEME;
* case 'monochrome': return MONOCHROME_THEME;
* case 'indigo': return INDIGO_THEME;
* case 'teal': return TEAL_THEME;
* }
* }
*/

View File

@ -0,0 +1,24 @@
# Page snapshot
```yaml
- generic [active] [ref=e1]:
- main [ref=e4]:
- generic [ref=e7]:
- heading "Sign in to your account" [level=1] [ref=e8]
- generic [ref=e9]:
- generic [ref=e10]:
- generic [ref=e11]: Email
- textbox "Email" [ref=e13]:
- /placeholder: Enter your email address
- generic [ref=e14]:
- generic [ref=e15]: Password
- textbox "Password" [ref=e17]:
- /placeholder: Enter your password
- link "Forgot password?" [ref=e19] [cursor=pointer]:
- /url: /forgot-password
- button "Sign In" [ref=e20]
- region "Notifications alt+T"
- button "Open Next.js Dev Tools" [ref=e27] [cursor=pointer]:
- img [ref=e28]
- alert [ref=e31]
```

View File

@ -82,4 +82,4 @@ Error generating stack: `+n.message+`
<div id='root'></div>
</body>
</html>
<script id="playwrightReportBase64" type="application/zip">data:application/zip;base64,UEsDBBQAAAgIAAtmMVw+tyFkyAAAABcBAAALAAAAcmVwb3J0Lmpzb25Vj81Ow0AMhF/F8nkVtQQ27d57rZDghnpwEwct+fHK69CiKO+OEgESc5r5DqOZGQc2asgIw7w4zEZqr3FgDPvKH/zT7vF4qMoHh82kZFFGDGVVlYX3xz95h23sOWN4uzhMKh9c25mGX5KNLGOY0cSox7BzyPfEtXGzhWn8F9ueuq/N5S6m9EOlw2A68eKQVUXXbjytLsCzSs05Q6syQC1jG9+LG19fWD9Z4UYZRjGga89gAtvHAk73aFBLwwH2eHEoab237lyWb1BLAQI/AxQAAAgIAAtmMVw+tyFkyAAAABcBAAALAAAAAAAAAAAAAAC0gQAAAAByZXBvcnQuanNvblBLBQYAAAAAAQABADkAAADxAAAAAAA=</script>
<script id="playwrightReportBase64" type="application/zip">data:application/zip;base64,UEsDBBQAAAgIANWbOFwzz5hazwgAALsgAAAZAAAAMjZkNTlhZjBhZTUxYWM3NDU5MDYuanNvbu1Zf3PjthH9KltMZyRPKZmiRMpi6mt91ybn9u4y03Mn05pOApErCTEJaADQss/n794BCFkURdnyNU06ncp/mBSBh/3x8HYF3pMZy/E8IzEJoiyc0JlPMRzQdDwKJ35EPPv8Ay2QxIQLjT2Jin1ifN5XS0z7WhGPaFRakfjy3l7tBeuN/SgMwnEwnmUYjGY0inzfTGc6N/BqIco8A8ZTiVQhmOVggWy+0LBaIId0Qfmc8Tko9glBC3hH5RyJR5ZS/ISpdmamCykKVhbEI7lIqWaCk/jeOvKEEznjSOKRR1KRlwUn8eTBI1kp3fwwGPkeoZwLbb8x/l55RNO5uxKlToVdv+R4u8RUY2ZMo3pB4kvywTjzN7csufKIRFXmLmzNZZSmUl8wixb4QdTzB71gdDE4iYd+PIz6k/H4n8RAaHlHYjsBly4DLpivcSYkwlshro13zyKe+AZxY8hgELXBfs1udSkREjKVYqVQJuQg9GgbPQrawN/RkqcLcMgH4U62ccPJBvfKI1Rrmi4K5Np9kYqSaxIPPKKu2XKJGYlnNFf48KLBXls8UsE13uoD4jHu+6OG3eO2cLyRSDWCAz4ENmwkMfrVorGkczwsFGGDGqNWOrtYGNiDQMfboMNfhBdfGrYP9IbNjXtaQEKOD4vb5KSxYcfB0z6+WAujjRYG/sN+VzyiuLnXJCaQlL4/mF5O/AIghM/udjgpYP3ZjDg+hm+EcXohiiq3m+EJ34KKnoIaRgVdUaZrI+pgw6K/eTIXWnTXt0HROe5snh1tZny11xLYsmR9OSjc1WBj3ebzvXsYBEUNt7rymwuM6wuQOk++o0zDTEhQmGOqhQSbUyG7nb5NZUpl1jnqz5hUunt0AIuC8bAhoL4/+plpFPg1HoUPHkEphSQxMXaJUv/Z3MZrV/omkV+bL9xjCH3fLxTgbYqYYdZP+Bua55CLeZzwdWALgB6YqaY9mD0XGcO5KcINU2yaYy05nLyc5oPJM9xMBVd1blojbD+w4dvpIdxd+1Sjb827Vh7XZjvfD+B44L9wtz161Lqwy2j3HpSm9TFxy0YKio5LS2dnqwyHhVeboiuCPI0XFoY/tQcP/7Fd/uKtHgz2bvWzmUZ5WPc2CvrBuFkMwqebt8PKcxuy/4WF9N/rmVp9HLQ2CrlQB/dMBvak0So8U0V/3U7hOyGvUcKbHCkvl4e4N2kI/OjnautbwcNfKHi2hFQuFKiU6Qr/O+uJ/Vvv/q2qcfJ8c/RY76mGHKnSILj7WWzW3yde/xsFqUVzXz2hrf+vWy11a9+nJbbtdeuJctV4FhzQ66MGxplmNHfHOnvRhi9ksIN9LW73UPhLOTAVJc8Yn78Wt/uYuqntNfEAs2f/FCdJhjc/cFz9sEyT5K+IyyS5Rlz2zC5RSWJPzpKktYGOAz8Owk1abGua4UxWp1wAMIDPwIqlkBruwUB5UJ08wQPMpCig88dlTu9W0sT62AzofJVwADDJMv+H8NnO62eoUsmm2O1snVN1POgewekruLfDR/DZ5sFM6Xa+8Liu4wFVdzyF7r0VFniorRC6Fcyn5dehGRLVhthc2kd9+7uuc9w5qjwcOw9PtgHdMQIFjqvKYC0AuTK1b4WwoDfmYWrKK6jcbHoDMqmBWObZqeqcL0sNp9X6j/WBmW8vlzlNcSHyDOVpQi7otV1UaOz3+wm5cmYO/G3zzmdg2hZgytabnH3CzAMt7x5/8dlZgx17Csp4uzmVHlujeqYrooyj9OAyo5r2TCJZdpqQzaCacY4lg+G2kX8plYY0Z+k1UH63WqBE0AtToky3Cjm7NvcIFg6mtLJ5tA3yrQSqVFlUQ2vRgsISaIqbCPTBIjS48RqVNtmbMZ7B9M4U35LmoLQsU9vLsBkshbKS7MG01FurMAWpKArB+xY72sZ+h7qjQGnjoxbWRLxlyjYDNvWQLjC9ruY6qg0aVHuueJspu8TalOL2RG73HV8l/JWteo0d8YjSb9SwGB6rlLeuQ7HtiODB7RyooTU/38MX91hQlQ/zP2hs8t2yYEYNd2JTE/nTHUe3hNp6EtQpx2bQ/c0G4Aj0QoqVlQHrixM+E15YUAVcwBoRpuLWbYkgbBglcpOheffH8y0HYvjt/WaxfvXlw48OJHJxGG/H4a24QWnophZiBTS1bxnswJMnErwws9YeTyrkYUNW3tjNmpD35o2AWFbAxOwJLTh0h3CDUrOU5pAJrY4sxGAb4u8KN6dOVjBNZJjZJmZ3GGozrliGcPH2/KOluYUJdqVKSHytOZzWnHgkeWVSvKCqq27m/bxMWYY9M6W3tvHI5WI43ImKg36C9GuWD0d7J1tlcwEdhi6gUXtAbUFLiIupHdlI6vk6QkOZAdNoWhV7rzAVPIO5FOUSSiNd+V0ldMOTHalcGWZyK7BWUgxAQW9ZYQorS93Su2qSG/u+tcbt6EnGbi6lyPE0IQXy0tiWkKud0LtVesFR56ifU6c6AKMGxy6MT+wTKqAS4WNB89yD95ixsvCqwt+HszyHUiG8d6CBtX0dkbSUErkG0+T8wS4xaFNlq7xgaHNWbRE7wSrMqKEs/0AVw+83q6U5Vcq8JDxNyKI3glVvBIXsBQmB41eVVud55UWF16h8Zzwzlt5ZFyXyDCWaBgiEzFDG7V5boEb1+yhq5GEVQd6dfbww+6ia4Ig32u12ajk9gOij8ZMAdbKPTtyakz2FrFxmriMKd0uOJZezx1WB7sD3/Qo7dNoftmi/0eCN7oe7us9xdajmhzuaX01+qd4DtedvtiVHJzjhXvH/8OiBEf5qxYboh070w4Y+nCmFsnK8vu2rNr67BXXU1+I1fmM7WHmxoLy7U2LgdxCuI77OoiNCZFK2vnbZiEw2jo+JOVExBCoVicmMsty+ud551719QnNPePVDxJ7F9DYnbvaK64u7pXlqvjwuqLzOxIo/vg8npv08nk1SH32fYkYxzQYTOk3pIIzCk2zgR3QcTVP0o+wk6hcZebgyVorrx0Ogh38BUEsDBBQAAAgIANWbOFxoIuFusgEAAEUDAAALAAAAcmVwb3J0Lmpzb26tks+OnDAMxl8F+ZyZDRTCkjeoVO2hWqmH1R68iYF0IEGJ0f4Z8e5VZuh0L+2p4mJi5/v8s3OGmRgtMoI+AxpecfoR4oliAl1uAhJj5Ec3E+iyVV3VdvUXVcpWgF0jsgsetKrq5thJJaB3EyXQT+dL9NWChkrZpsNeIjUlmrZuOqngWvmAWRZ8YDpESu7D+eGYFjJHTiCAKfFVLEd/FTu0UjVV01Ztb6mqe1RKynzd8ZTl0xjWyRbOm0iYqMh2xUhuGLl4HckXZkQ/OD8UyX1QwaH4hnEgELDE8JMM722aMYbZrTMImILZ0a+g/4CYnCfQtQATpnX2oLvt8+iaqpYC0PvAl5PM+yyAcdijsLIJF//V09tChsnm1pBH0E/wkGG+77aQ60+ge5wSCYiU1mmfIDKjGWfy+7+/MlGMIR5M8ExvDLlJz+T58X3J2Xx4N2M82fDqb6aQX8td3xlJUiJZJGPLDl8Mlo1q7m0pFbbqxZBU9l4dZwvbc/4ubynbn4ED4wS6FHBD0lJ8Jsy5fsLT+yWRTm5Z9qIb4JYlP60og/1Z0v+3E9dp/d7Lsq/rvG2/AFBLAQI/AxQAAAgIANWbOFwzz5hazwgAALsgAAAZAAAAAAAAAAAAAAC0gQAAAAAyNmQ1OWFmMGFlNTFhYzc0NTkwNi5qc29uUEsBAj8DFAAACAgA1Zs4XGgi4W6yAQAARQMAAAsAAAAAAAAAAAAAALSBBgkAAHJlcG9ydC5qc29uUEsFBgAAAAACAAIAgAAAAOEKAAAAAA==</script>

View File

@ -314,7 +314,6 @@ const config = {
"db"
],
"activeProvider": "sqlite",
"postinstall": false,
"inlineDatasources": {
"db": {
"url": {

View File

@ -315,7 +315,6 @@ const config = {
"db"
],
"activeProvider": "sqlite",
"postinstall": false,
"inlineDatasources": {
"db": {
"url": {

Binary file not shown.

View File

@ -1,4 +1,6 @@
{
"status": "failed",
"failedTests": []
"failedTests": [
"26d59af0ae51ac745906-706525727fde24fa6600"
]
}

View File

@ -0,0 +1,24 @@
# Page snapshot
```yaml
- generic [active] [ref=e1]:
- main [ref=e4]:
- generic [ref=e7]:
- heading "Sign in to your account" [level=1] [ref=e8]
- generic [ref=e9]:
- generic [ref=e10]:
- generic [ref=e11]: Email
- textbox "Email" [ref=e13]:
- /placeholder: Enter your email address
- generic [ref=e14]:
- generic [ref=e15]: Password
- textbox "Password" [ref=e17]:
- /placeholder: Enter your password
- link "Forgot password?" [ref=e19] [cursor=pointer]:
- /url: /forgot-password
- button "Sign In" [ref=e20]
- region "Notifications alt+T"
- button "Open Next.js Dev Tools" [ref=e27] [cursor=pointer]:
- img [ref=e28]
- alert [ref=e31]
```

View File

@ -0,0 +1,104 @@
import { test, expect } from '@playwright/test';
test.describe('Test des couleurs bleues', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3000');
});
test('Vérifier les couleurs bleues dans la page', async ({ page }) => {
// Attendre que la page charge
await page.waitForLoadState('networkidle');
// Capturer tous les éléments avec des couleurs bleues via les classes
const blueElements = await page.locator('*[class*="blue"]').all();
console.log('Nombre d\'éléments avec "blue" dans leur classe:', blueElements.length);
// Pour chaque élément avec une classe bleue, capturer les détails
for (let i = 0; i < Math.min(blueElements.length, 20); i++) {
const element = blueElements[i];
const tagName = await element.evaluate(el => el.tagName);
const className = await element.evaluate(el => el.className);
const backgroundColor = await element.evaluate(el => window.getComputedStyle(el).backgroundColor);
const color = await element.evaluate(el => window.getComputedStyle(el).color);
const textContent = await element.evaluate(el => el.textContent?.substring(0, 50));
console.log(`Élément ${i + 1}:`, {
tagName,
className: className.substring(0, 100),
backgroundColor,
color,
textContent
});
}
// Chercher aussi les styles inline bleus
const elementsWithBlueStyle = await page.locator('*[style*="blue"]').all();
console.log('Nombre d\'éléments avec style "blue":', elementsWithBlueStyle.length);
// Prendre une capture d'écran pour visualisation
await page.screenshot({ path: 'blue-color-test.png', fullPage: true });
// Vérifier spécifiquement les boutons
const buttons = await page.locator('button').all();
console.log('Nombre total de boutons:', buttons.length);
for (let i = 0; i < Math.min(buttons.length, 10); i++) {
const button = buttons[i];
const backgroundColor = await button.evaluate(el => window.getComputedStyle(el).backgroundColor);
const color = await button.evaluate(el => window.getComputedStyle(el).color);
const textContent = await button.evaluate(el => el.textContent?.substring(0, 30));
if (backgroundColor.includes('blue') || backgroundColor.includes('rgb(37, 99, 235)') ||
backgroundColor.includes('rgb(29, 78, 216)') || backgroundColor.includes('rgb(59, 130, 246)')) {
console.log('Bouton bleu trouvé:', {
backgroundColor,
color,
textContent
});
}
}
});
test('Vérifier les variables CSS du thème', async ({ page }) => {
// Attendre que la page charge
await page.waitForLoadState('networkidle');
// Récupérer les variables CSS du root
const cssVars = await page.evaluate(() => {
const rootStyle = getComputedStyle(document.documentElement);
const variables: Record<string, string> = {};
// Variables de thème principales
const varNames = [
'--background',
'--foreground',
'--primary',
'--primary-foreground',
'--secondary',
'--accent',
'--border',
'--ring'
];
varNames.forEach(varName => {
variables[varName] = rootStyle.getPropertyValue(varName).trim();
});
return variables;
});
console.log('Variables CSS du thème:', cssVars);
// Vérifier si les teintes contiennent des valeurs bleues (220-260)
for (const [varName, value] of Object.entries(cssVars)) {
const hueMatch = value.match(/(\d+)\s*\)/);
if (hueMatch) {
const hue = parseInt(hueMatch[1]);
if (hue >= 220 && hue <= 260) {
console.log(`⚠️ ${varName} contient une teinte bleue: ${value}`);
}
}
}
});
});

View File

@ -0,0 +1,60 @@
import { test, expect } from '@playwright/test';
test.describe('Note Resizing', () => {
test('should increase note height when changing size to Large', async ({ page }) => {
// Go to home page
await page.goto('/');
// Create a new note to ensure we have a clean slate
const notesInput = page.locator('input[placeholder="Take a note..."]');
// If text is localized, try selector
const mainInput = page.locator('.note-input-container, [data-testid="note-input"]');
// Just click anywhere that looks like the input bar
// Or assume the placeholder might be localized.
// Best to find by visual structure if possible, but placeholder is common.
// Let's stick to the existing notes check.
// Wait for at least one note card
const firstNote = page.locator('.note-card').first();
await firstNote.waitFor({ state: 'visible', timeout: 5000 });
// Get initial height
const initialBox = await firstNote.boundingBox();
if (!initialBox) throw new Error('Note card has no bounding box');
console.log(`Initial height: ${initialBox.height}`);
// Hover to show actions
await firstNote.hover();
// Click "More options" button (3 vertical dots)
// Use selector ensuring it's the one inside THIS note
const moreBtn = firstNote.locator('button:has(svg.lucide-more-vertical)');
await moreBtn.waitFor({ state: 'visible' });
await moreBtn.click();
// Click "Large" option
// It's the 3rd item in the second group usually.
// Or we can look for the maximize icon
const largeOption = page.locator('div[role="menuitem"]:has(svg.lucide-maximize-2)').last();
// The sizes are Small, Medium, Large. All use Maximize2 icon in the current code?
// Let's check NoteActions code.
// Yes: <Maximize2 className="h-4 w-4 mr-2" /> for all sizes.
// And they are rendered in order: Small, Medium, Large.
// So "Large" is the LAST one.
await largeOption.waitFor({ state: 'visible' });
await largeOption.click();
// Wait for update
await page.waitForTimeout(1000);
// Get new height
const newBox = await firstNote.boundingBox();
if (!newBox) throw new Error('Note card has no bounding box after resize');
console.log(`New height: ${newBox.height}`);
// Assert
expect(newBox.height).toBeGreaterThan(initialBox.height + 50);
});
});