Compare commits

...

2 Commits

Author SHA1 Message Date
e4da6f462a readme 2026-02-16 08:01:19 +01:00
b8b8e53f07 Concerts 2026-02-16 07:59:52 +01:00
70 changed files with 3090 additions and 274 deletions

833
README.md
View File

@@ -1,5 +1,6 @@
# Stack
- NUXT 4
- VueJS 3.5
# Dev du site en local
# NUXT
@@ -18,69 +19,787 @@ Créer un vrai /security.txt (ou /.well-known/security.txt) au lieu de le renvoy
# Fichier standard optionnel (si tu ne le fournis pas)
location = /security.txt { return 404; }
# METTRE EN LIGNE LE DEV AVEC GITEA
## Du dev vers Gitea
git add .
git commit -m "Mon message de commit"
git push origin main
# Dev
## MEDIAS QUERIES
### Avec VCS
1. Onglet **Source Control**
2. Vérifie la liste **Changes**
3. Clique sur + (stage)
- fichier par fichier **ou**
- + à côté de *Changes* (tout)
4. Renseigne un **message de commit**
5. Clique sur ✔️ **Commit**
6. clique sur **Sync Changes**
@media (max-width: 599px) {
. {
xxx
}
}
@media (min-width: 600px) {
. {
xxx
}
}
@media (min-width: 700px) {
. {
xxx
}
}
@media (min-width: 800px) {
. {
xxx
}
}
@media (min-width: 900px) {
. {
xxx
}
}
## Du Gitea vers VPS
cd /var/www/wondif_vue
@media (min-width: 1000px) {
. {
xxx
}
}
git pull origin main # récupère le dernier code
npm ci # installe / met à jour les deps (propre)
npm run build # rebuild Nuxt
pm2 restart wondif_vue # redémarre le process
@media (min-width: 1100px) {
. {
xxx
}
}
# STRAPI
## URL de PROD
bo.orchestre-ile.com
@media (min-width: 1200px) {
. {
xxx
}
}
## après modifications des fichiers src/... on rebuild strapiwww
cd strapi_wondif/
NODE_ENV=production npm run build
pm2 list
pm2 restart strapi_wondif
pm2 list
@media (min-width: 1300px) {
. {
xxx
}
}
@media (min-width: 1400px) {
. {
xxx
}
}
padded: { type: Boolean, default: true }, // padding vertical
## dev en local
- lancer MAMP pour activer la BD de STRAPI
- lancer : npm run develop
- URL de dev : http://localhost:1337
&--padded {
padding-top: 30px;
padding-bottom: 50px;
}
### vérifier le json de l'enpoint strapi
http://localhost:3000/api/__strapi__/concerts
http://localhost:3000/api/__strapi__/concerts?populate=*
http://localhost:3000/api/__strapi__/concerts?populate[artistes_ondif_concert][populate]=postes_artiste_ondif
http://localhost:3000/api/__strapi__/concerts?populate=*&populate[artistes_ondif_concert][populate]=postes_artiste_ondif
http://localhost:3000/api/__strapi__/concerts?populate[saison_concert]=true&populate[genre_concert]=true&populate[type_audience_concert]=true&populate[direction_ondif_concert][populate]=postes_artiste_ondif&populate[direction_invite_concert][populate]=postes_artiste_invite&populate[artistes_ondif_concert][populate]=postes_artiste_ondif&populate[artistes_invite_concert][populate]=postes_artiste_invite
API pour avoir tout de Concerts
http://localhost:3000/api/__strapi__/concerts?populate[saison_concert]=true&populate[genre_concert]=true&populate[type_audience_concert]=true&populate[direction_ondif_concert][populate]=postes_artiste_ondif&populate[direction_invite_concert][populate]=postes_artiste_invite&populate[artistes_ondif_concert][populate]=postes_artiste_ondif&populate[artistes_invite_concert][populate]=postes_artiste_invite&populate[image_illustration_concert]=true&populate[images_concert]=true&populate[videos_concert]=true&populate[audios_concert]=true&populate[programme_concert]=true&populate[representation_concert][populate]=lieu_representation&populate[liens_youtube_concert]=true
/api/__strapi__/concerts?
populate[saison_concert]=true&
populate[genre_concert]=true&
populate[type_audience_concert]=true&
populate[direction_ondif_concert][populate]=postes_artiste_ondif&
populate[direction_invite_concert][populate]=postes_artiste_invite&
populate[artistes_ondif_concert][populate]=postes_artiste_ondif&
populate[artistes_invite_concert][populate]=postes_artiste_invite&
populate[image_illustration_concert]=true&
populate[images_concert]=true&
populate[videos_concert]=true&
populate[audios_concert]=true&
populate[programme_concert]=true&
populate[representation_concert][populate]=lieu_representation&
populate[liens_youtube_concert]=true
| Niveau 1 | Niveau 2 | Niveau 3 |
|----------|----------|----------|
| saison_concert| | |
| genre_concert| | |
| type_audience_concert| | |
| direction_ondif_concert| postes_artiste_ondif| |
| direction_invite_concert|postes_artiste_invite| |
| artistes_ondif_concert| postes_artiste_ondif| |
| artistes_invite_concert| postes_artiste_invite| |
| A| B| C|
| A| B| C|
| A| B| C|
| A| B| C|
| A| B| C|
| A| B| C|
| A| B| C|
# CSS
## LAYOUT
@media (min-width: 0px) {
max-width: 100%;
}
@media (min-width: 600px) {
max-width: 580px;
}
@media (min-width: 700px) {
max-width: 660px;
}
@media (min-width: 800px) {
max-width: 780px;
}
@media (min-width: 900px) {
max-width: 860px;
}
@media (min-width: 1000px) {
max-width: 950px;
}
@media (min-width: 1100px) {
max-width: 1020px;
}
@media (min-width: 1200px) {
max-width: 1100px;
}
@media (min-width: 1300px) {
max-width: 1200px;
}
@media (min-width: 1400px) {
max-width: 1300px;
}
@media (min-width: 1500px) {
max-width: 1400px;
}
## débordement = les identifier visuellement
code javascript, à coller dans la partie "console" du navigateur, pour mettre cette ligne outile rouge et le backgroud à tous les éléments de body pour voir les débordements
Array.from(document.querySelectorAll('body > *')).forEach(el => {
el.style.outline = '1px solid red';
el.style.backgroundColor = 'rgba(255, 0, 0, 0.3)'; // Utilisation de rgba pour une semi-transparence
});
1 sudo nano ~/.bashrc
2 exit
3 ll
4 date
5 sudo ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime
6 date
7 cd /
8 sudo apt update
9 apt list --upgradable
10 sudo apt upgrade
11 apt list --upgradable
12 sudo nano /etc/ssh/sshd_config
13 sudo systemctl restart sshd
14 exit
15 sudo apt install fail2ban
16 cd /etc/fail2ban/
17 ll
18 sudo nano /etc/fail2ban/jail.conf
19 sudo service fail2ban reload
20 sudo service fail2ban status
21 sudo service fail2ban start
22 sudo service fail2ban status
23 fail2ban-client status
24 ll
25 www
26 cd /
27 ll
28 www
29 cd var
30 ll
31 date
32 cd /
33 sudo apt update
34 apt list --upgradable
35 sudo apt upgrade
36 cd /etc/fail2ban/
37 sudo service fail2ban status
38 systemctl status nginx
39 sudo apt install nginx
40 systemctl status nginx
41 sudo systemctl enable nginx
42 sudo systemctl is-enabled nginx
43 sudo nano /etc/nginx/nginx.conf
44 sudo systemctl reload nginx
45 systemctl status nginx
46 www
47 ll
48 cd ..
49 ll
50 cd www
51 ll
52 cd html/
53 ll
54 cd www
55 ll
56 www
57 ll
58 sudo mkdir wondif_2025
59 ll
60 cd wondif_2025/
61 sudo nano index.html
62 ll
63 more index.html
64 cd /etc/nginx/sites-available
65 ll
66 de default
67 more default
68 sudo nano wondif_2025
69 ll
70 more wondif_2025
71 cd /etc/nginx/sites-enabled
72 ll
73 sudo ln -s /etc/nginx/sites-available/wondif_2025 /etc/nginx/sites-enabled/
74 ll
75 sudo nginx -t
76 systemctl restart nginx
77 systemctl status nginx
78 www
79 ll
80 cd wondif_2025/
81 ll
82 pwd
83 cd /etc/nginx/sites-available
84 ll
85 more wondif_2025
86 www
87 ll
88 cd wondif_2025/
89 ll
90 sudo nano index.html
91 www
92 curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
93 exit
94 python3 --version
95 sudo apt install certbot python3-certbot-nginx
96 cd /etc/nginx/sites-available
97 ll
98 sudo nano strapi_wondif
99 cd /etc/nginx/sites-enabled
100 ll
101 sudo ln -s /etc/nginx/sites-available/strapi_wondif /etc/nginx/sites-enabled/
102 ll
103 sudo nginx -t
104 systemctl restart nginx
105 systemctl status nginx
106 cd /etc/nginx/sites-available
107 sudo certbot --nginx -d 2025.orchestre-ile.com
108 ll
109 more wondif_2025
110 sudo certbot --nginx -d bo.orchestre-ile.com
111 ll
112 more strapi_wondif
113 sudo certbot certificates
114 sudo nginx -t
115 systemctl restart nginx
116 systemctl status nginx
117 ll
118 sudo nano wondif_2025
119 systemctl restart nginx
120 systemctl status nginx
121 www
122 nvm --version
123 nvm ls-remote
124 nvm install 22.18.0
125 node -v
126 corepack enable yarn
127 yarn -v
128 www
129 yarn global add pm2
130 pm2 start
131 pm2 list
132 cd /
133 pm2 list
134 yarn global bin
135 more ~/.bashrc
136 sudo nano ~/.bashrc
137 nano ~/.bashrc
138 source ~/.bashrc
139 pm2 start
140 pm2 list
141 mysql -u root -p
142 cd /
143 ll
144 sudo apt install gnupg
145 cd /home/debian
146 wget https://dev.mysql.com/get/mysql-apt-config_0.8.34-1_all.deb // version du 04/08/2025
147 ll
148 sudo dpkg -i mysql-apt-config*
149 apt update
150 sudo apt update --allow-insecure-repositories
151 sudo apt-get upgrade
152 sudo apt update
153 sudo apt install mysql-server
154 sudo systemctl status mysql
155 sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
156 sudo systemctl restart mysql
157 sudo systemctl status mysql
158 mysql -u root -p
159 sav
160 ll
161 sudo nano strapi_wondif
162 sudo nginx -t
163 sudo nano strapi_wondif
164 www
165 ll
166 cd strapi_wondif/
167 ll
168 rm strapi_wondif.js
169 ll
170 sav
171 ll
172 sudo nginx -t
173 more strapi_wondif
174 sudo nano strapi_wondif
175 sudo nginx -t
176 sudo nano strapi_wondif
177 systemctl restart nginx
178 systemctl status nginx
179 www
180 ll
181 cd strapi_wondif/
182 ll
183 more .env
184 www
185 ll
186 cd /var
187 ll
188 chown debian www
189 ll
190 sudo chown debian www
191 wwww
192 www
193 ll
194 yarn create strapi
195 ll
196 cd strapi_wondif/
197 ll
198 sudo nano strapi_wondif.js
199 yarn strapi_wondif start
200 ll
201 more package.json
202 NODE_OPTIONS="--max-old-space-size=2048" NODE_ENV=production yarn build
203 yarn run start
204 ll
205 pm2 start yarn --name strapi_wondif -- start
206 pm2 logs strapi_wondif
207 pm2 list
208 pm2 logs strapi_wondif
209 pm2 list
210 pm2 save
211 pm2 startup systemd
212 sudo env PATH=$PATH:/home/debian/.nvm/versions/node/v22.18.0/bin /home/debian/.config/yarn/global/node_modules/pm2/bin/pm2 startup systemd -u debian --hp /home/debian
213 pm2 list
214 ll
215 date
216 cd /
217 sudo apt update
218 sudo apt list --upgradable
219 sudo apt upgrade
220 sudo apt update
221 sudo iptables -L
222 sudo apt install fail2ban
223 cd /etc/fail2ban/
224 ls
225 ll
226 cd /var/log/fail2ban.log
227 cd /var/log/
228 ll
229 more fail2ban.log
230 fail2ban-client status
231 sudo fail2ban-client status
232 sudo apt-get install net-tools
233 netstat -a
234 ls
235 cd /
236 ls
237 cd var
238 ls
239 ll
240 cd www
241 ll
242 sav
243 ll
244 more wondif_2025
245 ll
246 sudo nano wondif_media
247 more wondif_media
248 sudo nano wondif_media
249 more wondif_media
250 cd /etc/nginx/sites-enabled
251 sudo ln -s /etc/nginx/sites-available/wondif_media /etc/nginx/sites-enabled/
252 ll
253 sudo certbot --nginx -d media.orchestre-ile.com
254 sav
255 ll
256 more wondif_media
257 sudo nginx -t
258 systemctl restart nginx
259 systemctl status nginx
260 www
261 ll
262 pm2
263 pm2 list
264 ls
265 cd strapi_wondif/
266 ll
267 pwd
268 npm install @strapi/provider-upload-aws-s3 --save
269 ls
270 ll
271 cd config/
272 ll
273 more plugins.js
274 sudo nano plugins.js
275 ll
276 cd ;;
277 cd..
278 ll
279 cd ..
280 ll
281 sudo nano .env
282 npm run build
283 npm run start
284 ll
285 pm2 list
286 pm2 restart strapi_wondif
287 ll
288 cd config/
289 ll
290 more plugins.js
291 sudo nano plugins.js
292 pm2 restart strapi_wondif
293 sudo nano plugins.js
294 sav
295 ll
296 ls
297 sudo nano strapi_wondif
298 sudo nano wondif_media
299 sudo nginx -t
300 sudo systemctl reload nginx
301 pm2 restart strapi_wondif
302 www
303 ll
304 cd strapi_wondif/
305 ll
306 cd config/
307 ll
308 sudo nano middlewares.js
309 pm2 restart strapi_wondif
310 pm2 stop strapi_wondif
311 npm run build
312 pm2 start strapi_wondif
313 www
314 ll
315 cd wondif_2025/
316 pwd
317 ll
318 sav
319 ll
320 more wondif_2025
321 www
322 ll
323 cd wondif_2025/
324 ll
325 cd ..
326 ll
327 mv wondif_2025 wondif_2025_old
328 ll
329 git clone git@git.parisweb.art:gitea_admin/wondif_vue.git
330 sudo apt update
331 sudo apt install git -y
332 git --version
333 git clone git@git.parisweb.art:gitea_admin/wondif_vue.git
334 git clone https://git.parisweb.art/gitea_admin/wondif_vue
335 ll
336 cd wondif_vue/
337 ll
338 sav
339 ll
340 sudo nano wondif_2025
341 sudo nginx -t
342 sudo systemctl reload nginx
343 www
344 ll
345 cd wondif_vue/
346 ll
347 sudo nano .env
348 npm ci
349 npm run build
350 ll
351 pm2 list
352 pm2 start npm --name "wondif_vue" -- run start
353 pm2 save
354 pm2 logs wondif_vue
355 curl http://127.0.0.1:3000
356 sav
357 ll
358 sudo nano wondif_2025
359 www
360 ll
361 cd wondif_vue/
362 ll
363 wondif_vue/
364 cat package.json
365 sudo nano package.json
366 cat package.json
367 npm run build
368 pm2 list
369 pm2 stop wondif_vue
370 pm2 start wondif_vue
371 pm2 logs wondif_vue
372 curl http://127.0.0.1:3000
373 sav
374 ll
375 more wondif_2025
376 sudo nano wondif_2025
377 sudo nginx -t
378 sudo systemctl reload nginx
379 more wondif_2025
380 www
381 ll
382 cd wondif_vue/
383 ll
384 cd public/
385 ll
386 cd img/
387 ll
388 cd photos/
389 ll
390 mv zaho_2.jpg zaho.jpg
391 ll
392 cd /var/www/wondif_vue
393 ll
394 npm ci
395 npm run build
396 pm2 restart wondif_vue
397 git pull origin main
398 rm public/img/photos/zaho.jpg
399 git pull origin main
400 npm ci
401 npm run build
402 ll
403 more .env
404 pm2 list
405 pm2 logs wondif_vue
406 cd /home/debian/.pm2/logs/
407 ll
408 more strapi-wondif-error.log
409 ll
410 truncate -s 0 strapi-wondif-error.log
411 ll
412 more strapi-wondif-out.log
413 ll
414 truncate -s 0 strapi-wondif-out.log
415 ll
416 more wondif-vue-error.log
417 ll
418 truncate -s 0 wondif-vue-error.log
419 more wondif-vue-out.log
420 ll
421 truncate -s 0 wondif-vue-out.log
422 www
423 ll
424 cd wondif_vue/
425 ll
426 cd app
427 ll
428 cd components/
429 ll
430 more header_content.vue
431 ll
432 more header_full.vue
433 ll
434 cd ..
435 ll
436 more app.vue
437 ll
438 cd pages/
439 ll
440 more index.vue
441 ll
442 more agenda.vue
443 www
444 ll
445 cd wondif_vue/
446 ll
447 cd app/
448 ll
449 cd pages/
450 ll
451 more index.vue
452 cd ..
453 ll
454 cd components/
455 ll
456 more header_full.vue
457 ll
458 more header_content.vue
459 www
460 ll
461 cd wondif_vue/
462 ll
463 sav
464 ll
465 more wondif_2025
466 sudo adduser --disabled-password --gecos "" deploy
467 sudo usermod -aG www-data deploy
468 cd /
469 ll
470 cd srv
471 ll
472 sudo ll
473 sudo ls
474 ls
475 ll
476 cd 2025
477 sudo mkdir -p /srv/apps/wondif_vue
478 ll
479 sudo chown -R deploy:deploy /srv/apps/wondif_vue
480 ll
481 sudo -u deploy ssh-keygen -t ed25519 -C "deploy@server" -f /home/deploy/.ssh/id_ed25519
482 sudo -u deploy cat /home/deploy/.ssh/id_ed25519.pub
483 sudo -u deploy ssh -T git@git.parisweb.art
484 sudo -u deploy ssh -T gitea_admin@git.parisweb.art
485 sudo -u deploy ssh -T git@git.parisweb.art
486 sudo -u deploy ssh -T git@git.parisweb.art; echo "EXIT=$?"
487 cd /home
488 ll
489 cd deploy/
490 ll
491 sudo cd deploy/
492 sudo -u deploy rm -f /home/deploy/.ssh/id_ed25519
493 sudo -u deploy rm -f /home/deploy/.ssh/id_ed25519.pub
494 sudo -u deploy rm -f /home/deploy/.ssh/known_hosts
495 sudo -u deploy ls -la /home/deploy/.ssh
496 sudo -u deploy ls -la /home/deploy/
497 sudo -u deploy ssh-keygen -t ed25519 -C "deploy@bookstore" -f /home/deploy/.ssh/id_ed25519
498 sudo -u deploy cat /home/deploy/.ssh/id_ed25519.pub
499 sudo -u deploy rm -f /home/deploy/.ssh/id_ed25519
500 sudo -u deploy rm -f /home/deploy/.ssh/id_ed25519.pub
501 sudo -u deploy rm -f /home/deploy/.ssh/known_hosts
502 sudo -u deploy ls -la /home/deploy/.ssh
503 sudo -u deploy ssh-keygen -t ed25519 -C "deploy@wondif_vue" -f /home/deploy/.ssh/id_ed25519
504 sudo -u deploy cat /home/deploy/.ssh/id_ed25519.pub
505 sudo -u deploy ssh -T git@git.parisweb.art
506 sudo ss -lntp | grep -E ':22|:2222|:3000|:3022|:2200'
507 sudo -u deploy rm -f /home/deploy/.ssh/id_ed25519
508 sudo -u deploy rm -f /home/deploy/.ssh/id_ed25519.pub
509 sudo -u deploy rm -f /home/deploy/.ssh/known_hosts
510 sudo -u deploy ls -la /home/deploy/.ssh
511 sudo -u deploy nano /home/deploy/.netrc
512 sudo -u deploy more /home/deploy/.netr
513 sudo chmod 600 /home/deploy/.netrc
514 sudo chown deploy:deploy /home/deploy/.netrc
515 sudo -u deploy git ls-remote https://git.parisweb.art/gitea_admin/wondif_vue
516 cd /srv
517 ll
518 cd apps/
519 ll
520 cd wondif_vue/
521 ll
522 sudo -u deploy git clone https://git.parisweb.art/gitea_admin/wondif_vue /srv/apps/wondif_vue
523 ll
524 more .git
525 more README.md
526 cd /opt
527 ll
528 cd /var
529 ll
530 cd log/
531 ll
532 sudo mkdir -p /opt/deploy/wondif_vue /var/log/deploy
533 ll
534 sudo chown -R deploy:deploy /opt/deploy/wondif_vue /var/log/deploy
535 sudo -u deploy nano /opt/deploy/wondif_vue/deploy.sh
536 sudo chmod +x /opt/deploy/wondif_vue/deploy.sh
537 cd /opt/
538 ll
539 cd deploy/
540 ll
541 cd wondif_vue/
542 ll
543 sudo -u nano deploy.sh
544 sudo -u deploy nano deploy.sh
545 pm2 list
546 cd www
547 ll
548 www
549 ll
550 cd wondif_vue/
551 ll
552 more package.json
553 cd ..
554 ll
555 cd wondif_vue/
556 ll
557 sudo chmod +x /opt/deploy/wondif_vue/deploy.sh
558 www
559 ll
560 cd wondif_vue/
561 ll
562 git pull origin main
563 more package.json
564 more .gitignore
565 sudo nano .gitignore
566 more package.json
567 git pull origin main
568 git fetch origin
569 git reset --hard origin/main
570 git status
571 git pull origin main
572 npm ci
573 npm run build
574 ll
575 pm2 list
576 pm2 restart wondif_vue
577 pm2 describe wondif_vue
578 pm2 list
579 cd /home/debian/.pm2/logs/
580 ll
581 more wondif-vue-error.log
582 pm2 logs wondif_vue --lines 200
583 sav
584 ll
585 sudo nano wondif_2025
586 sudo nginx -t
587 sudo systemctl reload nginx
588 pm2 flush
589 pm2 list
590 pm2 logs wondif_vue --err --lines 80
591 pm2 reset wondif_vue
592 pm2 list
593 pm2 describe wondif_vue
594 pm2 logs wondif_vue
595 www
596 ll
597 cd wondif_vue
598 ll
599 git status
600 git pull origni main
601 git pull origin main
602 npm ci
603 npm run build
604 pm2 list
605 pm2 restart wondif_vue
606 git pull origin main
607 npm ci
608 git pull origin main
609 npm run build
610 pm2 restart wondif_vue
611 npx strapi version
612 ll
613 cd ..
614 ll
615 cd strapi_wondif/
616 ll
617 more README.md
618 npx strapi version
619 pm2 list
620 pm2 stop strapi_wondif
621 npx @strapi/upgrade latest
622 npx strapi version
623 NODE_ENV=production npm run build
624 pm2 list
625 pm2 start strapi_wondif
626 pm2 list
627 mysql --version
628 mysql -u root -p
629 pm2 list
630 www
631 ll
632 cd strapi_wondif/
633 ll
634 NODE_ENV=production npm run build
635 pm2 list
636 pm2 restart strapi_wondif
637 pm2 list
638 cd strapi_wondif/
639 www
640 cd strapi_wondif/
641 NODE_ENV=production npm run build
642 pm2 list
643 pm2 restart strapi_wondif
644 pm2 list
645 date
646 mysql -u root -p
647 SHOW VARIABLES LIKE '%time_zone%';
648 SHOW VARIABLES LIKE '%time_zone%'; SELECT @@global.time_zone, @@session.time_zone;
649 SELECT NOW() AS now_local, UTC_TIMESTAMP() AS now_utc, TIMEDIFF(NOW(), UTC_TIMESTAMP()) AS diff;
650 show databeses
651 show databeses;
652 history

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>

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 || {}),
},
})
}

