This commit is contained in:
2026-02-16 07:59:52 +01:00
parent 1fc267faa8
commit b8b8e53f07
70 changed files with 3088 additions and 272 deletions

View File

@@ -22,12 +22,17 @@
cursor: pointer;
&:hover {
border-bottom: none !important;
.header_nav_topbar_submenu {
visibility: visible;
/* Pour l'effet de transition */
opacity: 1;
}
}
&.is-active {
border-bottom: 2px solid var(--c-brand_rouge);
padding-bottom: 1px;
}
}
.header_nav_topbar_submenu {
position: absolute;
@@ -184,12 +189,19 @@
cursor: pointer;
&:hover {
border-bottom: none !important;
padding-bottom: 2px;
.header_nav_sub_menu {
visibility: visible;
/* Pour l'effet de transition */
opacity: 1;
}
}
&.is-active {
border-bottom: 2px solid var(--c-brand_rouge);
padding-bottom: 1px;
}
list-style: none;
@@ -219,25 +231,36 @@
.header_nav_sub_menu {
position: absolute;
left: 0;
@media (min-width: 0px) {
left: -10px;
}
@media (min-width: 900px) {
left: 0px;
}
z-index: 2;
visibility:hidden;
/* Pour l'effet de transition */
opacity: 0;
transition: visibility 0.2s,opacity 0.2s cubic-bezier(0.4, 0, 1, 1);
padding-top: 10px;
padding-left: 25px;
padding-right: 22px;
padding-left: 20px;
padding-right: 7px;
padding-bottom: 10px;
text-align: left;
background-color: rgba(255, 255, 255, 0.93);
background-color: rgba(255, 255, 255, 0.97);
border-radius: 3px;
.header_nav_sub_menu_item {
list-style: circle;
//font-family: 'brandontext_regular';
font-family: var(--font-roboto);
font-size: 18px;
@media (min-width: 0px) {
font-size: 16px;
}
@media (min-width: 900px) {
font-size: 18px;
}
padding-bottom: 4px;
&:hover {
a {
@@ -411,6 +434,7 @@
.header_drawer_link {
display: block;
width: fit-content;
text-decoration: none;
color: $blanc;
padding: 6px 0;
@@ -426,6 +450,11 @@
opacity: 1;
}
}
&.is-active {
border-bottom: 2px solid var(--c-brand_rouge);
padding-bottom: 1px;
}
list-style: none;
}
.header_drawer_sub_menu {

View File

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

View File

@@ -0,0 +1,117 @@
<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" :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 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' }]
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)))
crumbs.push({ to: acc, label })
})
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: 20px;
}
@media (min-width: 600px) {
padding-left: 20px;
}
@media (min-width: 700px) {
padding-left: 20px;
}
}
.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;
}
@media (min-width: 300px) {
max-width: 290px;
max-width: 250px;
}
@media (min-width: 400px) {
max-width: 390px;

View File

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

View File

@@ -1,30 +1,47 @@
<!-- app/components/concert/ConcertCardList.vue -->
<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 />
</div>
</template>
<script setup>
defineProps({
highlightFirst: { type: Boolean, default: true },
limitCardsOnBreakpoint: { type: Boolean, default: true },
})
</script>
<style lang="scss">
.concert-card-list {
display: flex;
flex-wrap: wrap;
gap: var(--gap-cards);
justify-content: center;
.concert-card {
max-width: 452px;
}
}
// Afficher seulement 1 cards < 600px
@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;
}
.concert-card-list > .concert-card:nth-child(3) {
.concert-card-list.concert-card-list--limit-cards > .concert-card:nth-child(3) {
display: none;
}
}
// Afficher seulement 2 cards < 900px
@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;
}
}
@@ -40,7 +57,7 @@
.concert-card-list > .concert-card {
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;
}
}
@@ -48,7 +65,7 @@
.concert-card-list > .concert-card {
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;
}
}
@@ -56,7 +73,7 @@
.concert-card-list > .concert-card {
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;
}
}
@@ -65,7 +82,7 @@
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;
}
}
@@ -75,7 +92,7 @@
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;
}
}
@@ -85,7 +102,7 @@
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;
}
}
@@ -95,7 +112,7 @@
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;
}
}
@@ -105,7 +122,7 @@
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;
}
}
@@ -116,7 +133,7 @@
flex: 1 1 360px;
}
//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;
}
}

View File

