Compare commits

...

16 Commits

Author SHA1 Message Date
bc6ad43ea5 front end 2026-03-18 12:00:19 +01:00
b0352c963c décalage 2026-02-24 23:58:32 +01:00
db365010e9 nuxt config 2026-02-24 19:35:05 +01:00
d35d174918 ecosystem 2026-02-24 19:24:00 +01:00
71aae090e2 ecosystem 2026-02-24 18:57:35 +01:00
35be3e4ee3 liste artistes 2026-02-24 18:41:20 +01:00
8dd7cbbb06 corrections 2026-02-20 22:44:41 +01:00
35d326ab03 env 2026-02-20 22:02:40 +01:00
c258a436a0 artistes 2026-02-20 21:55:35 +01:00
329b43e07c ecosystem 2026-02-18 12:42:05 +01:00
df381ca59f config 2026-02-18 12:27:53 +01:00
c073e8b4d4 ecosystem 2026-02-18 12:27:38 +01:00
7f2b0d69be runtimeconfig 2026-02-18 12:20:19 +01:00
b10001180c env 2026-02-16 08:52:47 +01:00
e4da6f462a readme 2026-02-16 08:01:19 +01:00
b8b8e53f07 Concerts 2026-02-16 07:59:52 +01:00
88 changed files with 6880 additions and 457 deletions

1026
README.md

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View File

@@ -14,3 +14,9 @@
object-fit: cover; object-fit: cover;
//filter: contrast(0.8); //filter: contrast(0.8);
} }
.img_placeholder {
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.04);
}

View File

@@ -22,12 +22,17 @@
cursor: pointer; cursor: pointer;
&:hover { &:hover {
border-bottom: none !important;
.header_nav_topbar_submenu { .header_nav_topbar_submenu {
visibility: visible; visibility: visible;
/* Pour l'effet de transition */ /* Pour l'effet de transition */
opacity: 1; opacity: 1;
} }
} }
&.is-active {
border-bottom: 2px solid var(--c-brand_rouge);
padding-bottom: 1px;
}
} }
.header_nav_topbar_submenu { .header_nav_topbar_submenu {
position: absolute; position: absolute;
@@ -43,7 +48,7 @@
padding-right: 16px; padding-right: 16px;
padding-bottom: 5px; padding-bottom: 5px;
text-align: left; text-align: left;
background-color: rgba(255, 255, 255, 0.95); background-color: var(--c-surface);
border-radius: 3px; border-radius: 3px;
.header_nav_topbar_submenu_item { .header_nav_topbar_submenu_item {
@@ -59,6 +64,7 @@
} }
} }
} }
.header_nav_lang { .header_nav_lang {
display: flex; display: flex;
font-family: 'brandontext_medium'; font-family: 'brandontext_medium';
@@ -100,7 +106,8 @@
} }
.header_nav_logo { .header_nav_logo {
position: relative;
z-index: 10;
@include media_queries.media_min(tablet_700) { @include media_queries.media_min(tablet_700) {
margin-top: -7px; margin-top: -7px;
} }
@@ -162,6 +169,19 @@
//font-family: 'brandontext_regular'; //font-family: 'brandontext_regular';
font-family: var(--font-roboto); font-family: var(--font-roboto);
// wght Graisse
// YTLC Hauteur miniscule
// YTUC Hauteur majuscule
// YTAS Hauteur des hampes ascendantes
// YTDE Hauteur des hampes descendantes
// wdth largeur horizontale des lettres
font-variation-settings:
"wdth" 41,
"wght" 662,
"YTLC" 570,
"YTUC" 760,
"YTAS" 794,
"YTDE" -275;
@include media_queries.media_min(tablet_600) { @include media_queries.media_min(tablet_600) {
font-size: 18px; font-size: 18px;
@@ -184,14 +204,26 @@
cursor: pointer; cursor: pointer;
&:hover { &:hover {
border-bottom: none !important;
padding-bottom: 2px;
.header_nav_sub_menu { .header_nav_sub_menu {
visibility: visible; visibility: visible;
/* Pour l'effet de transition */ /* Pour l'effet de transition */
opacity: 1; opacity: 1;
} }
} }
&.is-active {
border-bottom: 2px solid var(--c-brand_rouge);
padding-bottom: 1px;
}
list-style: none; list-style: none;
@media (min-width: 728px) and (max-width: 795px) {
.decalage_gauche {
margin-left: -73px;
}
}
&:nth-child(5) { &:nth-child(5) {
margin-right: 10px; margin-right: 10px;
@@ -219,26 +251,39 @@
.header_nav_sub_menu { .header_nav_sub_menu {
position: absolute; position: absolute;
left: 0;
@media (min-width: 0px) {
left: -10px;
}
@media (min-width: 900px) {
left: 0px;
}
z-index: 2; z-index: 2;
visibility:hidden; visibility:hidden;
/* Pour l'effet de transition */ /* Pour l'effet de transition */
opacity: 0; opacity: 0;
transition: visibility 0.2s,opacity 0.2s cubic-bezier(0.4, 0, 1, 1); transition: visibility 0.2s,opacity 0.2s cubic-bezier(0.4, 0, 1, 1);
padding-top: 10px; padding-top: 17px;
padding-left: 25px; padding-left: 27px;
padding-right: 22px; padding-right: 12px;
padding-bottom: 10px; padding-bottom: 10px;
text-align: left; text-align: left;
background-color: rgba(255, 255, 255, 0.93); background-color: var(--c-surface);
border-radius: 3px; border-radius: 3px;
.header_nav_sub_menu_item { .header_nav_sub_menu_item {
list-style: circle; list-style: circle;
//font-family: 'brandontext_regular'; //font-family: 'brandontext_regular';
font-family: var(--font-roboto); font-family: var(--font-roboto);
font-variation-settings:
"wdth" 41, "wght" 552, "YTLC" 570, "YTUC" 760, "YTAS" 794, "YTDE" -275;
@media (min-width: 0px) {
font-size: 16px;
}
@media (min-width: 900px) {
font-size: 18px; font-size: 18px;
padding-bottom: 4px; }
padding-bottom: 7px;
&:hover { &:hover {
a { a {
color: var(--c-brand_rouge); color: var(--c-brand_rouge);
@@ -392,6 +437,13 @@
margin-top: 47px; margin-top: 47px;
font-family: var(--font-roboto); font-family: var(--font-roboto);
font-variation-settings:
"wdth" 41,
"wght" 662,
"YTLC" 570,
"YTUC" 760,
"YTAS" 794,
"YTDE" -275;
padding-bottom: 10px; padding-bottom: 10px;
padding-right: 10px; padding-right: 10px;
@@ -411,6 +463,7 @@
.header_drawer_link { .header_drawer_link {
display: block; display: block;
width: fit-content;
text-decoration: none; text-decoration: none;
color: $blanc; color: $blanc;
padding: 6px 0; padding: 6px 0;
@@ -420,12 +473,18 @@
&.is-open { &.is-open {
border-bottom: none !important;
.header_drawer_sub_menu { .header_drawer_sub_menu {
display: block; display: block;
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
} }
} }
&.is-active {
border-bottom: 2px solid var(--c-brand_rouge);
padding-bottom: 1px;
}
list-style: none; list-style: none;
} }
.header_drawer_sub_menu { .header_drawer_sub_menu {
@@ -452,6 +511,8 @@
list-style: none; list-style: none;
//font-family: 'brandontext_regular'; //font-family: 'brandontext_regular';
font-family: var(--font-roboto); font-family: var(--font-roboto);
font-variation-settings:
"wdth" 41, "wght" 552, "YTLC" 570, "YTUC" 760, "YTAS" 794, "YTDE" -275;
font-size: 18px; font-size: 18px;
padding-bottom: 4px; padding-bottom: 4px;
list-style: circle; list-style: circle;

View File

@@ -9,7 +9,7 @@
} }
.page-enter-active, .page-enter-active,
.page-leave-active { .page-leave-active {
transition: all 0.4s ease; transition: all 0.2s ease;
} }
.page-enter-from, .page-enter-from,
.page-leave-to { .page-leave-to {

View File

@@ -78,7 +78,7 @@
} }
@media (min-width: 500px) { @media (min-width: 500px) {
.banniere_pros_wp { .banniere_pros_wp {
grid-template-columns: 150px 270px 40px 40px; grid-template-columns: 150px 250px 35px 35px;
grid-template-rows: auto; grid-template-rows: auto;
//justify-content: center; //justify-content: center;
align-items: center; align-items: center;

View File

@@ -0,0 +1,167 @@
<template>
<div v-if="items.length > 1" aria-label="Fil dAriane" class="breadcrumb">
<ul class="breadcrumb__list">
<li v-for="(item, i) in items" :key="item.to" class="breadcrumb__item">
<NuxtLink v-if="i < items.length - 1 && !item.noLink" :to="item.to">
<img
v-if="i === 0"
src="/img/icones/house-grey.svg"
alt="Accueil"
class="breadcrumb__home-icon"
/>
<span v-else>{{ item.label }}</span>
</NuxtLink>
<span v-else aria-current="page">{{ item.label }}</span>
</li>
</ul>
</div>
</template>
<script setup>
const props = defineProps({
currentLabel: { type: String, default: '' } // utile pour les pages dynamiques
})
const route = useRoute()
const labelMap = {
concerts: 'Concerts',
agenda: 'Agenda',
saison: 'Saison',
orchestre: "L'Orchestre",
professionnels: "Professionnels"
}
function resolveTo(part, index, parts, acc) {
if (part === 'concerts') {
const next = parts[index + 1] || ''
if (next.startsWith('concert-')) {
const from = typeof route.query.from === 'string' ? route.query.from : ''
if (from === 'agenda') return '/concerts/agenda'
if (from === 'saison') return '/concerts/saison'
if (from === 'jeune-public') return '/concerts/jeune-public'
}
}
if (part === 'orchestre') {
const next = parts[index + 1] || ''
if (next.startsWith('artiste-') || next.startsWith('artisteinvitee-') || next.startsWith('artistesinvitees-')) {
const from = typeof route.query.from === 'string' ? route.query.from : ''
if (from === 'musiciens') return '/orchestre/musiciens'
if (from === 'artistesinvitees') return '/orchestre/artistesinvitees'
}
}
return acc
}
function humanize(segment) {
return segment
.replace(/-/g, ' ')
.replace(/\b\w/g, (m) => m.toUpperCase())
}
const items = computed(() => {
const parts = route.path.split('/').filter(Boolean)
const crumbs = [{ to: '/', label: 'Accueil' }]
const from = typeof route.query.from === 'string' ? route.query.from : ''
let acc = ''
parts.forEach((part, index) => {
acc += `/${part}`
const isLast = index === parts.length - 1
const label = isLast && props.currentLabel
? props.currentLabel
: (labelMap[part] || humanize(decodeURIComponent(part)))
const noLink = part === 'orchestre' || part === 'concerts'
crumbs.push({ to: resolveTo(part, index, parts, acc), label, noLink })
if (part === 'concerts') {
const next = parts[index + 1] || ''
if (next.startsWith('concert-')) {
if (from === 'agenda') {
crumbs.push({ to: '/concerts/agenda', label: 'Agenda' })
}
if (from === 'saison') {
crumbs.push({ to: '/concerts/saison', label: 'Saison' })
}
if (from === 'jeune-public') {
crumbs.push({ to: '/concerts/jeune-public', label: 'Jeune public' })
}
}
}
if (part === 'orchestre') {
const next = parts[index + 1] || ''
if (next.startsWith('artiste-') || next.startsWith('artisteinvitee-')) {
if (from === 'musiciens') {
crumbs.push({ to: '/orchestre/musiciens', label: 'Les musiciens' })
}
if (from === 'artistesinvitees') {
crumbs.push({ to: '/orchestre/artistesinvitees', label: 'Les artistes invités' })
}
}
}
})
return crumbs
})
</script>
<style lang="scss">
.breadcrumb {
padding-top: 10px;
padding-bottom: 10px;
font-size: 15px;
font-family: var(--font-roboto);
font-weight: var(--fw-extralight);
color: #6D798A;
position: relative;
z-index: 1;
@media (min-width: 0px) {
padding-left: 10px;
}
@media (min-width: 600px) {
padding-left: 0px;
}
@media (min-width: 700px) {
padding-left: 0px;
}
}
.breadcrumb__list {
display: flex;
flex-wrap: wrap;
gap: 8px;
list-style: none;
padding: 0;
}
.breadcrumb__item { display: inline-flex; align-items: center; }
.breadcrumb a {
display: inline-flex;
align-items: center;
cursor: pointer;
pointer-events: auto;
}
.breadcrumb__item:not(:last-child)::after {
content: "";
display: inline-block;
width: 11px;
height: 11px;
margin-left: 8px;
background: url('/img/icones/angle-right-grey.svg') no-repeat center / contain;
vertical-align: middle;
position: relative;
top: -1px; /* ajuste entre 0 et 2px selon ton rendu */
pointer-events: none;
}
.breadcrumb__home-icon {
width: 14px;
height: 14px;
display: inline-block;
vertical-align: middle;
margin-bottom: 4px;
}
</style>

View File

@@ -167,7 +167,7 @@
max-width: 210px; max-width: 210px;
} }
@media (min-width: 300px) { @media (min-width: 300px) {
max-width: 290px; max-width: 250px;
} }
@media (min-width: 400px) { @media (min-width: 400px) {
max-width: 390px; max-width: 390px;

View File

@@ -16,12 +16,24 @@
</div> </div>
<!-- Hint icon (micro affordance) --> <!-- Hint icon (micro affordance) -->
<div v-if="showHint" class="hc__hint" aria-hidden="true"> <button
v-if="showHint"
type="button"
class="hc__hint"
aria-label="Défiler vers la droite"
@click="scrollByHint(1)"
>
<span class="hc__hint-icon">{{ hintIcon }}</span> <span class="hc__hint-icon">{{ hintIcon }}</span>
</div> </button>
<div v-if="showHint" class="hc__hint--left" aria-hidden="true"> <button
v-if="showHint"
type="button"
class="hc__hint--left"
aria-label="Défiler vers la gauche"
@click="scrollByHint(-1)"
>
<span class="hc__hint-icon">{{ hintIcon }}</span> <span class="hc__hint-icon">{{ hintIcon }}</span>
</div> </button>
<!-- Scroller --> <!-- Scroller -->
<div <div
@@ -103,6 +115,14 @@
t = setTimeout(() => {}, 80) t = setTimeout(() => {}, 80)
} }
const scrollByHint = (direction) => {
const el = scroller.value
if (!el) return
const distance = Math.max(140, Math.round(el.clientWidth * 0.8))
el.scrollBy({ left: distance * direction, behavior: 'smooth' })
}
onMounted(() => { onMounted(() => {
const el = scroller.value const el = scroller.value
if (!el) return if (!el) return
@@ -187,7 +207,11 @@
/* Hint icon */ /* Hint icon */
.hc__hint { .hc__hint {
pointer-events: none; pointer-events: auto;
border: 0;
padding: 0;
cursor: pointer;
appearance: none;
position: absolute; position: absolute;
right: 10px; right: 10px;
top: 50%; top: 50%;
@@ -204,7 +228,11 @@
z-index: 2; z-index: 2;
} }
.hc__hint--left { .hc__hint--left {
pointer-events: none; pointer-events: auto;
border: 0;
padding: 0;
cursor: pointer;
appearance: none;
position: absolute; position: absolute;
left: 10px; left: 10px;
top: 50%; top: 50%;

View File

@@ -13,7 +13,7 @@
<!-- Actions --> <!-- Actions -->
<div class="concert-card__actions"> <div class="concert-card__actions">
<DsButtonArrow :to="`/concerts/${id}`" variant="secondary"> <DsButtonArrow :to="`/concerts/concert-${id}`" variant="secondary">
Découvrir Découvrir
</DsButtonArrow> </DsButtonArrow>
</div> </div>

View File

@@ -15,7 +15,7 @@
<!-- Meta : date + lieu --> <!-- Meta : date + lieu -->
<div class="concert-card__meta"> <div class="concert-card__meta">
<DsHeading as="h5" tone="default"> <DsHeading as="h5" tone="default">
{{ venue }} {{ lieu }}
</DsHeading> </DsHeading>
<DsHeading as="h6" tone="default"> <DsHeading as="h6" tone="default">
<time :datetime="dateISO">{{ dateLabel }}</time> <time :datetime="dateISO">{{ dateLabel }}</time>
@@ -29,8 +29,8 @@
<!-- Actions --> <!-- Actions -->
<div class="concert-card__actions"> <div class="concert-card__actions">
<DsButtonArrow :to="`/concerts/${id}`" variant="secondary"> <DsButtonArrow :to="`${href}`" variant="secondary">
Réserver Découvrir
</DsButtonArrow> </DsButtonArrow>
</div> </div>
</div> </div>
@@ -49,12 +49,13 @@
defineProps({ defineProps({
id: { type: [String, Number], required: true }, id: { type: [String, Number], required: true },
title: { type: String, required: true }, title: { type: String, required: true },
venue: { type: String, required: true }, lieu: { type: String, default: '' },
dateISO: { type: String, required: true }, // ex: "2026-01-15T20:00:00+01:00" dateISO: { type: String, required: true }, // ex: "2026-01-15T20:00:00+01:00"
dateLabel: { type: String, required: true }, // ex: "Jeu. 15 jan. 2026 — 20h" dateLabel: { type: String, required: true }, // ex: "Jeu. 15 jan. 2026 — 20h"
description: { type: String, default: '' }, description: { type: String, default: '' },
imageUrl: { type: String, default: '' }, imageUrl: { type: String, default: '' },
imageAlt: { type: String, default: '' }, imageAlt: { type: String, default: '' },
href: { type: String, default: '' },
}) })
</script> </script>

View File

@@ -1,30 +1,47 @@
<!-- app/components/concert/ConcertCardList.vue -->
<template> <template>
<div class="concert-card-list"> <div
class="concert-card-list"
:class="{
'concert-card-list--highlight-first': highlightFirst,
'concert-card-list--limit-cards': limitCardsOnBreakpoint,
}"
>
<slot /> <slot />
</div> </div>
</template> </template>
<script setup>
defineProps({
highlightFirst: { type: Boolean, default: true },
limitCardsOnBreakpoint: { type: Boolean, default: true },
})
</script>
<style lang="scss"> <style lang="scss">
.concert-card-list { .concert-card-list {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: var(--gap-cards); gap: var(--gap-cards);
justify-content: center; justify-content: center;
.concert-card {
max-width: 452px;
}
} }
// Afficher seulement 1 cards < 600px // Afficher seulement 1 cards < 600px
@media (max-width: 599px) { @media (max-width: 599px) {
.concert-card-list > .concert-card:nth-child(2) { .concert-card-list.concert-card-list--limit-cards > .concert-card:nth-child(2) {
display: none; display: none;
} }
.concert-card-list > .concert-card:nth-child(3) { .concert-card-list.concert-card-list--limit-cards > .concert-card:nth-child(3) {
display: none; display: none;
} }
} }
// Afficher seulement 2 cards < 900px // Afficher seulement 2 cards < 900px
@media (max-width: 899px) { @media (max-width: 899px) {
.concert-card-list > .concert-card:nth-child(3) { .concert-card-list.concert-card-list--limit-cards > .concert-card:nth-child(3) {
display: none; display: none;
} }
} }
@@ -40,7 +57,7 @@
.concert-card-list > .concert-card { .concert-card-list > .concert-card {
flex: 1 1 260px; flex: 1 1 260px;
} }
.concert-card-list > .concert-card:first-child { .concert-card-list.concert-card-list--highlight-first > .concert-card:first-child {
flex: 2 1 280px; flex: 2 1 280px;
} }
} }
@@ -48,7 +65,7 @@
.concert-card-list > .concert-card { .concert-card-list > .concert-card {
flex: 1 1 280px; flex: 1 1 280px;
} }
.concert-card-list > .concert-card:first-child { .concert-card-list.concert-card-list--highlight-first > .concert-card:first-child {
flex: 2 1 300px; flex: 2 1 300px;
} }
} }
@@ -56,7 +73,7 @@
.concert-card-list > .concert-card { .concert-card-list > .concert-card {
flex: 1 1 280px; flex: 1 1 280px;
} }
.concert-card-list > .concert-card:first-child { .concert-card-list.concert-card-list--highlight-first > .concert-card:first-child {
flex: 2 1 300px; flex: 2 1 300px;
} }
} }
@@ -65,7 +82,7 @@
flex: 1 1 260px; flex: 1 1 260px;
} }
.concert-card-list > .concert-card:first-child { .concert-card-list.concert-card-list--highlight-first > .concert-card:first-child {
flex: 2 1 300px; flex: 2 1 300px;
} }
} }
@@ -75,7 +92,7 @@
flex: 1 1 280px; flex: 1 1 280px;
} }
.concert-card-list > .concert-card:first-child { .concert-card-list.concert-card-list--highlight-first > .concert-card:first-child {
flex: 2 1 340px; flex: 2 1 340px;
} }
} }
@@ -85,7 +102,7 @@
flex: 1 1 300px; flex: 1 1 300px;
} }
.concert-card-list > .concert-card:first-child { .concert-card-list.concert-card-list--highlight-first > .concert-card:first-child {
flex: 2 1 380px; flex: 2 1 380px;
} }
} }
@@ -95,7 +112,7 @@
flex: 1 1 320px; flex: 1 1 320px;
} }
.concert-card-list > .concert-card:first-child { .concert-card-list.concert-card-list--highlight-first > .concert-card:first-child {
flex: 2 1 400px; flex: 2 1 400px;
} }
} }
@@ -105,7 +122,7 @@
flex: 1 1 340px; flex: 1 1 340px;
} }
.concert-card-list > .concert-card:first-child { .concert-card-list.concert-card-list--highlight-first > .concert-card:first-child {
flex: 2 1 440px; flex: 2 1 440px;
} }
} }
@@ -116,8 +133,9 @@
flex: 1 1 360px; flex: 1 1 360px;
} }
//règle spécifique après la règle générale //règle spécifique après la règle générale
.concert-card-list > .concert-card:first-child { .concert-card-list.concert-card-list--highlight-first > .concert-card:first-child {
flex: 2 1 480px; flex: 2 1 480px;
min-width: 500px;
} }
} }

