diff --git a/.gitignore b/.gitignore index 4a7f73a..4453835 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,9 @@ logs .fleet .idea +# Sources originales (PDF, JPG — pas servies par l'appli) +sources/ + # Local env files .env .env.* diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..bc675e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,72 @@ +# Librodrome + +Site vitrine du projet Le Librodrome — livre + chansons sur l'économie du don. + +## Stack + +- **Nuxt 4** (Vue 3, TypeScript, Nitro) +- **Modules** : Nuxt Content, Pinia, UnoCSS, VueUse, Nuxt Image +- **Icônes** : Lucide + Phosphor (via @iconify-json) +- **Package manager** : pnpm +- **Déploiement** : Docker + Traefik, CI via Woodpecker + +## Structure + +``` +app/ + pages/ # Routes : index, lire/, ecouter/, autonomie, evenement, gratewizard, a-propos, admin/ + components/ # admin, book, content, home, layout, player, song, ui + composables/ # useAudioPlayer, useBookData, useGrateWizard, usePlaylist, usePageContent, useSiteContent... + assets/css/ # main.css (UnoCSS) +data/ + pages/ # Contenu YAML par page (home, lire, ecouter, autonomie, evenement...) + site.yml # Config globale du site +server/ + api/ # Endpoints API (admin, health) + middleware/ # Auth middleware +docker/ + Dockerfile # Build multi-stage (dev + prod) + docker-compose.yml # Production (Traefik) + docker-compose.dev.yml # Dev Docker +``` + +## Ports dev (CRITIQUE) + +Chaque projet a un port fixe pour éviter les conflits Nuxt auto-increment : + +| Projet | Port | Config | +|--------|------|--------| +| **librodrome** | **3000** | `nuxt.config.ts` → `devServer.port: 3000` | +| **GrateWizard** | **3001** | `package.json` → `next dev --port 3001` | +| **SejeteralO frontend** | **3009** | `frontend/nuxt.config.ts` → `devServer.port: 3009` | +| **SejeteralO backend** | **8000** | Makefile → `uvicorn --port 8000` | + +Script de gestion : `/home/yvv/Documents/PROD/DEV/dev-ports.sh` (status/kill/clean/start) + +**Ne jamais changer ces ports.** Si un port est occupé, tuer le process parasite plutôt que laisser Nuxt auto-incrémenter. + +## Intégration GrateWizard + +- URL dev configurée dans `app/app.config.ts` → `localhost:3001` +- URL prod : `https://gratewizard.ml` +- Ouverture en popup via `composables/useGrateWizard.ts` +- GrateWizard est un projet Next.js séparé (`/home/yvv/Documents/PROD/DEV/GrateWizard`) + +## Contenu + +Le contenu des pages est dans `data/pages/*.yml` et chargé via `composables/usePageContent.ts`. Le contenu riche (articles) utilise Nuxt Content avec des fichiers Markdown. + +## Commandes + +```bash +pnpm dev # Dev server sur :3000 +pnpm build # Build production +pnpm generate # Génération statique +``` + +## Conventions + +- Langue du site : français +- Commits en français, style concis +- CSS via UnoCSS (utility-first), pas de framework CSS externe +- Composants Vue SFC avec ` diff --git a/app/assets/css/animations.css b/app/assets/css/animations.css index 4c50d17..e65b9e5 100644 --- a/app/assets/css/animations.css +++ b/app/assets/css/animations.css @@ -60,10 +60,10 @@ @keyframes glow-pulse { 0%, 100% { - box-shadow: 0 0 8px hsl(12 76% 48% / 0.3); + box-shadow: 0 0 8px hsl(var(--color-primary) / 0.3); } 50% { - box-shadow: 0 0 24px hsl(12 76% 48% / 0.6); + box-shadow: 0 0 24px hsl(var(--color-primary) / 0.6); } } diff --git a/app/assets/css/fonts.css b/app/assets/css/fonts.css index 6bad2cc..b0bf479 100644 --- a/app/assets/css/fonts.css +++ b/app/assets/css/fonts.css @@ -2,11 +2,11 @@ /* This file provides fallback and utility classes */ .font-display { - font-family: 'Syne', system-ui, sans-serif; + font-family: 'Outfit', system-ui, sans-serif; } .font-sans { - font-family: 'Space Grotesk', system-ui, sans-serif; + font-family: 'Inter', system-ui, sans-serif; } .font-mono { diff --git a/app/assets/css/main.css b/app/assets/css/main.css index 8d30d5e..a120d51 100644 --- a/app/assets/css/main.css +++ b/app/assets/css/main.css @@ -3,20 +3,20 @@ @import './typography.css'; :root { - --color-primary: 12 76% 48%; - --color-accent: 36 80% 52%; - --color-bg: 20 8% 3.5%; - --color-surface: 20 8% 8%; - --color-surface-light: 20 8% 13%; + --color-primary: 18 80% 45%; + --color-accent: 32 85% 50%; + --color-bg: 20 10% 7%; + --color-surface: 20 10% 12%; + --color-surface-light: 20 8% 17%; --color-text: 0 0% 100%; - --color-text-muted: 0 0% 100% / 0.6; + --color-text-muted: 0 0% 65%; --header-height: 4rem; - --player-height: 5rem; + --player-height: 0rem; --sidebar-width: 280px; - --font-display: 'Syne', sans-serif; - --font-sans: 'Space Grotesk', sans-serif; + --font-display: 'Outfit', sans-serif; + --font-sans: 'Inter', sans-serif; --font-mono: 'JetBrains Mono', monospace; --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1); @@ -43,9 +43,22 @@ body { min-height: 100dvh; } +button { + border: none; + background: none; + cursor: pointer; + font: inherit; + color: inherit; +} + +a { + text-decoration: none; + color: inherit; +} + ::selection { background-color: hsl(var(--color-primary) / 0.3); - color: white; + color: hsl(var(--color-text)); } :focus-visible { @@ -72,6 +85,113 @@ body { background: hsl(var(--color-text) / 0.25); } +/* ═══ Light mode overrides ═══ */ +.palette-light { + color-scheme: light; +} + +/* Force all white text → adaptive text color in light mode. + Using !important to override scoped component styles and UnoCSS utilities. */ +.palette-light, +.palette-light .text-white { + color: hsl(var(--color-text)) !important; +} + +/* white with opacity → dark text with boosted opacity for punch */ +.palette-light .text-white\/20 { color: hsl(var(--color-text) / 0.28) !important; } +.palette-light .text-white\/30 { color: hsl(var(--color-text) / 0.38) !important; } +.palette-light .text-white\/40 { color: hsl(var(--color-text) / 0.48) !important; } +.palette-light .text-white\/45 { color: hsl(var(--color-text) / 0.52) !important; } +.palette-light .text-white\/50 { color: hsl(var(--color-text) / 0.58) !important; } +.palette-light .text-white\/60 { color: hsl(var(--color-text) / 0.68) !important; } +.palette-light .text-white\/70 { color: hsl(var(--color-text) / 0.78) !important; } +.palette-light .text-white\/80 { color: hsl(var(--color-text) / 0.88) !important; } +.palette-light .text-white\/85 { color: hsl(var(--color-text) / 0.92) !important; } + +/* white backgrounds → surface tones with more contrast */ +.palette-light .bg-white\/5 { background-color: hsl(var(--color-primary) / 0.05) !important; } +.palette-light .bg-white\/8 { background-color: hsl(var(--color-primary) / 0.07) !important; } +.palette-light .bg-white\/10 { background-color: hsl(var(--color-primary) / 0.09) !important; } + +/* borders with primary tint */ +.palette-light .border-white\/8 { border-color: hsl(var(--color-primary) / 0.15) !important; } + +/* hover overrides */ +.palette-light .hover\:text-white:hover, +.palette-light .hover\:text-white\/70:hover, +.palette-light .hover\:text-white\/80:hover { + color: hsl(var(--color-text)) !important; +} +.palette-light .hover\:text-white\/60:hover { + color: hsl(var(--color-text) / 0.7) !important; +} +.palette-light .hover\:bg-white\/5:hover { + background-color: hsl(var(--color-primary) / 0.08) !important; +} +.palette-light .hover\:bg-white\/10:hover { + background-color: hsl(var(--color-primary) / 0.12) !important; +} + +/* group-hover overrides */ +.palette-light .group:hover .group-hover\:text-primary\/60 { + color: hsl(var(--color-primary) / 0.7) !important; +} + +/* placeholder overrides */ +.palette-light .placeholder\:text-white\/30::placeholder { + color: hsl(var(--color-text) / 0.35) !important; +} + +/* Prose/content in light mode */ +.palette-light .prose { color: hsl(var(--color-text)); } +.palette-light .prose :where(h1,h2,h3,h4,h5,h6) { color: hsl(var(--color-text)); } + +/* text-gradient — solid primary color everywhere */ + +/* card surfaces — subtle shadow for depth */ +.palette-light .card-surface { + background: hsl(var(--color-surface)) !important; + border-color: hsl(var(--color-primary) / 0.12) !important; + box-shadow: 0 1px 3px hsl(var(--color-text) / 0.06); +} + +/* btn-primary text stays white on colored bg */ +.palette-light .btn-primary { + color: white !important; +} + +/* input fields — cleaner contrast */ +.palette-light input, +.palette-light textarea { + color: hsl(var(--color-text)); + background-color: white; + border-color: hsl(var(--color-text) / 0.18); +} + +.palette-light input:focus, +.palette-light textarea:focus { + border-color: hsl(var(--color-primary) / 0.5); + box-shadow: 0 0 0 3px hsl(var(--color-primary) / 0.1); +} + +/* Ecouter view toggle buttons */ +.palette-light .bg-white\/10 { + background-color: hsl(var(--color-primary) / 0.1) !important; +} + +/* Light mode scrollbar — tinted with primary */ +.palette-light ::-webkit-scrollbar-thumb { + background: hsl(var(--color-primary) / 0.2); +} +.palette-light ::-webkit-scrollbar-thumb:hover { + background: hsl(var(--color-primary) / 0.35); +} + +/* Light mode selection — vivid */ +.palette-light ::selection { + background-color: hsl(var(--color-accent) / 0.25); +} + /* Page transitions */ .page-enter-active, .page-leave-active { diff --git a/app/assets/css/typography.css b/app/assets/css/typography.css index 501bc74..65a152b 100644 --- a/app/assets/css/typography.css +++ b/app/assets/css/typography.css @@ -3,7 +3,7 @@ font-family: var(--font-sans); font-size: 1.125rem; line-height: 1.8; - color: hsl(0 0% 100% / 0.90); + color: hsl(var(--color-text) / 0.90); max-width: 65ch; } @@ -13,11 +13,11 @@ font-weight: 800; line-height: 1.25; letter-spacing: -0.02em; - color: white; + color: hsl(var(--color-text)); margin-top: 0; margin-bottom: 1.5rem; padding-bottom: 0.75rem; - border-bottom: 2px solid hsl(12 76% 48% / 0.4); + border-bottom: 2px solid hsl(var(--color-primary) / 0.4); } .prose h2 { @@ -26,11 +26,11 @@ font-weight: 700; line-height: 1.3; letter-spacing: -0.01em; - color: white; + color: hsl(var(--color-text)); margin-top: 3.5rem; margin-bottom: 1rem; padding-left: 0.75rem; - border-left: 3px solid hsl(12 76% 48% / 0.5); + border-left: 3px solid hsl(var(--color-primary) / 0.5); } .prose h3 { @@ -38,7 +38,7 @@ font-size: clamp(1.25rem, 3vw, 1.625rem); font-weight: 600; line-height: 1.4; - color: hsl(0 0% 100% / 0.92); + color: hsl(var(--color-text) / 0.92); margin-top: 3rem; margin-bottom: 0.75rem; } @@ -49,7 +49,7 @@ width: 0.5rem; height: 0.5rem; border-radius: 50%; - background: hsl(36 80% 52%); + background: hsl(var(--color-accent)); margin-right: 0.625rem; vertical-align: middle; position: relative; @@ -61,7 +61,7 @@ font-size: clamp(1.065rem, 2.5vw, 1.25rem); font-weight: 600; line-height: 1.45; - color: hsl(0 0% 100% / 0.85); + color: hsl(var(--color-text) / 0.85); margin-top: 2.5rem; margin-bottom: 0.625rem; } @@ -69,7 +69,7 @@ .prose h4::before { content: '//'; font-family: var(--font-mono); - color: hsl(36 80% 52%); + color: hsl(var(--color-accent)); margin-right: 0.5rem; font-weight: 500; } @@ -78,7 +78,7 @@ .prose h2 + p, .prose h3 + p { font-size: 1.175rem; - color: hsl(0 0% 100% / 0.75); + color: hsl(var(--color-text) / 0.75); line-height: 1.85; } @@ -88,25 +88,25 @@ } .prose a { - color: hsl(12 76% 68%); + color: hsl(var(--color-primary) / 0.85); text-decoration: underline; - text-decoration-color: hsl(12 76% 58% / 0.3); + text-decoration-color: hsl(var(--color-primary) / 0.3); text-underline-offset: 3px; transition: text-decoration-color 0.2s; } .prose a:hover { - text-decoration-color: hsl(12 76% 58%); + text-decoration-color: hsl(var(--color-primary)); } .prose blockquote { margin: 2rem 0; padding: 1rem 1.5rem; - border-left: 3px solid hsl(12 76% 58%); - background: hsl(240 10% 8%); + border-left: 3px solid hsl(var(--color-primary)); + background: hsl(var(--color-surface)); border-radius: 0 0.5rem 0.5rem 0; font-style: italic; - color: hsl(0 0% 100% / 0.75); + color: hsl(var(--color-text) / 0.75); } .prose blockquote p:last-child { @@ -116,17 +116,17 @@ .prose code { font-family: var(--font-mono); font-size: 0.875em; - background: hsl(240 10% 12%); + background: hsl(var(--color-surface-light)); padding: 0.2em 0.4em; border-radius: 0.25rem; - color: hsl(31 97% 66%); + color: hsl(var(--color-accent)); } .prose pre { margin: 2rem 0; padding: 1.5rem; - background: hsl(240 10% 6%); - border: 1px solid hsl(0 0% 100% / 0.08); + background: hsl(var(--color-bg)); + border: 1px solid hsl(var(--color-text) / 0.08); border-radius: 0.75rem; overflow-x: auto; } @@ -134,7 +134,7 @@ .prose pre code { background: none; padding: 0; - color: hsl(0 0% 100% / 0.87); + color: hsl(var(--color-text) / 0.87); } .prose ul, @@ -149,22 +149,22 @@ } .prose li::marker { - color: hsl(12 76% 58%); + color: hsl(var(--color-primary)); } .prose hr { margin: 3rem 0; border: none; - border-top: 1px solid hsl(0 0% 100% / 0.1); + border-top: 1px solid hsl(var(--color-text) / 0.1); } .prose strong { - color: white; + color: hsl(var(--color-text)); font-weight: 600; } .prose em { - color: hsl(0 0% 100% / 0.9); + color: hsl(var(--color-text) / 0.9); } .prose img { diff --git a/app/components/admin/AdminMarkdownEditor.vue b/app/components/admin/AdminMarkdownEditor.vue index 3cbf9a3..06c4678 100644 --- a/app/components/admin/AdminMarkdownEditor.vue +++ b/app/components/admin/AdminMarkdownEditor.vue @@ -1,34 +1,57 @@