@@ -10,7 +10,9 @@
<template>
<HeaderNav burger-color="hamburger_black">
<template #logo>
<NuxtImg :src="logoDefault" :alt="brand.logoAlt" class="logo-img" />
<NuxtLink to="/" aria-label="Accueil">
<NuxtImg :src="logoDefault" :alt="brand.logoAlt" class="logo-img" />
</NuxtLink>
</template>
<template #agenda-icon>
@@ -44,4 +46,4 @@
}
</style>
</style>

View File

@@ -3,15 +3,15 @@
<div class="height_10"></div>
<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
<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="/">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="/">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="/">Presse</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="/professionnels/studio">Le studio et les espaces</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="/professionnels/recrutement">Recrutement / Concours</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="/professionnels/presse">Presse</NuxtLink></li>
</ul>
</li>
<li class="header_nav_topbar_item header_nav_lang">
@@ -32,55 +32,56 @@
<nav class="header_nav_cont" aria-label="Primary navigation">
<!-- Desktop nav -->
<ul class="header_nav header_nav--desktop">
<li class="header_nav_item">
<li class="header_nav_item" :class="{ 'is-active': isOrchestre }">
L'Orchestre
<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="/">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="/">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="/">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="/orchestre/missions">Nos missions</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="/orchestre/musiciens">Les musiciens</NuxtLink></li>
<li class="header_nav_sub_menu_item"><NuxtLink to="/orchestre/artistes-invitees">Les artistes invités</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="/orchestre/partenaires">Nos partenaires</NuxtLink></li>
<li class="header_nav_sub_menu_item"><NuxtLink to="/mecenat/soutenir">Nous soutenir</NuxtLink></li>
</ul>
</li>
<li class="header_nav_item">
<li class="header_nav_item" :class="{ 'is-active': isConcerts }">
Concerts
<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="/">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="/">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/saison">Saison</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="/concerts/mode-emploi">Concert mode d'emploi</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="/concerts/live">ONDIF LIVE !</NuxtLink></li>
</ul>
</li>
<li class="header_nav_item">
<li class="header_nav_item" :class="{ 'is-active': isMediation }">
Éducation et médiation
<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="/">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="/">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="/">Ressources pédagogiques</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="/mediation/scolaires">Scolaires</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="/mediation/insertion-pro">Insertion professionnelle</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="/mediation/ressources-pedagogiques">Ressources pédagogiques</NuxtLink></li>
</ul>
</li>
<li class="header_nav_item">
<li class="header_nav_item" :class="{ 'is-active': isMecenat }">
Mécénat
<ul class="header_nav_sub_menu">
<li class="header_nav_sub_menu_item"><NuxtLink to="/">Entreprises</NuxtLink></li>
<li class="header_nav_sub_menu_item"><NuxtLink to="/">Les projets</NuxtLink></li>
<li class="header_nav_sub_menu_item"><NuxtLink to="/">Particuliers</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/soutenir">Nous soutenir</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="/mecenat/projets">Les projets</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>
</li>
<li class="header_nav_item header_nav_icones">
<div class="">
<NuxtLink to="/agenda">
<NuxtLink to="/concerts/agenda">
<div class="nav_icone">
<div class="nav_icone_img nav_icone_img--agenda">
<!-- ICÔNE injectée -->
@@ -91,7 +92,7 @@
</NuxtLink>
</div>
<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_img nav_icone_img--ticket">
<!-- ICÔNE injectée -->
@@ -124,7 +125,7 @@
<!-- Mobile icons -->
<div class="header_nav header_nav--mobile-icons">
<div class="header_nav_item">
<NuxtLink to="/agenda">
<NuxtLink to="/concerts/agenda">
<div class="nav_icone">
<div class="nav_icone_img nav_icone_img--agenda">
<!-- ICÔNE injectée -->
@@ -136,7 +137,7 @@
</div>
<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_img nav_icone_img--ticket">
<!-- ICÔNE injectée -->
@@ -157,33 +158,33 @@
<ul class="header_drawer_inner">
<li
class="header_drawer_link"
:class="{ 'is-open': activeDrawer === 'orchestre' }"
:class="{ 'is-open': activeDrawer === 'orchestre','is-active': isOrchestre }"
@click="toggleDrawer('orchestre')"
>
L'Orchestre
<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="/">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="/">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="/">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="/orchestre/missions">Nos missions</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="/orchestre/musiciens">Les musiciens</NuxtLink></li>
<li class="header_drawer_sub_menu_item"><NuxtLink to="/orchestre/artistes-invitees">Les artistes invités</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="/orchestre/partenaires">Nos partenaires</NuxtLink></li>
<li class="header_drawer_sub_menu_item"><NuxtLink to="/mecenat/soutenir">Nous soutenir</NuxtLink></li>
</ul>
</li>
<li
class="header_drawer_link"
:class="{ 'is-open': activeDrawer === 'concerts' }"
:class="{ 'is-open': activeDrawer === 'concerts','is-active': isConcerts }"
@click="toggleDrawer('concerts')"
>
Concerts
<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="/">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="/">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/saison">Saison</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="/concerts/mode-emploi">Concert mode d'emploi</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="/concerts/live">ONDIF LIVE !</NuxtLink></li>
</ul>
</li>
@@ -194,12 +195,12 @@
>
Éducation et médiation
<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="/">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="/">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="/">Ressources pédagogiques</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="/mediation/scolaires">Scolaires</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="/mediation/insertion-pro">Insertion professionnelle</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="/mediation/ressources-pedagogiques">Ressources pédagogiques</NuxtLink></li>
</ul>
</li>
@@ -210,20 +211,21 @@
>
Mécénat
<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="/">Les projets</NuxtLink></li>
<li class="header_drawer_sub_menu_item"><NuxtLink to="/">Particuliers</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/soutenir">Nous soutenir</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="/mecenat/projets">Les projets</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>
</li>
<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 -->
<slot name="mobile_agenda_icon" />
</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 -->
<slot name="mobile_ticket" />
</NuxtLink>
@@ -241,12 +243,16 @@
<script setup>
import { ref } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
import { watch } from 'vue'
defineProps({
burgerColor: { type: String, default: 'hamburger_black' }
})
/////////////////////////////////
// MENU MOBILE
/////////////////////////////////
const isOpen = ref(false)
const activeDrawer = ref(null)
const toggle = () => (isOpen.value = !isOpen.value)
@@ -256,11 +262,38 @@
}
// ✅ ferme automatiquement le mobile drawer si on navigue
const route = useRoute()
watch(() => route.fullPath, () => {
close()
activeDrawer.value = null
})
/////////////////////////////////
// MENU ACTIF
/////////////////////////////////
// L'Orchestre
const isOrchestre = computed(() =>
route.path.startsWith('/orchestre')
)
// Concerts
const isConcerts = computed(() =>
route.path.startsWith('/concerts')
)
// Éducation et médiation
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>
<style lang="scss">

