Files
portfolio-generator-app/components/templates/FreeTextPage.vue
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

70 lines
3.0 KiB
Vue

<script setup lang="ts">
// Page libre (équipe, méthodologie…) — langage du gabarit : filet couleur
// 5 mm, marges 8 mm, titre Syne 45 pt, texte Syne 12/16 pt. Contenu réparti en
// 1 à 3 colonnes de texte ou d'images, chacune avec un sous-titre optionnel.
// Pas de multi-colonnes CSS (Chromium les imprime en blanc) : flexbox explicite.
import type { Background, ResolvedMedia } from '~~/types'
interface ResolvedColumn {
id: string
kind: 'text' | 'media'
heading?: string
html?: string // colonnes texte
media?: ResolvedMedia[] // colonnes images
}
const props = withDefaults(
defineProps<{
title?: string
columns?: ResolvedColumn[]
background: Background
bandColor?: string
textScale?: number // taille du texte de labeur en % (défaut 100 = 12 pt)
page: { index: number; total: number }
renderMode?: 'preview' | 'print'
}>(),
{ renderMode: 'preview', textScale: 100, columns: () => [] },
)
const strip = computed(() => props.bandColor || FL_INK)
const pageStyle = computed(() => backgroundStyle(props.background, strip.value))
// Texte de labeur : base 16 px (12 pt), agrandissable ; interligne proportionnel.
const bodyStyle = computed(() => ({ fontSize: `${(16 * props.textScale) / 100}px`, lineHeight: '1.35' }))
</script>
<template>
<div class="fl-page" :style="pageStyle">
<div v-if="background.mode !== 'full'" class="absolute inset-x-0 top-0" :style="{ height: 'var(--fl-strip)', backgroundColor: strip }" />
<div class="absolute flex flex-col" :style="{ inset: 'var(--fl-margin)', top: '60px' }">
<h2 v-if="title" class="max-w-[1000px]" :style="{ fontSize: '60px', lineHeight: '1.05' }">{{ title }}</h2>
<div class="mt-12 flex min-h-0 flex-1" :style="{ gap: 'var(--fl-gutter)' }">
<section v-for="col in columns" :key="col.id" class="flex min-w-0 flex-1 flex-col">
<!-- Sous-titre optionnel en tête de colonne -->
<h3 v-if="col.heading" class="mb-4 shrink-0" :style="{ fontSize: '28px', lineHeight: '1.15' }">{{ col.heading }}</h3>
<!-- Colonne texte -->
<div
v-if="col.kind === 'text'"
class="fl-prose min-h-0 flex-1 overflow-hidden"
:style="bodyStyle"
v-html="col.html"
/>
<!-- Colonne images : entières, calées, titre du projet en légende -->
<div v-else class="flex min-h-0 flex-1 flex-col" :style="{ gap: 'var(--fl-gutter)' }">
<figure v-for="m in col.media" :key="m.filename" class="flex min-h-0 flex-1 flex-col">
<img :src="m.url" :alt="m.caption || m.filename" class="min-h-0 flex-1 object-contain object-left-top">
<figcaption
v-if="m.caption"
class="mt-1 shrink-0 text-left opacity-70"
:style="{ fontFamily: 'Syne, sans-serif', fontSize: '14px', lineHeight: '1.3' }"
>{{ m.caption }}</figcaption>
</figure>
</div>
</section>
</div>
</div>
</div>
</template>