View File

@@ -10,7 +10,9 @@
<template> <template>
<HeaderNav burger-color="hamburger_black"> <HeaderNav burger-color="hamburger_black">
<template #logo> <template #logo>
<NuxtLink to="/" aria-label="Accueil">
<NuxtImg :src="logoDefault" :alt="brand.logoAlt" class="logo-img" /> <NuxtImg :src="logoDefault" :alt="brand.logoAlt" class="logo-img" />
</NuxtLink>
</template> </template>
<template #agenda-icon> <template #agenda-icon>
@@ -38,9 +40,9 @@
margin-left: -11px; margin-left: -11px;
} }
.img_ticket_mob { .img_ticket_mob {
max-height: 37px; max-height: 31px;
margin-top: 9px; margin-top: 10px;
margin-left: -11px; // margin-left: -11px;
} }

View File

@@ -3,15 +3,15 @@
<div class="height_10"></div> <div class="height_10"></div>
<ul class="header_navigation_topbar" aria-label="Language selector"> <ul class="header_navigation_topbar" aria-label="Language selector">
<li class="header_nav_topbar_item"> <li class="header_nav_topbar_item" :class="{ 'is-active': isPro }">
Professionnels Professionnels
<ul class="header_nav_topbar_submenu"> <ul class="header_nav_topbar_submenu">
<li class="header_nav_topbar_submenu_item"><NuxtLink to="/">Programmer l'Orchestre</NuxtLink></li> <li class="header_nav_topbar_submenu_item"><NuxtLink to="/professionnels/programmer-orchestre">Programmer l'Orchestre</NuxtLink></li>
<li class="header_nav_topbar_submenu_item"><NuxtLink to="/">Le studio et les espaces</NuxtLink></li> <li class="header_nav_topbar_submenu_item"><NuxtLink to="/professionnels/studio">Le studio et les espaces</NuxtLink></li>
<li class="header_nav_topbar_submenu_item"><NuxtLink to="/">Louer des instruments</NuxtLink></li> <li class="header_nav_topbar_submenu_item"><NuxtLink to="/professionnels/louer">Louer des instruments</NuxtLink></li>
<li class="header_nav_topbar_submenu_item"><NuxtLink to="/">Recrutement / Concours</NuxtLink></li> <li class="header_nav_topbar_submenu_item"><NuxtLink to="/professionnels/recrutement">Recrutement / Concours</NuxtLink></li>
<li class="header_nav_topbar_submenu_item"><NuxtLink to="/">Espace candidats</NuxtLink></li> <li class="header_nav_topbar_submenu_item"><NuxtLink to="/professionnels/candidats">Espace candidats</NuxtLink></li>
<li class="header_nav_topbar_submenu_item"><NuxtLink to="/">Presse</NuxtLink></li> <li class="header_nav_topbar_submenu_item"><NuxtLink to="/professionnels/presse">Presse</NuxtLink></li>
</ul> </ul>
</li> </li>
<li class="header_nav_topbar_item header_nav_lang"> <li class="header_nav_topbar_item header_nav_lang">
@@ -32,55 +32,56 @@
<nav class="header_nav_cont" aria-label="Primary navigation"> <nav class="header_nav_cont" aria-label="Primary navigation">
<!-- Desktop nav --> <!-- Desktop nav -->
<ul class="header_nav header_nav--desktop"> <ul class="header_nav header_nav--desktop">
<li class="header_nav_item"> <li class="header_nav_item" :class="{ 'is-active': isOrchestre }">
L'Orchestre L'Orchestre
<ul class="header_nav_sub_menu"> <ul class="header_nav_sub_menu">
<li class="header_nav_sub_menu_item"><NuxtLink to="/">Nos missions</NuxtLink></li> <li class="header_nav_sub_menu_item"><NuxtLink to="/orchestre/missions">Nos missions</NuxtLink></li>
<li class="header_nav_sub_menu_item"><NuxtLink to="/">Direction musicale</NuxtLink></li> <li class="header_nav_sub_menu_item"><NuxtLink to="/orchestre/direction">Direction musicale</NuxtLink></li>
<li class="header_nav_sub_menu_item"><NuxtLink to="/">Les musiciens</NuxtLink></li> <li class="header_nav_sub_menu_item"><NuxtLink to="/orchestre/musiciens">Les musiciens</NuxtLink></li>
<li class="header_nav_sub_menu_item"><NuxtLink to="/">Les artistes invités</NuxtLink></li> <li class="header_nav_sub_menu_item"><NuxtLink to="/orchestre/artistesinvitees">Les artistes invités</NuxtLink></li>
<li class="header_nav_sub_menu_item"><NuxtLink to="/">Discographie</NuxtLink></li> <li class="header_nav_sub_menu_item"><NuxtLink to="/orchestre/discographie">Discographie</NuxtLink></li>
<li class="header_nav_sub_menu_item"><NuxtLink to="/">Nos partenaires</NuxtLink></li> <li class="header_nav_sub_menu_item"><NuxtLink to="/orchestre/partenaires">Nos partenaires</NuxtLink></li>
<li class="header_nav_sub_menu_item"><NuxtLink to="/">Nous soutenir</NuxtLink></li> <li class="header_nav_sub_menu_item"><NuxtLink to="/mecenat/soutenir">Nous soutenir</NuxtLink></li>
</ul> </ul>
</li> </li>
<li class="header_nav_item"> <li class="header_nav_item" :class="{ 'is-active': isConcerts }">
Concerts Concerts
<ul class="header_nav_sub_menu"> <ul class="header_nav_sub_menu">
<li class="header_nav_sub_menu_item"><NuxtLink to="/">Saison</NuxtLink></li> <li class="header_nav_sub_menu_item"><NuxtLink to="/concerts/saison">Saison</NuxtLink></li>
<li class="header_nav_sub_menu_item"><NuxtLink to="/">Jeune public</NuxtLink></li> <li class="header_nav_sub_menu_item"><NuxtLink to="/concerts/jeune-public">Jeune public</NuxtLink></li>
<li class="header_nav_sub_menu_item"><NuxtLink to="/">Concert mode d'emploi</NuxtLink></li> <li class="header_nav_sub_menu_item"><NuxtLink to="/concerts/mode-emploi">Concert mode d'emploi</NuxtLink></li>
<li class="header_nav_sub_menu_item"><NuxtLink to="/">ONDIF MAG</NuxtLink></li> <li class="header_nav_sub_menu_item"><NuxtLink to="/concerts/mag">ONDIF MAG</NuxtLink></li>
<li class="header_nav_sub_menu_item"><NuxtLink to="/">ONDIF LIVE !</NuxtLink></li> <li class="header_nav_sub_menu_item"><NuxtLink to="/concerts/live">ONDIF LIVE !</NuxtLink></li>
</ul> </ul>
</li> </li>
<li class="header_nav_item"> <li class="header_nav_item" :class="{ 'is-active': isMediation }">
Éducation et médiation Action culturelle
<ul class="header_nav_sub_menu"> <ul class="header_nav_sub_menu">
<li class="header_nav_sub_menu_item"><NuxtLink to="/">Petite enfance</NuxtLink></li> <li class="header_nav_sub_menu_item"><NuxtLink to="/mediation/petite-enfance">Petite enfance</NuxtLink></li>
<li class="header_nav_sub_menu_item"><NuxtLink to="/">Scolaires</NuxtLink></li> <li class="header_nav_sub_menu_item"><NuxtLink to="/mediation/scolaires">Scolaires</NuxtLink></li>
<li class="header_nav_sub_menu_item"><NuxtLink to="/">Champ social</NuxtLink></li> <li class="header_nav_sub_menu_item"><NuxtLink to="/mediation/social">Champ social</NuxtLink></li>
<li class="header_nav_sub_menu_item"><NuxtLink to="/">Insertion professionnelle</NuxtLink></li> <li class="header_nav_sub_menu_item"><NuxtLink to="/mediation/insertion-pro">Insertion professionnelle</NuxtLink></li>
<li class="header_nav_sub_menu_item"><NuxtLink to="/">Pratiques amateurs</NuxtLink></li> <li class="header_nav_sub_menu_item"><NuxtLink to="/mediation/amateurs">Pratiques amateurs</NuxtLink></li>
<li class="header_nav_sub_menu_item"><NuxtLink to="/">Ressources pédagogiques</NuxtLink></li> <li class="header_nav_sub_menu_item"><NuxtLink to="/mediation/ressources-pedagogiques">Ressources pédagogiques</NuxtLink></li>
</ul> </ul>
</li> </li>
<li class="header_nav_item"> <li class="header_nav_item" :class="{ 'is-active': isMecenat }">
Mécénat Mécénat
<ul class="header_nav_sub_menu"> <ul class="header_nav_sub_menu decalage_gauche">
<li class="header_nav_sub_menu_item"><NuxtLink to="/">Entreprises</NuxtLink></li> <li class="header_nav_sub_menu_item"><NuxtLink to="/mecenat/soutenir">Nous soutenir</NuxtLink></li>
<li class="header_nav_sub_menu_item"><NuxtLink to="/">Les projets</NuxtLink></li> <li class="header_nav_sub_menu_item"><NuxtLink to="/mecenat/entreprises">Entreprises</NuxtLink></li>
<li class="header_nav_sub_menu_item"><NuxtLink to="/">Particuliers</NuxtLink></li> <li class="header_nav_sub_menu_item"><NuxtLink to="/mecenat/projets">Les projets</NuxtLink></li>
<li class="header_nav_sub_menu_item"><NuxtLink to="/">Ils nous font confiance</NuxtLink></li> <li class="header_nav_sub_menu_item"><NuxtLink to="/mecenat/particuliers">Particuliers</NuxtLink></li>
<li class="header_nav_sub_menu_item"><NuxtLink to="/mecenat/mecenes">Ils nous font confiance</NuxtLink></li>
</ul> </ul>
</li> </li>
<li class="header_nav_item header_nav_icones"> <li class="header_nav_item header_nav_icones">
<div class=""> <div class="">
<NuxtLink to="/agenda"> <NuxtLink to="/concerts/agenda">
<div class="nav_icone"> <div class="nav_icone">
<div class="nav_icone_img nav_icone_img--agenda"> <div class="nav_icone_img nav_icone_img--agenda">
<!-- ICÔNE injectée --> <!-- ICÔNE injectée -->
@@ -91,7 +92,7 @@
</NuxtLink> </NuxtLink>
</div> </div>
<div class=" padding_top_1"> <div class=" padding_top_1">
<NuxtLink to="/agenda"> <NuxtLink to="https://orchestre-ile.com/shop" external target="_blank" rel="noopener noreferrer">
<div class="nav_icone"> <div class="nav_icone">
<div class="nav_icone_img nav_icone_img--ticket"> <div class="nav_icone_img nav_icone_img--ticket">
<!-- ICÔNE injectée --> <!-- ICÔNE injectée -->
@@ -124,7 +125,7 @@
<!-- Mobile icons --> <!-- Mobile icons -->
<div class="header_nav header_nav--mobile-icons"> <div class="header_nav header_nav--mobile-icons">
<div class="header_nav_item"> <div class="header_nav_item">
<NuxtLink to="/agenda"> <NuxtLink to="/concerts/agenda">
<div class="nav_icone"> <div class="nav_icone">
<div class="nav_icone_img nav_icone_img--agenda"> <div class="nav_icone_img nav_icone_img--agenda">
<!-- ICÔNE injectée --> <!-- ICÔNE injectée -->
@@ -136,7 +137,7 @@
</div> </div>
<div class="header_nav_item padding_top_1"> <div class="header_nav_item padding_top_1">
<NuxtLink to="/agenda"> <NuxtLink to="https://orchestre-ile.com/shop" external target="_blank" rel="noopener noreferrer">
<div class="nav_icone"> <div class="nav_icone">
<div class="nav_icone_img nav_icone_img--ticket"> <div class="nav_icone_img nav_icone_img--ticket">
<!-- ICÔNE injectée --> <!-- ICÔNE injectée -->
@@ -157,33 +158,33 @@
<ul class="header_drawer_inner"> <ul class="header_drawer_inner">
<li <li
class="header_drawer_link" class="header_drawer_link"
:class="{ 'is-open': activeDrawer === 'orchestre' }" :class="{ 'is-open': activeDrawer === 'orchestre','is-active': isOrchestre }"
@click="toggleDrawer('orchestre')" @click="toggleDrawer('orchestre')"
> >
L'Orchestre L'Orchestre
<ul class="header_drawer_sub_menu"> <ul class="header_drawer_sub_menu">
<li class="header_drawer_sub_menu_item"><NuxtLink to="/">Nos missions</NuxtLink></li> <li class="header_drawer_sub_menu_item"><NuxtLink to="/orchestre/missions">Nos missions</NuxtLink></li>
<li class="header_drawer_sub_menu_item"><NuxtLink to="/">Direction musicale</NuxtLink></li> <li class="header_drawer_sub_menu_item"><NuxtLink to="/orchestre/direction">Direction musicale</NuxtLink></li>
<li class="header_drawer_sub_menu_item"><NuxtLink to="/">Les musiciens</NuxtLink></li> <li class="header_drawer_sub_menu_item"><NuxtLink to="/orchestre/musiciens">Les musiciens</NuxtLink></li>
<li class="header_drawer_sub_menu_item"><NuxtLink to="/">Les artistes invités</NuxtLink></li> <li class="header_drawer_sub_menu_item"><NuxtLink to="/orchestre/artistesinvitees">Les artistes invités</NuxtLink></li>
<li class="header_drawer_sub_menu_item"><NuxtLink to="/">Discographie</NuxtLink></li> <li class="header_drawer_sub_menu_item"><NuxtLink to="/orchestre/discographie">Discographie</NuxtLink></li>
<li class="header_drawer_sub_menu_item"><NuxtLink to="/">Nos partenaires</NuxtLink></li> <li class="header_drawer_sub_menu_item"><NuxtLink to="/orchestre/partenaires">Nos partenaires</NuxtLink></li>
<li class="header_drawer_sub_menu_item"><NuxtLink to="/">Nous soutenir</NuxtLink></li> <li class="header_drawer_sub_menu_item"><NuxtLink to="/mecenat/soutenir">Nous soutenir</NuxtLink></li>
</ul> </ul>
</li> </li>
<li <li
class="header_drawer_link" class="header_drawer_link"
:class="{ 'is-open': activeDrawer === 'concerts' }" :class="{ 'is-open': activeDrawer === 'concerts','is-active': isConcerts }"
@click="toggleDrawer('concerts')" @click="toggleDrawer('concerts')"
> >
Concerts Concerts
<ul class="header_drawer_sub_menu"> <ul class="header_drawer_sub_menu">
<li class="header_drawer_sub_menu_item"><NuxtLink to="/">Saison</NuxtLink></li> <li class="header_drawer_sub_menu_item"><NuxtLink to="/concerts/saison">Saison</NuxtLink></li>
<li class="header_drawer_sub_menu_item"><NuxtLink to="/">Jeune public</NuxtLink></li> <li class="header_drawer_sub_menu_item"><NuxtLink to="/concerts/jeune-public">Jeune public</NuxtLink></li>
<li class="header_drawer_sub_menu_item"><NuxtLink to="/">Concert mode d'emploi</NuxtLink></li> <li class="header_drawer_sub_menu_item"><NuxtLink to="/concerts/mode-emploi">Concert mode d'emploi</NuxtLink></li>
<li class="header_drawer_sub_menu_item"><NuxtLink to="/">ONDIF MAG</NuxtLink></li> <li class="header_drawer_sub_menu_item"><NuxtLink to="/concerts/mag">ONDIF MAG</NuxtLink></li>
<li class="header_drawer_sub_menu_item"><NuxtLink to="/">ONDIF LIVE !</NuxtLink></li> <li class="header_drawer_sub_menu_item"><NuxtLink to="/concerts/live">ONDIF LIVE !</NuxtLink></li>
</ul> </ul>
</li> </li>
@@ -192,14 +193,14 @@
:class="{ 'is-open': activeDrawer === 'education' }" :class="{ 'is-open': activeDrawer === 'education' }"
@click="toggleDrawer('education')" @click="toggleDrawer('education')"
> >
Éducation et médiation Action culturelle
<ul class="header_drawer_sub_menu"> <ul class="header_drawer_sub_menu">
<li class="header_drawer_sub_menu_item"><NuxtLink to="/">Petite enfance</NuxtLink></li> <li class="header_drawer_sub_menu_item"><NuxtLink to="/mediation/petite-enfance">Petite enfance</NuxtLink></li>
<li class="header_drawer_sub_menu_item"><NuxtLink to="/">Scolaires</NuxtLink></li> <li class="header_drawer_sub_menu_item"><NuxtLink to="/mediation/scolaires">Scolaires</NuxtLink></li>
<li class="header_drawer_sub_menu_item"><NuxtLink to="/">Champ social</NuxtLink></li> <li class="header_drawer_sub_menu_item"><NuxtLink to="/mediation/social">Champ social</NuxtLink></li>
<li class="header_drawer_sub_menu_item"><NuxtLink to="/">Insertion professionnelle</NuxtLink></li> <li class="header_drawer_sub_menu_item"><NuxtLink to="/mediation/insertion-pro">Insertion professionnelle</NuxtLink></li>
<li class="header_drawer_sub_menu_item"><NuxtLink to="/">Pratiques amateurs</NuxtLink></li> <li class="header_drawer_sub_menu_item"><NuxtLink to="/mediation/amateurs">Pratiques amateurs</NuxtLink></li>
<li class="header_drawer_sub_menu_item"><NuxtLink to="/">Ressources pédagogiques</NuxtLink></li> <li class="header_drawer_sub_menu_item"><NuxtLink to="/mediation/ressources-pedagogiques">Ressources pédagogiques</NuxtLink></li>
</ul> </ul>
</li> </li>
@@ -210,20 +211,21 @@
> >
Mécénat Mécénat
<ul class="header_drawer_sub_menu"> <ul class="header_drawer_sub_menu">
<li class="header_drawer_sub_menu_item"><NuxtLink to="/">Entreprises</NuxtLink></li> <li class="header_drawer_sub_menu_item"><NuxtLink to="/mecenat/soutenir">Nous soutenir</NuxtLink></li>
<li class="header_drawer_sub_menu_item"><NuxtLink to="/">Les projets</NuxtLink></li> <li class="header_drawer_sub_menu_item"><NuxtLink to="/mecenat/entreprises">Entreprises</NuxtLink></li>
<li class="header_drawer_sub_menu_item"><NuxtLink to="/">Particuliers</NuxtLink></li> <li class="header_drawer_sub_menu_item"><NuxtLink to="/mecenat/projets">Les projets</NuxtLink></li>
<li class="header_drawer_sub_menu_item"><NuxtLink to="/">Ils nous font confiance</NuxtLink></li> <li class="header_drawer_sub_menu_item"><NuxtLink to="/mecenat/particuliers">Particuliers</NuxtLink></li>
<li class="header_drawer_sub_menu_item"><NuxtLink to="/mecenat/mecenes">Ils nous font confiance</NuxtLink></li>
</ul> </ul>
</li> </li>
<li class="icon_mobile"> <li class="icon_mobile">
<NuxtLink class="header_drawer_link icon_mobile_agenda" to="/agenda" @click="close"> <NuxtLink class="header_drawer_link icon_mobile_agenda" to="/concerts/agenda" @click="close">
<!-- ICÔNE injectée --> <!-- ICÔNE injectée -->
<slot name="mobile_agenda_icon" /> <slot name="mobile_agenda_icon" />
</NuxtLink> </NuxtLink>
<NuxtLink class="header_drawer_link icon_mobile_ticket" to="/agenda" @click="close"> <NuxtLink class="header_drawer_link icon_mobile_ticket" to="https://orchestre-ile.com/shop" external target="_blank" rel="noopener noreferrer" @click="close">
<!-- ICÔNE injectée --> <!-- ICÔNE injectée -->
<slot name="mobile_ticket" /> <slot name="mobile_ticket" />
</NuxtLink> </NuxtLink>
@@ -241,12 +243,16 @@
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
const route = useRoute()
import { watch } from 'vue' import { watch } from 'vue'
defineProps({ defineProps({
burgerColor: { type: String, default: 'hamburger_black' } burgerColor: { type: String, default: 'hamburger_black' }
}) })
/////////////////////////////////
// MENU MOBILE
/////////////////////////////////
const isOpen = ref(false) const isOpen = ref(false)
const activeDrawer = ref(null) const activeDrawer = ref(null)
const toggle = () => (isOpen.value = !isOpen.value) const toggle = () => (isOpen.value = !isOpen.value)
@@ -256,11 +262,38 @@
} }
// ✅ ferme automatiquement le mobile drawer si on navigue // ✅ ferme automatiquement le mobile drawer si on navigue
const route = useRoute()
watch(() => route.fullPath, () => { watch(() => route.fullPath, () => {
close() close()
activeDrawer.value = null activeDrawer.value = null
}) })
/////////////////////////////////
// MENU ACTIF
/////////////////////////////////
// L'Orchestre
const isOrchestre = computed(() =>
route.path.startsWith('/orchestre')
)
// Concerts
const isConcerts = computed(() =>
route.path.startsWith('/concerts')
)
// Action culturelle
const isMediation = computed(() =>
route.path.startsWith('/mediation')
)
// Mécénat
const isMecenat = computed(() =>
route.path.startsWith('/mecenat')
)
// professionnels
const isPro = computed(() =>
route.path.startsWith('/professionnels')
)
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@@ -0,0 +1,161 @@
<template>
<div class="decalage_cont"
:class="[
`decalage_cont--${position}`
]"
>
<div class="decalage"
:class="[
`decalage--${tone}`,
`decalage--${position}`
]"
>
<div class="decalage_inner">
<DsHeading v-if="$slots.title" :tone="titleTone" as="h2" textcase="uppercase">
<slot name="title" />
</DsHeading>
<slot />
<div v-if="$slots.button" class="decalage_button">
<slot name="button" />
</div>
<div v-if="ensavoirplusTarget" class="decalage_button">
<DsButton :textColor="buttonTone" :borderColor="buttonTone" @click="toggleTarget(ensavoirplusTarget)">En savoir +</DsButton>
</div>
</div>
</div>
</div>
</template>
<script setup>
import DsHeading from '@root/design-system/primitives/DsHeading.vue'
import DsText from '@root/design-system/primitives/DsText.vue'
import DsButton from '@root/design-system/primitives/DsButton.vue'
defineProps({
tone: { type: String, default: 'default' },
titleTone: { type: String, default: 'default' },
buttonTone: { type: String, default: 'default' },
position: { type: String, default: 'left' },
ensavoirplusTarget: { type: String, default: '' }
})
function toggleTarget(ensavoirplusTarget) {
document.getElementById(ensavoirplusTarget).classList.toggle("decalage_ensavoirplus--hidden");
}
</script>
<style lang="scss">
.decalage_cont {
margin-bottom: 70px;
display: flex;
&--left {
justify-content: flex-start;
}
&--right {
justify-content: flex-end;
}
}
.decalage {
padding-top: 50px;
padding-bottom: 50px;
display: flex;
@media (min-width: 0px) and (max-width: 700px) {
width: 89vw;
}
@media (min-width: 700px) {
width: 67vw;
}
@media (min-width: 800px) {
width: 60vw;
}
/* tons = arrière-plan section */
&--default { background: transparent; }
&--brand { background: var(--c-brand_rouge);}
&--dark { background: var(--c-backgroud-black); }
&--brandreverse { background: var(--c-backgroud-brandreverse); }
&--jaune { background: var(--c-background-jaune); }
/* position = arrière-plan section */
&--right {
.decalage_inner {
padding-right: 20px;
padding-left: 50px;
@media (max-width: 599px) {
padding-right: 15px;
padding-left: 40px;
}
}
}
&--left {
justify-content: flex-end;
.decalage_inner {
padding-right: 50px;
padding-left: 20px;
@media (max-width: 599px) {
padding-right: 40px;
padding-left: 15px;
}
}
}
}
.decalage_inner {
@media (min-width: 0px) {
width: 100%;
}
@media (min-width: 600px) {
width: calc(290px + 39vw);
}
@media (min-width: 700px) {
width: calc(330px + 17vw);
}
@media (min-width: 800px) {
width: calc(330px + 17vw);
}
@media (min-width: 900px) {
width: 600px;
}
@media (min-width: 1000px) {
width: 600px;
}
@media (min-width: 1100px) {
width: 600px;
}
@media (min-width: 1200px) {
width: 600px;
}
@media (min-width: 1300px) {
width: 600px;
}
@media (min-width: 1400px) {
width: 600px;
}
@media (min-width: 1500px) {
width: 600px;
}
}
.decalage_button {
margin-top: 10px;
text-align: right;
}
.decalage_ensavoirplus--hidden {
display: none;
}
</style>