View File

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

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,222 @@
<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;
}
h1 {
padding-bottom: 10px;
font-weight: var(--fw-bold);
font-size: 30px;
}
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,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

@@ -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>

883
app/pages/concerts/[id].vue Normal file
View File

@@ -0,0 +1,883 @@
<template>
<div>
<section v-if="pending" aria-busy="true" aria-live="polite">
<p>en cours de chargement...</p>
</section>
<template v-else>
<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>
<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>
<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 class="description_wp">
<StrapiBlocksConvert :blocks="concert?.description_concert" />
</section>
<section class="img-gallery_wp">
<div v-if="imagesConcert.length" 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 class="youtube_wp">
<div v-if="youtubeEmbeds.length" 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 { 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'
import DsButton from '@root/design-system/primitives/DsButton.vue'
const route = useRoute()
//////////////////////////////////////////////////////////////
// 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>
<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: 500px) {
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>

View File

@@ -0,0 +1,71 @@
<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="">
<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="`${c.slug_concert}`"
/>
</ConcertCardList>
</PageSection>
</div>
</template>
<script setup>
import { onMounted } from "vue"
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: "2025/2026",
},
},
},
upcomingOnly: true,
})
onMounted(() => {
if (!concerts.value?.length) {
refresh()
}
})
</script>

View File

@@ -1,24 +0,0 @@
<template>
<div>
<div v-if="toto">
<h1>#{{route.params.id }} / {{ toto.title }}</h1>
<p>{{ toto.body }}</p>
</div>
<div v-else>
<p>Chargement...</p>
</div>
</div>
</template>
<script setup>
const route = useRoute()
const {data: toto} = await useFetch(() => 'https://jsonplaceholder.typicode.com/posts/' + route.params.id, { lazy: true })
useSeoMeta({
title: () => toto.value?.title
})
</script>
<style lang="scss">
</style>

View File

