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:
83
components/templates/TeamTemplate.vue
Normal file
83
components/templates/TeamTemplate.vue
Normal 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>
|
||||
Reference in New Issue
Block a user