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
This commit is contained in:
2026-07-13 16:00:59 +02:00
parent ef1f14335d
commit 1c1604ea2f
29 changed files with 802 additions and 265 deletions

View File

@@ -0,0 +1,83 @@
<script setup lang="ts">
// Page « équipe » — langage du gabarit : filet couleur 5 mm en haut, marges
// 8 mm, titre Syne 45 pt. Les membres s'affichent en grille de 3 colonnes
// (portrait, nom, rôle, présentation), qui s'adapte à leur nombre. Pas de
// multi-colonnes CSS (Chromium les imprime en blanc) : grille explicite.
import type { Background } from '~~/types'
interface ResolvedMember {
id: string
name: string
portraitUrl: string
role?: string
bio?: string
}
const props = withDefaults(
defineProps<{
title?: string
members?: ResolvedMember[]
background: Background
bandColor?: string
page: { index: number; total: number }
renderMode?: 'preview' | 'print'
}>(),
{ title: 'Léquipe', renderMode: 'preview', members: () => [] },
)
const strip = computed(() => props.bandColor || FL_INK)
const pageStyle = computed(() => backgroundStyle(props.background, strip.value))
// 3 colonnes au plus. On prend le minimum de rangées possible, puis on répartit
// les membres pour équilibrer la grille (4 membres → 2×2, 5 → 3+2, etc.).
const count = computed(() => props.members.length || 1)
const rows = computed(() => Math.ceil(count.value / 3))
const cols = computed(() => Math.ceil(count.value / rows.value))
const layout = computed(() => {
if (rows.value <= 1) return { portrait: 280, name: 30, role: 20, bio: 15, top: 48, gapY: 40 }
if (rows.value === 2) return { portrait: 168, name: 23, role: 16, bio: 12, top: 36, gapY: 28 }
return { portrait: 124, name: 19, role: 14, bio: 10.5, top: 28, gapY: 22 }
})
// Peu de colonnes sur plusieurs rangées (ex. 4 membres en 2×2) = colonnes très
// larges : on rentre la grille des deux côtés pour resserrer les colonnes.
const gridInset = computed(() => (rows.value >= 2 && cols.value <= 2 ? 190 : 0))
const gridStyle = computed(() => ({
gridTemplateColumns: `repeat(${cols.value}, minmax(0, 1fr))`,
columnGap: '48px',
rowGap: `${layout.value.gapY}px`,
gridAutoRows: 'min-content',
marginTop: `${layout.value.top}px`,
paddingInline: `${gridInset.value}px`,
}))
</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 :style="{ fontSize: '60px', lineHeight: '1.05' }">{{ title }}</h2>
<div class="grid min-h-0 flex-1 content-start overflow-hidden" :style="gridStyle">
<div v-for="m in members" :key="m.id" class="flex min-w-0 flex-col">
<!-- Portrait carré, cadré (les portraits non carrés sont recentrés) -->
<div
class="w-full overflow-hidden"
:style="{ aspectRatio: '1 / 1', maxWidth: `${layout.portrait}px` }"
>
<img :src="m.portraitUrl" :alt="m.name" class="size-full object-cover">
</div>
<p class="mt-4" :style="{ fontSize: `${layout.name}px`, lineHeight: '1.15' }">{{ m.name }}</p>
<p v-if="m.role" class="opacity-70" :style="{ fontSize: `${layout.role}px`, lineHeight: '1.2' }">{{ m.role }}</p>
<p
v-if="m.bio"
class="mt-2"
:style="{ fontSize: `${layout.bio}px`, lineHeight: '1.35', hyphens: 'auto', textAlign: 'justify' }"
>{{ m.bio }}</p>
</div>
</div>
</div>
</div>
</template>