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

84 lines
3.4 KiB
Vue
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.

<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>