App Nuxt 3 + Nuxt UI qui lit les projets du CMS Grav et compose des portfolios A3 paysage exportés en PDF (gabarit book_v3) : composeur WYSIWYG, templates cover/toc/projet/grille/page libre, pipeline Playwright + Ghostscript, gel du contenu à la génération, partage public opt-in. Dépôt autonome : Dockerfile (base Playwright + Ghostscript + ffmpeg + rsync) inclus. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
96 lines
3.8 KiB
Vue
96 lines
3.8 KiB
Vue
<script setup lang="ts">
|
|
// Page « couverture » de projet — gabarit_book_v3 p.1/3/5/7.
|
|
// Bandeau pleine largeur 55 mm : titre (fonte de la catégorie, 45 pt),
|
|
// pastille catégorie (10 pt), présentation (Syne 12/16 pt, colonne à 50,5 %).
|
|
// Sous le bandeau : image de couverture. Taille du titre et hauteur du
|
|
// bandeau (= calage haut de l'image) ajustables pour éviter les débords.
|
|
import type { InkMode, ResolvedMedia } from '~~/types'
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
title: string
|
|
category: string // "publique" | "sociale" | "culturelle"
|
|
badge?: string // texte de la pastille (ex. « Utilité publique »)
|
|
intro?: string // ligne(s) d'intro en gras
|
|
text?: string // présentation, HTML
|
|
image?: ResolvedMedia
|
|
imageFit?: 'width' | 'height' // calage : pleine largeur ou pleine hauteur
|
|
imageAlign?: 'left' | 'center' | 'right' // fit hauteur : marge gauche / centrée / marge droite
|
|
titleScale?: number // % de la taille gabarit (100 = 45 pt)
|
|
bandHeight?: number // mm (gabarit : 55)
|
|
bandColor?: string
|
|
ink?: InkMode
|
|
}>(),
|
|
{ ink: 'auto', imageFit: 'width', imageAlign: 'center', titleScale: 100, bandHeight: 55 },
|
|
)
|
|
|
|
const PX_PER_MM = 3.7796
|
|
|
|
const band = computed(() => bandColorOf(props.category, props.bandColor))
|
|
const inkColor = computed(() => inkOn(band.value, props.ink))
|
|
const titleClass = computed(() => CATEGORY_TITLE_CLASS[props.category] ?? '')
|
|
// L'Avara est plus grasse : le gabarit la compose en 42 pt au lieu de 45 pt.
|
|
const titleSize = computed(() => {
|
|
const base = props.category === 'culturelle' ? 56 : 60
|
|
return `${(base * props.titleScale) / 100}px`
|
|
})
|
|
const bandPx = computed(() => `${props.bandHeight * PX_PER_MM}px`)
|
|
|
|
// Fit hauteur : calage horizontal sur la marge gauche, centré, ou marge droite
|
|
const alignStyle = computed(() => {
|
|
if (props.imageFit !== 'height') return { justifyContent: 'center' }
|
|
switch (props.imageAlign) {
|
|
case 'left': return { justifyContent: 'flex-start', paddingLeft: 'var(--fl-margin)' }
|
|
case 'right': return { justifyContent: 'flex-end', paddingRight: 'var(--fl-margin)' }
|
|
default: return { justifyContent: 'center' }
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="fl-page">
|
|
<!-- Bandeau -->
|
|
<div class="absolute inset-x-0 top-0" :style="{ height: bandPx, backgroundColor: band, color: inkColor }">
|
|
<h1
|
|
class="absolute"
|
|
:class="titleClass"
|
|
:style="{ left: 'var(--fl-margin)', top: '14px', fontSize: titleSize, lineHeight: '1.15', maxWidth: '620px' }"
|
|
>
|
|
{{ title }}
|
|
</h1>
|
|
<span
|
|
v-if="badge"
|
|
class="absolute whitespace-nowrap rounded-full border px-[12px] py-[3px]"
|
|
:style="{ left: '42.33%', top: '54px', transform: 'translateY(-50%)', fontSize: '13.3px', lineHeight: '1.2', borderColor: 'currentColor' }"
|
|
>
|
|
{{ badge }}
|
|
</span>
|
|
<div
|
|
class="absolute overflow-hidden"
|
|
:style="{ left: '50.47%', right: 'var(--fl-margin)', top: '33px', bottom: '12px' }"
|
|
>
|
|
<p v-if="intro" class="font-bold">{{ intro }}</p>
|
|
<div v-if="text" class="fl-prose" v-html="text" />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Image de couverture : calée sur la largeur (pleine largeur, fixée en
|
|
bas de page, débord haut coupé) ou sur la hauteur (pleine hauteur,
|
|
centrée). Le fond visible sous/à côté de l'image reprend la couleur
|
|
du bandeau. -->
|
|
<div
|
|
class="absolute inset-x-0 bottom-0 flex overflow-hidden"
|
|
:style="{ top: bandPx, backgroundColor: image ? band : '#ffffff', ...alignStyle }"
|
|
>
|
|
<img
|
|
v-if="image"
|
|
:src="image.url"
|
|
:alt="title"
|
|
:style="imageFit === 'height'
|
|
? { height: '100%', width: 'auto', maxWidth: 'none' }
|
|
: { width: '100%', height: 'auto', alignSelf: 'flex-end' }"
|
|
>
|
|
</div>
|
|
</div>
|
|
</template>
|