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

5.7 KiB
Raw Blame History

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.

# 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].vuerendu 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 : useThemebackgroundColorOf(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).