Textes projet éditables suivant Grav + écriture atomique du store
- Champ Présentation prérempli avec le texte Grav, suivi modifié/synchro (textOverride undefined = suit Grav, sinon figé ; bouton Réinitialiser) - Fix génération PDF : savePortfolio écrit en atomique (temp+rename) pour éviter la corruption de lecture par l'autosave concurrent du rendu print - Toast d'échec de génération explicite (message serveur) - Refonte architecture visuelle, fond unifié white/tint/full, dispositions couverture bandeau/colonne, instantané public complet, index 3 lignes
This commit is contained in:
@@ -7,28 +7,59 @@ import type { Background } from '~~/types'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
title: string // utilisé pour les métadonnées PDF, pas affiché
|
||||
title: string // titre du portfolio — en-tête + métadonnées PDF
|
||||
subtitle?: string
|
||||
recipient?: string
|
||||
background: Background
|
||||
bandColor?: string // filet couleur en bas (définit aussi celui du sommaire)
|
||||
date?: string // texte libre de l'en-tête (ex. « Juillet 2026 »)
|
||||
siteUrl?: string
|
||||
contactEmail?: string
|
||||
renderMode?: 'preview' | 'print'
|
||||
}>(),
|
||||
{ renderMode: 'preview' },
|
||||
)
|
||||
|
||||
// Le site affiche l'intro sur fond gris clair : « Blanc » rend le gris charte.
|
||||
const strip = computed(() => props.bandColor || FL_INK)
|
||||
|
||||
// Fond : « Plein » = couleur du filet ; « Blanc » rend le gris charte du site
|
||||
// (l'intro y est présentée sur gris clair) ; « Teinté » = aplat beige.
|
||||
const pageStyle = computed(() => {
|
||||
const s = backgroundStyle(props.background)
|
||||
if (props.background.mode === 'white') s.backgroundColor = FL_BASE
|
||||
return s
|
||||
const base
|
||||
= props.background.mode === 'full'
|
||||
? strip.value
|
||||
: props.background.mode === 'tint'
|
||||
? TINT_COLOR
|
||||
: FL_BASE
|
||||
return { backgroundColor: base, color: inkOn(base) }
|
||||
})
|
||||
|
||||
const strip = computed(() => props.bandColor || FL_INK)
|
||||
// En-tête : coordonnées en liens actifs (le PDF et l'aperçu partagent le
|
||||
// template ; Chromium convertit les <a href> en liens cliquables du PDF).
|
||||
const siteHref = computed(() => props.siteUrl?.trim() || DEFAULT_SITE_URL)
|
||||
const siteLabel = computed(() => siteHref.value.replace(/^https?:\/\//, '').replace(/\/$/, ''))
|
||||
const email = computed(() => props.contactEmail?.trim() || DEFAULT_CONTACT_EMAIL)
|
||||
// Laissée vide, la date affiche par défaut le mois courant (ex. « Juillet 2026 »).
|
||||
const displayDate = computed(() => props.date?.trim() || formatMonthYear())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="fl-page" :style="pageStyle">
|
||||
<!-- En-tête : titre du portfolio + date, site et mail (liens actifs PDF/viewer) -->
|
||||
<header
|
||||
class="absolute inset-x-0 top-0 flex items-start justify-between gap-8"
|
||||
:style="{ padding: 'var(--fl-margin)', fontFamily: 'Syne, sans-serif' }"
|
||||
>
|
||||
<div class="max-w-[60%]">
|
||||
<h1 :style="{ fontSize: '30px', lineHeight: '1.2' }">{{ title }}</h1>
|
||||
<p class="opacity-70" :style="{ fontSize: '19px', lineHeight: '1.5' }">{{ displayDate }}</p>
|
||||
</div>
|
||||
<div class="flex shrink-0 flex-col items-end" :style="{ fontSize: '19px', lineHeight: '1.5' }">
|
||||
<a :href="siteHref" target="_blank" rel="noopener" class="text-current no-underline">{{ siteLabel }}</a>
|
||||
<a :href="`mailto:${email}`" class="text-current no-underline">{{ email }}</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="absolute inset-0 flex flex-col items-center justify-center">
|
||||
<!-- Phrase d'intro du site (pages/01.home) — Lato, keywords dans les
|
||||
fontes d'« utilité » du thème Grav, non soulignés -->
|
||||
@@ -40,20 +71,9 @@ const strip = computed(() => props.bandColor || FL_INK)
|
||||
<span class="fl-font-culturelle">culturelle</span>
|
||||
qui œuvre sur logiciels libres.
|
||||
</p>
|
||||
|
||||
<!-- Flèche vers le bas (theme://images/pictos/arrow-down.svg) -->
|
||||
<svg viewBox="0 0 24 30" :style="{ width: '60px', height: '54px', marginTop: '90px' }" aria-hidden="true">
|
||||
<g transform="rotate(90,11,12.999996)">
|
||||
<path
|
||||
d="m 16.681932,5.2684395 0.08629,0.091376 5.042819,6.0548205 v 0 l 0.06362,0.100098 v 0 l 0.06561,0.144066 v 0 l 0.0389,0.137168 v 0 l 0.01717,0.117449 v 0 L 22,12 v 0 l -0.0025,0.07061 v 0 l -0.01594,0.12126 v 0 l -0.02359,0.09573 -0.0317,0.08965 v 0 l -0.06326,0.128377 v 0 l -0.09484,0.134549 -5,6 c -0.353564,0.424277 -0.984128,0.481601 -1.408405,0.128037 C 14.968124,18.441846 14.889154,17.879455 15.15741,17.46116 L 15.231779,17.359816 18.864,12.999975 3,13 C 2.4477153,13 2,12.552285 2,12 2,11.487164 2.3860402,11.064493 2.8833789,11.006728 L 3,11 18.864,10.999975 15.231779,6.6401844 c -0.353564,-0.4242769 -0.29624,-1.0548416 0.128037,-1.4084057 0.39164,-0.3263668 0.959052,-0.3026278 1.322116,0.036661 z"
|
||||
fill="currentColor"
|
||||
fill-rule="nonzero"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Filet couleur pleine largeur en bas -->
|
||||
<div class="absolute inset-x-0 bottom-0" :style="{ height: 'var(--fl-strip)', backgroundColor: strip }" />
|
||||
<!-- Filet couleur pleine largeur en bas (masqué en fond plein) -->
|
||||
<div v-if="background.mode !== 'full'" class="absolute inset-x-0 bottom-0" :style="{ height: 'var(--fl-strip)', backgroundColor: strip }" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user