Files
portfolio-generator-app/CLAUDE.md
Valentin Le Moign 1c1604ea2f Pages équipe + conclusion, colonnes de page libre, réglages couverture
- Page « équipe » : roster statique du collectif (composables/useTeam),
  portraits servis depuis public/portraits, grille ≤ 3 colonnes équilibrée
  (portrait/nom/rôle/présentation), sélection + surcharges par membre
  (repliables, réordonnables, réinitialisables) dans le composeur
- Page « conclusion » : « Merci ! » centré + rappel des contacts
- Page libre : 1 à 3 colonnes texte (markdown) ou images avec sous-titres,
  réordonnables ; migration des champs body/media legacy vers columns
- Couverture : texte libre sous l'intro, adresse en bas à gauche (3 lignes),
  case « afficher les années » au niveau du document
- Sommaire : items centrés (self-center), filet en points dégradés stable
- Retrait de la dépendance morte pagedjs
- Docs à jour (CLAUDE.md, README) ; spec marquée comme archive réalisée
2026-07-13 16:00:59 +02:00

103 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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/<id>` 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, équipe `TeamTemplate`, conclusion `OutroTemplate`).
- `components/preview/``PageRenderer` (dispatch) + `PageViewport` (aperçu à l'échelle).
- `components/composer/` — UI de composition (curation médias, fonds, pickers).
- `composables/useTeam.ts` — roster statique du collectif (`TEAM_ROSTER` : nom,
portrait, rôle et présentation par défaut de chaque membre) ; les portraits
sont servis en statique depuis `public/portraits/`.
- `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` — couverture (titre, texte libre, date, site/mail, adresse en bas à
gauche) ; `showYears` (au niveau du portfolio) masque les années des projets.
- `toc` — sommaire, généré depuis les pages projet (hérite du filet de la couverture).
- `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` — page libre : 1 à 3 **colonnes** (`FreeColumn`) de texte (markdown) ou
d'images, chacune avec sous-titre optionnel (les champs `body`/`media` legacy
sont migrés vers `columns` à l'ouverture, avec repli au rendu disque).
- `team` — page équipe : membres du roster en grille (≤ 3 colonnes, équilibrée),
portrait + nom + rôle + présentation ; sélection et surcharges par membre.
- `outro` — page de conclusion : « Merci ! » centré + rappel des contacts.
Le contenu Grav (`Project`, `MediaAsset`) est **en lecture seule**. À la
génération, le contenu utilisé est **figé** dans `data/frozen/<id>/`
(`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/équipe/conclusion) 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/<id>/`, `cache/` (régénérable),
`content-cache/` (rsync prod).