Files
wondif_vue/app/pages/orchestre/artisteinvitee-[id].vue
2026-04-12 23:25:07 +02:00

481 lines
12 KiB
Vue

<template>
<div class="artiste_invitee-wp">
<section v-if="pending" aria-busy="true" aria-live="polite">
<p>en cours de chargement...</p>
</section>
<section v-else-if="error" aria-live="polite">
<p>Impossible de charger la fiche artiste.</p>
</section>
<template v-else>
<PageSection tone="" content-size="default" class="breadcrum_wp">
<Breadcrumb :current-label="artiste?.nom_artiste_invite || ''" />
</PageSection>
<section class="fiche_header_wp">
<div class="fiche_header_wp_gauche"></div>
<div class="fiche_header_wp_gauche_carre"></div>
<div class="fiche_header_inner">
<div class="fiche_header_titres">
<div>
<DsHeading as="h1" tone="default" textcase="uppercase" class="concert-card__title">
{{ artiste.nom_artiste_invite }}
</DsHeading>
</div>
<div>
</div>
</div>
<div class="fiche_header_img">
<DsMedia
v-if="illustration?.url"
:src="illustration.url"
:alt="illustration.alternativeText || artiste?.nom_artiste_invite || ''"
ratio="3-4"
/>
<div v-else class="img_placeholder" aria-hidden="true" />
</div>
<div class="fiche_header_bandeau"></div>
<div class="fiche_header_infos">
<div>
<DsHeading as="h2" tone="invert" textcase="uppercase" class="fiche_header_infos_genre" v-if="postesLabel">
{{ postesLabel }}
</DsHeading>
</div>
<div v-if="artiste.url_artiste_invite">
<DsText as="p" tone="invert" weight="bold" spacing="space-0" class="">
<a :href="artiste.url_artiste_invite" target="_blank" rel="noopener noreferrer">Site internet</a>
</DsText>
</div>
<div>
</div>
</div>
</div>
<div class="fiche_header_wp_droite"></div>
</section>
<PageSection tone="" content-size="default" class="fiche_description">
<section v-if="descriptionBlocks" class="description_wp">
<StrapiBlocksConvert :blocks="descriptionBlocks" />
</section>
<section v-if="imagesArtiste.length" class="img-gallery_wp">
<div class="img-gallery">
<DsMedia
v-for="img in imagesArtiste"
:key="img.id || img.url"
:src="img.url"
:alt="img.alternativeText || artiste?.nom_artiste_invite || ''"
/>
</div>
</section>
<section v-if="youtubeEmbeds.length" class="youtube_wp">
<div class="youtube-list">
<div v-for="v in youtubeEmbeds" :key="v.id" class="youtube-item">
<iframe
:src="v.src"
title="Vidéo YouTube"
loading="lazy"
referrerpolicy="strict-origin-when-cross-origin"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
/>
</div>
</div>
</section>
</PageSection>
</template>
</div>
</template>
<script setup>
import DsMedia from '@root/design-system/primitives/DsMedia.vue'
import DsHeading from '@root/design-system/primitives/DsHeading.vue'
import DsText from '@root/design-system/primitives/DsText.vue'
const route = useRoute()
//////////////////////////////////////////////////////////////
// RÉCUPÉRATION DU CONTENU
//////////////////////////////////////////////////////////////
const artisteSlug = computed(() => String(route.params.id || ''))
const populate = {
image_illustration_artiste_invite: true,
postes_artiste_invite: true,
}
const filters = computed(() => ({
slug_artiste_invite: {
$eq: artisteSlug.value,
},
}))
const { items: artistesinvitees, pending, error } = useStrapi(
"/api/__strapi__/artistesinvitees",
{
locale: "fr-FR",
populate,
filters,
limit: 1,
}
)
const artiste = computed(() => artistesinvitees.value?.[0] || {})
useSeoMeta({
title: () => artiste.value?.nom_artiste_invite || 'Artiste',
})
const postesLabel = computed(() =>
extractStrapiList(artiste.value?.postes_artiste_invite)
.map((p) => p?.nom_poste)
.filter(Boolean)
.join(', ')
)
const illustration = computed(() => {
const m = artiste.value?.image_illustration_artiste_invite
if (!m) return null
if (m?.data?.attributes) return { id: m.data.id, ...m.data.attributes }
if (m.url) return m
return null
})
const imagesArtiste = computed(() => {
const value = artiste.value?.images_artiste_invite
return extractStrapiList(value).filter((img) => Boolean(img?.url))
})
const descriptionBlocks = computed(() =>
artiste.value?.description_artiste_invite || artiste.value?.description_concert || null
)
const youtube = computed(() => {
const value = artiste.value?.liens_youtube_artiste_invite
if (!value) return []
return extractStrapiList(value)
})
function getYoutubeId(url = '') {
try {
const u = new URL(url)
if (u.hostname.includes('youtu.be')) return u.pathname.slice(1)
if (u.pathname.startsWith('/shorts/')) return u.pathname.split('/')[2]
if (u.pathname.startsWith('/embed/')) return u.pathname.split('/')[2]
return u.searchParams.get('v')
} catch {
return null
}
}
const youtubeEmbeds = computed(() =>
youtube.value
.map((item) => {
const id = getYoutubeId(item?.lien_youtube)
if (!id) return null
return {
id: item.id || id,
src: `https://www.youtube-nocookie.com/embed/${id}?rel=0&modestbranding=1&iv_load_policy=3&playsinline=1`,
}
})
.filter(Boolean)
)
function extractStrapiList(value) {
if (!value) return []
if (Array.isArray(value)) return value.map(normalizeStrapiItem).filter(Boolean)
if (value?.data) {
const rows = Array.isArray(value.data) ? value.data : [value.data]
return rows.map(normalizeStrapiItem).filter(Boolean)
}
if (typeof value === 'object') return [normalizeStrapiItem(value)].filter(Boolean)
return []
}
function normalizeStrapiItem(item) {
if (!item || typeof item !== 'object') return null
if (item.attributes && typeof item.attributes === 'object') {
return { id: item.id, ...item.attributes }
}
return item
}
function appendPopulate(query, populateObj, prefix = "populate") {
Object.entries(populateObj).forEach(([key, value]) => {
if (value === true) {
query.set(`${prefix}[${key}]`, "true")
return
}
if (value && typeof value === "object") {
appendPopulate(query, value, `${prefix}[${key}][populate]`)
}
})
}
</script>
<style lang="scss">
.breadcrum_wp {
padding-top: 30px;
}
/* ============================ */
/* DISTRIBUTION / PROGRAMME */
/* ============================ */
.fiche_details_wp {
.distribution_wp {
padding-top: 50px;
padding-bottom: 50px;
padding-left: 20px;
.distribution_item {
display: flex;
.distribution_item_poste {
padding-right: 10px;
font-weight: 200;
}
.direction {
font-weight: 400;
}
}
}
.programme_wp {
background-color: var(--c-backgroud-brandreverse);
margin-bottom: 70px;
padding-top: 50px;
padding-right: 30px;
padding-left: 40px;
padding-bottom: 50px;
/* DÉCALAGE DU BLOC VERS LA DROITE */
position: relative;
@media (min-width: 0px) and (max-width: 700px) {
width: 89vw;
left: 5%;
}
@media (min-width: 700px) {
width: 67vw;
left: 30%;
}
@media (min-width: 800px) {
width: 50vw;
left: 49%;
}
transform: translateX(0px);
margin-left: 0px;
margin-right: 0px;
box-sizing: border-box;
display: block;
justify-content: initial;
align-items: initial;
.programme_titre {
padding-bottom: 20px;
}
.programme_list {
display: flex;
flex-wrap: wrap;
column-gap: 20px;
row-gap: 20px;
}
.programme_item {
flex: 1 0 200px;
background-color: var(--c-surface);
padding-top: 14px;
padding-right: 20px;
padding-left: 18px;
padding-bottom: 10px;
}
}
}
/* ============================ */
/* REPRÉSENTATIONS */
/* ============================ */
.representation_wp {
display: flex;
flex-wrap: wrap;
/* justify-content: center; */
column-gap: 30px;
row-gap: 30px;
padding-bottom: 70px;
@media (min-width: 0px) and (max-width: 600px) {
padding-left: 20px;
padding-right: 5px;
}
.representation_item {
@media (min-width: 0px) and (max-width: 500px) {
width: 100%;
}
@media (min-width: 500px) {
max-width: 215px;
}
@media (min-width: 600px) {
max-width: 262px;
}
@media (min-width: 700px) {
max-width: 300px;
}
flex: 1 1 300px;
display: grid;
border: 2px var(--c-brand_rouge) solid;
padding-top: 20px;
> * {
padding-left: 20px;
padding-right: 20px;
}
.representation_item_comment_wp {
padding-top: 5px;
}
.representation_item_comment {
background-color: lightgray;
padding: 5px;
}
}
.representation_cta {
color: var(--c-surface);
background-color: var(--c-brand_rouge);
margin-top: 15px;
padding-top: 10px;
padding-bottom: 10px;
text-align: center;
a {
font-family: var(--font-roboto);
font-weight: 600;
font-size: 18px;
}
}
}
/* ============================ */
/* DESCRIPTION */
/* ============================ */
.artiste_invitee-wp {
.fiche_description {
display: flex;
justify-content: center;
padding-top: 70px;
padding-bottom: 10px;
padding-left: 10px;
padding-right: 10px;
> * {
max-width: 640px;
display: flex;
flex-direction: column;
}
}
}
/* ============================ */
/* GALERIES */
/* ============================ */
.img-gallery_wp {
padding-bottom: 50px;
}
.img-gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 520px));
gap: 1rem;
justify-content: center;
margin-top: 1.5rem;
width: 100%;
}
.img-gallery > * {
display: block;
width: 100%;
overflow: hidden;
border-radius: 5px;
}
.img-gallery :deep(.ds-media) {
display: block;
width: 100%;
height: auto;
background: transparent;
}
.img-gallery :deep(.ds-media__img) {
display: block;
width: 100%;
max-width: 100%;
height: auto;
transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.img-gallery :deep(.ds-media__img:hover) {
transform: scale(1.02);
}
@media (max-width: 1100px) {
.img-gallery {
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
}
@media (max-width: 820px) {
.img-gallery {
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 0.75rem;
}
}
@media (max-width: 520px) {
.img-gallery {
grid-template-columns: 1fr;
}
}
.youtube_wp {
margin-bottom: 70px;
}
.youtube-list {
display: grid;
justify-content: center;
/* flex-wrap: wrap; */
gap: 20px;
}
.youtube-item {
@media (min-width: 0px) and (max-width: 300px) {
min-width: 290px;
}
@media (min-width: 300px) {
min-width: 298px;
}
@media (min-width: 400px) {
min-width: 398px;
}
@media (min-width: 500px) {
min-width: 480px;
}
@media (min-width: 600px) {
min-width: 580px;
}
@media (min-width: 700px) {
min-width: 670px;
}
}
.youtube-item iframe {
aspect-ratio: 16 / 9;
border: 0;
}
</style>