View File

@@ -27,8 +27,6 @@
</template> </template>
<script setup> <script setup>
import { computed } from 'vue'
const props = defineProps({ const props = defineProps({
tone: { type: String, default: 'default' }, // default / brand / muted / dark… tone: { type: String, default: 'default' }, // default / brand / muted / dark…
padded_size: { type: String, default: '' }, // none | sm | md | lg padded_size: { type: String, default: '' }, // none | sm | md | lg
@@ -39,7 +37,6 @@
position : { type: String, default: '' }, position : { type: String, default: '' },
overflow : { type: String, default: '' } overflow : { type: String, default: '' }
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
@@ -56,6 +53,10 @@
&--brand { background: var(--c-brand_rouge);} &--brand { background: var(--c-brand_rouge);}
&--dark { background: var(--c-backgroud-black); } &--dark { background: var(--c-backgroud-black); }
&--brandreverse { background: var(--c-backgroud-brandreverse); } &--brandreverse { background: var(--c-backgroud-brandreverse); }
&--jaune { background: var(--c-background-jaune); }
&--bleu { background: var(--c-background-bleu); }
&--rouge45 { background: var(--c-brand_rouge45); }
&--rouge-weak { background: var(--c-brand_rouge-weak); }
// padding en haut et en bas // padding en haut et en bas
&--padded { &--padded {
@@ -68,7 +69,6 @@
padding-bottom: 120px; padding-bottom: 120px;
} }
} }
} }
</style> </style>

View File

