# CLAUDE.md — générateur de portfolios App Nuxt (Nuxt 3 + Nuxt UI v3) qui lit les projets du CMS Grav de Figures Libres et compose des portfolios **A3 paysage** exportés en PDF, fidèles au gabarit `book_v3`. Ce dépôt est **autonome et déployable seul** (son `Dockerfile` est ici). ## Tout tourne dans Docker Node, build Nuxt, Playwright/Chromium, Ghostscript, ffmpeg, rsync — **rien sur l'hôte**. En dev, l'app est lancée via le dépôt d'orchestration `portfolio-stack` (`docker compose up -d`), qui monte ce dossier en volume sur le port 8091. ```bash # depuis le dépôt stack (ce dossier cloné en ./generator) : docker compose run --rm --no-deps generator npm install # dépendances docker compose exec generator npm run build # build de prod (.output) docker logs -f figureslibres-portfolio-generator # logs du dev server ``` Mot de passe dev : `figureslibres` (`PORTFOLIO_PASSWORD`). ## Architecture - `server/lib/` — le cœur : - `content-source.ts` : abstrait la source de contenu. Mode `dev` = lecture directe du FS Grav monté en `:ro` ; mode `prod` = cache alimenté par rsync/SSH. - `grav-reader.ts` : parse les pages Grav (frontmatter YAML, `media_order`, taxonomies) en `Project` (voir `types/index.ts`). - `pdf-pipeline.ts` : rend `/print/` via Playwright/Chromium en A3 paysage (1190,55 × 841,89 pt), images à 2× (sharp), post-traitement Ghostscript, métadonnées. - `portfolio-store.ts` : CRUD des portfolios (JSON sur disque), gel/dégel du contenu. - `media.ts`, `session.ts`. - `server/api/` — endpoints (auth, projects, portfolios, media, sync, exports, public). - `components/templates/` — pages du gabarit (cover, toc, projet, grille, page libre). - `components/preview/` — `PageRenderer` (dispatch) + `PageViewport` (aperçu à l'échelle). - `components/composer/` — UI de composition (curation médias, fonds, pickers). - `pages/print/[id].vue` — **rendu print servant à la génération PDF** (ne pas casser). - `types/index.ts` — modèle de données complet et commenté (source de vérité du schéma). ## Modèle de données (résumé) Un `Portfolio` = `cover` (CoverConfig) + `pages[]`. Types de page : `cover`, `toc`, `project` (couverture d'un projet, disposition `band` = bandeau haut **ou** `split` = colonne texte + image pleine hauteur à droite), `project-grid` (grille d'images entières), `free`. Le contenu Grav (`Project`, `MediaAsset`) est **en lecture seule**. À la génération, le contenu utilisé est **figé** dans `data/frozen//` (`FrozenSnapshot`) : régénérer redonne le même PDF ; « Réactualiser depuis Grav » resynchronise volontairement. Chaque page porte un **fond unifié** `background` à trois modes : `white` | `tint` (aplat beige fixe `#efece3`) | `full` (la page — ou le bandeau / la colonne d'une couverture projet — prend la couleur du filet `bandColor`, défaut encre `FL_INK`). Il n'y a **plus** de couleur automatique par catégorie (`bandColorOf`/`stripColorOf` retombent sur `FL_INK`). ## Contraintes fermes - Le contenu Grav n'est jamais modifié par l'app (monté `:ro` côté stack). - Les images ne sont **jamais tronquées** dans les grilles (calées entières). - Les vidéos ne sont pas sélectionnables dans les grilles. - Format PDF : A3 paysage aux dimensions exactes du gabarit. ## Conventions de rendu (templates) - **Fond `background`** : `useTheme` → `backgroundColorOf(bg, fullColor)` / `backgroundStyle(bg, fullColor)`. Le mode `full` **dérive** sa couleur du filet au rendu (`fullColor`) — ne rien stocker en dur ; le `BackgroundEditor` (Blanc / Teinté / Plein) est le même sur tous les types de page. - **Syne toujours en Regular** (poids 400), y compris titres et gras de prose ; seule exception : le mot « Figures Libres » de la couverture (Bold). - **Liens Grav** : `stripTargetParam` retire le `?target=_blank` que Grav accole à chaque URL (lien projet + liens dans les corps de texte), à l'affichage. - Filet : `--fl-strip` (5 mm, couverture/sommaire/page libre) ou `--fl-strip-light` (2 mm, filet fin des grilles et des couvertures projet non « plein »). ## Pièges Chromium print (avant de toucher aux templates) - **`columns-*` (CSS multicol) = page PDF blanche** dans Chromium print. Ne pas utiliser de colonnes CSS dans les templates ; vérifier chaque template via `pdftoppm`. - **Webfonts en `font-display: block` = texte absent du PDF**. Le pipeline réinvalide les styles avant `page.pdf()` — ne pas retirer cette étape. ## Données Tout est sur disque dans `data/` (sauvegarde = copie du dossier), git-ignoré : `portfolios/*.json`, `exports/*.pdf`, `frozen//`, `cache/` (régénérable), `content-cache/` (rsync prod).