View File

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

View File

@@ -25,6 +25,7 @@
overflow: hidden;
&--16-9 { aspect-ratio: 16 / 9; }
&--4-3 { aspect-ratio: 4 / 3; }
&--3-4 { aspect-ratio: 3 / 4; }
&--square { aspect-ratio: 1 / 1; }
.ds-media__img {

View File

@@ -100,6 +100,7 @@
&--space-20 { margin-bottom: var(--sp-20); }
&--space-16 { margin-bottom: var(--sp-16); }
&--space-6 { margin-bottom: var(--sp-6); }
&--space-0 { margin-bottom: 0px; }
&--default { color: var(--c-text, #111); }
&--muted { color: var(--c-text-muted, #555); }

View File

@@ -4,6 +4,7 @@
--c-surface: #ffffff;
--c-text-muted: #555;
--c-text-invert: #fff;
--c-text-black-soft: #595959;
/* Marque / accent (ex: rouge ONDIF) */
//--c-brand_rouge: #E30613;

View File

@@ -69,8 +69,14 @@
--title-lg2: var(--fs-30);
--title-xl: var(--fs-32);
--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 */
@media (max-width: 700px) {
:root {

View File

@@ -47,8 +47,8 @@ export default defineNuxtConfig({
runtimeConfig: {
// Server-side only (jamais exposé au client)
strapiToken: process.env.STRAPI_API_TOKEN || '',
instagramAppId: process.env.NUXT_INSTAGRAM_APP_ID,
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: {

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,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",
})
}
}
}