@@ -12,7 +12,8 @@
size: { type: String, default: 'default' }, // default / wide / narrow size: { type: String, default: 'default' }, // default / wide / narrow
padb : { type: String, default: '' }, padb : { type: String, default: '' },
padt : { type: String, default: '' }, padt : { type: String, default: '' },
position : { type: String, dafault : ''} position : { type: String, dafault : ''},
overflow : { type: String, default: '' }
}) })
</script> </script>
@@ -39,13 +40,9 @@
&--default { &--default {
/* mobile / small screens */ /* mobile / small screens */
@media (max-width: 700px) {
//padding-inline: var(--page-padding-mobile);
}
@media (min-width: 0px) { @media (min-width: 0px) {
max-width: 100%; max-width: 100%;
} }
@media (min-width: 600px) { @media (min-width: 600px) {
max-width: 580px; max-width: 580px;

View File

@@ -0,0 +1,91 @@
<template>
<component v-if="isText && hasTextValue && wrapTag" :is="wrapTag">
{{ textValue }}
</component>
<template v-else-if="isText && hasTextValue">
{{ textValue }}
</template>
<component v-else-if="isList" :is="listTag">
<StrapiBlockChildsConvert
v-for="(child, i) in children"
:key="i"
:node="child"
/>
</component>
<li v-else-if="isListItem">
<StrapiBlockChildsConvert
v-for="(child, i) in children"
:key="i"
:node="child"
/>
</li>
<a
v-else-if="isLink"
:href="href"
class="strapi-inline__link"
rel="noopener noreferrer"
target="_blank"
>
<StrapiBlockChildsConvert
v-for="(child, i) in children"
:key="i"
:node="child"
/>
</a>
<template v-else>
<StrapiBlockChildsConvert
v-for="(child, i) in children"
:key="i"
:node="child"
/>
</template>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
node: { type: Object, required: true },
})
const isText = computed(() => props.node?.type === 'text')
const isLink = computed(() => props.node?.type === 'link')
const isList = computed(() => props.node?.type === 'list')
const isListItem = computed(() => props.node?.type === 'list-item')
const children = computed(() => props.node?.children || [])
const listTag = computed(() => (
props.node?.format === 'ordered' ? 'ol' : 'ul'
))
const textValue = computed(() => (props.node?.text ?? '').toString())
const hasTextValue = computed(() => textValue.value.length > 0)
// Dans Strapi blocks, les liens peuvent être "url" ou "href" selon les versions/plugins
const href = computed(() => props.node?.url || props.node?.href || '#')
/**
* Gestion des "marks" (bold/italic/underline/...)
* Ici on choisit une stratégie simple : UN seul wrapper.
* -> si tu veux combiner plusieurs marks (bold + italic), on le fait après.
*/
const wrapTag = computed(() => {
const n = props.node || {}
if (!isText.value) return null
if (n.code) return 'code'
if (n.bold) return 'strong'
if (n.italic) return 'em'
if (n.underline) return 'u'
if (n.strikethrough) return 's'
return null
})
</script>
<style lang="scss">
</style>

View File

@@ -0,0 +1,223 @@
<template>
<div class="strapi-blocks">
<template v-for="(block, i) in blocks" :key="i">
<!-- Paragraph -->
<p v-if="block.type === 'paragraph'" class="strapi-blocks--p">
<template v-for="(child, j) in (block.children)" :key="j">
<StrapiBlockChildsConvert :node="child" />
</template>
</p>
<!-- Heading -->
<component
v-else-if="block.type === 'heading'"
:is="`h${block.level}`"
class="strapi-blocks__h"
>
<template v-for="(child, j) in (block.children)" :key="j">
<StrapiBlockChildsConvert :node="child" />
</template>
</component>
<!-- Quote -->
<blockquote
v-else-if="block.type === 'quote'"
>
<template v-for="(child, j) in (block.children)" :key="j">
<StrapiBlockChildsConvert :node="child" />
</template>
</blockquote>
<!-- Lists -->
<ul
v-else-if="block.type === 'list' && block.format === 'unordered'"
>
<li
v-for="(item, j) in normalizeListItems(block.children)"
:key="j"
>
<template v-for="(child, k) in (item.children)" :key="k">
<StrapiBlockChildsConvert :node="child" />
</template>
</li>
</ul>
<ol
v-else-if="block.type === 'list' && block.format === 'ordered'"
>
<li
v-for="(item, j) in normalizeListItems(block.children)"
:key="j"
>
<template v-for="(child, k) in (item.children)" :key="k">
<StrapiBlockChildsConvert :node="child" />
</template>
</li>
</ol>
<!-- Fallback -->
<div v-else class="strapi-blocks--unknown">
<!-- debug éventuel -->
</div>
</template>
</div>
</template>
<script setup>
import StrapiBlockChildsConvert from './StrapiBlockChildsConvert.vue'
const props = defineProps({
blocks: { type: Array, default: () => [] },
})
const normalizeListItems = (children = []) => {
const normalized = []
for (const child of children) {
if (!child || typeof child !== 'object') continue
if (child.type === 'list-item') {
normalized.push({
...child,
children: Array.isArray(child.children) ? [...child.children] : [],
})
continue
}
// Certains contenus Strapi placent une sous-liste comme sibling d'un list-item.
// On la rattache au dernier list-item pour produire un HTML imbriqué valide.
if (child.type === 'list' && normalized.length > 0) {
normalized[normalized.length - 1].children.push(child)
}
}
return normalized
}
</script>
<style lang="scss">
.strapi-blocks {
font-family: var(--font-roboto);
&--p {
font-weight: var(--fw-light);
font-size: 17px;
line-height: 22px;
margin-bottom: 5px;
text-align: justify;
}
h1 {
padding-bottom: 10px;
font-weight: var(--fw-bold);
font-size: 30px;
text-transform: uppercase;
}
h2 {
padding-bottom: 7px;
font-weight: var(--fw-bold);
font-size: 27px;
color: var(--c-brand_rouge);
}
h3 {
padding-bottom: 5px;
font-weight: var(--fw-semibold);
font-size: 26px;
}
h4 {
padding-bottom: 3px;
font-weight: var(--fw-medium);
font-size: 22px;
}
h4 {
padding-bottom: 3px;
font-weight: var(--fw-medium);
font-size: 22px;
}
h4 {
padding-bottom: 3px;
font-weight: var(--fw-medium);
font-size: 22px;
}
a {
text-decoration: underline;
color: var(--c-brand_rouge-weak);
text-decoration-thickness: from-font;
}
ul,
ol {
list-style: none;
margin: 0 0 5px;
padding-left: 0;
}
li {
position: relative;
margin: 0;
padding-left: 23px;
margin-bottom: 5px;
}
ul > li::before {
content: "•";
position: absolute;
left: 0;
top: -7px;
width: 0.5rem;
height: 0.5rem;
/*background-image: var(--strapi-li-icon);
background-repeat: no-repeat;
background-size: contain;
background-position: center;*/
font-size: 39px;
line-height: 1;
/* color: var(--c-brand_rouge); /* couleur */
}
ol {
counter-reset: item;
}
ol > li {
counter-increment: item;
}
ol > li::before {
content: counter(item) ". ";
}
li > ul,
li > ol {
margin-top: 4px;
padding-left: 1rem;
}
li > ul > li::before {
/* background-image: var(--strapi-li-icon-nested); */
content: "◦";
top: -6px;
font-size: 30px;
line-height: 1;
color: var(--c-text); /* couleur */
}
blockquote {
border-left: 11px var(--c-text-muted) solid;
padding-left: 20px;
margin-left: 2%;
margin-bottom: 30px;
margin-top: 30px;
}
// Espace uniquement avant un titre s'il suit un bloc de contenu
&--p + &__h,
&--ul + &__h,
&--ol + &__h,
&--quote + &__h {
margin-top: 13px;
}
}
</style>

View File

@@ -0,0 +1,96 @@
export function useArtistesInvitees(options = {}) {
const queryString = computed(() => {
const locale = unref(options.locale) ?? "fr-FR"
const sort = unref(options.sort) ?? null
const populate = unref(options.populate) ?? null
const filters = unref(options.filters) ?? null
const query = new URLSearchParams()
query.set("locale", locale)
if (sort) {
query.set("sort[0]", sort)
}
if (populate && typeof populate === "object") {
appendPopulate(query, populate)
}
if (filters && typeof filters === "object") {
appendFilters(query, filters)
}
return query.toString()
})
const { data, pending, error, refresh } = useFetch(
() => `/api/__strapi__/artistesinvitees?${queryString.value}`,
{
server: true,
key: () => `artistesinvitees:${queryString.value}`,
watch: [queryString],
}
)
const artistesinvitees = computed(() => {
let list = (data.value?.data || []).map(normalizeArtiste)
const limit = unref(options.limit)
if (typeof limit === "number") {
list = list.slice(0, Math.max(0, limit))
}
return list
})
return {
artistesinvitees,
concerts: artistesinvitees,
pending,
error,
refresh,
}
}
function appendPopulate(query, populate, prefix = "populate") {
Object.entries(populate).forEach(([key, value]) => {
if (value === true) {
query.set(`${prefix}[${key}]`, "true")
return
}
if (value && typeof value === "object") {
const entries = Object.entries(value)
const allTrue = entries.length > 0 && entries.every(([, v]) => v === true)
if (allTrue) {
const list = entries.map(([k]) => k).join(",")
query.set(`${prefix}[${key}][populate]`, list)
return
}
appendPopulate(query, value, `${prefix}[${key}][populate]`)
}
})
}
function appendFilters(query, filters, prefix = "filters") {
Object.entries(filters).forEach(([key, value]) => {
if (value === null || value === undefined) return
if (typeof value !== "object") {
query.set(`${prefix}[${key}]`, String(value))
return
}
Object.entries(value).forEach(([k, v]) => {
if (v === null || v === undefined) return
if (typeof v !== "object") {
query.set(`${prefix}[${key}][${k}]`, String(v))
return
}
appendFilters(query, v, `${prefix}[${key}][${k}]`)
})
})
}
function normalizeArtiste(item) {
const a = item.attributes || item || {}
return {
id: item.id,
...a,
}
}

View File

@@ -0,0 +1,96 @@
export function useArtistes(options = {}) {
const queryString = computed(() => {
const locale = unref(options.locale) ?? "fr-FR"
const sort = unref(options.sort) ?? null
const populate = unref(options.populate) ?? null
const filters = unref(options.filters) ?? null
const query = new URLSearchParams()
query.set("locale", locale)
if (sort) {
query.set("sort[0]", sort)
}
if (populate && typeof populate === "object") {
appendPopulate(query, populate)
}
if (filters && typeof filters === "object") {
appendFilters(query, filters)
}
return query.toString()
})
const { data, pending, error, refresh } = useFetch(
() => `/api/__strapi__/artistes?${queryString.value}`,
{
server: true,
key: () => `artistes:${queryString.value}`,
watch: [queryString],
}
)
const artistes = computed(() => {
let list = (data.value?.data || []).map(normalizeArtiste)
const limit = unref(options.limit)
if (typeof limit === "number") {
list = list.slice(0, Math.max(0, limit))
}
return list
})
return {
artistes,
concerts: artistes,
pending,
error,
refresh,
}
}
function appendPopulate(query, populate, prefix = "populate") {
Object.entries(populate).forEach(([key, value]) => {
if (value === true) {
query.set(`${prefix}[${key}]`, "true")
return
}
if (value && typeof value === "object") {
const entries = Object.entries(value)
const allTrue = entries.length > 0 && entries.every(([, v]) => v === true)
if (allTrue) {
const list = entries.map(([k]) => k).join(",")
query.set(`${prefix}[${key}][populate]`, list)
return
}
appendPopulate(query, value, `${prefix}[${key}][populate]`)
}
})
}
function appendFilters(query, filters, prefix = "filters") {
Object.entries(filters).forEach(([key, value]) => {
if (value === null || value === undefined) return
if (typeof value !== "object") {
query.set(`${prefix}[${key}]`, String(value))
return
}
Object.entries(value).forEach(([k, v]) => {
if (v === null || v === undefined) return
if (typeof v !== "object") {
query.set(`${prefix}[${key}][${k}]`, String(v))
return
}
appendFilters(query, v, `${prefix}[${key}][${k}]`)
})
})
}
function normalizeArtiste(item) {
const a = item.attributes || item || {}
return {
id: item.id,
...a,
}
}

View File

@@ -0,0 +1,128 @@
export function useConcerts(options = {}) {
const queryString = computed(() => {
const locale = unref(options.locale) ?? "fr-FR"
const sort = unref(options.sort) ?? null
const populate = unref(options.populate) ?? null
const filters = unref(options.filters) ?? null
const query = new URLSearchParams()
query.set("locale", locale)
if (sort) {
query.set("sort[0]", sort)
}
if (populate && typeof populate === "object") {
appendPopulate(query, populate)
}
if (filters && typeof filters === "object") {
appendFilters(query, filters)
}
return query.toString()
})
const { data, pending, error, refresh } = useFetch(
() => `/api/__strapi__/concerts?${queryString.value}`,
{
server: true,
key: () => `concerts:${queryString.value}`,
watch: [queryString],
}
)
const concerts = computed(() => {
const rows = (data.value?.data || []).map(normalizeConcert)
let list = rows.sort(compareByRepresentationDate)
const upcomingOnly = Boolean(unref(options.upcomingOnly) ?? false)
if (upcomingOnly) {
const todayStart = new Date()
todayStart.setHours(0, 0, 0, 0)
list = list.filter((c) => {
const d = getFirstRepresentationDate(c)
return d ? d >= todayStart : false
})
}
const limit = unref(options.limit)
if (typeof limit === "number") {
list = list.slice(0, Math.max(0, limit))
}
return list
})
return {
concerts,
pending,
error,
refresh,
}
}
function appendPopulate(query, populate, prefix = "populate") {
Object.entries(populate).forEach(([key, value]) => {
if (value === true) {
query.set(`${prefix}[${key}]`, "true")
return
}
if (value && typeof value === "object") {
const entries = Object.entries(value)
const allTrue = entries.length > 0 && entries.every(([, v]) => v === true)
if (allTrue) {
const list = entries.map(([k]) => k).join(",")
query.set(`${prefix}[${key}][populate]`, list)
return
}
appendPopulate(query, value, `${prefix}[${key}][populate]`)
}
})
}
function appendFilters(query, filters, prefix = "filters") {
Object.entries(filters).forEach(([key, value]) => {
if (value === null || value === undefined) return
if (typeof value !== "object") {
query.set(`${prefix}[${key}]`, String(value))
return
}
Object.entries(value).forEach(([k, v]) => {
if (v === null || v === undefined) return
if (typeof v !== "object") {
query.set(`${prefix}[${key}][${k}]`, String(v))
return
}
appendFilters(query, v, `${prefix}[${key}][${k}]`)
})
})
}
function normalizeConcert(item) {
const a = item.attributes || item || {}
return {
id: item.id,
...a,
}
}
// tri par date de représentation
function compareByRepresentationDate(a, b) {
const da = getFirstRepresentationDate(a)
const db = getFirstRepresentationDate(b)
if (!da && !db) return 0
if (!da) return 1
if (!db) return -1
return da - db
}
function getFirstRepresentationDate(concert) {
const reps = concert?.representation_concert || []
let earliest = null
reps.forEach((r) => {
const iso = r?.date_debut_representation
if (!iso) return
const d = new Date(iso)
if (!earliest || d < earliest) earliest = d
})
return earliest
}

View File

@@ -0,0 +1,101 @@
export function useStrapi(endpoint, options = {}) {
const queryString = computed(() => {
const locale = unref(options.locale) ?? "fr-FR"
const sort = unref(options.sort) ?? null
const populate = unref(options.populate) ?? null
const filters = unref(options.filters) ?? null
const query = new URLSearchParams()
query.set("locale", locale)
if (sort) {
query.set("sort[0]", sort)
}
if (populate && typeof populate === "object") {
appendPopulate(query, populate)
}
if (filters && typeof filters === "object") {
appendFilters(query, filters)
}
return query.toString()
})
const { data, pending, error, refresh } = useFetch(
() => `${endpoint}?${queryString.value}`,
{
server: true,
key: () => `${endpoint}:${queryString.value}`,
watch: [queryString],
}
)
const items = computed(() => {
const raw = data.value?.data
let list = []
if (Array.isArray(raw)) {
list = raw.map(normalizeStrapiItem)
} else if (raw && typeof raw === "object") {
list = [normalizeStrapiItem(raw)]
}
const limit = unref(options.limit)
if (typeof limit === "number") {
list = list.slice(0, Math.max(0, limit))
}
return list
})
return {
items,
pending,
error,
refresh,
}
}
function appendPopulate(query, populate, prefix = "populate") {
Object.entries(populate).forEach(([key, value]) => {
if (value === true) {
query.set(`${prefix}[${key}]`, "true")
return
}
if (value && typeof value === "object") {
const entries = Object.entries(value)
const allTrue = entries.length > 0 && entries.every(([, v]) => v === true)
if (allTrue) {
const list = entries.map(([k]) => k).join(",")
query.set(`${prefix}[${key}][populate]`, list)
return
}
appendPopulate(query, value, `${prefix}[${key}][populate]`)
}
})
}
function appendFilters(query, filters, prefix = "filters") {
Object.entries(filters).forEach(([key, value]) => {
if (value === null || value === undefined) return
if (typeof value !== "object") {
query.set(`${prefix}[${key}]`, String(value))
return
}
Object.entries(value).forEach(([k, v]) => {
if (v === null || v === undefined) return
if (typeof v !== "object") {
query.set(`${prefix}[${key}][${k}]`, String(v))
return
}
appendFilters(query, v, `${prefix}[${key}][${k}]`)
})
})
}
function normalizeStrapiItem(item) {
const a = item.attributes || item || {}
return {
id: item.id,
...a,
}
}

View File

@@ -1,15 +0,0 @@
<template>
<ConcertCard
v-for="c in concerts"
:key="c.id"
:title="c.title"
:date-label="c.dateLabel"
:venue="c.venue"
:city="c.city"
:image="{ src: c.imageUrl, alt: c.imageAlt }"
:tags="c.tags"
:price-from="c.priceFrom"
:is-sold-out="c.soldOut"
:href="`/concerts/${c.slug}`"
/>
</template>

View File

@@ -0,0 +1,72 @@
<template>
<div>
<PageSection tone="dark" content-size="default">
<SectionTitle tone="invert" pad="md">
LES CONCERTS À VENIR
</SectionTitle>
</PageSection>
<PageSection padded_size="md" content-size="default" class="remonter_concert_list">
<ConcertCardList :highlight-first="false" :limit-cards-on-breakpoint="false">
<ConcertCard
v-for="c in concerts"
:key="c.id"
:id="c.slug_concert"
:title="c.titre_concert"
:dateISO="c.representation_concert?.[0]?.date_debut_representation"
:dateLabel="formatDateLong(c.representation_concert?.[0]?.date_debut_representation)"
:lieu="c.representation_concert?.[0]?.lieu_representation?.nom_lieu"
:adresse_lieu="c.representation_concert?.[0]?.lieu_representation?.nom_lieu"
:description="c.resume_concert"
:imageUrl="c.image_illustration_concert?.url"
:imageAlt="c.image_illustration_concert?.alternativeText"
:href="`concert-${c.slug_concert}?from=agenda`"
/>
</ConcertCardList>
</PageSection>
</div>
</template>
<script setup>
import { onMounted } from "vue"
const runtimeConfig = useRuntimeConfig()
import { formatDateLong } from "@/utils/dateFormat.js"
const { concerts, refresh } = useConcerts({
locale: "fr-FR",
populate: {
saison_concert: true,
genre_concert: true,
type_audience_concert: true,
direction_ondif_concert: { postes_artiste_ondif: true },
direction_invite_concert: { postes_artiste_invite: true },
artistes_ondif_concert: { postes_artiste_ondif: true },
artistes_invite_concert: { postes_artiste_invite: true },
image_illustration_concert: true,
images_concert: true,
videos_concert: true,
audios_concert: true,
programme_concert: true,
representation_concert: { lieu_representation: true },
liens_youtube_concert: true,
},
filters: {
saison_concert: {
nom_saison: {
$eq: String(runtimeConfig.public.saison),
},
},
},
upcomingOnly: true,
})
onMounted(() => {
if (!concerts.value?.length) {
refresh()
}
})
</script>

View File

@@ -1,24 +1,884 @@
<template> <template>
<div> <div>
<div v-if="toto"> <section v-if="pending" aria-busy="true" aria-live="polite">
<h1>#{{route.params.id }} / {{ toto.title }}</h1> <p>en cours de chargement...</p>
<p>{{ toto.body }}</p> </section>
</div>
<div v-else> <template v-else>
<p>Chargement...</p> <PageSection tone="" content-size="default" class="breadcrum_wp">
<Breadcrumb :current-label="concert?.titre_concert || ''" />
</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">
{{ concert.titre_concert }}
</DsHeading>
</div> </div>
<div>
<DsText as="p" size="md" tone="default" class="" v-if="concert.sous_titre_concert">
{{ concert.sous_titre_concert }}
</DsText>
</div>
</div>
<div class="fiche_header_img">
<DsMedia
v-if="illustration?.url"
:src="illustration.url"
:alt="illustration.alternativeText || concert?.titre_concert || ''"
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="genreLabel">
{{ genreLabel }}
</DsHeading>
</div>
<div v-if="concert.duree_concert">
<DsText as="p" tone="invert" weight="bold" spacing="space-0" class="">
DURÉE {{ concert.duree_concert }}
</DsText>
<DsText as="p" tone="invert" class="" v-if="concert.duree_entracte">
Entracte {{ concert.duree_entracte }}
</DsText>
</div>
<div v-if="concert.production_concert">
<DsText as="p" tone="invert" class="">
{{ concert.production_concert }}
</DsText>
</div>
</div>
</div>
<div class="fiche_header_wp_droite"></div>
</section>
<PageSection tone="" content-size="default">
<section class="fiche_details_wp">
<section class="distribution_wp">
<div v-if="directionsOndif.length">
<div v-for="d in directionsOndif" :key="d.id" class="distribution_item">
<DsText as="p" tone="default" size="lg" v-if="d.postes_artiste_ondif?.length" class="distribution_item_poste direction">
{{ d.postes_artiste_ondif.map((p) => p.nom_poste).join(', ') }}
</DsText>
<DsText as="p" tone="default" size="lg" weight="bold" class="">
{{ d.nom_artiste_ondif }}
</DsText>
</div>
</div>
<div v-if="directionsInvite.length">
<div v-for="d in directionsInvite" :key="d.id" class="distribution_item">
<DsText as="p" tone="default" size="lg" v-if="d.postes_artiste_invite?.length" class="distribution_item_poste direction">
{{ d.postes_artiste_invite.map((p) => p.nom_poste).join(', ') }}
</DsText>
<DsText as="p" tone="default" size="lg" weight="bold" class="">
{{ d.nom_artiste_invite }}
</DsText>
</div>
</div>
<div v-if="artistesOndif.length">
<div v-for="d in artistesOndif" :key="d.id" class="distribution_item">
<DsText as="p" tone="default" size="lg" v-if="d.postes_artiste_ondif?.length" class="distribution_item_poste">
{{ d.postes_artiste_ondif.map((p) => p.nom_poste).join(', ') }}
</DsText>
<DsText as="p" tone="default" size="lg" class="">
{{ d.nom_artiste_ondif }}
</DsText>
</div>
</div>
<div v-if="artistesInvite.length">
<div v-for="d in artistesInvite" :key="d.id" class="distribution_item">
<DsText as="p" tone="default" size="lg" v-if="d.postes_artiste_invite?.length" class="distribution_item_poste">
{{ d.postes_artiste_invite.map((p) => p.nom_poste).join(', ') }}
</DsText>
<DsText as="p" tone="default" size="lg" class="">
{{ d.nom_artiste_invite }}
</DsText>
</div>
</div>
</section>
<section class="programme_wp">
<div>
<DsHeading as="h2" tone="invert" textcase="uppercase" class="programme_titre">
PROGRAMME
</DsHeading>
</div>
<div class="programme_list">
<div v-for="(p, i) in programmes" :key="p.id || i" class="programme_item" >
<DsText as="p" tone="default" size="lg" spacing="space-0" weight="semibold" class="programme_compositeur">
{{ p.compositeur_programme }}
</DsText>
<DsText as="p" tone="default" size="lg" spacing="space-0" class="programme_oeuvre">
{{ p.oeuvre_programme }}
</DsText>
<DsText as="p" tone="default" size="lg" class="" v-if="p.piece_programme">
{{ p.piece_programme }}
</DsText>
</div>
</div>
</section>
<div v-if="representations.length" class="representation_wp">
<div v-for="(r, i) in representations" :key="r.id || i" class="representation_item">
<div>
<DsHeading as="h6" tone="default" v-if="r.date_debut_representation">
{{ formatDateLong(r.date_debut_representation) }} <span v-if="r.date_fin_representation">- {{ formatDateLong(r.date_fin_representation) }}</span>
</DsHeading>
</div>
<div>
<DsHeading as="h5" tone="default" v-if="r.lieu_representation?.nom_lieu" class="representation_item_lieu">
{{ r.lieu_representation.nom_lieu }}
</DsHeading>
</div>
<div>
<DsText as="p" tone="default" spacing="space-0" v-if="r.lieu_representation?.adresse_lieu">
{{ r.lieu_representation.adresse_lieu }}
</DsText>
</div>
<div class="representation_item_comment_wp">
<DsText as="p" tone="default" spacing="space-0" v-if="r.commentaire_representation" class="representation_item_comment">
{{ r.commentaire_representation }}
</DsText>
</div>
<div class="representation_cta">
<a
v-if="r.lien_billetterie_representation"
:href="r.lien_billetterie_representation"
target="_blank"
rel="noopener noreferrer"
>
Réserver
</a>
</div>
</div>
</div>
</section>
<section v-if="concert.description_concert" class="description_wp">
<StrapiBlocksConvert :blocks="concert?.description_concert" />
</section>
<section v-if="imagesConcert.length" class="img-gallery_wp">
<div class="img-gallery">
<DsMedia
v-for="img in imagesConcert"
:key="img.id || img.url"
:src="img.url"
:alt="img.alternativeText || concert?.titre_concert || ''"
/>
</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> </div>
</template> </template>
<script setup> <script setup>
import { formatDateLong } from "@/utils/dateFormat.js"
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() const route = useRoute()
const {data: toto} = await useFetch(() => 'https://jsonplaceholder.typicode.com/posts/' + route.params.id, { lazy: true })
useSeoMeta({ //////////////////////////////////////////////////////////////
title: () => toto.value?.title // RÉCUPÉRATION DU CONTENU
//////////////////////////////////////////////////////////////
const concertSlug = computed(() => String(route.params.id || ''))
const populate = {
saison_concert: true,
genre_concert: true,
type_audience_concert: true,
direction_ondif_concert: { postes_artiste_ondif: true },
direction_invite_concert: { postes_artiste_invite: true },
artistes_ondif_concert: { postes_artiste_ondif: true },
artistes_invite_concert: { postes_artiste_invite: true },
image_illustration_concert: true,
images_concert: true,
videos_concert: true,
audios_concert: true,
programme_concert: true,
representation_concert: { lieu_representation: true },
liens_youtube_concert: true,
}
const filters = computed(() => ({
slug_concert: {
$eq: concertSlug.value,
},
}))
const { concerts, pending, error } = useConcerts({
locale: 'fr-FR',
populate,
filters,
limit: 1,
upcomingOnly: false,
}) })
const concert = computed(() => concerts.value?.[0] || {})
useSeoMeta({
title: () => concert.value?.titre_concert || 'Concert',
description: () => concert.value?.resume_concert || undefined,
})
const genreLabel = computed(() => {
const g = concert.value?.genre_concert
// Strapi relation classique
if (Array.isArray(g?.data)) return g.data.map(x => x?.attributes?.nom_genre).filter(Boolean).join(', ')
if (g?.data) return g.data?.attributes?.nom_genre || ''
// Si déjà normalisé/flat
if (Array.isArray(g)) return g.map(x => x?.nom_genre).filter(Boolean).join(', ')
return g?.nom_genre || ''
})
const directionsOndif = computed(() => {
const value = concert.value?.direction_ondif_concert
if (!value) return []
return Array.isArray(value) ? value : [value]
})
const directionsInvite = computed(() => {
const value = concert.value?.direction_invite_concert
if (!value) return []
return Array.isArray(value) ? value : [value]
})
const artistesOndif = computed(() => {
const value = concert.value?.artistes_ondif_concert
if (!value) return []
return Array.isArray(value) ? value : [value]
})
const artistesInvite = computed(() => {
const value = concert.value?.artistes_invite_concert
if (!value) return []
return Array.isArray(value) ? value : [value]
})
const programmes = computed(() => {
const value = concert.value?.programme_concert
if (!value) return []
return Array.isArray(value) ? value : [value]
})
const representations = computed(() => {
const value = concert.value?.representation_concert
if (!value) return []
return Array.isArray(value) ? value : [value]
})
const illustration = computed(() => {
const m = concert.value?.image_illustration_concert
if (!m) return null
if (m.url) return m
return null
})
const imagesConcert = computed(() => {
const value = concert.value?.images_concert
if (!value) return []
if (Array.isArray(value) && value[0]?.url) return value
return Array.isArray(value) ? value : []
})
const youtube = computed(() => {
const value = concert.value?.liens_youtube_concert
if (!value) return []
return Array.isArray(value) ? value : [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)
)
</script> </script>
<style lang="scss"> <style lang="scss">
.breadcrum_wp {
padding-top: 30px;
}
.fiche_header_wp {
display: grid;
@media (min-width: 0px) and (max-width: 600px) {
grid-template-columns: 1fr;
grid-template-rows: auto 510px 20px 200px;
padding-top: 40px;
}
@media (min-width: 600px) {
grid-template-columns: minmax(10px, 10px) 580px 0px;
grid-template-rows: 40px 280px 20px 230px;
}
@media (min-width: 700px) {
grid-template-columns: minmax(10px, 10px) 660px 0px;
grid-template-rows: 70px 250px 90px 300px;
}
@media (min-width: 800px) {
grid-template-columns: minmax(10px, 10px) 780px minmax(10px, 10px);
grid-template-rows: 60px 280px 70px 300px;
}
@media (min-width: 900px) {
grid-template-columns: minmax(10px, 10px) 860px minmax(10px, 10px);
grid-template-rows: 90px 340px 100px 250px;
}
@media (min-width: 1000px) {
grid-template-columns: minmax(20px, auto) 950px minmax(10px, auto);
grid-template-rows: 90px 340px 120px 270px;
}
@media (min-width: 1100px) {
grid-template-columns: minmax(20px, auto) 1020px minmax(20px, auto);
grid-template-rows: 90px 340px 140px 290px;
}
@media (min-width: 1200px) {
grid-template-columns: minmax(20px, auto) 1100px minmax(20px, auto);
grid-template-rows: 90px 340px 160px 330px;
}
@media (min-width: 1300px) {
grid-template-columns: minmax(20px, auto) 1200px minmax(20px, auto);
grid-template-rows: 90px 340px 160px 330px;
}
@media (min-width: 1400px) {
grid-template-columns: minmax(20px, auto) 1300px minmax(20px, auto);
grid-template-rows: 90px 340px 160px 380px;
}
@media (min-width: 1500px) {
grid-template-columns: minmax(20px, auto) 1400px minmax(20px, auto);
grid-template-rows: 90px 340px 160px 380px;
}
}
.fiche_header_wp_gauche {
@media (min-width: 0px) and (max-width: 600px) {
display: none;
}
@media (min-width: 600px) {
grid-column: 1;
}
}
.fiche_header_wp_gauche_carre {
@media (min-width: 0px) and (max-width: 600px) {
display: none;
}
@media (min-width: 600px) {
grid-column: 1;
grid-row: 4;
}
background-color: var(--c-backgroud-black);
}
.fiche_header_wp_droite {
@media (min-width: 0px) and (max-width: 600px) {
display: none;
}
@media (min-width: 600px) {
grid-column: 3;
}
}
.fiche_header_inner {
@media (min-width: 0px) and (max-width: 600px) {
grid-column: 1;
grid-row: 1/5;
}
@media (min-width: 600px) {
grid-column: 2;
grid-row: 1/5;
}
display: grid;
@media (min-width: 0px) and (max-width: 600px) {
grid-template-columns: 4fr 1fr 0.5fr;
grid-template-rows: auto 510px 20px 200px;
}
@media (min-width: 600px) {
width: 575px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 40px 280px 20px 230px;
}
@media (min-width: 700px) {
width: 675px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 70px 250px 90px 300px;
}
@media (min-width: 800px) {
width: 780px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 60px 280px 70px 300px;
}
@media (min-width: 900px) {
width: 860px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 100px 250px;
}
@media (min-width: 1000px) {
width: 950px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 120px 270px;
}
@media (min-width: 1100px) {
width: 1020px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 140px 290px;
}
@media (min-width: 1200px) {
width: 1100px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 160px 330px;
}
@media (min-width: 1300px) {
width: 1200px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 160px 330px;
}
@media (min-width: 1400px) {
width: 1300px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 160px 380px;
}
@media (min-width: 1500px) {
width: 1400px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 160px 380px;
}
@media (min-width: 1600px) {
width: 1400px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 160px 380px;
}
@media (min-width: 1700px) {
width: 1400px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 160px 380px;
}
@media (min-width: 1800px) {
width: 1400px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 160px 380px;
}
}
.fiche_header_titres {
@media (min-width: 0px) and (max-width: 600px) {
grid-column: 1;
grid-row: 1;
padding-bottom: 20px;
padding-left: 10px;
}
@media (min-width: 600px) {
grid-column: 1;
grid-row: 2;
}
display: grid;
align-content: start;
gap: 0.75rem;
h1 {
font-size: 55px;
@media (min-width: 0px) and (max-width: 600px) {
font-size: 25px;
}
@media (min-width: 600px) {
font-size: 25px;
}
@media (min-width: 700px) {
font-size: 30px;
}
@media (min-width: 800px) {
font-size: 30px;
}
@media (min-width: 900px) {
font-size: 40px;
}
@media (min-width: 1000px) {
font-size: 40px;
}
@media (min-width: 1100px) {
font-size: 40px;
}
@media (min-width: 1200px) {
font-size: 50px;
}
@media (min-width: 1300px) {
font-size: 55px;
}
@media (min-width: 1400px) {
font-size: 55px;
}
@media (min-width: 1500px) {
font-size: 55px;
}
@media (min-width: 1600px) {
font-size: 55px;
}
@media (min-width: 1700px) {
font-size: 55px;
}
@media (min-width: 1800px) {
font-size: 55px;
}
}
}
.fiche_header_img {
@media (min-width: 0px) and (max-width: 600px) {
grid-column: 1 / 4;
grid-row: 2 / 4;
}
@media (min-width: 600px) {
grid-column: 3 / 5;
grid-row: 1 / 5;
}
overflow: hidden;
.ds-media {
height: 100%;
}
}
.fiche_header_infos {
@media (min-width: 0px) and (max-width: 600px) {
grid-column: 1 / 2;
grid-row: 4;
margin-top: -30px;
}
@media (min-width: 600px) {
grid-column: 1;
grid-row: 4;
}
display: grid;
align-content: center;
justify-content: end;
@media (min-width: 0px) and (max-width: 700px) {
gap: 7px;
}
@media (min-width: 700px) {
gap: 20px;
}
text-align: right;
.fiche_header_infos_genre {
font-weight: 900;
}
}
.fiche_header_bandeau {
@media (min-width: 0px) and (max-width: 600px) {
grid-column: 1 / 3;
grid-row: 4;
margin-top: -30px;
}
@media (min-width: 600px) {
grid-column: 1 / 4;
grid-row: 4;
}
background-color: var(--c-backgroud-black);
}
/* ============================ */
/* 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: 50px;
@media (max-width: 599px) {
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 */
/* ============================ */
.description_wp {
display: flex;
justify-content: center;
padding-bottom: 70px;
padding-left: 10px;
padding-right: 10px;
> * {
max-width: 570px;
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> </style>

View File

@@ -0,0 +1,4 @@
<script setup>
await navigateTo('/concerts/agenda', { redirectCode: 301 })
</script>

View File

@@ -0,0 +1,94 @@
<template>
<div>
<PageSection tone="jaune" content-size="default">
<SectionTitle tone="invert" pad="md">
LA SAISON 2025/2026 POUR LES JEUNES
</SectionTitle>
</PageSection>
<PageSection padded_size="md" content-size="default" class="remonter_concert_list">
<ConcertCardList :highlight-first="false" :limit-cards-on-breakpoint="false">
<ConcertCard
v-for="c in concerts"
:key="c.id"
:id="c.slug_concert"
:title="c.titre_concert"
:dateISO="c.representation_concert?.[0]?.date_debut_representation"
:dateLabel="formatDateLong(c.representation_concert?.[0]?.date_debut_representation)"
:lieu="c.representation_concert?.[0]?.lieu_representation?.nom_lieu"
:adresse_lieu="c.representation_concert?.[0]?.lieu_representation?.nom_lieu"
:description="c.resume_concert"
:imageUrl="c.image_illustration_concert?.url"
:imageAlt="c.image_illustration_concert?.alternativeText"
:href="`concert-${c.slug_concert}?from=jeune-public`"
/>
</ConcertCardList>
</PageSection>
</div>
</template>
<script setup>
import { onMounted } from "vue"
import { formatDateLong } from "@/utils/dateFormat.js"
const runtimeConfig = useRuntimeConfig()
const { concerts, refresh } = useConcerts({
locale: "fr-FR",
populate: {
saison_concert: true,
genre_concert: true,
type_audience_concert: true,
direction_ondif_concert: { postes_artiste_ondif: true },
direction_invite_concert: { postes_artiste_invite: true },
artistes_ondif_concert: { postes_artiste_ondif: true },
artistes_invite_concert: { postes_artiste_invite: true },
image_illustration_concert: true,
images_concert: true,
videos_concert: true,
audios_concert: true,
programme_concert: true,
representation_concert: { lieu_representation: true },
liens_youtube_concert: true,
},
filters: {
$and: [
{
saison_concert: {
nom_saison: {
$eq: String(runtimeConfig.public.saison),
},
},
},
{
$or: [
{
genre_concert: {
nom_genre: {
$eq: "Jeune public",
},
},
},
{
type_audience_concert: {
nom_audience: {
$eq: "Jeune public",
},
},
},
],
},
],
},
upcomingOnly: false,
})
onMounted(() => {
if (!concerts.value?.length) {
refresh()
}
})
</script>

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script lang="ts" setup>
</script>
<style>
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script lang="ts" setup>
</script>
<style>
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script lang="ts" setup>
</script>
<style>
</style>

View File

@@ -0,0 +1,72 @@
<template>
<div>
<PageSection tone="dark" content-size="default">
<SectionTitle tone="invert" pad="md">
SAISON 2025/2026
</SectionTitle>
</PageSection>
<PageSection padded_size="md" content-size="default" class="remonter_concert_list">
<ConcertCardList :highlight-first="false" :limit-cards-on-breakpoint="false">
<ConcertCard
v-for="c in concerts"
:key="c.id"
:id="c.slug_concert"
:title="c.titre_concert"
:dateISO="c.representation_concert?.[0]?.date_debut_representation"
:dateLabel="formatDateLong(c.representation_concert?.[0]?.date_debut_representation)"
:lieu="c.representation_concert?.[0]?.lieu_representation?.nom_lieu"
:adresse_lieu="c.representation_concert?.[0]?.lieu_representation?.nom_lieu"
:description="c.resume_concert"
:imageUrl="c.image_illustration_concert?.url"
:imageAlt="c.image_illustration_concert?.alternativeText"
:href="`concert-${c.slug_concert}?from=saison`"
/>
</ConcertCardList>
</PageSection>
</div>
</template>
<script setup>
import { onMounted } from "vue"
const runtimeConfig = useRuntimeConfig()
import { formatDateLong } from "@/utils/dateFormat.js"
const { concerts, refresh } = useConcerts({
locale: "fr-FR",
populate: {
saison_concert: true,
genre_concert: true,
type_audience_concert: true,
direction_ondif_concert: { postes_artiste_ondif: true },
direction_invite_concert: { postes_artiste_invite: true },
artistes_ondif_concert: { postes_artiste_ondif: true },
artistes_invite_concert: { postes_artiste_invite: true },
image_illustration_concert: true,
images_concert: true,
videos_concert: true,
audios_concert: true,
programme_concert: true,
representation_concert: { lieu_representation: true },
liens_youtube_concert: true,
},
filters: {
saison_concert: {
nom_saison: {
$eq: String(runtimeConfig.public.saison),
},
},
},
upcomingOnly: false,
})
onMounted(() => {
if (!concerts.value?.length) {
refresh()
}
})
</script>

View File

@@ -82,7 +82,7 @@
<ConcertCard <ConcertCard
id="1" id="1"
title="TITRE DU CONCERT EN MAJUSCULE" title="TITRE DU CONCERT EN MAJUSCULE"
venue="Nom du lieu, éventuellement de la salle" lieu="Nom du lieu, éventuellement de la salle"
dateISO="2026-01-15T20:30:00+01:00" dateISO="2026-01-15T20:30:00+01:00"
dateLabel="Jeudi 15 janvier 2026 — 20h30" dateLabel="Jeudi 15 janvier 2026 — 20h30"
description="Description du concert assez courte qui reprend l'essentiel, les artistes... On pourra écrire un nombre de lettres limitées." description="Description du concert assez courte qui reprend l'essentiel, les artistes... On pourra écrire un nombre de lettres limitées."

View File

@@ -6,7 +6,7 @@
<ConcertCard <ConcertCard
id="1" id="1"
title="TITRE DU CONCERT EN MAJUSCULE" title="TITRE DU CONCERT EN MAJUSCULE"
venue="Nom du lieu, éventuellement de la salle" lieu="Nom du lieu, éventuellement de la salle"
dateISO="2026-01-15T20:30:00+01:00" dateISO="2026-01-15T20:30:00+01:00"
dateLabel="Jeudi 15 janvier 2026 — 20h30" dateLabel="Jeudi 15 janvier 2026 — 20h30"
description="Description du concert assez courte qui reprend l'essentiel, les artistes... On pourra écrire un nombre de lettres limitées." description="Description du concert assez courte qui reprend l'essentiel, les artistes... On pourra écrire un nombre de lettres limitées."
@@ -39,10 +39,8 @@
const runtimeConfig = useRuntimeConfig() const runtimeConfig = useRuntimeConfig()
const STRAPI_URL = runtimeConfig.public.strapiUrl const STRAPI_URL = runtimeConfig.public.strapiUrl
console.log("STRAPI_URL : ",STRAPI_URL)
// Config app
// Config app (pour ton SEO)
const config = useAppConfig() const config = useAppConfig()
useSeoMeta({ useSeoMeta({
title: config.title title: config.title
@@ -78,10 +76,6 @@
const appConfig = useAppConfig() const appConfig = useAppConfig()
console.log("test 3 : ",appConfig.title) // "Mon site Nuxt" console.log("test 3 : ",appConfig.title) // "Mon site Nuxt"
onMounted(() => {
clientLog('info', 'test de log depuis vuejs', { })
clientLog('info', `STRAPI_URL : ${STRAPI_URL}`, { strapiUrl: STRAPI_URL })
})
</script> </script>
<style> <style>

View File

@@ -1,72 +1,76 @@
<template> <template>
<div>
<!-- ================== --> <!-- ================== -->
<!-- Fond noir --> <!-- PROCHAIN CONCERTS -->
<!-- ================== --> <!-- ================== -->
<!-- Fond noir -->
<PageSection tone="dark" content-size="default"> <PageSection tone="dark" content-size="default">
<SectionTitle tone="invert" pad="md"> <SectionTitle tone="invert" pad="md">
CONCERTS À VENIR CONCERTS À VENIR
</SectionTitle> </SectionTitle>
</PageSection> </PageSection>
<!-- ================== -->
<!-- Listes des prochains conncerts --> <!-- Listes des prochains conncerts -->
<!-- ================== -->
<PageSection padded_size="md" content-size="default" class="remonter_concert_list"> <PageSection padded_size="md" content-size="default" class="remonter_concert_list">
<ConcertCardList> <ConcertCardList>
<ConcertCard <ConcertCard
id="1" v-for="c in concerts"
title="TITRE DU CONCERT EN MAJUSCULE" :key="c.id"
venue="Nom du lieu, éventuellement de la salle" :id="c.slug_concert"
dateISO="2026-01-15T20:30:00+01:00" :title="c.titre_concert"
dateLabel="Jeudi 15 janvier 2026 — 20h30" :lieu="c.representation_concert?.[0]?.lieu_representation?.nom_lieu"
description="Description du concert assez courte qui reprend l'essentiel, les artistes... On pourra écrire un nombre de lettres limitées." :dateISO="c.representation_concert?.[0]?.date_debut_representation"
imageUrl="https://picsum.photos/id/56/500/700" :dateLabel="formatDateLong(c.representation_concert?.[0]?.date_debut_representation)"
imageAlt="Orchestre sur scène" :description="c.resume_concert"
ctaHref="/concert[id]" :imageUrl="c.image_illustration_concert?.url"
detailsHref="/concerts/concert_template" :imageAlt="c.image_illustration_concert?.alternativeText"
/> :href="`/concerts/concert-${c.slug_concert}?from=agenda`"
<ConcertCard
id="1"
title="TITRE DU CONCERT EN MAJUSCULE"
venue="Nom du lieu, éventuellement de la salle"
dateISO="2026-01-15T20:30:00+01:00"
dateLabel="Jeudi 15 janvier 2026 — 20h30"
description="Description du concert assez courte qui reprend l'essentiel, les artistes... On pourra écrire un nombre de lettres limitées."
imageUrl="https://picsum.photos/id/56/500/700"
imageAlt="Orchestre sur scène"
ctaHref="/concert[id]"
detailsHref="/concerts/concert_template"
/>
<ConcertCard
id="1"
title="TITRE DU CONCERT EN MAJUSCULE"
venue="Nom du lieu, éventuellement de la salle"
dateISO="2026-01-15T20:30:00+01:00"
dateLabel="Jeudi 15 janvier 2026 — 20h30"
description="Description du concert assez courte qui reprend l'essentiel, les artistes... On pourra écrire un nombre de lettres limitées."
imageUrl="https://picsum.photos/id/56/500/700"
imageAlt="Orchestre sur scène"
ctaHref="/concert[id]"
detailsHref="/concerts/concert_template"
/> />
</ConcertCardList> </ConcertCardList>
</PageSection> </PageSection>
<PageSection tone="dark" padded_size="md" content-size="default" padb="xs" class="theme_tao photo_orchestre_wp remonter_bloc_dessous">
<SectionContent>
<SectionTitle tone="invert" pad="xs">
LORCHESTRE NATIONAL D'ÎLE-DE-FRANCE
</SectionTitle>
<DsMedia :src="orchestre_img" alt="L'Orchestre" class="photo_orchestre_img"/>
</SectionContent>
<SectionContent pad="xs" class="photo_orchestre_cta">
<DsButtonArrow to="/" variant="invert">
Découvrir les musiciens
</DsButtonArrow>
</SectionContent>
</PageSection>
<!-- ================== --> <!-- ================== -->
<!-- Carte Île-De-France Partout et pour tous --> <!-- PARTOUT ET POUR TOUS -->
<!-- ================== --> <!-- ================== -->
<PageSection padded_size="md" class="theme_ppt_wp"> <PageSection padded_size="md" class="theme_ppt_wp">
<SectionContent class="theme_ppt"> <SectionContent class="theme_ppt">
<!-- PHOTO DE L'ORCHETSRE -->
<!-- <DsMedia :src="orchestre_img" alt="L'Orchestre" class="theme_ppt--img"/> -->
<!-- PHOTO DE LA CARTE IDF -->
<DsMedia :src="ppt_img" alt="Carte Île-De-France" class="theme_ppt--img"/> <DsMedia :src="ppt_img" alt="Carte Île-De-France" class="theme_ppt--img"/>
<!-- Avec couleur rouge par dessus -->
<SectionContent tone="brand_rouge45" pad="" class="theme_ppt--content"> <SectionContent tone="brand_rouge45" pad="" class="theme_ppt--content">
<!-- Sans couleur rouge par dessus -->
<!-- <SectionContent tone="" pad="" class="theme_ppt--content"> -->
<SectionTitle tone="invert" pad="xs"> <SectionTitle tone="invert" pad="xs">
PARTOUT ET POUR TOUS PARTOUT ET POUR TOUS
</SectionTitle> </SectionTitle>
<SectionContent pad="xs" class="theme_ppt--description"> <SectionContent pad="xs" class="theme_ppt--description">
<DsText tone="invert" size="lg" class="theme_ppt--txt" > <DsText tone="invert" size="lg" class="theme_ppt--txt" >
Ici le texte qui décrit le concept de Tous à lOrchestre - Dans les régions - Ici le texte qui décrit le concept de Tous à lOrchestre - Dans les régions - Ici le texte qui décrit le concept de Tous à lOrchestre - Dans les régions - Les 95 musiciennes et musiciens proposent chaque saison plus de 120 concerts dans des salles et théâtres, des lieux culturels et des espaces atypiques de la région francilienne. Porté par une forte mission territoriale, lorchestre sengage à rendre la musique symphonique accessible à toutes et tous, en la faisant vivre au plus près des habitants grâce notamment à des actions culturelles, pédagogiques et participatives au cœur du territoire.
</DsText> </DsText>
<!-- UN CTA / LIEN SUR L'IMAGE -->
<DsButtonArrow to="/" variant="invert"> <DsButtonArrow to="/" variant="invert">
Carte des événements Carte des événements
</DsButtonArrow> </DsButtonArrow>
@@ -75,6 +79,15 @@
</SectionContent> </SectionContent>
</PageSection> </PageSection>
<!-- CARTES PARTOUT ET POUR TOUS -->
<PageSection padded_size="md" class="theme_ppt_wp">
<SectionContent class="theme_ppt">
<SquareCardBlocTextList >
</SquareCardBlocTextList>
</SectionContent>
</PageSection>
<!-- ================== --> <!-- ================== -->
<!-- Tous à l'Orchestre --> <!-- Tous à l'Orchestre -->
<!-- ================== --> <!-- ================== -->
@@ -100,11 +113,11 @@
<PageSection padded_size="md"> <PageSection padded_size="md">
<SectionContent> <SectionContent>
<SectionTitle tone="" pad="xs"> <SectionTitle tone="" pad="xs">
LES DERNIÈRES ACTUALITÉS ACTUALITÉS
</SectionTitle> </SectionTitle>
</SectionContent> </SectionContent>
<SquareCardList > <SquareCardBlocTextList >
<SquareCard <SquareCardBlocText
v-for="actuscard in actuscards" v-for="actuscard in actuscards"
:key="actuscard.id" :key="actuscard.id"
:id="actuscard.id" :id="actuscard.id"
@@ -113,8 +126,8 @@
:title="actuscard.title" :title="actuscard.title"
:description="actuscard.description" :description="actuscard.description"
:url="actuscard.url" :url="actuscard.url"
></SquareCard> ></SquareCardBlocText>
</SquareCardList> </SquareCardBlocTextList>
</PageSection> </PageSection>
<!-- ================== --> <!-- ================== -->
@@ -161,66 +174,66 @@
<BannierePros /> <BannierePros />
</SectionContent> </SectionContent>
</PageSection> </PageSection>
</div>
</template> </template>
<script setup> <script setup>
import { onMounted, computed } from 'vue' import { onMounted, computed } from 'vue'
const runtimeConfig = useRuntimeConfig()
const config = useAppConfig()
import { clientLog } from '~/utils/clientLog' import { clientLog } from '~/utils/clientLog'
import { formatDateLong } from "@/utils/dateFormat.js"
import SectionContent from '../components/section/SectionContent.vue' import SectionContent from '../components/section/SectionContent.vue'
import DsHeading from '@root/design-system/primitives/DsHeading.vue' import DsHeading from '@root/design-system/primitives/DsHeading.vue'
import DsText from '@root/design-system/primitives/DsText.vue' import DsText from '@root/design-system/primitives/DsText.vue'
import DsMedia from '@root/design-system/primitives/DsMedia.vue' import DsMedia from '@root/design-system/primitives/DsMedia.vue'
import DsButton from '@root/design-system/primitives/DsButton.vue' import DsButton from '@root/design-system/primitives/DsButton.vue'
import DsButtonArrow from '@root/design-system/primitives/DsButtonArrow.vue' import DsButtonArrow from '@root/design-system/primitives/DsButtonArrow.vue'
import orchestre_img from '@/assets/img/illustrations/orchestre_1.jpg'
import ppt_img from '@/assets/img/illustrations/map_idf.jpg' import ppt_img from '@/assets/img/illustrations/map_idf.jpg'
import DsCard from '@root/design-system/components/DsCard.vue' import DsCard from '@root/design-system/components/DsCard.vue'
// Layout utilisé // Layout utilisé
definePageMeta({ layout: 'default' }) definePageMeta({ layout: 'default' })
const runtimeConfig = useRuntimeConfig()
const STRAPI_URL = runtimeConfig.public.strapiUrl const STRAPI_URL = runtimeConfig.public.strapiUrl
console.log("STRAPI_URL : ",STRAPI_URL)
// Config app (pour SEO) // Config app (pour SEO)
const config = useAppConfig()
useSeoMeta({ useSeoMeta({
title: config.title title: config.title
}) })
// On récupère le fichier le plus récent de la Media Library Strapi
const { data, error } = await useFetch(
() => `${STRAPI_URL}/api/upload/files?pagination[pageSize]=1&sort=createdAt:desc`
)
const imageUrl = computed(() => { console.log("Bienvenue : ",config.title)
const file = data.value?.[0]
console.log("file : ",file)
if (!file) return null
// Si Strapi renvoie une URL absolue (S3/OVH)
if (file.url?.startsWith('http')) {
return file.url
}
// Si jamais c'était une URL relative //--------------------
return `${STRAPI_URL}${file.url}` // DONNÉES POUR LES CONCERTS À VENIR …
//--------------------
const { concerts, refresh } = useConcerts({
locale: "fr-FR",
populate: {
saison_concert: true,
image_illustration_concert: true,
representation_concert: { lieu_representation: true },
},
filters: {
saison_concert: {
nom_saison: {
$eq: String(runtimeConfig.public.saison),
},
},
},
upcomingOnly: true,
limit: 3,
}) })
if (error.value) {
console.error('Erreur en récupérant les fichiers Strapi :', error.value)
clientLog('error', 'Erreur en récupérant les fichiers Strapi', {
endpoint: `${STRAPI_URL}/api/upload/files?pagination[pageSize]=1&sort=createdAt:desc`,
error: error.value?.message || error.value
})
}
const appConfig = useAppConfig()
console.log("test 3 : ",appConfig.title) // "Mon site Nuxt"
onMounted(() => { onMounted(() => {
clientLog('info', 'test de log depuis vuejs', { }) if (!concerts.value?.length) {
clientLog('info', `STRAPI_URL : ${STRAPI_URL}`, { strapiUrl: STRAPI_URL }) refresh()
}
}) })
//-------------------- //--------------------
@@ -292,11 +305,35 @@
.remonter_concert_list { .remonter_concert_list {
transform: translateY(-170px); transform: translateY(-170px);
} }
.remonter_bloc_dessous {
// parce que le bloc du dessus à un transform: translateY(-170px); alors cela laisse un espace vide que l'on comble avec ce margin-top négatif
margin-top: -170px;
}
.photo_orchestre_wp {
.photo_orchestre_img {
.page-section--inner--default {
max-width: calc(+500px)
}
img {
border-radius: 50px;
max-height: 660px;
}
}
.photo_orchestre_cta {
padding-top: 20px;
}
}
//========================
// PARTOUT ET POUR TOUS
//========================
.theme_ppt_wp { .theme_ppt_wp {
margin-bottom: 50px; margin-bottom: 50px;
// parce que le bloc du dessus à un transform: translateY(-170px); alors cela laisse un espace vide que l'on comble avec ce margin-top négatif
margin-top: -170px;
} }
.theme_ppt { .theme_ppt {
display: grid; display: grid;
@@ -304,6 +341,7 @@
&--img { &--img {
grid-row: 1; grid-row: 1;
grid-column: 1; grid-column: 1;
max-height: 400px;
} }
&--content { &--content {
grid-row: 1; grid-row: 1;
@@ -322,6 +360,9 @@
} }
} }
//========================
// TOUS A L'ORCHESTRE
//========================
.theme_tao { .theme_tao {
margin-bottom: 50px; margin-bottom: 50px;
@@ -402,14 +443,13 @@
width: clamp(360px, 28vw, 560px); width: clamp(360px, 28vw, 560px);
} }
} }
} }
} }
//========================
// MAGAZINE
//========================
.theme_mag { .theme_mag {

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script lang="ts" setup>
</script>
<style>
</style>

View File

@@ -0,0 +1,4 @@
<script setup>
await navigateTo('/mecenat/soutenir', { redirectCode: 301 })
</script>

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script lang="ts" setup>
</script>
<style>
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script lang="ts" setup>
</script>
<style>
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script lang="ts" setup>
</script>
<style>
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script lang="ts" setup>
</script>
<style>
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script lang="ts" setup>
</script>
<style>
</style>

View File

@@ -0,0 +1,4 @@
<script setup>
await navigateTo('/mediation/petite-enfance', { redirectCode: 301 })
</script>

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script lang="ts" setup>
</script>
<style>
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script lang="ts" setup>
</script>
<style>
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script lang="ts" setup>
</script>
<style>
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script lang="ts" setup>
</script>
<style>
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script lang="ts" setup>
</script>
<style>
</style>

View File

@@ -0,0 +1,760 @@
<template>
<div>
<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_ondif || ''" />
</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_ondif }}
</DsHeading>
</div>
<div>
</div>
</div>
<div class="fiche_header_img">
<DsMedia
v-if="illustration?.url"
:src="illustration.url"
:alt="illustration.alternativeText || artiste?.nom_artiste_ondif || ''"
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_ondif">
<DsText as="p" tone="invert" weight="bold" spacing="space-0" class="">
<a :href="artiste.url_artiste_ondif" 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_ondif || ''"
/>
</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 STRAPI
//////////////////////////////////////////////////////////////
const artisteSlug = computed(() => String(route.params.id || ''))
const populate = {
image_illustration_artiste_ondif: true,
postes_artiste_ondif: true,
}
const filters = computed(() => ({
slug_artiste_ondif: {
$eq: artisteSlug.value,
},
}))
const { items: artistes, pending, error } = useStrapi(
"/api/__strapi__/artistes",
{
locale: "fr-FR",
populate,
filters,
limit: 1,
}
)
const artiste = computed(() => artistes.value?.[0] || {})
useSeoMeta({
title: () => artiste.value?.nom_artiste_ondif || 'Artiste',
})
const postesLabel = computed(() =>
extractStrapiList(artiste.value?.postes_artiste_ondif)
.map((p) => p?.nom_poste)
.filter(Boolean)
.join(', ')
)
const illustration = computed(() => {
const m = artiste.value?.image_illustration_artiste_ondif
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_ondif
return extractStrapiList(value).filter((img) => Boolean(img?.url))
})
const descriptionBlocks = computed(() =>
artiste.value?.description_artiste_ondif || artiste.value?.description_concert || null
)
const youtube = computed(() => {
const value = artiste.value?.liens_youtube_artiste_ondif
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;
}
.fiche_header_wp {
display: grid;
@media (min-width: 0px) and (max-width: 600px) {
grid-template-columns: 1fr;
grid-template-rows: auto 510px 20px 200px;
padding-top: 40px;
}
@media (min-width: 600px) {
grid-template-columns: minmax(10px, 10px) 580px 0px;
grid-template-rows: 40px 280px 20px 230px;
}
@media (min-width: 700px) {
grid-template-columns: minmax(10px, 10px) 660px 0px;
grid-template-rows: 70px 250px 90px 300px;
}
@media (min-width: 800px) {
grid-template-columns: minmax(10px, 10px) 780px minmax(10px, 10px);
grid-template-rows: 60px 280px 70px 300px;
}
@media (min-width: 900px) {
grid-template-columns: minmax(10px, 10px) 860px minmax(10px, 10px);
grid-template-rows: 90px 340px 100px 250px;
}
@media (min-width: 1000px) {
grid-template-columns: minmax(20px, auto) 950px minmax(10px, auto);
grid-template-rows: 90px 340px 120px 270px;
}
@media (min-width: 1100px) {
grid-template-columns: minmax(20px, auto) 1020px minmax(20px, auto);
grid-template-rows: 90px 340px 140px 290px;
}
@media (min-width: 1200px) {
grid-template-columns: minmax(20px, auto) 1100px minmax(20px, auto);
grid-template-rows: 90px 340px 160px 330px;
}
@media (min-width: 1300px) {
grid-template-columns: minmax(20px, auto) 1200px minmax(20px, auto);
grid-template-rows: 90px 340px 160px 330px;
}
@media (min-width: 1400px) {
grid-template-columns: minmax(20px, auto) 1300px minmax(20px, auto);
grid-template-rows: 90px 340px 160px 380px;
}
@media (min-width: 1500px) {
grid-template-columns: minmax(20px, auto) 1400px minmax(20px, auto);
grid-template-rows: 90px 340px 160px 380px;
}
}
.fiche_header_wp_gauche {
@media (min-width: 0px) and (max-width: 600px) {
display: none;
}
@media (min-width: 600px) {
grid-column: 1;
}
}
.fiche_header_wp_gauche_carre {
@media (min-width: 0px) and (max-width: 600px) {
display: none;
}
@media (min-width: 600px) {
grid-column: 1;
grid-row: 4;
}
background-color: var(--c-backgroud-black);
}
.fiche_header_wp_droite {
@media (min-width: 0px) and (max-width: 600px) {
display: none;
}
@media (min-width: 600px) {
grid-column: 3;
}
}
.fiche_header_inner {
@media (min-width: 0px) and (max-width: 600px) {
grid-column: 1;
grid-row: 1/5;
}
@media (min-width: 600px) {
grid-column: 2;
grid-row: 1/5;
}
display: grid;
@media (min-width: 0px) and (max-width: 600px) {
grid-template-columns: 4fr 1fr 0.5fr;
grid-template-rows: auto 510px 20px 200px;
}
@media (min-width: 600px) {
width: 575px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 40px 280px 20px 230px;
}
@media (min-width: 700px) {
width: 675px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 70px 250px 90px 300px;
}
@media (min-width: 800px) {
width: 780px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 60px 280px 70px 300px;
}
@media (min-width: 900px) {
width: 860px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 100px 250px;
}
@media (min-width: 1000px) {
width: 950px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 120px 270px;
}
@media (min-width: 1100px) {
width: 1020px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 140px 290px;
}
@media (min-width: 1200px) {
width: 1100px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 160px 330px;
}
@media (min-width: 1300px) {
width: 1200px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 160px 330px;
}
@media (min-width: 1400px) {
width: 1300px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 160px 380px;
}
@media (min-width: 1500px) {
width: 1400px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 160px 380px;
}
@media (min-width: 1600px) {
width: 1400px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 160px 380px;
}
@media (min-width: 1700px) {
width: 1400px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 160px 380px;
}
@media (min-width: 1800px) {
width: 1400px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 160px 380px;
}
}
.fiche_header_titres {
@media (min-width: 0px) and (max-width: 600px) {
grid-column: 1;
grid-row: 1;
padding-bottom: 20px;
padding-left: 10px;
}
@media (min-width: 600px) {
grid-column: 1;
grid-row: 2;
}
display: grid;
align-content: start;
gap: 0.75rem;
h1 {
font-size: 55px;
@media (min-width: 0px) and (max-width: 600px) {
font-size: 25px;
}
@media (min-width: 600px) {
font-size: 25px;
}
@media (min-width: 700px) {
font-size: 30px;
}
@media (min-width: 800px) {
font-size: 30px;
}
@media (min-width: 900px) {
font-size: 40px;
}
@media (min-width: 1000px) {
font-size: 40px;
}
@media (min-width: 1100px) {
font-size: 40px;
}
@media (min-width: 1200px) {
font-size: 50px;
}
@media (min-width: 1300px) {
font-size: 55px;
}
@media (min-width: 1400px) {
font-size: 55px;
}
@media (min-width: 1500px) {
font-size: 55px;
}
@media (min-width: 1600px) {
font-size: 55px;
}
@media (min-width: 1700px) {
font-size: 55px;
}
@media (min-width: 1800px) {
font-size: 55px;
}
}
}
.fiche_header_img {
@media (min-width: 0px) and (max-width: 600px) {
grid-column: 1 / 4;
grid-row: 2 / 4;
}
@media (min-width: 600px) {
grid-column: 3 / 5;
grid-row: 1 / 5;
}
overflow: hidden;
.ds-media {
height: 100%;
}
}
.fiche_header_infos {
@media (min-width: 0px) and (max-width: 600px) {
grid-column: 1 / 2;
grid-row: 4;
margin-top: -30px;
}
@media (min-width: 600px) {
grid-column: 1;
grid-row: 4;
}
display: grid;
align-content: center;
justify-content: end;
@media (min-width: 0px) and (max-width: 700px) {
gap: 7px;
}
@media (min-width: 700px) {
gap: 20px;
}
text-align: right;
.fiche_header_infos_genre {
font-weight: 900;
}
}
.fiche_header_bandeau {
@media (min-width: 0px) and (max-width: 600px) {
grid-column: 1 / 3;
grid-row: 4;
margin-top: -30px;
}
@media (min-width: 600px) {
grid-column: 1 / 4;
grid-row: 4;
}
background-color: var(--c-backgroud-black);
}
/* ============================ */
/* 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 */
/* ============================ */
.fiche_description {
display: flex;
justify-content: center;
padding-top: 70px;
padding-bottom: 70px;
padding-left: 10px;
padding-right: 10px;
> * {
max-width: 570px;
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>

View File

@@ -0,0 +1,758 @@
<template>
<div>
<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;
}
.fiche_header_wp {
display: grid;
@media (min-width: 0px) and (max-width: 600px) {
grid-template-columns: 1fr;
grid-template-rows: auto 510px 20px 200px;
padding-top: 40px;
}
@media (min-width: 600px) {
grid-template-columns: minmax(10px, 10px) 580px 0px;
grid-template-rows: 40px 280px 20px 230px;
}
@media (min-width: 700px) {
grid-template-columns: minmax(10px, 10px) 660px 0px;
grid-template-rows: 70px 250px 90px 300px;
}
@media (min-width: 800px) {
grid-template-columns: minmax(10px, 10px) 780px minmax(10px, 10px);
grid-template-rows: 60px 280px 70px 300px;
}
@media (min-width: 900px) {
grid-template-columns: minmax(10px, 10px) 860px minmax(10px, 10px);
grid-template-rows: 90px 340px 100px 250px;
}
@media (min-width: 1000px) {
grid-template-columns: minmax(20px, auto) 950px minmax(10px, auto);
grid-template-rows: 90px 340px 120px 270px;
}
@media (min-width: 1100px) {
grid-template-columns: minmax(20px, auto) 1020px minmax(20px, auto);
grid-template-rows: 90px 340px 140px 290px;
}
@media (min-width: 1200px) {
grid-template-columns: minmax(20px, auto) 1100px minmax(20px, auto);
grid-template-rows: 90px 340px 160px 330px;
}
@media (min-width: 1300px) {
grid-template-columns: minmax(20px, auto) 1200px minmax(20px, auto);
grid-template-rows: 90px 340px 160px 330px;
}
@media (min-width: 1400px) {
grid-template-columns: minmax(20px, auto) 1300px minmax(20px, auto);
grid-template-rows: 90px 340px 160px 380px;
}
@media (min-width: 1500px) {
grid-template-columns: minmax(20px, auto) 1400px minmax(20px, auto);
grid-template-rows: 90px 340px 160px 380px;
}
}
.fiche_header_wp_gauche {
@media (min-width: 0px) and (max-width: 600px) {
display: none;
}
@media (min-width: 600px) {
grid-column: 1;
}
}
.fiche_header_wp_gauche_carre {
@media (min-width: 0px) and (max-width: 600px) {
display: none;
}
@media (min-width: 600px) {
grid-column: 1;
grid-row: 4;
}
background-color: var(--c-backgroud-black);
}
.fiche_header_wp_droite {
@media (min-width: 0px) and (max-width: 600px) {
display: none;
}
@media (min-width: 600px) {
grid-column: 3;
}
}
.fiche_header_inner {
@media (min-width: 0px) and (max-width: 600px) {
grid-column: 1;
grid-row: 1/5;
}
@media (min-width: 600px) {
grid-column: 2;
grid-row: 1/5;
}
display: grid;
@media (min-width: 0px) and (max-width: 600px) {
grid-template-columns: 4fr 1fr 0.5fr;
grid-template-rows: auto 510px 20px 200px;
}
@media (min-width: 600px) {
width: 575px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 40px 280px 20px 230px;
}
@media (min-width: 700px) {
width: 675px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 70px 250px 90px 300px;
}
@media (min-width: 800px) {
width: 780px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 60px 280px 70px 300px;
}
@media (min-width: 900px) {
width: 860px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 100px 250px;
}
@media (min-width: 1000px) {
width: 950px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 120px 270px;
}
@media (min-width: 1100px) {
width: 1020px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 140px 290px;
}
@media (min-width: 1200px) {
width: 1100px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 160px 330px;
}
@media (min-width: 1300px) {
width: 1200px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 160px 330px;
}
@media (min-width: 1400px) {
width: 1300px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 160px 380px;
}
@media (min-width: 1500px) {
width: 1400px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 160px 380px;
}
@media (min-width: 1600px) {
width: 1400px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 160px 380px;
}
@media (min-width: 1700px) {
width: 1400px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 160px 380px;
}
@media (min-width: 1800px) {
width: 1400px;
grid-template-columns: 4fr 0.5fr 0.5fr 6.5fr;
grid-template-rows: 90px 340px 160px 380px;
}
}
.fiche_header_titres {
@media (min-width: 0px) and (max-width: 600px) {
grid-column: 1;
grid-row: 1;
padding-bottom: 20px;
padding-left: 10px;
}
@media (min-width: 600px) {
grid-column: 1;
grid-row: 2;
}
display: grid;
align-content: start;
gap: 0.75rem;
h1 {
font-size: 55px;
@media (min-width: 0px) and (max-width: 600px) {
font-size: 25px;
}
@media (min-width: 600px) {
font-size: 25px;
}
@media (min-width: 700px) {
font-size: 30px;
}
@media (min-width: 800px) {
font-size: 30px;
}
@media (min-width: 900px) {
font-size: 40px;
}
@media (min-width: 1000px) {
font-size: 40px;
}
@media (min-width: 1100px) {
font-size: 40px;
}
@media (min-width: 1200px) {
font-size: 50px;
}
@media (min-width: 1300px) {
font-size: 55px;
}
@media (min-width: 1400px) {
font-size: 55px;
}
@media (min-width: 1500px) {
font-size: 55px;
}
@media (min-width: 1600px) {
font-size: 55px;
}
@media (min-width: 1700px) {
font-size: 55px;
}
@media (min-width: 1800px) {
font-size: 55px;
}
}
}
.fiche_header_img {
@media (min-width: 0px) and (max-width: 600px) {
grid-column: 1 / 4;
grid-row: 2 / 4;
}
@media (min-width: 600px) {
grid-column: 3 / 5;
grid-row: 1 / 5;
}
overflow: hidden;
.ds-media {
height: 100%;
}
}
.fiche_header_infos {
@media (min-width: 0px) and (max-width: 600px) {
grid-column: 1 / 2;
grid-row: 4;
margin-top: -30px;
}
@media (min-width: 600px) {
grid-column: 1;
grid-row: 4;
}
display: grid;
align-content: center;
justify-content: end;
@media (min-width: 0px) and (max-width: 700px) {
gap: 7px;
}
@media (min-width: 700px) {
gap: 20px;
}
text-align: right;
.fiche_header_infos_genre {
font-weight: 900;
}
}
.fiche_header_bandeau {
@media (min-width: 0px) and (max-width: 600px) {
grid-column: 1 / 3;
grid-row: 4;
margin-top: -30px;
}
@media (min-width: 600px) {
grid-column: 1 / 4;
grid-row: 4;
}
background-color: var(--c-backgroud-black);
}
/* ============================ */
/* 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 */
/* ============================ */
.fiche_description {
display: flex;
justify-content: center;
padding-top: 70px;
padding-bottom: 70px;
padding-left: 10px;
padding-right: 10px;
> * {
max-width: 570px;
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>

View File

@@ -0,0 +1,320 @@
<template>
<div>
<!-- ================== -->
<!-- Fond noir -->
<!-- ================== -->
<PageSection tone="brandreverse" content-size="default" class="theme_bandeau--grid">
<SectionContent pad="xs" class="theme_bandeau--grid--left">
<SectionTitle tone="invert" pad="">
LES ARTISTES INVITÉS
</SectionTitle>
<DsHeading as="h3" tone="invert">
La patte de lOrchestre national dÎle-de-France, cest aussi la richesse de ses collaborations artistiques
</DsHeading>
</SectionContent>
<SectionContent pad="xs" class="theme_bandeau--grid--right">
<DsText tone="invert" size="lg" class="theme_bandeau--grid--right--text" >
Chefs, solistes, comédiens, chaque saison, de nombreux talents viennent enrichir la programmation de lOrchestre : découvrez ici tous les artistes de la saison !
</DsText>
</SectionContent>
</PageSection>
<!-- ================== -->
<!-- Listes des musiciens -->
<!-- ================== -->
<!-- ================== -->
<!-- DIRECTION -->
<!-- ================== -->
<PageSection padded_size="md" content-size="default" class="musiciens_list_section remonter_artistes_list">
<section v-if="pending" aria-busy="true" aria-live="polite">
<DsText as="p" tone="default">Chargement des artistes...</DsText>
</section>
<section v-else-if="error">
<DsText as="p" tone="default">Impossible de charger les artistes.</DsText>
</section>
<section v-else-if="artistesDirection.length" class="musiciens_list">
<article v-for="a in artistesDirection" :key="a.id" class="musicien_card">
<DsMedia
v-if="a.image?.url"
:src="a.image.url"
:alt="a.image.alternativeText || a.nom_artiste_invite || ''"
ratio="square"
/>
<div v-else class="musicien_card_media-placeholder" aria-hidden="true" />
<DsHeading as="h4" tone="default" class="musicien_card_nom">
<NuxtLink :to="`/orchestre/artisteinvitee-${a.slug_artiste_invite}?from=artistesinvitees`">
{{ a.nom_artiste_invite }}
</NuxtLink>
</DsHeading>
<DsText
v-if="a.postesLabel"
as="p"
tone="default"
size="md"
class="musicien_card_postes"
>
{{ a.postesLabel }}
</DsText>
</article>
</section>
<section v-else>
<DsText as="p" tone="default">Aucun artiste pour la direction.</DsText>
</section>
</PageSection>
<!-- ================== -->
<!-- MUSICIENS -->
<!-- ================== -->
<PageSection padded_size="md" content-size="default" class="musiciens_list_section musiciens_list_wp">
<section v-if="pending" aria-busy="true" aria-live="polite">
<DsText as="p" tone="default">Chargement des artistes...</DsText>
</section>
<section v-else-if="error">
<DsText as="p" tone="default">Impossible de charger les artistes.</DsText>
</section>
<section v-else-if="artistesAutres.length" class="musiciens_list">
<article v-for="a in artistesAutres" :key="a.id" class="musicien_card">
<DsMedia
v-if="a.image?.url"
:src="a.image.url"
:alt="a.image.alternativeText || a.nom_artiste_invite || ''"
ratio="square"
/>
<div v-else class="musicien_card_media-placeholder" aria-hidden="true" />
<DsHeading as="h4" tone="default" class="musicien_card_nom">
<NuxtLink :to="`/orchestre/artisteinvitee-${a.slug_artiste_invite}?from=artistesinvitees`">
{{ a.nom_artiste_invite }}
</NuxtLink>
</DsHeading>
<DsText
v-if="a.postesLabel"
as="p"
tone="default"
size="md"
class="musicien_card_postes"
>
{{ a.postesLabel }}
</DsText>
</article>
</section>
<section v-else>
<DsText as="p" tone="default">Aucun autre artiste trouvé.</DsText>
</section>
</PageSection>
</div>
</template>
<script setup>
import DsHeading from '@root/design-system/primitives/DsHeading.vue'
import DsText from '@root/design-system/primitives/DsText.vue'
import DsMedia from '@root/design-system/primitives/DsMedia.vue'
const runtimeConfig = useRuntimeConfig()
const saisonFiltre = computed(() => String(runtimeConfig.public.saison || '').trim())
const artistesFilters = computed(() => {
if (!saisonFiltre.value) return null
return {
saisons_artiste_invite: {
nom_saison: {
$eq: saisonFiltre.value,
},
},
}
})
//--------------------
// DONNÉES POUR LES ARTISTES
//--------------------
const { items: artistesinvitees, pending, error } = useStrapi(
"/api/__strapi__/artistesinvitees",
{
locale: "fr-FR",
sort: "nom_artiste_invite:asc",
populate: {
saisons_artiste_invite: true,
image_illustration_artiste_invite: true,
postes_artiste_invite: true,
},
filters: artistesFilters,
}
)
const artistesDisplay = computed(() => {
return (artistesinvitees.value || []).map((artiste) => ({
...artiste,
image: getArtisteImage(artiste),
postesLabel: getPostesLabel(artiste),
}))
})
const postesDirection = [
"direction",
"cheffe assistante",
"chef assistante",
]
const artistesDirection = computed(() =>
artistesDisplay.value.filter((a) => isDirectionArtist(a))
)
const artistesAutres = computed(() =>
artistesDisplay.value.filter((a) => !isDirectionArtist(a))
)
function getArtisteImage(artiste) {
const media = artiste?.image_illustration_artiste_invite
const rows = extractStrapiList(media)
if (rows.length) return rows[0]
if (media && typeof media === "object" && media.url) return media
return null
}
function getPostesLabel(artiste) {
const postes = extractStrapiList(artiste?.postes_artiste_invite)
return postes
.map((p) => p?.nom_poste)
.filter(Boolean)
.join(", ")
}
function isDirectionArtist(artiste) {
const postes = extractStrapiList(artiste?.postes_artiste_invite)
.map((p) => String(p?.nom_poste || "").trim().toLowerCase())
.filter(Boolean)
return postes.some((poste) => postesDirection.includes(poste))
}
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
}
</script>
<style lang="scss">
.remonter_artistes_list {
transform: translateY(-90px);
}
.theme_bandeau--grid {
> .page-section--inner {
padding-top: 70px;
padding-bottom: 150px;
display: flex;
flex-wrap: wrap;
column-gap: 120px;
row-gap: 30px;
}
&--left {
max-width: 40%;
h1 {
padding-bottom: 20px;
}
}
&--right {
max-width: 60%;
&--text {
max-width: 500px;
}
}
}
.musiciens_list_wp {
// parce que le bloc du dessus à un transform: translateY(-170px); alors cela laisse un espace vide que l'on comble avec ce margin-top négatif
margin-top: -120px;
}
.musiciens_list {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 28px;
}
.musicien_card {
display: flex;
flex-direction: column;
gap: 10px;
}
.musicien_card_media-placeholder {
width: 100%;
aspect-ratio: 1 / 1;
background: rgba(0, 0, 0, 0.04);
}
.musicien_card_nom {
margin-top: 8px;
margin-left: 5px;
&:hover {
color: var(--c-brand_rouge);
}
}
.musicien_card_postes {
margin-left: 5px;
text-transform: lowercase;
&::first-letter {
text-transform: uppercase;
}
}
@media (max-width: 980px) {
.musiciens_list {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 400px) {
.musiciens_list {
grid-template-columns: 1fr;
padding-left: 5px;
padding-right: 5px;
}
}
@media (max-width: 600px) {
.musiciens_list {
padding-left: 5px;
padding-right: 5px;
}
}
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div>
Page en construction direction
</div>
</template>
<script setup>
</script>
<style>
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div>
test
</div>
</template>
<script setup>
</script>
<style>
</style>

View File

@@ -0,0 +1,4 @@
<script setup>
await navigateTo('/orchestre/missions', { redirectCode: 301 })
</script>

View File

@@ -0,0 +1,474 @@
<template>
<div>
<!-- ================== -->
<!-- FILS D'ARIANE -->
<!-- ================== -->
<PageSection tone="" content-size="default" class="breadcrum_wp">
<Breadcrumb/>
</PageSection>
<!-- ================== -->
<!-- EN-TêTE -->
<!-- ================== -->
<section class="fiche_header_wp">
<div class="fiche_header_wp_gauche"></div>
<div class="fiche_header_inner">
<div class="fiche_header_titres">
<div>
<DsHeading as="h1" tone="default" textcase="uppercase" class="concert-card__title">
{{ missions?.header_titre }}
</DsHeading>
</div>
<DsText as="p" align="justify">
{{ missions?.header_text }}
</DsText>
</div>
<div class="fiche_header_img">
<DsMedia
v-if="missions?.image_illustration_header?.url"
:src="missions.image_illustration_header.url"
:alt="missions.image_illustration_header.alternativeText || ''"
fit="contain"
ratio="square"
/>
<div v-else class="img_placeholder" aria-hidden="true" />
</div>
</div>
<div class="fiche_header_wp_droite"></div>
</section>
<!-- ================== -->
<!-- PARTIE 1 -->
<!-- ================== -->
<PageSection tone="" content-size="default" padded_size="md" class="fiche_description">
<SectionContent v-if="missions?.partie_1" class="description_wp">
<StrapiBlocksConvert :blocks="missions.partie_1" />
</SectionContent>
</PageSection>
<!-- ================== -->
<!-- decalage_gauche 1 -->
<!-- ================== -->
<PageSection :content="false">
<Decalage tone="dark" title-tone="invert" position="left" button-tone="invert">
<template #title>
{{ missions?.decalage_gauche_1?.decalage_titre }}
</template>
<DsText as="p" tone="invert" align="justify">
{{ missions?.decalage_gauche_1?.decalage_texte }}
</DsText>
</Decalage>
</PageSection>
<!-- ================== -->
<!-- PARTIE 2 -->
<!-- ================== -->
<PageSection tone="" content-size="default" padded_size="md" class="fiche_description">
<SectionContent v-if="missions?.partie_2" class="description_wp">
<StrapiBlocksConvert :blocks="missions.partie_2" />
</SectionContent>
</PageSection>
<!-- ================== -->
<!-- decalage_droite_2 -->
<!-- ================== -->
<PageSection :content="false">
<Decalage tone="brandreverse" title-tone="invert" position="right" button-tone="invert">
<template #title>
{{ missions?.decalage_droite_2?.decalage_titre }}
</template>
<DsText as="p" align="justify">
{{ missions?.decalage_droite_2?.decalage_texte }}
</DsText>
</Decalage>
</PageSection>
<!-- ================== -->
<!-- PARTIE 3 -->
<!-- ================== -->
<PageSection tone="" content-size="default" padded_size="md" class="fiche_description">
<SectionContent v-if="missions?.partie_3" class="description_wp">
<StrapiBlocksConvert :blocks="missions.partie_3" />
</SectionContent>
</PageSection>
<!-- ================== -->
<!-- decalage_droite_clic_4 -->
<!-- ================== -->
<PageSection :content="false">
<Decalage tone="brand" title-tone="invert" position="right" button-tone="invert" ensavoirplus-target="texte_cache">
<template #title>
{{ missions?.decalage_droite_clic_4?.decalage_titre }}
</template>
<DsText as="p" tone="invert" align="justify">
{{ missions?.decalage_droite_clic_4?.decalage_texte }}
</DsText>
</Decalage>
</PageSection>
<PageSection>
<div id="texte_cache" class="decalage_ensavoirplus--hidden">Texte caché</div>
</PageSection>
<!-- ================== -->
<!-- L'ORCHESTRE POUR LES PROS -->
<!-- ================== -->
<PageSection padded_size="lg">
<SectionContent>
<SectionTitle tone="" pad="xs">
L'ORCHESTRE POUR LES PROS
</SectionTitle>
</SectionContent>
<SectionContent>
<BannierePros />
</SectionContent>
</PageSection>
</div>
</template>
<script setup>
import DsHeading from '@root/design-system/primitives/DsHeading.vue'
import DsText from '@root/design-system/primitives/DsText.vue'
import DsMedia from '@root/design-system/primitives/DsMedia.vue'
import DsButton from '@root/design-system/primitives/DsButton.vue'
//--------------------
// RÉCUPÉRATION DES DONNÉES STRAPI
//--------------------
const { items: mission, pending, error, refresh } = useStrapi(
"/api/__strapi__/missions",
{ locale: "fr-FR",
populate: {
decalage_gauche_1: true,
decalage_droite_2: true,
decalage_gauche_3: true,
decalage_droite_clic_4: true,
image_illustration_header: true,
},
}
)
const missions = computed(() => mission.value?.[0] || null)
</script>
<style lang="scss">
.breadcrum_wp {
padding-top: 30px;
}
.fiche_header_wp {
display: grid;
justify-content: center;
padding-bottom: 100px;
@media (min-width: 0px) and (max-width: 600px) {
grid-template-columns: 1fr;
grid-template-rows: auto 510px;
padding-top: 40px;
}
@media (min-width: 600px) {
grid-template-columns: minmax(10px, 10px) 580px 0px;
grid-template-rows: 40px 380px;
}
@media (min-width: 700px) {
grid-template-columns: minmax(10px, 10px) 660px 0px;
grid-template-rows: 40px 380px;
}
@media (min-width: 800px) {
grid-template-columns: minmax(10px, 10px) auto minmax(10px, 10px);
grid-template-rows: 40px 340px;
}
@media (min-width: 900px) {
grid-template-columns: minmax(10px, 10px) 860px minmax(10px, 10px);
grid-template-rows: 40px 400px;
}
@media (min-width: 1000px) {
grid-template-columns: minmax(20px, auto) 950px minmax(10px, auto);
grid-template-rows: 40px 400px;
}
@media (min-width: 1100px) {
grid-template-columns: minmax(20px, auto) 1020px minmax(20px, auto);
grid-template-rows: 40px 400px;
}
@media (min-width: 1200px) {
grid-template-columns: minmax(20px, auto) 1100px minmax(20px, auto);
grid-template-rows: 40px 400px;
}
@media (min-width: 1300px) {
grid-template-columns: minmax(20px, auto) 1200px minmax(20px, auto);
grid-template-rows: 40px 400px;
}
@media (min-width: 1400px) {
grid-template-columns: minmax(20px, auto) 1300px minmax(20px, auto);
grid-template-rows: 40px 400px;
}
@media (min-width: 1500px) {
grid-template-columns: minmax(20px, auto) 1400px minmax(20px, auto);
grid-template-rows: 40px 400px;
}
}
.fiche_header_wp_gauche {
@media (min-width: 0px) and (max-width: 600px) {
display: none;
}
@media (min-width: 600px) {
grid-column: 1;
}
}
.fiche_header_wp_gauche_carre {
@media (min-width: 0px) and (max-width: 600px) {
display: none;
}
@media (min-width: 600px) {
grid-column: 1;
grid-row: 4;
}
}
.fiche_header_wp_droite {
@media (min-width: 0px) and (max-width: 600px) {
display: none;
}
@media (min-width: 600px) {
grid-column: 3;
}
}
.fiche_header_inner {
@media (min-width: 0px) and (max-width: 600px) {
grid-column: 1;
grid-row: 1/3;
}
@media (min-width: 600px) {
grid-column: 2;
grid-row: 1/3;
}
display: grid;
@media (min-width: 0px) and (max-width: 600px) {
grid-template-columns: 1fr;
grid-template-rows: auto 510px;
}
@media (min-width: 600px) {
width: 575px;
grid-template-columns: 4fr 0.5fr 3.5fr;
grid-template-rows: 40px 380px;
}
@media (min-width: 700px) {
width: 675px;
grid-template-columns: 4fr 0.5fr 3.5fr;
grid-template-rows: 40px 380px;
}
@media (min-width: 800px) {
width: 780px;
grid-template-columns: 4fr 0.5fr 3.5fr;
grid-template-rows: 40px 340px;
}
@media (min-width: 900px) {
width: 860px;
grid-template-columns: 4fr 0.5fr 3.5fr;
grid-template-rows: 40px 400px;
}
@media (min-width: 1000px) {
width: 950px;
grid-template-columns: 4fr 0.5fr 3.5fr;
grid-template-rows: 40px 400px;
}
@media (min-width: 1100px) {
width: 1020px;
grid-template-columns: 4fr 0.5fr 3.5fr;
grid-template-rows: 40px 400px;
}
@media (min-width: 1200px) {
width: 1100px;
grid-template-columns: 4fr 0.5fr 3.5fr;
grid-template-rows: 40px 400px;
}
@media (min-width: 1300px) {
width: 1200px;
grid-template-columns: 4fr 0.5fr 3.5fr;
grid-template-rows: 40px 400px;
}
@media (min-width: 1400px) {
width: 1300px;
grid-template-columns: 4fr 0.5fr 3.5fr;
grid-template-rows: 40px 400px;
}
@media (min-width: 1500px) {
width: 1400px;
grid-template-columns: 4fr 0.5fr 3.5fr;
grid-template-rows: 40px 400px;
}
@media (min-width: 1600px) {
width: 1400px;
grid-template-columns: 4fr 0.5fr 3.5fr;
grid-template-rows: 40px 400px;
}
@media (min-width: 1700px) {
width: 1400px;
grid-template-columns: 4fr 0.5fr 3.5fr;
grid-template-rows: 40px 400px;
}
@media (min-width: 1800px) {
width: 1400px;
grid-template-columns: 4fr 0.5fr 3.5fr;
grid-template-rows: 40px 400px;
}
}
.fiche_header_titres {
@media (min-width: 0px) and (max-width: 600px) {
grid-column: 1;
grid-row: 1;
padding-bottom: 20px;
padding-left: 10px;
}
@media (min-width: 600px) {
grid-column: 1;
grid-row: 2;
}
display: grid;
align-content: start;
gap: 0.75rem;
max-width: 475px;
h1 {
font-size: 55px;
@media (min-width: 0px) and (max-width: 600px) {
font-size: 25px;
}
@media (min-width: 600px) {
font-size: 25px;
}
@media (min-width: 700px) {
font-size: 30px;
}
@media (min-width: 800px) {
font-size: 30px;
}
@media (min-width: 900px) {
font-size: 40px;
}
@media (min-width: 1000px) {
font-size: 40px;
}
@media (min-width: 1100px) {
font-size: 40px;
}
@media (min-width: 1200px) {
font-size: 50px;
}
@media (min-width: 1300px) {
font-size: 55px;
}
@media (min-width: 1400px) {
font-size: 55px;
}
@media (min-width: 1500px) {
font-size: 55px;
}
@media (min-width: 1600px) {
font-size: 55px;
}
@media (min-width: 1700px) {
font-size: 55px;
}
@media (min-width: 1800px) {
font-size: 55px;
}
}
}
.fiche_header_img {
@media (min-width: 0px) and (max-width: 600px) {
grid-column: 1 / 4;
grid-row: 2 / 3;
padding-left: 10px;
padding-right: 10px;
}
@media (min-width: 600px) {
grid-column: 3 / 5;
grid-row: 1 / 3;
}
overflow: hidden;
.ds-media {
@media (min-width: 600px) {
height: 100%;
}
}
}
.fiche_header_infos {
@media (min-width: 0px) and (max-width: 600px) {
grid-column: 1 / 2;
grid-row: 4;
margin-top: -30px;
}
@media (min-width: 600px) {
grid-column: 1;
grid-row: 4;
}
display: grid;
align-content: center;
justify-content: end;
@media (min-width: 0px) and (max-width: 700px) {
gap: 7px;
}
@media (min-width: 700px) {
gap: 20px;
}
text-align: right;
.fiche_header_infos_genre {
font-weight: 900;
}
}
.fiche_header_bandeau {
@media (min-width: 0px) and (max-width: 600px) {
grid-column: 1 / 3;
grid-row: 4;
margin-top: -30px;
}
@media (min-width: 600px) {
grid-column: 1 / 4;
grid-row: 4;
}
background-color: var(--c-backgroud-black);
}
.fiche_description > * {
max-width: 570px;
display: flex;
flex-direction: column;
}
.fiche_description {
.page-section--inner {
@media (max-width: 599px) {
padding-left: 10px;
padding-right: 10px;
}
}
}
</style>

View File

@@ -0,0 +1,320 @@
<template>
<div>
<!-- ================== -->
<!-- Fond noir -->
<!-- ================== -->
<PageSection tone="bleu" content-size="default" class="theme_bandeau--grid">
<SectionContent pad="xs" class="theme_bandeau--grid--left">
<SectionTitle tone="invert" pad="">
LES MUSICIENS
</SectionTitle>
<DsHeading as="h3" tone="invert">
95 musiciens engagés Partout et pour tous en Île-de-France !
</DsHeading>
</SectionContent>
<SectionContent pad="xs" class="theme_bandeau--grid--right">
<DsText tone="invert" size="lg" class="theme_bandeau--grid--right--text" >
Les 95 musiciennes et musiciens proposent chaque saison plus de 120 concerts dans des salles et théâtres, des lieux culturels et des espaces atypiques de la région francilienne. Porté par une forte mission territoriale, lorchestre sengage à rendre la musique symphonique accessible à toutes et tous, en la faisant vivre au plus près des habitants grâce notamment à des actions culturelles, pédagogiques et participatives au cœur du territoire.
</DsText>
</SectionContent>
</PageSection>
<!-- ================== -->
<!-- Listes des musiciens -->
<!-- ================== -->
<!-- ================== -->
<!-- DIRECTION -->
<!-- ================== -->
<PageSection padded_size="md" content-size="default" class="musiciens_list_section remonter_artistes_list">
<section v-if="pending" aria-busy="true" aria-live="polite">
<DsText as="p" tone="default">Chargement des artistes...</DsText>
</section>
<section v-else-if="error">
<DsText as="p" tone="default">Impossible de charger les artistes.</DsText>
</section>
<section v-else-if="artistesDirection.length" class="musiciens_list">
<article v-for="a in artistesDirection" :key="a.id" class="musicien_card">
<DsMedia
v-if="a.image?.url"
:src="a.image.url"
:alt="a.image.alternativeText || a.nom_artiste_ondif || ''"
ratio="square"
/>
<div v-else class="musicien_card_media-placeholder" aria-hidden="true" />
<DsHeading as="h4" tone="default" class="musicien_card_nom">
<NuxtLink :to="`/orchestre/artiste-${a.slug_artiste_ondif}?from=musiciens`">
{{ a.nom_artiste_ondif }}
</NuxtLink>
</DsHeading>
<DsText
v-if="a.postesLabel"
as="p"
tone="default"
size="md"
class="musicien_card_postes"
>
{{ a.postesLabel }}
</DsText>
</article>
</section>
<section v-else>
<DsText as="p" tone="default">Aucun artiste pour la direction.</DsText>
</section>
</PageSection>
<!-- ================== -->
<!-- MUSICIENS -->
<!-- ================== -->
<PageSection padded_size="md" content-size="default" class="musiciens_list_section musiciens_list_wp">
<section v-if="pending" aria-busy="true" aria-live="polite">
<DsText as="p" tone="default">Chargement des artistes...</DsText>
</section>
<section v-else-if="error">
<DsText as="p" tone="default">Impossible de charger les artistes.</DsText>
</section>
<section v-else-if="artistesAutres.length" class="musiciens_list">
<article v-for="a in artistesAutres" :key="a.id" class="musicien_card">
<DsMedia
v-if="a.image?.url"
:src="a.image.url"
:alt="a.image.alternativeText || a.nom_artiste_ondif || ''"
ratio="square"
/>
<div v-else class="musicien_card_media-placeholder" aria-hidden="true" />
<DsHeading as="h4" tone="default" class="musicien_card_nom">
<NuxtLink :to="`/orchestre/artiste-${a.slug_artiste_ondif}?from=musiciens`">
{{ a.nom_artiste_ondif }}
</NuxtLink>
</DsHeading>
<DsText
v-if="a.postesLabel"
as="p"
tone="default"
size="md"
class="musicien_card_postes"
>
{{ a.postesLabel }}
</DsText>
</article>
</section>
<section v-else>
<DsText as="p" tone="default">Aucun autre artiste trouvé.</DsText>
</section>
</PageSection>
</div>
</template>
<script setup>
import DsHeading from '@root/design-system/primitives/DsHeading.vue'
import DsText from '@root/design-system/primitives/DsText.vue'
import DsMedia from '@root/design-system/primitives/DsMedia.vue'
const runtimeConfig = useRuntimeConfig()
const saisonFiltre = computed(() => String(runtimeConfig.public.saison || '').trim())
const artistesFilters = computed(() => {
if (!saisonFiltre.value) return null
return {
saisons_artiste_ondif: {
nom_saison: {
$eq: saisonFiltre.value,
},
},
}
})
//--------------------
// DONNÉES POUR LES ARTISTES
//--------------------
const { items: artistes, pending, error } = useStrapi(
"/api/__strapi__/artistes",
{
locale: "fr-FR",
sort: "ordre_artiste_ondif:asc",
populate: {
saisons_artiste_ondif: true,
image_illustration_artiste_ondif: true,
postes_artiste_ondif: true,
},
filters: artistesFilters,
}
)
const artistesDisplay = computed(() => {
return (artistes.value || []).map((artiste) => ({
...artiste,
image: getArtisteImage(artiste),
postesLabel: getPostesLabel(artiste),
}))
})
const postesDirection = [
"direction",
"cheffe assistante",
"chef assistante",
]
const artistesDirection = computed(() =>
artistesDisplay.value.filter((a) => isDirectionArtist(a))
)
const artistesAutres = computed(() =>
artistesDisplay.value.filter((a) => !isDirectionArtist(a))
)
function getArtisteImage(artiste) {
const media = artiste?.image_illustration_artiste_ondif
const rows = extractStrapiList(media)
if (rows.length) return rows[0]
if (media && typeof media === "object" && media.url) return media
return null
}
function getPostesLabel(artiste) {
const postes = extractStrapiList(artiste?.postes_artiste_ondif)
return postes
.map((p) => p?.nom_poste)
.filter(Boolean)
.join(", ")
}
function isDirectionArtist(artiste) {
const postes = extractStrapiList(artiste?.postes_artiste_ondif)
.map((p) => String(p?.nom_poste || "").trim().toLowerCase())
.filter(Boolean)
return postes.some((poste) => postesDirection.includes(poste))
}
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
}
</script>
<style lang="scss">
.remonter_artistes_list {
transform: translateY(-90px);
}
.theme_bandeau--grid {
> .page-section--inner {
padding-top: 70px;
padding-bottom: 150px;
display: flex;
flex-wrap: wrap;
column-gap: 120px;
row-gap: 30px;
}
&--left {
max-width: 40%;
h1 {
padding-bottom: 20px;
}
}
&--right {
max-width: 60%;
&--text {
max-width: 500px;
}
}
}
.musiciens_list_wp {
// parce que le bloc du dessus à un transform: translateY(-170px); alors cela laisse un espace vide que l'on comble avec ce margin-top négatif
margin-top: -120px;
}
.musiciens_list {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 28px;
}
.musicien_card {
display: flex;
flex-direction: column;
gap: 10px;
}
.musicien_card_media-placeholder {
width: 100%;
aspect-ratio: 1 / 1;
background: rgba(0, 0, 0, 0.04);
}
.musicien_card_nom {
margin-top: 8px;
margin-left: 5px;
&:hover {
color: var(--c-brand_rouge);
}
}
.musicien_card_postes {
margin-left: 5px;
text-transform: lowercase;
&::first-letter {
text-transform: uppercase;
}
}
@media (max-width: 980px) {
.musiciens_list {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 400px) {
.musiciens_list {
grid-template-columns: 1fr;
padding-left: 5px;
padding-right: 5px;
}
}
@media (max-width: 600px) {
.musiciens_list {
padding-left: 5px;
padding-right: 5px;
}
}
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script setup>
</script>
<style>
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script setup>
</script>
<style>
</style>

View File

@@ -14,7 +14,6 @@
const runtimeConfig = useRuntimeConfig() const runtimeConfig = useRuntimeConfig()
//const STRAPI_URL = "http://localhost:1337" //const STRAPI_URL = "http://localhost:1337"
const STRAPI_URL = runtimeConfig.public.strapiUrl const STRAPI_URL = runtimeConfig.public.strapiUrl
console.log("STRAPI_URL : ",STRAPI_URL)
// Config app (pour SEO) // Config app (pour SEO)
const config = useAppConfig() const config = useAppConfig()
@@ -51,11 +50,7 @@
} }
const appConfig = useAppConfig() const appConfig = useAppConfig()
console.log("test 3 : ",appConfig.title) // "Mon site Nuxt" console.log("Bienvenue : ",appConfig.title)
onMounted(() => {
clientLog('info', 'test de log depuis vuejs', { })
clientLog('info', `STRAPI_URL : ${STRAPI_URL}`, { strapiUrl: STRAPI_URL })
})
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script lang="ts" setup>
</script>
<style>
</style>

View File

@@ -0,0 +1,4 @@
<script setup>
await navigateTo('/professionnels/programmer-orchestre', { redirectCode: 301 })
</script>

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script lang="ts" setup>
</script>
<style>
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script lang="ts" setup>
</script>
<style>
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script lang="ts" setup>
</script>
<style>
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script lang="ts" setup>
</script>
<style>
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script lang="ts" setup>
</script>
<style>
</style>

View File

@@ -31,7 +31,7 @@
title: config.title title: config.title
}) })
const appConfig = useAppConfig() const appConfig = useAppConfig()
console.log("test 3 : ",appConfig.title) // "Mon site Nuxt" console.log("test 3 : ",appConfig.title)
</script> </script>
<style lang="scss"> <style lang="scss">

16
app/utils/dateFormat.js Normal file
View File

@@ -0,0 +1,16 @@
export function formatDateLong(iso) {
if (!iso) return ""
const d = new Date(iso)
const date = new Intl.DateTimeFormat("fr-FR", {
weekday: "long",
day: "numeric",
month: "long",
year: "numeric",
}).format(d)
const time = new Intl.DateTimeFormat("fr-FR", {
hour: "2-digit",
minute: "2-digit",
hour12: false,
}).format(d).replace(":", "h")
return `${date}${time}`
}

33
app/utils/strapi.js Normal file
View File

@@ -0,0 +1,33 @@
// app/utils/strapi.js
export function getStrapiBaseUrl(event) {
// En server/Nitro, on récupère runtimeConfig via l'event si dispo,
// sinon on retombe sur la variable d'env publique.
const base =
event?.context?.runtimeConfig?.public?.strapiUrl ||
process.env.NUXT_PUBLIC_STRAPI_URL
if (!base) {
throw new Error("Missing runtimeConfig.public.strapiUrl (NUXT_PUBLIC_STRAPI_URL)")
}
try {
new URL(base)
} catch {
throw new Error(`Invalid Strapi base URL: ${base}`)
}
return base.replace(/\/$/, "")
}
export async function strapiFetch(event, path, options = {}) {
const base = getStrapiBaseUrl(event)
const url = `${base}${path.startsWith("/") ? path : `/${path}`}`
return await $fetch(url, {
...options,
headers: {
...(options.headers || {}),
},
})
}

View File

@@ -7,7 +7,8 @@
`ds-heading--${resolvedsize}`, `ds-heading--${resolvedsize}`,
`ds-heading--${resolvedWeight}`, `ds-heading--${resolvedWeight}`,
`ds-heading--${resolvedspacing}`, //margin-bottom `ds-heading--${resolvedspacing}`, //margin-bottom
`ds-heading--${tone}` `ds-heading--${tone}`,
`ds-heading--${textcase}`
]" ]"
> >
<slot /> <slot />
@@ -21,6 +22,7 @@
as: { type: String, default: 'h2' }, // h1/h2/h3/p/span... as: { type: String, default: 'h2' }, // h1/h2/h3/p/span...
font: { type: String, default: 'roboto' }, // barlow | brandon font: { type: String, default: 'roboto' }, // barlow | brandon
tone: { type: String, default: 'default' }, // default/muted/invert tone: { type: String, default: 'default' }, // default/muted/invert
textcase: { type: String, default: 'default' }, // uppercase
}) })
const resolvedWeight = computed(() => { const resolvedWeight = computed(() => {
@@ -133,7 +135,8 @@
&--bleu_fonce { color: var(--c-bleu_fonce); } &--bleu_fonce { color: var(--c-bleu_fonce); }
&--bleu_clair { color: var(--c-bleu_clair); } &--bleu_clair { color: var(--c-bleu_clair); }
// CASE
&--uppercase {text-transform: uppercase;}
&--info { color: var(--c-info); } &--info { color: var(--c-info); }

View File

@@ -1,7 +1,8 @@
<template> <template>
<div class="ds-media" :class="`ds-media--${ratio}`"> <div class="ds-media" :class="[`ds-media--${ratio}`]">
<img <img
class="ds-media__img" class="ds-media__img"
:class="[`ds-media__img--${fit}`]"
:src="src" :src="src"
:alt="alt" :alt="alt"
loading="lazy" loading="lazy"
@@ -14,6 +15,7 @@
defineProps({ defineProps({
src: { type: String, required: true }, src: { type: String, required: true },
alt: { type: String, default: '' }, alt: { type: String, default: '' },
fit: { type: String, default: 'cover' },
ratio: { type: String, default: '' }, // 16-9 / 4-3 / square ratio: { type: String, default: '' }, // 16-9 / 4-3 / square
}) })
</script> </script>
@@ -25,6 +27,7 @@
overflow: hidden; overflow: hidden;
&--16-9 { aspect-ratio: 16 / 9; } &--16-9 { aspect-ratio: 16 / 9; }
&--4-3 { aspect-ratio: 4 / 3; } &--4-3 { aspect-ratio: 4 / 3; }
&--3-4 { aspect-ratio: 3 / 4; }
&--square { aspect-ratio: 1 / 1; } &--square { aspect-ratio: 1 / 1; }
.ds-media__img { .ds-media__img {
@@ -32,6 +35,9 @@
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
display: block; display: block;
&--cover { object-fit:cover; }
&--contain { object-fit:contain; }
} }
} }
</style> </style>

View File

@@ -8,6 +8,7 @@
`ds-text--${props.weight || resolvedWeight}`, //si la classe weight n'est pas donné dans la classe on prend la mapping par défaut `ds-text--${props.weight || resolvedWeight}`, //si la classe weight n'est pas donné dans la classe on prend la mapping par défaut
`ds-text--${props.spacing || resolvedspacing}`, `ds-text--${props.spacing || resolvedspacing}`,
`ds-text--${props.tone}`, `ds-text--${props.tone}`,
`ds-text--${props.align}`,
props.clamp ? `ds-text--clamp_${props.clamp}` : '', props.clamp ? `ds-text--clamp_${props.clamp}` : '',
]" ]"
> >
@@ -25,6 +26,7 @@
spacing: { type: String, default: '' }, spacing: { type: String, default: '' },
tone: { type: String, default: 'default' }, // default/muted/invert tone: { type: String, default: 'default' }, // default/muted/invert
weight: { type: String, default: 'regular' }, // regular/medium weight: { type: String, default: 'regular' }, // regular/medium
align: { type: String, default: '' },
clamp: { type: Number, default: undefined }, // nombre de lignes du contenu clamp: { type: Number, default: undefined }, // nombre de lignes du contenu
}) })
@@ -100,6 +102,7 @@
&--space-20 { margin-bottom: var(--sp-20); } &--space-20 { margin-bottom: var(--sp-20); }
&--space-16 { margin-bottom: var(--sp-16); } &--space-16 { margin-bottom: var(--sp-16); }
&--space-6 { margin-bottom: var(--sp-6); } &--space-6 { margin-bottom: var(--sp-6); }
&--space-0 { margin-bottom: 0px; }
&--default { color: var(--c-text, #111); } &--default { color: var(--c-text, #111); }
&--muted { color: var(--c-text-muted, #555); } &--muted { color: var(--c-text-muted, #555); }
@@ -112,6 +115,10 @@
&--bleu_fonce { color: var(--c-bleu_fonce); } &--bleu_fonce { color: var(--c-bleu_fonce); }
&--bleu_clair { color: var(--c-bleu_clair); } &--bleu_clair { color: var(--c-bleu_clair); }
&--justify {
text-align: justify;
}
// clampé sur 2 lignes pour les cartes (résumé, programme, etc.). // clampé sur 2 lignes pour les cartes (résumé, programme, etc.).
// Si ça dépasse, ça coupe proprement. Pour du texte de description qui doit tenir dans un cadre, mais qui a été écrit trop long, pour ne pas casser le design du site, on va limiter l'affichage à 2 lignes. c'est pour des espace réduit. Pour ne pas casser le design de la page. J'ai presque écrit 2 lignes, je vais bientôt être censurée. // Si ça dépasse, ça coupe proprement. Pour du texte de description qui doit tenir dans un cadre, mais qui a été écrit trop long, pour ne pas casser le design du site, on va limiter l'affichage à 2 lignes. c'est pour des espace réduit. Pour ne pas casser le design de la page. J'ai presque écrit 2 lignes, je vais bientôt être censurée.
&--clamp_3 { &--clamp_3 {

View File

@@ -4,15 +4,18 @@
--c-surface: #ffffff; --c-surface: #ffffff;
--c-text-muted: #555; --c-text-muted: #555;
--c-text-invert: #fff; --c-text-invert: #fff;
--c-text-black-soft: #595959;
/* Marque / accent (ex: rouge ONDIF) */ /* Marque / accent (ex: rouge ONDIF) */
//--c-brand_rouge: #E30613; //--c-brand_rouge: #E30613;
--c-brand_rouge: #E20018; --c-brand_rouge: #E20018;
--c-brand_rouge45: rgba(226, 0, 24, 0.45); --c-brand_rouge45: #e2001873;
--c-brand_rouge-weak: #e3061391; --c-brand_rouge-weak: #e3061391;
--c-backgroud-black: #111; --c-backgroud-black: #111;
--c-backgroud-brandreverse: #ACCFCF; --c-backgroud-brandreverse: #ACCFCF;
--c-background-blanc-casse: #FCFCFC; --c-background-blanc-casse: #FCFCFC;
--c-background-jaune: #fac020;
--c-background-bleu: #002b95;
/* États */ /* États */
--c-success: green; --c-success: green;

View File

@@ -69,8 +69,14 @@
--title-lg2: var(--fs-30); --title-lg2: var(--fs-30);
--title-xl: var(--fs-32); --title-xl: var(--fs-32);
--title-2xl: var(--fs-40); --title-2xl: var(--fs-40);
/* ICONES PUCE LISTE */
--strapi-li-icon: url('/icons/list-bullet.svg');
--strapi-li-icon-nested: url('/icons/list-bullet-nested.svg');
} }
/* Option : ajustements desktop */ /* Option : ajustements desktop */
@media (max-width: 700px) { @media (max-width: 700px) {
:root { :root {

21
ecosystem.config.cjs Normal file
View File

@@ -0,0 +1,21 @@
module.exports = {
apps: [
{
name: "wondif_vue",
cwd: "/var/www/wondif_vue",
script: "node",
args: ".output/server/index.mjs",
env: {
NODE_ENV: "production",
// Auto-mapping Nuxt runtimeConfig.public.strapiUrl
NUXT_PUBLIC_STRAPI_URL: "http://localhost:1337",
// Auto-mapping Nuxt runtimeConfig.strapiToken
// (si tu l'utilises vraiment)
NUXT_STRAPI_TOKEN: "",
NUXT_PUBLIC_SAISON: "2025/2026"
},
},
],
}

View File

@@ -46,13 +46,16 @@ export default defineNuxtConfig({
runtimeConfig: { runtimeConfig: {
// Server-side only (jamais exposé au client) // Server-side only (jamais exposé au client)
strapiToken: process.env.STRAPI_API_TOKEN || '', // ceci strapiToken: process.env.NUXT_STRAPI_TOKEN || '', est remplacé par la ligne ci-dessous car la récup des variable .env est automatique si on les nomme correctement
instagramAppId: process.env.NUXT_INSTAGRAM_APP_ID, strapiToken: '',
instagramClientToken: process.env.NUXT_INSTAGRAM_CLIENT_TOKEN, //instagramAppId: process.env.NUXT_INSTAGRAM_APP_ID,
//instagramClientToken: process.env.NUXT_INSTAGRAM_CLIENT_TOKEN,
// 🌍 Public (accessible dans le navigateur) // 🌍 Public (accessible dans le navigateur)
// lit implicitement la variables .env process.env.NUXT_PUBLIC_STRAPI_URL
public: { public: {
strapiUrl: process.env.NUXT_PUBLIC_STRAPI_URL || 'http://localhost:1337', strapiUrl: '',
saison: '',
}, },
}, },

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="225px" height="386px" viewBox="0 0 225 386" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Path</title>
<g id="Flèche" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="500" transform="translate(-234, -31)" fill="#6D798A" fill-rule="nonzero">
<g id="angle-right" transform="translate(10, -96)">
<path d="M439.1,297.4 C451.6,309.9 451.6,330.2 439.1,342.7 L279.1,502.7 C266.6,515.2 246.3,515.2 233.8,502.7 C221.3,490.2 221.3,469.9 233.8,457.4 L371.2,320 L233.9,182.6 C221.4,170.1 221.4,149.8 233.9,137.3 C246.4,124.8 266.7,124.8 279.2,137.3 L439.2,297.3 L439.1,297.4 Z" id="Path"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 801 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M439.1 297.4C451.6 309.9 451.6 330.2 439.1 342.7L279.1 502.7C266.6 515.2 246.3 515.2 233.8 502.7C221.3 490.2 221.3 469.9 233.8 457.4L371.2 320L233.9 182.6C221.4 170.1 221.4 149.8 233.9 137.3C246.4 124.8 266.7 124.8 279.2 137.3L439.2 297.3z"/></svg>

After

Width:  |  Height:  |  Size: 470 B

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="512px" height="466px" viewBox="0 0 512 466" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Shape</title>
<g id="Flèche" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="house" transform="translate(-64, -64)" fill="#6D798A" fill-rule="nonzero">
<path d="M341.8,72.6 C329.5,61.2 310.5,61.2 298.3,72.6 L74.3,280.6 C64.7,289.6 61.5,303.5 66.3,315.7 C71.1,327.9 82.8,336 96,336 L112,336 L112,499.041234 C112,534.341234 146.941152,529.030576 182.241152,529.030576 L462.664813,529.030576 C497.964813,529.030576 528,534.341234 528,499.041234 L528,336 L544,336 C557.2,336 569,327.9 573.8,315.7 C578.6,303.5 575.4,289.5 565.8,280.6 L341.8,72.6 Z M304,384 L336,384 C362.5,384 384,405.5 384,432 L384,528 L256,528 L256,432 C256,405.5 277.5,384 304,384 Z" id="Shape"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 944 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M341.8 72.6C329.5 61.2 310.5 61.2 298.3 72.6L74.3 280.6C64.7 289.6 61.5 303.5 66.3 315.7C71.1 327.9 82.8 336 96 336L112 336L112 512C112 547.3 140.7 576 176 576L464 576C499.3 576 528 547.3 528 512L528 336L544 336C557.2 336 569 327.9 573.8 315.7C578.6 303.5 575.4 289.5 565.8 280.6L341.8 72.6zM304 384L336 384C362.5 384 384 405.5 384 432L384 528L256 528L256 432C256 405.5 277.5 384 304 384z"/></svg>

After

Width:  |  Height:  |  Size: 619 B

View File

@@ -0,0 +1,24 @@
import { createError, getRouterParam } from "h3"
import { createStrapiProxyHandler } from "~~/server/utils/strapiEndpoint"
const COLLECTION_MAP = {
artistes: "/api/artistes-ondifs",
artistesinvitees: "/api/artistes-invites",
concerts: "/api/concerts",
missions: "/api/mission",
mission: "/api/mission",
}
export default defineEventHandler(async (event) => {
const collection = getRouterParam(event, "collection")
const strapiPath = COLLECTION_MAP[collection]
if (!strapiPath) {
throw createError({
statusCode: 404,
statusMessage: "Unknown Strapi collection",
})
}
return createStrapiProxyHandler({ strapiPath })(event)
})

View File

@@ -0,0 +1,7 @@
import { createStrapiProxyHandler } from "~~/server/utils/strapiEndpoint"
export default defineEventHandler(
createStrapiProxyHandler({
strapiPath: "/api/artistes-ondifs",
})
)

View File

@@ -0,0 +1,7 @@
import { createStrapiProxyHandler } from "~~/server/utils/strapiEndpoint"
export default defineEventHandler(
createStrapiProxyHandler({
strapiPath: "/api/artistes-invites",
})
)

View File

@@ -0,0 +1,9 @@
// server/api/__strapi__/concerts.get.js
import { createStrapiProxyHandler } from "~~/server/utils/strapiEndpoint"
export default defineEventHandler(
createStrapiProxyHandler({
strapiPath: "/api/concerts",
})
)

View File

@@ -0,0 +1,33 @@
import { createError, getQuery } from "h3"
import { strapiFetch } from "@/utils/strapi.js"
import logger from "~~/server/utils/logger"
export function createStrapiProxyHandler({ strapiPath }) {
return async (event) => {
try {
const query = getQuery(event)
const qs = new URLSearchParams(query).toString()
const path = qs ? `${strapiPath}?${qs}` : strapiPath
return await strapiFetch(event, path)
} catch (err) {
logger.error("Strapi request failed", {
label: "back-end",
statusCode: err?.statusCode,
statusMessage: err?.statusMessage,
message: err?.message,
data: err?.data,
url: event?.path,
method: event?.method,
})
throw createError({
statusCode: err?.statusCode || 502,
statusMessage:
err?.statusMessage ||
err?.message ||
"Strapi request failed",
})
}
}
}