@@ -0,0 +1,4 @@
<script setup>
await navigateTo('/concerts/agenda', { 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,71 @@
<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="">
<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="`${c.slug_concert}`"
/>
</ConcertCardList>
</PageSection>
</div>
</template>
<script setup>
import { onMounted } from "vue"
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: "2025/2026",
},
},
},
upcomingOnly: false,
})
onMounted(() => {
if (!concerts.value?.length) {
refresh()
}
})
</script>

View File

@@ -82,7 +82,7 @@
<ConcertCard
id="1"
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"
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."

View File

@@ -6,7 +6,7 @@
<ConcertCard
id="1"
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"
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."
@@ -39,10 +39,8 @@
const runtimeConfig = useRuntimeConfig()
const STRAPI_URL = runtimeConfig.public.strapiUrl
console.log("STRAPI_URL : ",STRAPI_URL)
// Config app (pour ton SEO)
// Config app
const config = useAppConfig()
useSeoMeta({
title: config.title
@@ -78,10 +76,6 @@
const appConfig = useAppConfig()
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>
<style>

View File

@@ -1,4 +1,5 @@
<template>
<div>
<!-- ================== -->
<!-- Fond noir -->
@@ -15,40 +16,17 @@
<PageSection padded_size="md" content-size="default" class="remonter_concert_list">
<ConcertCardList>
<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"
/>
<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"
v-for="c in concerts"
:key="c.id"
:id="c.slug_concert"
:title="c.titre_concert"
:lieu="c.representation_concert?.[0]?.lieu_representation?.nom_lieu"
:dateISO="c.representation_concert?.[0]?.date_debut_representation"
:dateLabel="formatDateLong(c.representation_concert?.[0]?.date_debut_representation)"
:description="c.resume_concert"
:imageUrl="c.image_illustration_concert?.url"
:imageAlt="c.image_illustration_concert?.alternativeText"
:href="`/concerts/${c.slug_concert}`"
/>
</ConcertCardList>
</PageSection>
@@ -65,7 +43,7 @@
</SectionTitle>
<SectionContent pad="xs" class="theme_ppt--description">
<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>
<DsButtonArrow to="/" variant="invert">
Carte des événements
@@ -161,12 +139,14 @@
<BannierePros />
</SectionContent>
</PageSection>
</div>
</template>
<script setup>
import { onMounted, computed } from 'vue'
import { clientLog } from '~/utils/clientLog'
import { formatDateLong } from "@/utils/dateFormat.js"
import SectionContent from '../components/section/SectionContent.vue'
import DsHeading from '@root/design-system/primitives/DsHeading.vue'
import DsText from '@root/design-system/primitives/DsText.vue'
@@ -181,7 +161,6 @@
const runtimeConfig = useRuntimeConfig()
const STRAPI_URL = runtimeConfig.public.strapiUrl
console.log("STRAPI_URL : ",STRAPI_URL)
// Config app (pour SEO)
const config = useAppConfig()
@@ -189,38 +168,34 @@
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 appConfig = useAppConfig()
console.log("Bienvenue : ",appConfig.title)
const imageUrl = computed(() => {
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: "2025/2026",
},
},
},
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(() => {
clientLog('info', 'test de log depuis vuejs', { })
clientLog('info', `STRAPI_URL : ${STRAPI_URL}`, { strapiUrl: STRAPI_URL })
if (!concerts.value?.length) {
refresh()
}
})
//--------------------

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,13 @@
<template>
<div>
</div>
</template>
<script lang="ts" setup>
</script>
<style>
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div>
Page en construction direction
</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('/orchestre/missions', { redirectCode: 301 })
</script>

View File

@@ -0,0 +1,13 @@
<template>
<div>
Page en construction Missions
</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

@@ -14,7 +14,6 @@
const runtimeConfig = useRuntimeConfig()
//const STRAPI_URL = "http://localhost:1337"
const STRAPI_URL = runtimeConfig.public.strapiUrl
console.log("STRAPI_URL : ",STRAPI_URL)
// Config app (pour SEO)
const config = useAppConfig()
@@ -51,11 +50,7 @@
}
const appConfig = useAppConfig()
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 })
})
console.log("Bienvenue : ",appConfig.title)
</script>
<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
})
const appConfig = useAppConfig()
console.log("test 3 : ",appConfig.title) // "Mon site Nuxt"
console.log("test 3 : ",appConfig.title)
</script>
<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 || {}),
},
})
}