{% extends 'base.html.twig' %}
{% block title %}{{ title ?? 'Accueil' }}{% endblock %}
{% block meta_description %}ACOA — Conseil & Formation.{% endblock %}
{% set canEdit = is_granted('ROLE_MANAGER') %}
{% block stylesheets %}
{{ parent() }}
<link rel="stylesheet" href="{{ asset('assets/home.css') }}">
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script defer src="{{ asset('assets/home.js') }}"></script>
{% endblock %}
{% block content %}
{% if not canEdit %}
<style>
#cms-devbar,
.cms-edit-fab,
.cms-edit-btn,
.cms-actu-edit,
.cms-kpis-edit,
.catalogue-edit-btn,
#catalogue-edit-modal,
#actu-editor,
#kpis-editor {
display: none !important;
}
</style>
{% endif %}
<style>
.catalogue-grid{
display:grid;
grid-template-columns:1fr 420px;
align-items:center;
gap:56px;
}
.catalogue-art{
position:relative;
max-width:420px;
margin-left:auto;
overflow:visible;
}
.catalogue-art::before{
content:"";
position:absolute;
top:-22px;
right:-12px;
width:calc(100% + 12px);
height:calc(100% + 36px);
background:#0A2A45;
border-radius:14px;
z-index:0;
box-shadow:0 12px 26px rgba(7,51,73,.22);
}
.cat-card{
position:relative;
z-index:1;
left:-80px;
top:50px;
width:calc(100% + 28px);
border-radius:14px;
overflow:hidden;
}
.cat-card img{
display:block;
width:100%;
height:auto;
border-radius:14px;
}
.cat-veil{
position:absolute;
inset:0;
background:rgba(255,255,255,.42);
backdrop-filter:blur(1px);
-webkit-backdrop-filter:blur(1px);
border-radius:14px;
z-index:2;
pointer-events:none;
}
.cat-overlay{
position:absolute;
inset:0;
border-radius:14px;
z-index:3;
pointer-events:none;
}
.cat-vrail{ position:absolute; top:16px; left:18px; display:flex; gap:12px; }
.cat-vtitle{
writing-mode:vertical-rl; transform:rotate(180deg);
font-family: 'Inter', system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
font-weight:800; letter-spacing:.02em;
font-size:clamp(24px,2.6vw,44px);
color:#143F63;
}
.cat-vsubtitle{
writing-mode:vertical-rl; transform:rotate(180deg);
font-family: 'Inter', system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
font-weight:700; letter-spacing:.02em;
font-size:clamp(12px,1.4vw,22px);
color:#F14816;
}
.cat-year{
position:absolute;
left:22px; bottom:78px;
font-family: 'Inter', system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
font-weight:800;
font-size:clamp(22px,2.4vw,38px);
color:#143F63;
}
.cat-badge{
position:absolute;
left:50%; bottom:14px; transform:translateX(-50%);
width:84%;
padding:12px 18px;
border-radius:9999px;
background:#F14816;
color:#fff;
font-family: 'Inter', system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
font-weight:800;
text-transform:uppercase;
text-align:center;
line-height:1.25;
font-size:clamp(10px,1.1vw,13px);
box-shadow:0 6px 16px rgba(241,72,22,.30);
}
@media (max-width:768px){
.catalogue-grid{
grid-template-columns:1fr;
gap:28px;
text-align:center;
}
.catalogue-art{ margin:0 auto; }
.cat-card{ left:-22px; top:6px; width:calc(100% + 22px); }
}
.cat-veil{
inset: 0 4px 0 0;
}
.catalogue-grid{
grid-template-columns: minmax(0, 1fr) minmax(0, 420px);
column-gap: clamp(28px, 5vw, 120px);
}
@media (max-width: 1100px){
.catalogue-grid{
grid-template-columns: 1fr;
gap: 28px;
}
}
section.catalogue .catalogue-copy{
max-width: 640px;
}
section.catalogue .catalogue-copy .sec-kicker{
font-family: 'Inter', system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
color: #F14816;
font-weight: 800;
text-transform: uppercase;
letter-spacing: .08em;
font-size: clamp(12px, 1.1vw, 16px);
margin: 0 0 12px 0;
}
section.catalogue .catalogue-copy .sec-title{
font-weight:800;
line-height:1.25;
letter-spacing:.02em;
text-transform:uppercase;
font-size:clamp(18px,1.05vw,22px);
max-width:520px;
}
section.catalogue .catalogue-copy .sec-text{
color: #536273;
font-size: clamp(14px, 1.15vw, 18px);
line-height: 1.65;
margin: 0 0 20px 0;
max-width: 620px;
}
section.catalogue .catalogue-copy .btn-row{
display: flex;
gap: 16px;
align-items: center;
}
section.catalogue .catalogue-copy .btn.btn-primary{
background: #F14816;
color: #fff;
border: 0;
padding: 12px 22px;
border-radius: 9999px;
font-weight: 700;
font-size: clamp(14px, 1.05vw, 16px);
box-shadow: 0 6px 16px rgba(241,72,22,.25);
}
section.catalogue .catalogue-copy .btn.btn-primary:hover{
filter: brightness(1.05);
}
section.catalogue .catalogue-copy .btn.btn-outline{
background: #fff;
color: #F14816;
border: 1px solid rgba(20,63,99,.10);
padding: 12px 22px;
border-radius: 9999px;
font-weight: 700;
font-size: clamp(14px, 1.05vw, 16px);
box-shadow: 0 6px 18px rgba(17,24,39,.08);
}
section.catalogue .catalogue-copy .btn.btn-outline:hover{
background: #fff7f3;
border-color: rgba(241,72,22,.35);
}
@media (max-width: 768px){
section.catalogue .catalogue-copy{ max-width: 100%; }
section.catalogue .catalogue-copy .btn-row{ flex-wrap: wrap; }
}
section.catalogue .catalogue-copy .sec-title{
color:#F14816;
}
.container{max-width:1180px;margin:0 auto;padding-left:12px;padding-right:12px}
.hero-title{
font-family:'Inter',system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;
font-weight:700; line-height:1.05; letter-spacing:.002em;
font-size:clamp(28px,3.6vw,46px);
color:#fff; margin:10px 0 18px 0;
}
.hero-kicker{
font-family:'Inter',system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;
text-transform:uppercase; font-weight:800; letter-spacing:.08em;
font-size:clamp(11px,1.1vw,13px); color:#fff;
opacity:.95;
}
.hero-ctas .btn{display:inline-flex;align-items:center;justify-content:center;
padding:12px 22px;border-radius:9999px;font-weight:700}
.hero-ctas .btn-primary{background:#F14816;color:#fff;box-shadow:0 6px 16px rgba(241,72,22,.25)}
.hero-ctas .btn-primary:hover{filter:brightness(1.05)}
.hero-dots{position:absolute;left:50%;bottom:24px;transform:translateX(-50%);z-index:4;display:flex;gap:8px}
.hero-dots button{width:8px;height:8px;border-radius:9999px;background:rgba(255,255,255,.6)}
.hero-dots button[aria-selected="true"]{background:#fff}
.panel{padding:34px 26px}
/* Supprimer le radius uniquement du background principal */
.hero,
.hero-track,
.hero-slide {
border-radius: 0 !important;
}
.panel--blue{
background:#0A2A45;
color:#fff;
background-image:linear-gradient(180deg,rgba(255,255,255,.02),transparent);
}
.sec-title{font-family:'Inter',system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;
font-weight:800;letter-spacing:.01em;margin:0 0 22px 0;color:#143F63;font-size:clamp(24px,2.8vw,34px)}
.panel--blue .sec-title{color:#fff}
.sec-kicker{font-family:'Inter',system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;
color:#F14816;text-transform:uppercase;font-weight:800;letter-spacing:.08em;font-size:12px;margin:0 0 10px 0}
.center{text-align:center}
.competences .competences-grid{
margin-top:18px; display:grid; grid-template-columns:repeat(5,minmax(0,1fr)); gap:18px;
}
.comp-card{
display:flex;flex-direction:column;align-items:center;gap:10px;
padding:14px;border-radius:12px;background:rgba(255,255,255,.06);min-height:88px;text-align:center
}
.comp-card .comp-icon{width:28px;height:28px}
.comp-card p{font-size:13px;line-height:1.35}
.prestations .prestations-grid{
margin-top:10px; display:grid; grid-template-columns:repeat(3,1fr); gap:18px;
}
.presta-card{
position:relative;border-radius:14px;overflow:hidden;min-height:330px;
background: #0a2a45;
}
.presta-card::before{
content:"";position:absolute;inset:0;background:var(--img) center/cover no-repeat;z-index:0
}
.presta-card::after{
content:"";position:absolute;inset:0;background:linear-gradient(180deg,rgba(10,42,69,.0) 20%,rgba(10,42,69,.75) 75%);z-index:1
}
.presta-inner{position:relative;z-index:2;color:#fff;display:flex;flex-direction:column;gap:10px;
padding:20px 18px;height:100%;justify-content:flex-end}
.presta-inner h3{font-weight:800;font-size:18px;margin:0}
.presta-inner p{font-size:14px;opacity:.95}
.presta-cta{align-self:flex-start}
.actu .actu-wrap{display:grid;grid-template-columns:1.1fr .9fr;align-items:center;gap:20px}
.actu-side{position:relative;height:360px}
.side-card{position:absolute;right:0;width:68%;height:92%;border-radius:14px;background:#6C9FB4}
.side-card.layer-1{transform:translate(0,0)}
.side-card.layer-2{right:18px;top:18px;background:#2C72A0}
.side-card.layer-3{right:36px;top:36px;background:#0A2A45}
.side-label{position:absolute;right:12px;bottom:18px;transform:rotate(-90deg);transform-origin:bottom right;
color:#fff;font-weight:800;letter-spacing:.08em}
.logos-row{display:flex;gap:36px;align-items:center;justify-content:center;flex-wrap:wrap}
.logo{height:38px;width:auto;opacity:.9}
.kpi-row{display:grid;grid-template-columns:repeat(3,1fr);gap:18px;margin-top:10px}
.kpi{background:rgba(255,255,255,.06);border-radius:14px;padding:18px;color:#fff;text-align:center}
.kpi-value{font-weight:800;font-size:38px;line-height:1}
.kpi-label{opacity:.95;margin-top:6px}
@media (max-width: 992px){
.competences .competences-grid{grid-template-columns:repeat(3,1fr)}
.prestations .prestations-grid{grid-template-columns:1fr}
.actu .actu-wrap{grid-template-columns:1fr}
}
.competences .panel--blue{
background-color:#041F60;
background-image:
linear-gradient(180deg, rgba(255,255,255,.02), transparent),
url('{{ asset('assets/img/img-conseiller.png') }}');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
.competences .panel--blue {
position: relative;
background: url('{{ asset('assets/img/img-conseiller.png') }}') center/cover no-repeat;
border-radius: 18px;
overflow: hidden;
}
.competences .panel--blue::before {
content: "";
position: absolute;
inset: 0;
background: rgba(4, 31, 96, 0.75);
z-index: 0;
}
.competences .panel--blue > * {
position: relative;
z-index: 1;
}
.competences .sec-title{
margin: 0 0 18px 0;
text-align: left !important;
color: #fff !important;
text-transform: uppercase;
font-weight: 800;
letter-spacing: .02em;
font-size: clamp(18px, 1.7vw, 22px);
}
.competences .competences-grid{
margin-top: 8px;
display: grid;
grid-template-columns: repeat(5, minmax(0,1fr));
gap: 22px 26px;
}
.comp-card{
display: inline-flex;
align-items: center;
gap: 12px;
padding: 14px 18px;
border-radius: 16px;
background: transparent;
color: #fff;
box-shadow: none;
transition: background .18s ease, color .18s ease, box-shadow .18s ease, transform .16s ease;
}
.comp-icon{
width: 28px; height: 28px; object-fit: contain;
filter: brightness(0) invert(1);
transition: filter .18s ease;
}
.comp-card p{
margin: 0;
font-weight: 700;
font-size: clamp(13px, 1.05vw, 16px);
line-height: 1.25;
text-align: left;
}
.panel .btn.btn-secondary{
background: transparent !important;
color: #F14816 !important;
border: 2px solid #F14816 !important;
border-radius: 9999px;
padding: 12px 28px;
font-weight: 800;
box-shadow: none !important;
transition: background .18s ease, color .18s ease, transform .16s ease;
}
.panel .btn.btn-secondary:hover{
background: #F14816 !important;
color: #fff !important;
transform: translateY(-1px);
}
@media (max-width: 1100px){
.competences .competences-grid{ grid-template-columns: repeat(3,1fr); }
}
@media (max-width: 640px){
.competences .competences-grid{ grid-template-columns: repeat(2,1fr); }
}
.competences .sec-title{
margin: 0 0 18px 0;
text-align: left !important;
color: #fff !important;
text-transform: uppercase;
font-weight: 800;
letter-spacing: .02em;
font-size: clamp(18px,1.7vw,22px);
}
.competences .competences-grid{
margin-top: 10px;
display: grid;
grid-template-columns: repeat(5, minmax(0,1fr));
gap: 22px 28px;
align-items: start;
}
.competences .comp-card{
display: inline-flex !important;
flex-direction: row !important;
align-items: center !important;
gap: 12px !important;
padding: 14px 18px !important;
border-radius: 16px !important;
background: transparent !important;
box-shadow: none !important;
color: #fff !important;
transition: background .18s ease, color .18s ease,
box-shadow .18s ease, transform .16s ease;
}
.competences .comp-card .comp-icon{
width: 28px; height: 28px; object-fit: contain;
margin: 0; display: inline-block;
filter: brightness(0) invert(1);
transition: filter .18s ease, opacity .18s ease;
}
.competences .comp-card p{
margin: 0;
font-weight: 800;
font-size: clamp(14px,1.05vw,16px);
line-height: 1.25;
text-align: left;
}
.competences .comp-card:hover,
.competences .comp-card.is-active{
background: #fff !important;
color: #143F63 !important;
box-shadow: 0 10px 22px rgba(10,42,69,.18);
transform: translateY(-1px);
}
.competences .comp-card:hover .comp-icon,
.competences .comp-card.is-active .comp-icon{
filter: invert(15%) sepia(18%) saturate(1431%)
hue-rotate(173deg);
}
.panel .btn.btn-secondary{
background: transparent !important;
color: #F14816 !important;
border: 2px solid #F14816 !important;
border-radius: 9999px !important;
padding: 12px 32px !important;
font-weight: 800 !important;
box-shadow: none !important;
transition: background .18s ease, color .18s ease, transform .16s ease;
}
.panel .btn.btn-secondary:hover{
background: #F14816 !important;
color: #fff !important;
transform: translateY(-1px);
}
@media (max-width: 1100px){
.competences .competences-grid{ grid-template-columns: repeat(3,1fr); }
}
@media (max-width: 640px){
.competences .competences-grid{ grid-template-columns: repeat(2,1fr); }
}
.prestations .prestations-grid{
display:grid;
grid-template-columns:repeat(3,1fr);
gap:24px;
}
.prestations .presta-card{
position:relative;
border-radius:28px;
overflow:hidden;
min-height:360px;
}
.prestations .presta-card::before{
content:"";
position:absolute; inset:0;
background:var(--img) center/cover no-repeat;
z-index:0;
}
.prestations .presta-card::after{
content:"";
position:absolute; inset:0; z-index:1;
background:
linear-gradient(-45deg, var(--corner, transparent) 0 50%, transparent 50%) top right / 150px 150px no-repeat,
linear-gradient(0deg, var(--tint, rgba(10,42,69,.50))) center/cover no-repeat,
linear-gradient(180deg, rgba(10,42,69,0) 0%, rgba(10,42,69,.65) 100%);
box-shadow: inset 0 0 0 9999px rgba(0,0,0,.0);
}
.prestations .tone-c::after{
background:
linear-gradient(0deg, var(--line, transparent), var(--line, transparent)) center 46% / 100% 2px no-repeat,
linear-gradient(0deg, var(--tint, rgba(10,42,69,.50))) center/cover no-repeat,
linear-gradient(180deg, rgba(10,42,69,0) 0%, rgba(10,42,69,.65) 100%);
}
.prestations .tone-a{ --tint: rgba(34,142,160,.50); }
.prestations .tone-b{ --tint: rgba(88,110,176,.52); --corner: rgba(255,255,255,.12); }
.prestations .tone-c{ --tint: rgba(38,74,66,.52); --line: rgba(255,255,255,.35); }
.prestations .presta-inner{
position:relative; z-index:2;
display:flex; flex-direction:column; height:100%;
padding:22px 22px 20px 22px;
color:#fff;
}
.prestations .presta-inner h3{
margin:0 0 10px 0;
font-weight:800; font-size:22px; line-height:1.15;
}
.prestations .presta-inner p{
margin:0 0 14px 0;
font-size:14px; line-height:1.65; opacity:.95;
max-width:90%;
}
.prestations .presta-cta{
margin-top:auto;
align-self:center;
background:#F14816; color:#fff;
border-radius:9999px;
padding:10px 24px;
font-weight:800;
box-shadow:0 10px 22px rgba(241,72,22,.25);
transition:transform .15s ease, filter .2s ease;
}
.prestations .presta-cta:hover{
filter:brightness(1.06);
transform:translateY(-1px);
}
@media (max-width: 980px){
.prestations .prestations-grid{ grid-template-columns:1fr; }
}
.actu .actu-wrap{
display:grid;
grid-template-columns: minmax(520px,1fr) 520px;
align-items:center;
gap: clamp(16px,3vw,32px);
position:relative;
border-radius:28px;
overflow:hidden;
background:
radial-gradient(700px 280px at 160px calc(100% - 70px), rgba(0,0,0,.30), transparent 65%),
linear-gradient(180deg, #153E55 0%, #0A2A45 100%);
color:#fff;
padding: clamp(22px,4.2vw,56px);
}
.actu .actu-copy .sec-kicker{
color:#F14816; text-transform:uppercase; font-weight:800;
letter-spacing:.08em; margin:0 0 12px 0;
font-size: clamp(12px,1.05vw,15px);
}
.actu .actu-copy .sec-title{
margin:0 0 16px 0; color:#fff; font-weight:800; line-height:1.04;
letter-spacing:.002em; font-size: clamp(34px,5vw,72px);
}
.actu .actu-copy p{
margin:0 0 22px 0; max-width:58ch; line-height:1.65;
font-size: clamp(14px,1.15vw,18px); color:rgba(255,255,255,.94);
}
.actu .actu-copy .btn.btn-primary{
background:#F14816; color:#fff; border:0;
border-radius:9999px; padding:16px 28px; font-weight:800;
box-shadow:0 20px 38px rgba(241,72,22,.22);
transition:filter .2s ease, transform .15s ease;
}
.actu .actu-copy .btn.btn-primary:hover{ filter:brightness(1.07); transform:translateY(-1px); }
.actu .actu-side{
position:relative;
width:100%; height: clamp(280px, 40vw, 520px);
}
.actu .side-card{
position:absolute; border-radius:26px;
box-shadow: 0 14px 28px rgba(0,0,0,.18);
}
.actu .side-card.layer-1{
top: 18px; right: 0; left: 24%;
height: 64%;
border-radius: 24px;
background: #8FB8C7;
}
.actu .side-card.layer-2{
top: 62px; right: 28px; left: 20%;
height: 72%;
border-radius: 22px;
background: #2D7199;
}
.actu .side-card.layer-3{
top: 110px; right: 56px; left: 16%; bottom: 14px;
border-radius: 28px;
background: #08283B;
box-shadow:
inset 0 1px 0 rgba(255,255,255,.05),
0 18px 36px rgba(0,0,0,.22);
}
.actu .side-label{
position:absolute; right: -10px; bottom: -6px;
writing-mode: vertical-rl; transform: rotate(180deg);
color:#fff; font-weight:800; letter-spacing:.14em;
font-size: clamp(10px,1vw,13px); opacity:.9;
}
@media (max-width: 980px){
.actu .actu-wrap{ grid-template-columns: 1fr; }
.actu .actu-side{ order: 2; height: 320px; margin-top: 8px; }
}
.actu .actu-hero{
--brand:#F14816;
max-width: 1280px;
margin: 28px auto 56px;
min-height: 560px;
border-radius: 44px;
overflow: hidden;
position: relative;
display: grid;
grid-template-columns: 1fr 480px;
align-items: stretch;
background: var(--bg, none) 55% center/cover no-repeat;
box-shadow: 0 22px 40px rgba(0,0,0,.14), 0 2px 0 rgba(0,0,0,.05) inset;
color:#fff;
}
.actu .actu-hero::after{
content:""; position:absolute; left:40px; right:40px; bottom:-16px;
height:24px; border-radius:24px; filter: blur(16px); background: rgba(0,0,0,.2);
}
.actu .actu-shade{
position:absolute; inset:0;
background: linear-gradient(90deg, rgba(0,0,0,.70) 0%, rgba(0,0,0,.68) 60%, rgba(0,0,0,.38) 70%, rgba(0,0,0,.16) 75%, rgba(0,0,0,0) 78%);
pointer-events:none;
}
.actu .actu__content{
position: relative; z-index:2;
padding: 60px 56px 64px 56px;
display:flex; flex-direction:column; justify-content:center;
}
.actu .actu__content .sec-kicker{
color:#ffffff; text-transform:uppercase; font-weight:900; letter-spacing:.08em;
font-size: 28px; margin: 0 0 6px; text-shadow: 0 1px 0 rgba(0,0,0,.2);
margin-bottom: 40px;
}
.actu .actu__content .sec-title{
color:#fff; font-weight:800; font-size: 24px; margin: 0 0 28px;
text-shadow: 0 1px 0 rgba(0,0,0,.2);
}
.actu .actu__content p{
font-size: 16px; line-height:1.6; max-width:78ch; margin-bottom: 20px;
color:rgba(255,255,255,.95); text-shadow: 0 1px 0 rgba(0,0,0,.18);
}
.actu .actu__content .btn.btn-primary{
background: #FF5A1C; color: #FFFFFF; border:0; border-radius: 20px; width: 167px; height: 32px; font-weight:900; font-size:14px;
}
.actu .actu__pills{ position:relative; z-index:2; }
.actu .pill{
position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
color:#fff; font-weight:900; letter-spacing:.18em; text-transform:uppercase; font-size:18px;
writing-mode: vertical-rl; transform: rotate(180deg);
text-shadow: 0 1px 0 rgba(0,0,0,.24);
}
.actu .pill--2{
right: 24px; z-index:2;
background-image: linear-gradient(90deg, rgba(43,134,255,.58), rgba(43,134,255,.58)), var(--pill-2-img);
width: 300px;
}
.actu .pill--3{
right: 0; z-index:1;
background-image: linear-gradient(90deg, rgba(122,208,255,.46), rgba(122,208,255,.46)), var(--pill-3-img);
width: 300px;
}
.actu .pill__label{
position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
color:#fff; font-weight:900; letter-spacing:.18em; text-transform:uppercase; font-size:18px;
writing-mode: vertical-rl; transform: rotate(180deg);
text-shadow: 0 1px 0 rgba(0,0,0,.24);
}
@media (max-width: 900px){
.actu .actu-hero{ grid-template-columns:1fr; min-height:720px; }
.actu .actu__pills{ height: auto; min-height: 260px; }
.actu .pill{ width:52%; right:6%; }
.actu .pill--2{ right:-2%; }
.actu .pill--3{ display:none; }
.actu .actu-shade{
background: linear-gradient(0deg, rgba(0,0,0,.72), rgba(0,0,0,.45));
}
}
.actu .actu__pills{
--wide: 140px;
--narrow: 190px;
--gap: 18px;
--radius: 60px;
--shadow: 0 18px 36px rgba(0,0,0,.22);
position: relative;
width: calc(var(--wide) + var(--gap) + var(--narrow) + 24px);
height: 100%;
margin-left: auto;
overflow: visible;
}
.actu .pill{ all: unset; }
.actu .pill{
position: absolute; top: 0; bottom: 0;
border-radius: var(--radius);
overflow: hidden;
background-position: center;
background-size: cover;
box-shadow: var(--shadow);
}
.actu .pill--3{
right: 0;
width: var(--wide);
z-index: 1;
background-image:
linear-gradient(0deg, rgba(8,40,59,.72), rgba(8,40,59,.72)),
var(--pill-3-img);
}
.actu .pill--2{
width: var(--wide);
z-index: 2;
background-image:
linear-gradient(0deg, rgba(31,110,194,.62), rgba(31,110,194,.62)),
var(--pill-2-img);
}
.actu .pill--1{
right: calc(var(--wide) + var(--gap) + 100px);
width: var(--narrow);
z-index: 3;
background-image:
linear-gradient(0deg, rgba(54,154,255,.58), rgba(54,154,255,.58)),
var(--pill-1-img);
}
.actu .pill__label{
position: absolute; inset: 0; padding-top: 50px;
display: flex; align-items: center; justify-content: flex-start;
color: #fff; font-weight: 900; letter-spacing: .18em;
text-transform: uppercase; font-size: 20px;
writing-mode: vertical-rl; transform: rotate(180deg);
text-shadow: 0 1px 0 rgba(0,0,0,.25);
}
.actu .actu__pills,
.actu .actu-side{ overflow: visible !important; }
@media (max-width: 980px){
.actu .actu__pills{
--wide: 180px;
--narrow: 96px;
--gap: 14px;
height: 100%;
}
}
.actu .pill--1, .actu .pill--2, .actu .pill--3 {
width: var(--narrow);
z-index: 3;
background-image: linear-gradient(0deg, rgba(54, 154, 255, .58), rgba(54, 154, 255, .58)), var(--pill-1-img);
}
.actu .actu__pills{
--pillW: 130px;
--gap: 18px;
--radius: 44px;
}
.actu .pill{
top: 0; bottom: 0;
width: var(--pillW) !important;
border-radius: var(--radius);
}
.actu .pill--3{
right: 0;
z-index: 1;
background-image:
linear-gradient(0deg, rgba(8,40,59,.72), rgba(8,40,59,.72)),
var(--pill-3-img) !important;
}
.actu .pill--2{
z-index: 2;
background-image: linear-gradient(0deg, rgba(31, 110, 194, .62), rgba(31, 110, 194, .62)), var(--pill-2-img) !important;
margin-left: 162px;
}
.actu .pill--1{
z-index: 3;
background-image:
linear-gradient(0deg, rgba(54,154,255,.58), rgba(54,154,255,.58)),
var(--pill-1-img) !important;
}
@media (max-width: 991.98px){
.actu .pill--1{
position: relative;
right: calc(var(--pillW) * var(--gap));
}
}
@media (min-width: 992px){
.actu .pill--1{
}
}
.actu .actu__pills, .actu .actu-side{ overflow: visible !important; }
.actu .pill--2 {
margin-right: 45px;
}
.actu .pill--1 {
margin-right: -100px;
}
:root{
--chiffres-bg: #0e2b5a;
--card-radius: 18px;
--box-radius: 26px;
}
.chiffres{
background: var(--chiffres-bg);
border-radius: var(--box-radius);
padding: 22px 28px 28px;
color: #fff;
}
.chiffres__title{
font-size: 16px;
font-weight: 700;
margin: 0 0 14px;
opacity: .95;
}
.chiffres__grid{
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 22px;
margin: 0;
padding: 0;
list-style: none;
}
.chiffres__cell{ display: contents; }
.kpi-card{
position: relative;
height: 210px;
border-radius: var(--card-radius);
overflow: hidden;
margin: 0;
box-shadow: 0 6px 18px rgba(0,0,0,.25);
}
.kpi-card__img{
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transform: scale(1.02);
}
.kpi-card::after{
content: "";
position: absolute; inset: 0;
background:
linear-gradient(180deg, rgba(0,0,0,0) 38%, rgba(0,0,0,.55) 72%, rgba(0,0,0,.65) 100%);
}
.kpi-card__caption{
position: absolute;
left: 14px; right: 14px; bottom: 12px;
display: grid;
row-gap: 6px;
z-index: 1;
color: #fff;
text-shadow: 0 1px 2px rgba(0,0,0,.5);
}
.kpi-card__value{
font-weight: 800;
font-size: 28px;
line-height: 1;
}
.kpi-card__label{
font-size: 13px;
opacity: .95;
}
.kpi-card:hover .kpi-card__img{ transform: scale(1.06); }
.kpi-card:hover{ box-shadow: 0 10px 24px rgba(0,0,0,.3); }
@media (max-width: 980px){
.chiffres__grid{ grid-template-columns: 1fr; }
.kpi-card{ height: 200px; }
}
.kpi-card{
aspect-ratio: 16 / 11;
min-height: 230px;
max-height: 300px;
}
.kpi-card__img{
width: 100%;
height: 100%;
object-fit: cover;
object-position: top center;
display: block;
}
.chiffres__grid{
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
justify-content: center;
max-width: 1100px;
margin: 0 auto;
padding: 0;
list-style: none;
}
:root{
--chiffres-bg:#0e2b5a;
--plate-bg:rgba(255,255,255,.18);
--box-radius:26px;
--plate-radius:18px;
}
.chiffres{ padding: 0; }
.chiffres__panel{
background:var(--chiffres-bg);
border-radius:var(--box-radius);
padding:18px 22px;
color:#fff;
}
.chiffres__title{
font-size:16px; font-weight:700;
margin:6px 6px 12px; opacity:.95;
}
.chiffres__grid{
display:grid;
grid-template-columns:repeat(3,1fr);
gap:24px;
margin:0;
padding:6px;
list-style:none;
}
.chiffre{
border-radius:var(--plate-radius);
padding:10px;
margin-left:-30px;
}
.chiffre__media{
position:relative;
border-radius:14px;
overflow:hidden;
min-height:300px;
height:clamp(300px, 28vw, 380px);
}
.chiffre__media>img{
position:absolute; inset:0; width:100%; height:100%;
object-fit:cover; object-position:center 20%;
transform:scale(1.02);
filter:saturate(80%) contrast(85%) brightness(78%);
transition: filter 0.3s ease;
}
.chiffre:hover .chiffre__media>img{
filter: none;
}
.chiffre__media::after{
content:""; position:absolute; inset:0;
background:linear-gradient(180deg, rgba(0,0,0,.08), rgba(0,0,0,.45));
}
.chiffre__content{
position:absolute; inset:0; display:flex; flex-direction:column;
justify-content:flex-end; gap:10px; padding:18px 18px 20px; color:#fff;
text-shadow:0 1px 2px rgba(0,0,0,.45);
}
.chiffre__value{font-weight:900; font-size:clamp(2.9rem,4.6vw,5.1rem); line-height:.95; margin-bottom:0}
.chiffre__label{
font-size: clamp(1.15rem,1.9vw,2.35rem);
line-height: 1.12;
font-weight:700;
opacity:.96;
overflow-wrap:anywhere;
text-wrap:balance;
max-width:12ch;
}
@media (max-width:980px){
.chiffres__grid{ grid-template-columns:1fr; }
.chiffre__media{ min-height:260px; height:260px; }
.chiffre__label{ max-width:none; }
}
.chiffres .narrow{
width: min(1100px, 100%);
margin: 0 auto;
}
.chiffres{
background: transparent !important;
padding: 0 !important;
}
.chiffres > .container{
max-width: 1180px;
margin: 0 auto;
padding-left: 12px;
padding-right: 12px;
}
.chiffres__panel{
background: #0e2b5a;
border-radius: 26px;
padding: 18px 22px;
color: #fff;
}
.trust{ margin: clamp(18px,5vw,44px) 0 0; }
.trust .container{ padding-left: 75px; }
.trust__kicker{
font-family:'Inter',system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;
color:#F14816;
font-weight: bold;
text-transform:uppercase;
letter-spacing:.08em;
font-size:clamp(12px,1.2vw,16px);
margin:0 0 4px 0;
}
.trust__group + .trust__group{
margin-top:40px;
}
.trust__label{
font-family:'Inter',system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;
color:#111827;
font-weight:800;
text-align:center;
font-size:clamp(22px,2.4vw,34px);
margin:0 0 22px 0;
}
.trust__logos{
display:flex;
align-items:center;
justify-content:center;
flex-wrap:wrap;
gap:clamp(18px,3vw,48px);
}
.trust__group:last-child .trust__logos{
padding-bottom:56px;
}
.trust .logo{
height:84px;
width:auto;
opacity:.95;
filter:none;
transform:none;
}
.prefooter-gap{
height: 56px;
background: #fff;
}
.panel .btn.btn-secondary{
background:#F14816 !important;
color:#fff !important;
border:2px solid #F14816 !important;
border-radius:9999px !important;
padding:12px 32px !important;
font-weight:800 !important;
box-shadow:0 10px 22px rgba(241,72,22,.25) !important;
transition:filter .18s ease, transform .16s ease;
}
.panel .btn.btn-secondary:hover{
filter:brightness(1.06);
transform:translateY(-1px);
}
.catalogue-text-wrap{ position:relative; }
.catalogue-edit-btn{
position:absolute; top:-8px; right:-8px;
background:#fff; border:1px solid rgba(20,63,99,.12);
border-radius:9999px; padding:6px 10px; line-height:1;
box-shadow:0 6px 16px rgba(17,24,39,.08); cursor:pointer;
}
.catalogue-edit-btn:hover{ background:#fff7f3; border-color:rgba(241,72,22,.35); }
.catalogue-modal{
position:fixed; inset:0; display:grid; place-items:center;
background:rgba(0,0,0,.45); z-index:9999;
}
.catalogue-modal[hidden]{ display:none; }
.catalogue-modal__dialog{
width:min(720px,92vw); background:#fff; border-radius:16px;
padding:18px; box-shadow:0 18px 38px rgba(0,0,0,.28);
}
#catalogue-edit-textarea{
width:100%; border:1px solid #e5e7eb; border-radius:10px; padding:12px; margin:12px 0 16px;
font: inherit; line-height:1.5;
}
.catalogue-modal__actions{ display:flex; gap:12px; justify-content:flex-end; }
#hero1, #hero2{
font-size: 40px;
}
#hero1_h1, #hero2_h2{
font-size: 30px;
}
/*******************************************************/
button.is-loading {
position: relative;
pointer-events: none;
opacity: .9;
}
button.is-loading::before {
content: "";
display: inline-block;
width: 1em; height: 1em;
border: 2px solid currentColor;
border-right-color: transparent;
border-radius: 50%;
vertical-align: -2px;
margin-right: .5em;
animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg) } }
.hero-ribbon-shell{
position:absolute;
top: calc(var(--topbar-h) + var(--nav-offset) + var(--pill-h) + 8px);
left:0;
right:0;
z-index:4;
display:flex;
justify-content:center;
}
.hero-ribbon-shell .home-event-ribbon{
margin:0;
max-width:80%;
}
.hero {
position: relative;
overflow: hidden;
}
.hero-track {
display: flex;
width: 100%;
height: 100%;
transition: transform 0.6s ease;
will-change: transform;
}
.hero-track > article.hero-slide,
.hero-track > article.hero-slide-config {
flex: 0 0 100%;
}
.hero-track > article {
display: none;
}
.hero-track > article.is-active {
display: block;
}
.swiper-slide {
position: relative;
min-height: calc(100vh - 100px);
}
.hero-bandeau {
position: absolute;
top: 110px;
left: 50%;
transform: translateX(-50%);
z-index: 5;
}
.hero-ribbon-shell {
display: flex;
justify-content: center;
padding: 0 1rem;
}
.hero-ribbon-shell .home-event-ribbon {
display: inline-flex;
align-items: center;
background: #BA501D;
border-radius: 9999px;
padding: 6px 20px;
width: auto !important;
max-width: 100%;
text-align: center;
font-weight: 700;
color: #fff;
font-size: 0.875rem;
}
.hero-ribbon-shell .home-event-text {
white-space: normal;
}
.hero-kicker{
font-family:'Inter',system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;
text-transform:uppercase;
font-weight:800;
letter-spacing:.08em;
font-size:clamp(13px,1.2vw,18px);
color:#fff;
opacity:.95;
}
.hero-title{
font-family:'Inter',system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;
font-weight:800;
line-height:1.05;
letter-spacing:.01em;
font-size:clamp(34px,3.9vw,54px);
color:#fff;
margin:10px 0 18px 0;
}
.chiffres__grid{
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 22px;
margin: 0;
padding: 0;
list-style: none;
justify-content: center;
}
.chiffres__grid:has(> li:nth-child(4):last-child){
grid-template-columns:repeat(2, minmax(0,1fr));
}
.chiffres__grid:has(> li:nth-child(5):last-child) > li:nth-child(4){
grid-column:1;
}
.chiffres__grid:has(> li:nth-child(5):last-child) > li:nth-child(5){
grid-column:3;
}
@media (max-width:980px){
.chiffres__grid{
grid-template-columns:1fr;
}
}
.chiffres__grid{
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 22px;
margin: 0;
padding: 0;
list-style: none;
justify-content: center;
}
.chiffres__grid:has(> li:nth-child(4):last-child){
grid-template-columns: repeat(2, 1fr);
}
.chiffres__grid:has(> li:nth-child(5):last-child) > li:nth-child(4){
grid-column: 1;
}
.chiffres__grid:has(> li:nth-child(5):last-child) > li:nth-child(5){
grid-column: 3;
}
@media (max-width:980px){
.chiffres__grid{ grid-template-columns:1fr; }
.chiffre__media{ height:240px; }
}
/*******************************************************/
@media (max-width: 767.98px){
body.is-home .hero-ribbon-shell{ display:none !important; }
body.is-home .hero-nav{ display:none !important; }
body.is-home .hero-track > article.is-active{
display:flex !important;
flex-direction:column;
min-height: calc(100vh - 78px);
min-height: calc(100svh - 78px);
}
body.is-home .hero .container.hero-content{
flex: 1;
max-width: 100% !important;
margin: 0 !important;
padding: 0 18px 72px !important;
align-items: center !important;
justify-content: center !important;
text-align: center !important;
}
body.is-home .hero .hero-copy{
width: 100% !important;
align-items: center !important;
text-align: center !important;
}
body.is-home .hero-slide .hero-kicker,
body.is-home .hero-slide .hero-title{
text-align: center !important;
}
body.is-home .hero-sub{
margin-left:auto;
margin-right:auto;
max-width: 340px;
}
body.is-home .hero-slide .hero-ctas{
width: 100% !important;
justify-content: center !important;
text-align: center !important;
}
body.is-home .hero-ctas .btn,
body.is-home .hero-ctas .btn-primary{
border-radius: 9999px !important;
padding: 12px 26px !important;
}
}
.trust .container{ padding-left: 12px; padding-right: 12px; }
@media (min-width: 992px){
.trust .container{ padding-left: 75px; padding-right: 12px; }
}
@media (max-width: 991.98px){
.trust__kicker,
.trust__label{ text-align:center; }
}
.only-mobile .chiffres__viewport{
width: 100%;
max-width: 620px;
margin-inline:auto;
overflow:hidden;
position:relative;
}
.chiffres__grid > li{ margin:0; }
.chiffres__grid .chiffre{ margin:0; }
.chiffres__grid{ padding:0; }
.chiffres__grid:has(> li:nth-child(5):last-child){
grid-template-columns:repeat(6, minmax(0,1fr));
}
.chiffres__grid:has(> li:nth-child(5):last-child) > li{
grid-column:span 2;
}
.chiffres__grid:has(> li:nth-child(5):last-child) > li:nth-child(4){
grid-column:2 / span 2;
}
.chiffres__grid:has(> li:nth-child(5):last-child) > li:nth-child(5){
grid-column:4 / span 2;
}
.trust,
.chiffres{
overflow-x:hidden;
}
.trust .container{
padding-left:18px;
padding-right:18px;
}
.chiffre{
margin-left:0 !important;
}
.only-mobile .chiffres__viewport{
width:100% !important;
max-width:620px !important;
}
@media (max-width: 767.98px){
body.is-home .hero-dots{ display:none !important; }
}
</style>
{# --------------------------- HERO / CAROUSEL --------------------------- #}
<section class="hero">
<div class="hero-track" aria-live="polite">
{% set slide1Bg = hero.slide1.bg|default('default') %}
{% set slide1IsDefault = (slide1Bg == 'default') %}
{% set slide1Ribbon = hero.slide1.ribbon|default('JEUDI 5 ET VENDREDI 6 DÉCEMBRE 2024 · HÔTEL ARAWAK · GOSIER, GUADELOUPE') %}
<article
data-scope="hero-slide1"
data-ribbon-text="{{ slide1Ribbon|e('html_attr') }}"
class="{{ slide1IsDefault ? 'hero-slide-config' : 'hero-slide' }}"
{% if not slide1IsDefault %}
style="--img:url('{{ asset(slide1Bg) }}'); --bg:url('{{ asset(slide1Bg) }}')"
{% endif %}
>
<button
style="opacity:0"
class="cms-edit-fab"
type="button"
aria-label="Éditer le slide 1"
data-edit-scope="hero-slide1"
data-kicker-key="hero_slide1_kicker"
data-title-key="hero_slide1_title"
data-cta-key="hero_slide1_cta"
data-img-key="hero_slide1_bg"
data-ribbon-key="hero_slide1_ribbon"
data-link-key="hero_slide1_link"
>✏️</button>
{% if not slide1IsDefault %}
<div class="hero-overlay"></div>
<div class="hero-ribbon-shell">
<div class="home-event-ribbon">
<span class="home-event-text">{{ slide1Ribbon }}</span>
</div>
</div>
<div class="container hero-content">
<div class="hero-copy">
<h1 class="hero-title">{{ hero.slide1.title|raw }}</h1>
{% if hero.slide1.kicker %}
<div class="hero-sub">{{ hero.slide1.kicker|raw }}</div>
{% endif %}
<div class="hero-ctas">
<a href="{{ hero.slide1.link|default('#') }}" class="btn btn-primary">{{ hero.slide1.cta }}</a>
</div>
</div>
</div>
{% endif %}
</article>
{# --- SLIDE 2 --- #}
{% set slide2Bg = hero.slide2.bg|default('default') %}
{% set slide2IsDefault = (slide2Bg == 'default') %}
{% set slide2Ribbon = hero.slide2.ribbon|default('JEUDI 5 ET VENDREDI 6 DÉCEMBRE 2024 · HÔTEL ARAWAK · GOSIER, GUADELOUPE') %}
<article
data-scope="hero-slide2"
data-ribbon-text="{{ slide2Ribbon|e('html_attr') }}"
class="hero-slide {{ slide2IsDefault ? 'hero-slide-config' : '' }}"
{% if not slide2IsDefault %}
style="--img:url('{{ asset(slide2Bg) }}'); --bg:url('{{ asset(slide2Bg) }}')"
{% endif %}
>
<button
style="opacity:0"
class="cms-edit-fab"
type="button"
aria-label="Éditer le slide 2"
data-edit-scope="hero-slide2"
data-kicker-key="hero_slide2_kicker"
data-title-key="hero_slide2_title"
data-cta-key="hero_slide2_cta"
data-img-key="hero_slide2_bg"
data-ribbon-key="hero_slide2_ribbon"
data-link-key="hero_slide2_link"
>✏️</button>
{% if not slide2IsDefault %}
<div class="hero-overlay"></div>
<div class="hero-ribbon-shell">
<div class="home-event-ribbon">
<span class="home-event-text">{{ slide2Ribbon }}</span>
</div>
</div>
<div class="container hero-content">
<div class="hero-copy">
<h1 class="hero-title" id="hero2_h1">{{ hero.slide2.title|raw }}</h1>
{% if hero.slide2.kicker %}
<div class="hero-sub" id="hero2_kicker">{{ hero.slide2.kicker|raw }}</div>
{% endif %}
<div class="hero-ctas">
<a href="{{ hero.slide2.link|default('#') }}" class="btn btn-primary">{{ hero.slide2.cta }}</a>
</div>
</div>
</div>
{% endif %}
</article>
{# --- SLIDE 3 --- #}
{% set slide3Bg = hero.slide3.bg|default('default') %}
{% set slide3IsDefault = (slide3Bg == 'default') %}
{% set slide3Ribbon = hero.slide3.ribbon|default('JEUDI 5 ET VENDREDI 6 DÉCEMBRE 2024 · HÔTEL ARAWAK · GOSIER, GUADELOUPE') %}
<article
data-scope="hero-slide3"
data-ribbon-text="{{ slide3Ribbon|e('html_attr') }}"
class="{{ slide3IsDefault ? 'hero-slide-config' : 'hero-slide' }}"
{% if not slide3IsDefault %}
style="--img:url('{{ asset(slide3Bg) }}'); --bg:url('{{ asset(slide3Bg) }}')"
{% endif %}
>
<button
style="opacity:0"
class="cms-edit-fab"
type="button"
aria-label="Éditer le slide 3"
data-edit-scope="hero-slide3"
data-kicker-key="hero_slide3_kicker"
data-title-key="hero_slide3_title"
data-cta-key="hero_slide3_cta"
data-img-key="hero_slide3_bg"
data-ribbon-key="hero_slide3_ribbon"
data-link-key="hero_slide3_link"
>✏️</button>
{% if not slide3IsDefault %}
<div class="hero-overlay"></div>
<div class="hero-ribbon-shell">
<div class="home-event-ribbon">
<span class="home-event-text">{{ slide3Ribbon }}</span>
</div>
</div>
<div class="container hero-content">
<div class="hero-copy">
<h1 class="hero-title" id="hero3_h1">{{ hero.slide3.title|raw }}</h1>
{% if hero.slide3.kicker %}
<div class="hero-sub" id="hero3_kicker">{{ hero.slide3.kicker|raw }}</div>
{% endif %}
<div class="hero-ctas">
<a href="{{ hero.slide3.link|default('#') }}" class="btn btn-primary">{{ hero.slide3.cta }}</a>
</div>
</div>
</div>
{% endif %}
</article>
{# --- SLIDE 4 --- #}
{% set slide4Bg = hero.slide4.bg|default('default') %}
{% set slide4IsDefault = (slide4Bg == 'default') %}
{% set slide4Ribbon = hero.slide4.ribbon|default('JEUDI 5 ET VENDREDI 6 DÉCEMBRE 2024 · HÔTEL ARAWAK · GOSIER, GUADELOUPE') %}
<article
data-scope="hero-slide4"
data-ribbon-text="{{ slide4Ribbon|e('html_attr') }}"
class="hero-slide {{ slide4IsDefault ? 'hero-slide-config' : '' }}"
{% if not slide4IsDefault %}
style="--img:url('{{ asset(slide4Bg) }}'); --bg:url('{{ asset(slide4Bg) }}')"
{% endif %}
>
<button
style="opacity:0"
class="cms-edit-fab"
type="button"
aria-label="Éditer le slide 4"
data-edit-scope="hero-slide4"
data-kicker-key="hero_slide4_kicker"
data-title-key="hero_slide4_title"
data-cta-key="hero_slide4_cta"
data-img-key="hero_slide4_bg"
data-ribbon-key="hero_slide4_ribbon"
data-link-key="hero_slide4_link"
>✏️</button>
{% if not slide4IsDefault %}
<div class="hero-overlay"></div>
<div class="hero-ribbon-shell">
<div class="home-event-ribbon">
<span class="home-event-text">{{ slide4Ribbon }}</span>
</div>
</div>
<div class="container hero-content">
<div class="hero-copy">
<h1 class="hero-title" id="hero4_h1">{{ hero.slide4.title|raw }}</h1>
{% if hero.slide4.kicker %}
<div class="hero-sub" id="hero4_kicker">{{ hero.slide4.kicker|raw }}</div>
{% endif %}
<div class="hero-ctas">
<a href="{{ hero.slide4.link|default('#') }}" class="btn btn-primary">{{ hero.slide4.cta }}</a>
</div>
</div>
</div>
{% endif %}
</article>
{# --- SLIDE 5 --- #}
{% set slide5Bg = hero.slide5.bg|default('default') %}
{% set slide5IsDefault = (slide5Bg == 'default') %}
{% set slide5Ribbon = hero.slide5.ribbon|default('JEUDI 5 ET VENDREDI 6 DÉCEMBRE 2024 · HÔTEL ARAWAK · GOSIER, GUADELOUPE') %}
<article
data-scope="hero-slide5"
data-ribbon-text="{{ slide5Ribbon|e('html_attr') }}"
class="hero-slide {{ slide5IsDefault ? 'hero-slide-config' : '' }}"
{% if not slide5IsDefault %}
style="--img:url('{{ asset(slide5Bg) }}'); --bg:url('{{ asset(slide5Bg) }}')"
{% endif %}
>
<button
style="opacity:0"
class="cms-edit-fab"
type="button"
aria-label="Éditer le slide 5"
data-edit-scope="hero-slide5"
data-kicker-key="hero_slide5_kicker"
data-title-key="hero_slide5_title"
data-cta-key="hero_slide5_cta"
data-img-key="hero_slide5_bg"
data-ribbon-key="hero_slide5_ribbon"
data-link-key="hero_slide5_link"
>✏️</button>
{% if not slide5IsDefault %}
<div class="hero-overlay"></div>
<div class="hero-ribbon-shell">
<div class="home-event-ribbon">
<span class="home-event-text">{{ slide5Ribbon }}</span>
</div>
</div>
<div class="container hero-content">
<div class="hero-copy">
<h1 class="hero-title" id="hero5_h1">{{ hero.slide5.title|raw }}</h1>
{% if hero.slide5.kicker %}
<div class="hero-sub" id="hero5_kicker">{{ hero.slide5.kicker|raw }}</div>
{% endif %}
<div class="hero-ctas">
<a href="{{ hero.slide5.link|default('#') }}" class="btn btn-primary">{{ hero.slide5.cta }}</a>
</div>
</div>
</div>
{% endif %}
</article>
{# --- SLIDE 6 --- #}
{% set slide6Bg = hero.slide6.bg|default('default') %}
{% set slide6IsDefault = (slide6Bg == 'default') %}
{% set slide6Ribbon = hero.slide6.ribbon|default('JEUDI 5 ET VENDREDI 6 DÉCEMBRE 2024 · HÔTEL ARAWAK · GOSIER, GUADELOUPE') %}
<article
data-scope="hero-slide6"
data-ribbon-text="{{ slide6Ribbon|e('html_attr') }}"
class="hero-slide {{ slide6IsDefault ? 'hero-slide-config' : '' }}"
{% if not slide6IsDefault %}
style="--img:url('{{ asset(slide6Bg) }}'); --bg:url('{{ asset(slide6Bg) }}')"
{% endif %}
>
<button
style="opacity:0"
class="cms-edit-fab"
type="button"
aria-label="Éditer le slide 6"
data-edit-scope="hero-slide6"
data-kicker-key="hero_slide6_kicker"
data-title-key="hero_slide6_title"
data-cta-key="hero_slide6_cta"
data-img-key="hero_slide6_bg"
data-ribbon-key="hero_slide6_ribbon"
data-link-key="hero_slide6_link"
>✏️</button>
{% if not slide6IsDefault %}
<div class="hero-overlay"></div>
<div class="hero-ribbon-shell">
<div class="home-event-ribbon">
<span class="home-event-text">{{ slide6Ribbon }}</span>
</div>
</div>
<div class="container hero-content">
<div class="hero-copy">
<h1 class="hero-title" id="hero6_h1">{{ hero.slide6.title|raw }}</h1>
{% if hero.slide6.kicker %}
<div class="hero-sub" id="hero6_kicker">{{ hero.slide6.kicker|raw }}</div>
{% endif %}
<div class="hero-ctas">
<a href="{{ hero.slide6.link|default('#') }}" class="btn btn-primary">{{ hero.slide6.cta }}</a>
</div>
</div>
</div>
{% endif %}
</article>
{# --- SLIDE 7 --- #}
{% set slide7Bg = hero.slide7.bg|default('default') %}
{% set slide7IsDefault = (slide7Bg == 'default') %}
{% set slide7Ribbon = hero.slide7.ribbon|default('JEUDI 5 ET VENDREDI 6 DÉCEMBRE 2024 · HÔTEL ARAWAK · GOSIER, GUADELOUPE') %}
<article
data-scope="hero-slide7"
data-ribbon-text="{{ slide7Ribbon|e('html_attr') }}"
class="hero-slide {{ slide7IsDefault ? 'hero-slide-config' : '' }}"
{% if not slide7IsDefault %}
style="--img:url('{{ asset(slide7Bg) }}'); --bg:url('{{ asset(slide7Bg) }}')"
{% endif %}
>
<button
style="opacity:0"
class="cms-edit-fab"
type="button"
aria-label="Éditer le slide 7"
data-edit-scope="hero-slide7"
data-kicker-key="hero_slide7_kicker"
data-title-key="hero_slide7_title"
data-cta-key="hero_slide7_cta"
data-img-key="hero_slide7_bg"
data-ribbon-key="hero_slide7_ribbon"
data-link-key="hero_slide7_link"
>✏️</button>
{% if not slide7IsDefault %}
<div class="hero-overlay"></div>
<div class="hero-ribbon-shell">
<div class="home-event-ribbon">
<span class="home-event-text">{{ slide7Ribbon }}</span>
</div>
</div>
<div class="container hero-content">
<div class="hero-copy">
<h1 class="hero-title" id="hero7_h1">{{ hero.slide7.title|raw }}</h1>
{% if hero.slide7.kicker %}
<div class="hero-sub" id="hero7_kicker">{{ hero.slide7.kicker|raw }}</div>
{% endif %}
<div class="hero-ctas">
<a href="{{ hero.slide7.link|default('#') }}" class="btn btn-primary">{{ hero.slide7.cta }}</a>
</div>
</div>
</div>
{% endif %}
</article>
{# --- SLIDE 8 --- #}
{% set slide8Bg = hero.slide8.bg|default('default') %}
{% set slide8IsDefault = (slide8Bg == 'default') %}
{% set slide8Ribbon = hero.slide8.ribbon|default('JEUDI 5 ET VENDREDI 6 DÉCEMBRE 2024 · HÔTEL ARAWAK · GOSIER, GUADELOUPE') %}
<article
data-scope="hero-slide8"
data-ribbon-text="{{ slide8Ribbon|e('html_attr') }}"
class="hero-slide {{ slide8IsDefault ? 'hero-slide-config' : '' }}"
{% if not slide8IsDefault %}
style="--img:url('{{ asset(slide8Bg) }}'); --bg:url('{{ asset(slide8Bg) }}')"
{% endif %}
>
<button
style="opacity:0"
class="cms-edit-fab"
type="button"
aria-label="Éditer le slide 8"
data-edit-scope="hero-slide8"
data-kicker-key="hero_slide8_kicker"
data-title-key="hero_slide8_title"
data-cta-key="hero_slide8_cta"
data-img-key="hero_slide8_bg"
data-ribbon-key="hero_slide8_ribbon"
data-link-key="hero_slide8_link"
>✏️</button>
{% if not slide8IsDefault %}
<div class="hero-overlay"></div>
<div class="hero-ribbon-shell">
<div class="home-event-ribbon">
<span class="home-event-text">{{ slide8Ribbon }}</span>
</div>
</div>
<div class="container hero-content">
<div class="hero-copy">
<h1 class="hero-title" id="hero8_h1">{{ hero.slide8.title|raw }}</h1>
{% if hero.slide8.kicker %}
<div class="hero-sub" id="hero8_kicker">{{ hero.slide8.kicker|raw }}</div>
{% endif %}
<div class="hero-ctas">
<a href="{{ hero.slide8.link|default('#') }}" class="btn btn-primary">{{ hero.slide8.cta }}</a>
</div>
</div>
</div>
{% endif %}
</article>
{# --- SLIDE 9 --- #}
{% set slide9Bg = hero.slide9.bg|default('default') %}
{% set slide9IsDefault = (slide9Bg == 'default') %}
{% set slide9Ribbon = hero.slide9.ribbon|default('JEUDI 5 ET VENDREDI 6 DÉCEMBRE 2024 · HÔTEL ARAWAK · GOSIER, GUADELOUPE') %}
<article
data-scope="hero-slide9"
data-ribbon-text="{{ slide9Ribbon|e('html_attr') }}"
class="hero-slide {{ slide9IsDefault ? 'hero-slide-config' : '' }}"
{% if not slide9IsDefault %}
style="--img:url('{{ asset(slide9Bg) }}'); --bg:url('{{ asset(slide9Bg) }}')"
{% endif %}
>
<button
style="opacity:0"
class="cms-edit-fab"
type="button"
aria-label="Éditer le slide 9"
data-edit-scope="hero-slide9"
data-kicker-key="hero_slide9_kicker"
data-title-key="hero_slide9_title"
data-cta-key="hero_slide9_cta"
data-img-key="hero_slide9_bg"
data-ribbon-key="hero_slide9_ribbon"
data-link-key="hero_slide9_link"
>✏️</button>
{% if not slide9IsDefault %}
<div class="hero-overlay"></div>
<div class="hero-ribbon-shell">
<div class="home-event-ribbon">
<span class="home-event-text">{{ slide9Ribbon }}</span>
</div>
</div>
<div class="container hero-content">
<div class="hero-copy">
<h1 class="hero-title" id="hero9_h1">{{ hero.slide9.title|raw }}</h1>
{% if hero.slide9.kicker %}
<div class="hero-sub" id="hero9_kicker">{{ hero.slide9.kicker|raw }}</div>
{% endif %}
<div class="hero-ctas">
<a href="{{ hero.slide9.link|default('#') }}" class="btn btn-primary">{{ hero.slide9.cta }}</a>
</div>
</div>
</div>
{% endif %}
</article>
{# --- SLIDE 10 --- #}
{% set slide10Bg = hero.slide10.bg|default('default') %}
{% set slide10IsDefault = (slide10Bg == 'default') %}
{% set slide10Ribbon = hero.slide10.ribbon|default('JEUDI 5 ET VENDREDI 6 DÉCEMBRE 2024 · HÔTEL ARAWAK · GOSIER, GUADELOUPE') %}
<article
data-scope="hero-slide10"
data-ribbon-text="{{ slide10Ribbon|e('html_attr') }}"
class="hero-slide {{ slide10IsDefault ? 'hero-slide-config' : '' }}"
{% if not slide10IsDefault %}
style="--img:url('{{ asset(slide10Bg) }}'); --bg:url('{{ asset(slide10Bg) }}')"
{% endif %}
>
<button
style="opacity:0"
class="cms-edit-fab"
type="button"
aria-label="Éditer le slide 10"
data-edit-scope="hero-slide10"
data-kicker-key="hero_slide10_kicker"
data-title-key="hero_slide10_title"
data-cta-key="hero_slide10_cta"
data-img-key="hero_slide10_bg"
data-ribbon-key="hero_slide10_ribbon"
data-link-key="hero_slide10_link"
>✏️</button>
{% if not slide10IsDefault %}
<div class="hero-overlay"></div>
<div class="hero-ribbon-shell">
<div class="home-event-ribbon">
<span class="home-event-text">{{ slide10Ribbon }}</span>
</div>
</div>
<div class="container hero-content">
<div class="hero-copy">
<h1 class="hero-title" id="hero10_h1">{{ hero.slide10.title|raw }}</h1>
{% if hero.slide10.kicker %}
<div class="hero-sub" id="hero10_kicker">{{ hero.slide10.kicker|raw }}</div>
{% endif %}
<div class="hero-ctas">
<a href="{{ hero.slide10.link|default('#') }}" class="btn btn-primary">{{ hero.slide10.cta }}</a>
</div>
</div>
</div>
{% endif %}
</article>
</div>
<button class="hero-nav prev" aria-label="Slide précédente">‹</button>
<button class="hero-nav next" aria-label="Slide suivante">›</button>
<div class="hero-dots" role="tablist" aria-label="Sélecteur de diapos"></div>
</section>
<div id="cms-devbar">
<button id="cms-devbar-toggle" type="button"
aria-label="Ouvrir le menu d’édition"
aria-expanded="false">
☰
</button>
<div id="cms-devbar-menu" hidden>
<button type="button" data-hero="1">Éditer bandeau slide #1</button>
<button type="button" data-hero="2">Éditer bandeau slide #2</button>
<button type="button" data-hero="3">Éditer bandeau slide #3</button>
<button type="button" data-hero="4">Éditer bandeau slide #4</button>
<button type="button" data-hero="5">Éditer bandeau slide #5</button>
<button type="button" data-hero="6">Éditer bandeau slide #6</button>
<button type="button" data-hero="7">Éditer bandeau slide #7</button>
<button type="button" data-hero="8">Éditer bandeau slide #8</button>
<button type="button" data-hero="9">Éditer bandeau slide #9</button>
<button type="button" data-hero="10">Éditer bandeau slide #10</button>
</div>
</div>
</div>
<style>
#cms-devbar{
position:fixed;
top:12px;
right:12px;
z-index:2147483647;
font-family:'Inter',system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;
}
#cms-devbar-toggle{
width:40px;
height:40px;
border-radius:9999px;
border:0;
background:#0A2A45;
color:#fff;
font-size:20px;
display:flex;
align-items:center;
justify-content:center;
box-shadow:0 8px 20px rgba(0,0,0,.25);
cursor:pointer;
}
/* 👇 menu is HIDDEN by default */
#cms-devbar-menu{
position:absolute;
top:48px;
right:0;
background:#0A2A45;
color:#fff;
border-radius:10px;
padding:8px;
box-shadow:0 8px 20px rgba(0,0,0,.25);
min-width:190px;
display:none; /* <= here */
flex-direction:column;
gap:4px;
}
#cms-devbar.is-open #cms-devbar-menu{
display:flex;
}
#cms-devbar-menu button{
background:transparent;
border:0;
color:#fff;
text-align:left;
padding:6px 10px;
border-radius:6px;
font-size:13px;
cursor:pointer;
white-space:nowrap;
}
#cms-devbar-menu button:hover{
background:rgba(255,255,255,.12);
}
#cms-devbar.is-open #cms-devbar-toggle{
background:#F14816;
}
</style>
<script>
(function(){
const bar = document.getElementById('cms-devbar');
if (!bar) return;
const toggle = document.getElementById('cms-devbar-toggle');
const menu = document.getElementById('cms-devbar-menu');
function setOpen(open){
bar.classList.toggle('is-open', open);
if (open) {
menu.hidden = false;
toggle.setAttribute('aria-expanded','true');
} else {
menu.hidden = true;
toggle.setAttribute('aria-expanded','false');
}
}
toggle.addEventListener('click', function(){
const isOpen = bar.classList.contains('is-open');
setOpen(!isOpen);
});
bar.addEventListener('click', function(e){
const btn = e.target.closest('button[data-hero]');
if (!btn) return;
const idx = btn.dataset.hero;
const fab = document.querySelector('[data-scope=hero-slide' + idx + '] .cms-edit-fab');
if (fab) fab.click();
setOpen(false);
});
document.addEventListener('click', function(e){
if (!bar.contains(e.target) && bar.classList.contains('is-open')) {
setOpen(false);
}
});
setOpen(false);
})();
</script>
{# {% endif %} #}
<style>
.hero-slide{ position:relative; background:var(--img) center/cover no-repeat; height: min(72vh, 860px); width: 100%; }
/* Force hero content alignment */
.hero-slide .container.hero-content{
position: relative !important;
z-index: 1 !important;
height: 100% !important;
display: flex !important;
align-items: center !important;
justify-content: flex-start !important;
max-width: 1180px !important;
margin: 0 auto !important;
padding: 0 12px !important;
}
.hero-slide .hero-text-content {
width: 100% !important;
text-align: left !important;
display: flex !important;
flex-direction: column !important;
align-items: flex-start !important;
}
.hero-slide .hero-kicker {
text-align: left !important;
margin-bottom: 6px !important;
width: 100% !important;
}
.hero-slide .hero-title {
text-align: left !important;
margin: 10px 0 18px 0 !important;
width: 100% !important;
}
.hero-slide .hero-ctas {
text-align: left !important;
margin-top: 18px !important;
justify-content: flex-start !important;
width: 100% !important;
}
/* Override any existing styles */
.hero .hero-content {
display: flex !important;
align-items: center !important;
justify-content: flex-start !important;
}
.cms-edit-fab{
position:absolute; top:12px; right:12px; z-index:9;
width:36px; height:36px; border:0; border-radius:50%;
background:rgba(255,255,255,.95); box-shadow:0 2px 8px rgba(0,0,0,.2);
cursor:pointer; font-size:16px; line-height:36px; text-align:center;
}
.cms-modal[hidden]{ display:none !important; }
.cms-modal{ position:fixed; inset:0; display:flex; align-items:center; justify-content:center; background:rgba(0,0,0,.45); z-index:9999; }
.cms-modal__dialog{ background:#fff; width:min(720px,92vw); max-height:90vh; overflow:auto; border-radius:12px; box-shadow:0 10px 40px rgba(0,0,0,.25); }
.cms-modal__header,.cms-modal__footer{ padding:12px 16px; background:#f7f7f8; display:flex; align-items:center; justify-content:space-between; }
.cms-modal__body{ padding:16px; display:grid; gap:12px; }
.cms-field span{ display:block; font-size:.9rem; color:#555; margin-bottom:4px; }
.cms-field input[type="text"], .cms-field textarea{ width:100%; padding:8px 10px; border:1px solid #ddd; border-radius:6px; }
</style>
<script>
(function () {
// --- helper loader bouton ---
function setLoading(btn, isLoading, text = 'Enregistrement…') {
if (!btn) return;
if (isLoading) {
if (!btn.dataset._label) btn.dataset._label = btn.textContent;
btn.disabled = true;
btn.setAttribute('aria-busy', 'true');
btn.innerHTML =
'<svg width="16" height="16" viewBox="0 0 50 50" aria-hidden="true" focusable="false" style="vertical-align:-2px;margin-right:8px;">' +
'<circle cx="25" cy="25" r="20" fill="none" stroke="currentColor" stroke-width="6" stroke-opacity="0.25"></circle>' +
'<path d="M25 5 a20 20 0 0 1 0 40" stroke="currentColor" stroke-width="6" fill="none">' +
'<animateTransform attributeName="transform" type="rotate" from="0 25 25" to="360 25 25" dur="0.8s" repeatCount="indefinite"></animateTransform>' +
'</path>' +
'</svg>' + text;
} else {
btn.disabled = false;
btn.removeAttribute('aria-busy');
btn.textContent = btn.dataset._label || 'Enregistrer';
}
}
const modal = document.createElement('div');
modal.id = 'cms-hero-modal';
modal.className = 'cms-modal';
modal.hidden = true;
modal.innerHTML = `
<div class="cms-modal__dialog" role="dialog" aria-modal="true" aria-labelledby="cms-hero-modal-title">
<div class="cms-modal__header">
<strong id="cms-hero-modal-title">Éditer le slide</strong>
<button type="button" class="cms-modal__close" aria-label="Fermer">×</button>
</div>
<div class="cms-modal__body">
<label class="cms-field"><span>Kicker</span><input type="text" id="cms-hero-kicker"></label>
<label class="cms-field"><span>Titre (HTML autorisé)</span><textarea id="cms-hero-title" rows="4"></textarea></label>
<label class="cms-field">
<span>Texte du bouton</span>
<input type="text" id="cms-hero-cta">
</label>
<label class="cms-field">
<span>Lien du bouton</span>
<input type="text" id="cms-hero-link" placeholder="https://exemple.com ou /route-interne">
</label>
<label class="cms-field">
<span>Texte du bandeau orange</span>
<input type="text" id="cms-hero-ribbon">
</label>
<label class="cms-field"><span>Image de fond (png/jpg/webp)</span><input type="file" id="cms-hero-file" accept="image/png,image/jpeg,image/webp"></label>
<p class="cms-hint">Astuce : privilégiez un visuel ≥ 1600px de large.</p>
</div>
<div class="cms-modal__footer">
<button type="button" class="btn btn-secondary" id="cms-hero-cancel">Annuler</button>
<button type="button" class="btn btn-primary" id="cms-hero-save">Enregistrer</button>
</div>
</div>`;
document.body.appendChild(modal);
const $ = (s, r=document)=>r.querySelector(s);
const closeBtn = $('.cms-modal__close', modal);
const cancelBtn= $('#cms-hero-cancel', modal);
const saveBtn = $('#cms-hero-save', modal);
const inK = $('#cms-hero-kicker', modal);
const inT = $('#cms-hero-title', modal);
const inC = $('#cms-hero-cta', modal);
const inF = $('#cms-hero-file', modal);
const inR = $('#cms-hero-ribbon', modal);
const inL = $('#cms-hero-link', modal);
let ctx = null;
function openModal(context){
ctx = context;
const s = ctx.slideEl;
inK.value = (s.querySelector('.hero-sub')?.textContent || '').trim();
inT.value = (s.querySelector('.hero-title')?.innerHTML || '').trim();
inC.value = (s.querySelector('.btn')?.textContent || '').trim();
const btn = s.querySelector('.btn');
inL.value = (btn?.getAttribute('href') || '').trim();
const ribbonText =
s.querySelector('.home-event-text')?.textContent ||
s.dataset.ribbonText ||
'';
inR.value = ribbonText.trim();
inF.value = '';
modal.hidden = false;
}
function closeModal(){
modal.hidden = true;
ctx = null;
}
closeBtn.addEventListener('click', closeModal);
cancelBtn.addEventListener('click', closeModal);
document.addEventListener('click', (e)=>{
const fab = e.target.closest('.cms-edit-fab');
if (!fab) return;
const slide = fab.closest('[data-scope]');
openModal({
slideEl: slide,
kickerKey: fab.dataset.kickerKey,
titleKey: fab.dataset.titleKey,
ctaKey: fab.dataset.ctaKey,
imgKey: fab.dataset.imgKey,
ribbonKey: fab.dataset.ribbonKey,
linkKey: fab.dataset.linkKey
});
});
async function postText(key, text){
const r = await fetch('{{ path("cms_text_update") }}', {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({ key, text })
});
const d = await r.json();
if (!d.ok) throw new Error(d.error || 'Erreur texte');
return d;
}
function desiredHeroName(file, key) {
const prefix = key === 'hero_slide1_bg' ? 'hero1_' :
key === 'hero_slide4_bg' ? 'hero4_' :
key === 'hero_slide5_bg' ? 'hero5_' :
key === 'hero_slide6_bg' ? 'hero6_' :
key === 'hero_slide7_bg' ? 'hero7_' :
key === 'hero_slide8_bg' ? 'hero8_' :
key === 'hero_slide9_bg' ? 'hero9_' :
key === 'hero_slide10_bg' ? 'hero10_' :
key === 'hero_slide3_bg' ? 'hero3_' :
key === 'hero_slide2_bg' ? 'hero2_' : '';
const ext = (file.name.split('.').pop() || '').toLowerCase();
const base = file.name.replace(/\.[^.]+$/, '')
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '') || 'image';
const stamp = new Date().toISOString().replace(/[-:TZ.]/g,'').slice(0,14);
return `${prefix}${base}-${stamp}.${ext}`;
}
async function uploadImage(key, file){
const fd = new FormData();
fd.append('key', key);
fd.append('file', file, desiredHeroName(file, key));
const r = await fetch('{{ path("cms_page_media_upload") }}', { method:'POST', body: fd });
const d = await r.json();
if (!d.ok) throw new Error(d.error || 'Erreur upload');
return d.path;
}
saveBtn.addEventListener('click', async ()=>{
if (!ctx) return;
setLoading(saveBtn, true);
try{
const tasks = [];
if (ctx.kickerKey) tasks.push(postText(ctx.kickerKey, inK.value.trim()));
if (ctx.titleKey) tasks.push(postText(ctx.titleKey, inT.value.trim()));
if (ctx.ctaKey) tasks.push(postText(ctx.ctaKey, inC.value.trim()));
if (ctx.ribbonKey) tasks.push(postText(ctx.ribbonKey, inR.value.trim()));
if (ctx.linkKey) tasks.push(postText(ctx.linkKey, inL.value.trim()));
if (tasks.length) await Promise.all(tasks);
let newPath = null;
if (inF.files && inF.files[0]) {
newPath = await uploadImage(ctx.imgKey, inF.files[0]);
}
const s = ctx.slideEl;
if (ctx.kickerKey) {
const kickerHtml = inK.value.trim();
let sub = s.querySelector('.hero-sub');
if (kickerHtml) {
if (!sub) {
sub = document.createElement('div');
sub.className = 'hero-sub';
const titleEl = s.querySelector('.hero-title');
if (titleEl) titleEl.insertAdjacentElement('afterend', sub);
else s.querySelector('.hero-copy')?.appendChild(sub);
}
sub.innerHTML = kickerHtml;
} else if (sub) {
sub.remove();
}
}
if (ctx.titleKey) { const t = s.querySelector('.hero-title'); if (t) t.innerHTML = inT.value.trim(); }
if (ctx.ctaKey) { const c = s.querySelector('.btn'); if (c) c.textContent = inC.value.trim(); }
if (ctx.linkKey) {
const cta = s.querySelector('.btn');
if (cta) cta.setAttribute('href', inL.value.trim() || '#');
}
if (ctx.ribbonKey) {
const raw = inR.value.trim();
const txt = (raw && raw !== '-') ? raw : '';
const shell = s.querySelector('.hero-ribbon-shell');
const rEl = s.querySelector('.home-event-text');
if (rEl) rEl.textContent = txt;
if (shell) shell.style.display = txt ? '' : 'none';
s.dataset.ribbonText = txt;
}
if (newPath) {
const BASE = "{{ asset('/') }}";
s.style.setProperty('--img', `url('${BASE}${newPath}')`);
s.style.setProperty('--bg', `url('${BASE}${newPath}')`);
}
closeModal();
} catch(err){
alert(err.message || 'Erreur inconnue');
} finally{
setLoading(saveBtn, false);
}
});
})();
</script>
{# --------------------------- CATALOGUE CTA --------------------------- #}
<section class="catalogue">
<div class="container">
<div class="catalogue-grid">
<div class="catalogue-copy">
<div class="catalogue-text-wrap">
<h2 class="sec-title">DÉCOUVREZ NOTRE NOUVEAU CATALOGUE DE CONSEIL ET DE FORMATION 2026</h2>
<p id="catalogue-text-left" class="sec-text">
{{ catalogue_text_left }}
</p>
<button
type="button"
class="catalogue-edit-btn"
aria-label="Modifier le texte du catalogue"
data-url="{{ path('catalogue_text_update') }}"
data-csrf="{{ csrf_token('catalogue_text_update') }}"
data-current-text="{{ catalogue_text_left|e('html_attr') }}"
>
✏️
</button>
<div class="btn-row">
<a href="https://catalogue.acoa.fr/" class="btn btn-outline">Voir plus</a>
<a style ="display:none;" href="{{ asset('catalogue-2026.pdf') }}" class="btn btn-primary" download="catalogue-2026.pdf">Telecharger</a>
</div>
</div>
</div>
{# Modal (render once anywhere inside this template, e.g. after the section) #}
<div id="catalogue-edit-modal" class="catalogue-modal" hidden>
<div class="catalogue-modal__dialog" role="dialog" aria-modal="true" aria-labelledby="catalogue-edit-title">
<h3 id="catalogue-edit-title">Modifier le texte du catalogue</h3>
<textarea id="catalogue-edit-textarea" rows="8"></textarea>
<div class="catalogue-modal__actions">
<button type="button" class="btn btn-outline" id="catalogue-cancel">Annuler</button>
<button type="button" class="btn btn-primary" id="catalogue-validate">Valider</button>
</div>
</div>
</div>
<div class="catalogue-art">
<div class="cat-card">
<img src="{{ asset('assets/img/pexels-thirdman-5684445.png') }}" alt="Catalogue 2026"/>
<div class="cat-veil"></div>
</div>
</div>
</div>
</div>
</section>
{# --------------------------- DOMAINES DE COMPÉTENCES --------------------------- #}
<section class="competences">
<div class="container">
<div class="panel panel--blue"
style="background-image:url('{{ asset('assets/img/img-conseiller.png') }}');
background-size:cover;
background-position:center;
background-repeat:no-repeat;">
<h2 class="sec-title">Nos domaines de compétences</h2>
<div class="competences-grid">
{% set items = [
["Organisation du travail par l'approche Qualité", 'stonks.png', '/approches-de-la-relation'],
['Élaboration et conduite de projet', 'eos-icons_content-lifecycle-management.png', '/elaboration-et-conduite-de-projet'],
['Équipe de cadres et managers', 'ri_team-fill.png', '/equipe-cadres-managers'],
['Santé au travail', 'mage_heart-health-fill.png', '/sante-au-travail'],
['Gestion globale des risques, des crises & développement durable', 'graph.png', '/gestion-des-risques'],
['Management des ressources humaines', 'carbon_id-management.png', '/management'],
["Mobilisation & cohésion d'équipes", 'hugeicons_agreement-01.png', '/mobilisation-et-cohesion-d-equipes'],
['Organisation sociale & médico-sociale', 'ion_share-social-sharp.png', '/organisation-sociale-medico-social'],
['Évolution et transition professionnelle', 'ph_plant-bold.png', '/evolution-transition-pro'],
] %}
{% for i in items %}
<a class="comp-card" href="{{ i[2] }}">
<img src="{{ asset('assets/img/icons/' ~ i[1]) }}" alt="" class="comp-icon">
<p>{{ i[0] }}</p>
</a>
{% endfor %}
</div>
<div class="center mt-24">
<a href="https://catalogue.acoa.fr/" class="btn btn-secondary">Explorer</a>
</div>
</div>
</div>
</section>
<style>
.competences .competences-grid{
margin-top: 10px;
display: grid;
grid-template-columns: repeat(3, minmax(0,1fr));
gap: 22px 28px;
align-items: start;
}
@media (max-width: 1100px){
.competences .competences-grid{
grid-template-columns: repeat(2, minmax(0,1fr));
}
}
@media (max-width: 640px){
.competences .competences-grid{
grid-template-columns: 1fr;
}
}
.competences .comp-card p{
margin: 0;
font-weight: 800;
font-size: clamp(13px,0.95vw,16px);
line-height: 1.3;
text-align: left;
overflow-wrap: anywhere;
text-wrap: balance;
}
.competences .comp-card{
text-decoration: none;
color: inherit;
cursor: pointer;
}
</style>
{# --------------------------- PRESTATIONS (3 cards over images) --------------------------- #}
<section class="prestations">
<style>
@media (max-width: 768px){
.prestations .presta-carousel{
position: relative;
}
#presta-track{
display: flex !important;
overflow-x: auto;
gap: 10px;
padding: 0 4px;
scroll-snap-type: x mandatory;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
}
#presta-track::-webkit-scrollbar{
display:none;
}
#presta-track .presta-card{
flex: 0 0 calc(100% - 8px);
min-width: calc(100% - 8px);
scroll-snap-align: center;
border-radius: 0;
min-height: clamp(520px, 78vh, 680px);
}
.prestations .prestations--overimg .presta-card{
padding: 0 !important;
background-position: center !important;
background-size: cover !important;
}
.prestations .prestations--overimg .presta-card::after{
background: linear-gradient(180deg, rgba(10,43,88,.18) 0%, rgba(10,43,88,.62) 70%, rgba(10,43,88,.82) 100%) !important;
}
/* Keep text and CTA visible on mobile cards */
.prestations .presta-inner{
display: flex !important;
flex-direction: column;
justify-content: flex-end;
height: 100%;
padding: 18px 16px;
}
.prestations .presta-inner h3{
font-size: clamp(22px, 6vw, 34px);
line-height: 1.1;
margin: 0 0 10px 0;
}
.prestations .presta-inner p{
font-size: clamp(13px, 3.8vw, 18px);
line-height: 1.5;
margin: 0 0 12px 0;
max-width: 92%;
}
.prestations .presta-cta{
margin-top: auto;
align-self: center;
}
.prestations .presta-nav{
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 42px;
height: 42px;
border: 0;
border-radius: 999px;
background: rgba(41, 52, 58, .45);
color: #fff;
box-shadow: 0 8px 18px rgba(0,0,0,.22);
display: grid;
place-items: center;
z-index: 5;
}
.prestations .presta-nav.prev{ left: 10px; }
.prestations .presta-nav.next{ right: 10px; }
.prestations .presta-dots{
display: flex;
justify-content: center;
gap: 7px;
margin-top: 10px;
}
.prestations .presta-dots button{
width: 7px;
height: 7px;
border-radius: 999px;
border: 0;
background: rgba(255,255,255,.55);
}
.prestations .presta-dots button[aria-selected="true"]{
background: #fff;
transform: scale(1.1);
}
}
@media (min-width: 769px){
.prestations .presta-nav,
.prestations .presta-dots{
display: none;
}
}
</style>
<div class="container">
<h2 class="sec-title sec-title--orange"> NOS PRESTATIONS {# (Optional) make the section title editable too — uncomment to use: <button class="cms-edit-btn" data-url="{{ path('cms_text_update') }}" data-key="prestations_title" data-target="#prestations-title" data-mode="text" aria-label="Modifier le titre de la section">✏️</button> #} </h2>
<div class="presta-carousel">
<div class="prestations-grid prestations--overimg" id="presta-track">
<article class="presta-card tone-a"
style="--img:url('{{ asset(prestations[0].img) }}')"
data-title-key="presta_card1_title"
data-text-key="text_action_formation_card1"
data-cta-key="presta_card1_cta"
data-img-key="presta_card1_img">
<div class="presta-inner">
<h3 id="presta1-title">
{{ (prestations is defined ? prestations[0].title : 'Action de formation inter') }}
<button
class="cms-edit-btn"
data-url="{{ path('cms_text_update') }}"
data-key="presta_card1_title"
data-target="#presta1-title"
data-mode="text"
aria-label="Modifier le titre">✏️</button>
</h3>
<p id="presta1-text">
{{ (prestations is defined ? prestations[0].text : text_action_formation_card1)|raw }}
<button
class="cms-edit-btn"
data-url="{{ path('cms_text_update') }}"
data-key="text_action_formation_card1"
data-target="#presta1-text"
data-mode="html"
aria-label="Modifier le texte">✏️</button>
</p>
<a class="btn btn-primary presta-cta" id="presta1-cta" href="https://acoa.fr/intra">
{{ (prestations is defined ? prestations[0].cta : 'Voir plus') }}
</a>
</div>
</article>
<article class="presta-card tone-b"
style="--img:url('{{ asset(prestations[1].img) }}')"
data-title-key="presta_card2_title"
data-text-key="text_action_formation_card2"
data-cta-key="presta_card2_cta"
data-img-key="presta_card2_img">
<div class="presta-inner">
<h3 id="presta2-title">
{{ (prestations is defined ? prestations[1].title : 'Bilan de compétence') }}
<button
class="cms-edit-btn"
data-url="{{ path('cms_text_update') }}"
data-key="presta_card2_title"
data-target="#presta2-title"
data-mode="text"
aria-label="Modifier le titre">✏️</button>
</h3>
<p id="presta2-text">
{{ (prestations is defined ? prestations[1].text : text_action_formation_card2)|raw }}
<button
class="cms-edit-btn"
data-url="{{ path('cms_text_update') }}"
data-key="text_action_formation_card2"
data-target="#presta2-text"
data-mode="html"
aria-label="Modifier le texte">✏️</button>
</p>
<a class="btn btn-primary presta-cta" id="presta2-cta" href="https://acoa.fr/competency-domains">
{{ (prestations is defined ? prestations[1].cta : 'Voir plus') }}
</a>
</div>
</article>
<article class="presta-card tone-c"
style="--img:url('{{ asset(prestations[2].img) }}')"
data-title-key="presta_card3_title"
data-text-key="text_action_formation_card3"
data-cta-key="presta_card3_cta"
data-img-key="presta_card3_img">
<div class="presta-inner">
<h3 id="presta3-title">
{{ (prestations is defined ? prestations[2].title : 'Prestation conseils') }}
<button
class="cms-edit-btn"
data-url="{{ path('cms_text_update') }}"
data-key="presta_card3_title"
data-target="#presta3-title"
data-mode="text"
aria-label="Modifier le titre">✏️</button>
</h3>
<p id="presta3-text">
{{ (prestations is defined ? prestations[2].text : text_action_formation_card3)|raw }}
<button
class="cms-edit-btn"
data-url="{{ path('cms_text_update') }}"
data-key="text_action_formation_card3"
data-target="#presta3-text"
data-mode="html"
aria-label="Modifier le texte">✏️</button>
</p>
</div>
</article>
</div>
<button class="presta-nav prev" aria-label="Slide précédente">‹</button>
<button class="presta-nav next" aria-label="Slide suivante">›</button>
<div class="presta-dots" role="tablist" aria-label="Sélecteur de prestations"></div>
</div>
</div>
<script>
(() => {
const car = document.querySelector('.prestations .presta-carousel');
if (!car) return;
const track = car.querySelector('#presta-track');
const slides = [...track.querySelectorAll('.presta-card')];
const prev = car.querySelector('.presta-nav.prev');
const next = car.querySelector('.presta-nav.next');
const dots = car.querySelector('.presta-dots');
if (!track || !slides.length || !prev || !next || !dots) return;
dots.innerHTML = '';
slides.forEach((_, i) => {
const b = document.createElement('button');
b.type = 'button';
b.setAttribute('role', 'tab');
b.addEventListener('click', () => goTo(i, 'smooth'));
dots.appendChild(b);
});
let index = 0;
let raf = 0;
function clamp(i) {
return Math.max(0, Math.min(slides.length - 1, i));
}
function setActive(i) {
index = clamp(i);
[...dots.children].forEach((b, idx) => {
b.setAttribute('aria-selected', idx === index ? 'true' : 'false');
});
car.dataset.index = String(index);
}
function goTo(i, behavior = 'smooth') {
i = clamp(i);
const tr = track.getBoundingClientRect();
const sr = slides[i].getBoundingClientRect();
const trCenter = tr.left + tr.width / 2;
const srCenter = sr.left + sr.width / 2;
let target = track.scrollLeft + (srCenter - trCenter);
const max = track.scrollWidth - track.clientWidth;
if (target < 0) target = 0;
if (target > max) target = max;
track.scrollTo({ left: target, behavior });
setActive(i);
}
function computeActiveFromScroll() {
const tr = track.getBoundingClientRect();
const center = tr.left + tr.width / 2;
let best = 0;
let bestDist = Infinity;
for (let i = 0; i < slides.length; i++) {
const r = slides[i].getBoundingClientRect();
const c = r.left + r.width / 2;
const d = Math.abs(c - center);
if (d < bestDist) {
bestDist = d;
best = i;
}
}
setActive(best);
}
function onScroll() {
if (raf) return;
raf = requestAnimationFrame(() => {
raf = 0;
computeActiveFromScroll();
});
}
prev.addEventListener('click', () => goTo((+car.dataset.index || 0) - 1, 'smooth'));
next.addEventListener('click', () => goTo((+car.dataset.index || 0) + 1, 'smooth'));
track.addEventListener('scroll', onScroll, { passive: true });
window.addEventListener('resize', () => {
computeActiveFromScroll();
});
setActive(0);
goTo(0, 'auto');
})();
</script>
<div id="cms-modal" class="cms-modal" hidden>
<div class="cms-modal__dialog">
<div class="cms-modal__header"> <strong id="cms-modal-title">Éditer le contenu</strong> <button type="button" class="cms-modal__close" aria-label="Fermer">×</button> </div>
<div class="cms-modal__body">
<textarea id="cms-editor" rows="8"></textarea>
<p class="cms-hint">Astuce : pour les paragraphes, vous pouvez utiliser des sauts de ligne ou du HTML simple (strong, br, a, etc.).</p>
</div>
<div class="cms-modal__footer"> <button type="button" class="btn btn-secondary" id="cms-cancel">Annuler</button> <button type="button" class="btn btn-primary" id="cms-save">Enregistrer</button> </div>
</div>
</div>
</section>
<script>
(() => {
// --- Loader helper for the "Enregistrer" button ---
function setLoading(btn, isLoading, text = 'Enregistrement…') {
if (!btn) return;
if (isLoading) {
if (!btn.dataset._label) btn.dataset._label = btn.textContent;
btn.disabled = true;
btn.setAttribute('aria-busy', 'true');
btn.innerHTML =
'<svg width="16" height="16" viewBox="0 0 50 50" aria-hidden="true" focusable="false" style="vertical-align:-2px;margin-right:8px;">' +
'<circle cx="25" cy="25" r="20" fill="none" stroke="currentColor" stroke-width="6" stroke-opacity="0.25"></circle>' +
'<path d="M25 5 a20 20 0 0 1 0 40" stroke="currentColor" stroke-width="6" fill="none">' +
'<animateTransform attributeName="transform" type="rotate" from="0 25 25" to="360 25 25" dur="0.8s" repeatCount="indefinite"></animateTransform>' +
'</path>' +
'</svg>' + text;
} else {
btn.disabled = false;
btn.removeAttribute('aria-busy');
btn.textContent = btn.dataset._label || 'Enregistrer';
}
}
const modal = document.getElementById('cms-modal');
const editor = document.getElementById('cms-editor');
const btnSave = document.getElementById('cms-save');
const btnCancel = document.getElementById('cms-cancel');
const btnClose = modal.querySelector('.cms-modal__close');
const titleEl = document.getElementById('cms-modal-title');
let current = { url: '', key: '', targetSel: '', mode: 'text' };
function openModal({ url, key, targetSel, mode }) {
current = { url, key, targetSel, mode };
const target = document.querySelector(targetSel);
if (!target) return;
const value = mode === 'html' ? target.innerHTML.trim() : target.textContent.trim();
editor.value = value;
titleEl.textContent = `Éditer: ${key}`;
modal.hidden = false;
editor.focus();
}
function closeModal() {
modal.hidden = true;
editor.value = '';
current = { url: '', key: '', targetSel: '', mode: 'text' };
}
document.addEventListener('click', (ev) => {
const btn = ev.target.closest('.cms-edit-btn');
if (btn) {
ev.preventDefault();
openModal({
url: btn.dataset.url,
key: btn.dataset.key,
targetSel: btn.dataset.target,
mode: btn.dataset.mode || 'text'
});
}
});
btnSave.addEventListener('click', async () => {
const { url, key, targetSel, mode } = current;
if (!url || !key || !targetSel) return;
const text = editor.value.trim();
setLoading(btnSave, true);
try {
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
body: JSON.stringify({ key, text })
});
const data = await res.json();
if (!data.ok) throw new Error(data.error || 'Erreur inconnue');
const target = document.querySelector(targetSel);
if (target) {
if (mode === 'html') target.innerHTML = text;
else target.textContent = text;
}
closeModal();
} catch (e) {
alert('Impossible de sauvegarder: ' + e.message);
} finally {
setLoading(btnSave, false);
}
});
[btnCancel, btnClose, modal].forEach(el => {
el.addEventListener('click', (e) => {
if (e.target === modal || e.target === btnCancel || e.target === btnClose) closeModal();
});
});
document.addEventListener('keydown', (e) => {
if (!modal.hidden && e.key === 'Escape') closeModal();
});
})();
</script>
<script>
(function () {
// --- Loader helper for the "Enregistrer" button ---
function setLoading(btn, isLoading, text = 'Enregistrement…') {
if (!btn) return;
if (isLoading) {
if (!btn.dataset._label) btn.dataset._label = btn.textContent;
btn.disabled = true;
btn.setAttribute('aria-busy', 'true');
btn.innerHTML =
'<svg width="16" height="16" viewBox="0 0 50 50" aria-hidden="true" focusable="false" style="vertical-align:-2px;margin-right:8px;">' +
'<circle cx="25" cy="25" r="20" fill="none" stroke="currentColor" stroke-width="6" stroke-opacity="0.25"></circle>' +
'<path d="M25 5 a20 20 0 0 1 0 40" stroke="currentColor" stroke-width="6" fill="none">' +
'<animateTransform attributeName="transform" type="rotate" from="0 25 25" to="360 25 25" dur="0.8s" repeatCount="indefinite"></animateTransform>' +
'</path>' +
'</svg>' + text;
} else {
btn.disabled = false;
btn.removeAttribute('aria-busy');
btn.textContent = btn.dataset._label || 'Enregistrer';
}
}
const UPDATE_URL = "{{ path('cms_text_update') }}";
const $ = (sel, root=document) => root.querySelector(sel);
let modal, closeEl, btnSave, btnCancel;
let inKicker, inTitle, inP1, inP2, inCta, inPill1;
let ctx = null;
function openActuEditor(root) {
if (!root) return;
const els = {
kicker: $('#actu-kicker', root),
title: $('#actu-title', root),
p1: $('#actu-p1', root),
p2: $('#actu-p2', root),
cta: $('#actu-cta', root),
pill1: $('#actu-pill1-label', root)
};
const keys = {
kicker: root.dataset.kickerKey || '',
title: root.dataset.titleKey || '',
p1: root.dataset.p1Key || '',
p2: root.dataset.p2Key || '',
cta: root.dataset.ctaKey || '',
pill1: root.dataset.pill1Key || ''
};
ctx = { root, els, keys };
inKicker.value = (els.kicker?.textContent || 'Actualités du moment').trim();
inTitle.value = (els.title?.textContent || 'Titre de l’actualité').trim();
inP1.value = (els.p1?.innerHTML || '').trim();
inP2.value = (els.p2?.innerHTML || '').trim();
inCta.value = (els.cta?.textContent || 'Découvrir').trim();
inPill1.value = (els.pill1?.textContent || 'Mieux Gérer').trim();
modal.hidden = false;
inTitle.focus();
}
function closeModal() {
if (!modal) return;
modal.hidden = true;
ctx = null;
}
async function saveAll() {
if (!ctx) return;
const payloads = [];
if (ctx.keys.kicker) payloads.push({ key: ctx.keys.kicker, text: inKicker.value.trim() });
if (ctx.keys.title) payloads.push({ key: ctx.keys.title, text: inTitle.value.trim() });
if (ctx.keys.p1) payloads.push({ key: ctx.keys.p1, text: inP1.value.trim() });
if (ctx.keys.p2) payloads.push({ key: ctx.keys.p2, text: inP2.value.trim() });
if (ctx.keys.cta) payloads.push({ key: ctx.keys.cta, text: inCta.value.trim() });
if (ctx.keys.pill1) payloads.push({ key: ctx.keys.pill1, text: inPill1.value.trim() });
setLoading(btnSave, true);
try {
const results = await Promise.all(payloads.map(pl =>
fetch(UPDATE_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
body: JSON.stringify(pl)
}).then(r => r.json())
));
const bad = results.find(r => !r || r.ok === false);
if (bad) throw new Error(bad.error || 'Erreur inconnue');
if (ctx.els.kicker) ctx.els.kicker.textContent = inKicker.value.trim();
if (ctx.els.title) ctx.els.title.textContent = inTitle.value.trim();
if (ctx.els.p1) ctx.els.p1.innerHTML = inP1.value.trim();
if (ctx.els.p2) ctx.els.p2.innerHTML = inP2.value.trim();
if (ctx.els.cta) ctx.els.cta.textContent = inCta.value.trim();
if (ctx.els.pill1) ctx.els.pill1.textContent = inPill1.value.trim();
closeModal();
} catch (err) {
location.reload();
//alert('Modification effectuée!');
} finally {
setLoading(btnSave, false);
}
}
document.addEventListener('DOMContentLoaded', () => {
modal = $('#actu-editor');
if (!modal) { console.warn('[Actu Editor] Modal #actu-editor not found.'); return; }
closeEl = $('.cms-modal__close', modal);
btnSave = $('#ae-save', modal);
btnCancel = $('#ae-cancel', modal);
inKicker = $('#ae-kicker', modal);
inTitle = $('#ae-title', modal);
inP1 = $('#ae-p1', modal);
inP2 = $('#ae-p2', modal);
inCta = $('#ae-cta', modal);
inPill1 = $('#ae-pill1', modal);
document.addEventListener('click', (e) => {
const btn = e.target.closest('.cms-actu-edit');
if (!btn) return;
const root = btn.closest('.actu-hero');
if (root) openActuEditor(root);
});
btnSave?.addEventListener('click', saveAll);
btnCancel?.addEventListener('click', closeModal);
closeEl?.addEventListener('click', closeModal);
modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(); });
document.addEventListener('keydown', (e) => { if (!modal.hidden && e.key === 'Escape') closeModal(); });
});
})();
</script>
<style>
.cms-modal{
position: fixed; inset: 0;
background: rgba(0,0,0,.45);
display: grid; place-items: center;
z-index: 9999;
color: #111;
}
.cms-modal__dialog{
background: #fff;
width: min(740px, 96vw);
max-height: 90vh;
border-radius: .75rem;
box-shadow: 0 10px 30px rgba(0,0,0,.25);
overflow: hidden;
display: flex;
flex-direction: column;
}
.cms-modal__header,
.cms-modal__footer{
flex: 0 0 auto;
padding: 12px 16px;
background: #f7f7f8;
display: flex; align-items: center; justify-content: space-between;
}
.cms-modal__body{
flex: 1 1 auto;
padding: 12px 16px;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.cms-label{ display: grid; gap: 6px; font-weight: 600; color: #111; }
.cms-label input, .cms-label textarea{
background: #fff; color: #111;
font: inherit; padding: 10px;
border: 1px solid #e5e7eb; border-radius: .5rem;
}
.cms-label input::placeholder, .cms-label textarea::placeholder{ color:#6b7280; }
.cms-modal__dialog, .cms-modal__dialog *{ color: #111; }
.cms-modal__close{ background:none; border:none; font-size:1.25rem; cursor:pointer }
.cms-modal[hidden]{ display:none !important }
html.modal-open, body.modal-open{ overflow: hidden; }
.actu-hero{ position: relative; }
.cms-actu-edit{
position: absolute; top: 12px; right: 12px;
z-index: 9;
background: #fff; border: 1px solid #e5e7eb; border-radius: .5rem;
font-size: .9rem; padding: .15rem .4rem; cursor: pointer;
pointer-events: auto;
}
.actu-shade{ pointer-events: none; }
</style>
<script>
(function () {
// --- Loader helper for the "Enregistrer" button ---
function setLoading(btn, isLoading, text = 'Enregistrement…') {
if (!btn) return;
if (isLoading) {
if (!btn.dataset._label) btn.dataset._label = btn.textContent;
btn.disabled = true;
btn.setAttribute('aria-busy', 'true');
btn.innerHTML =
'<svg width="16" height="16" viewBox="0 0 50 50" aria-hidden="true" focusable="false" style="vertical-align:-2px;margin-right:8px;">' +
'<circle cx="25" cy="25" r="20" fill="none" stroke="currentColor" stroke-width="6" stroke-opacity="0.25"></circle>' +
'<path d="M25 5 a20 20 0 0 1 0 40" stroke="currentColor" stroke-width="6" fill="none">' +
'<animateTransform attributeName="transform" type="rotate" from="0 25 25" to="360 25 25" dur="0.8s" repeatCount="indefinite"></animateTransform>' +
'</path>' +
'</svg>' + text;
} else {
btn.disabled = false;
btn.removeAttribute('aria-busy');
btn.textContent = btn.dataset._label || 'Enregistrer';
}
}
const UPDATE_URL = "{{ path('cms_text_update') }}";
const $ = (sel, root=document) => root.querySelector(sel);
let modal, closeEl, btnSave, btnCancel;
let inTitle, inV1, inL1, inV2, inL2, inV3, inL3, inV4, inL4, inV5, inL5, inImg1, inImg2, inImg3, inImg4, inImg5;
let ctx = null;
function openEditor(root) {
const els = {
title: $('#chiffres-title', root),
v1: $('#chiffre1-value', root), l1: $('#chiffre1-label', root),
v2: $('#chiffre2-value', root), l2: $('#chiffre2-label', root),
v3: $('#chiffre3-value', root), l3: $('#chiffre3-label', root),
v4: $('#chiffre4-value', root), l4: $('#chiffre4-label', root),
v5: $('#chiffre5-value', root), l5: $('#chiffre5-label', root),
img1: $('li:nth-child(1) img', root),
img2: $('li:nth-child(2) img', root),
img3: $('li:nth-child(3) img', root),
img4: $('li:nth-child(4) img', root),
img5: $('li:nth-child(5) img', root),
};
const keys = {
title: root.dataset.titleKey || '',
v1: root.dataset.v1Key || '', l1: root.dataset.l1Key || '',
v2: root.dataset.v2Key || '', l2: root.dataset.l2Key || '',
v3: root.dataset.v3Key || '', l3: root.dataset.l3Key || '',
v4: root.dataset.v4Key || '', l4: root.dataset.l4Key || '',
v5: root.dataset.v5Key || '', l5: root.dataset.l5Key || '',
img1: root.dataset.img1Key || '',
img2: root.dataset.img2Key || '',
img3: root.dataset.img3Key || '',
img4: root.dataset.img4Key || '',
img5: root.dataset.img5Key || '',
};
ctx = { root, els, keys };
inTitle.value = (els.title?.textContent || '').trim();
inV1.value = (els.v1?.textContent || '').trim();
inL1.value = (els.l1?.textContent || '').trim();
inV2.value = (els.v2?.textContent || '').trim();
inL2.value = (els.l2?.textContent || '').trim();
inV3.value = (els.v3?.textContent || '').trim();
inL3.value = (els.l3?.textContent || '').trim();
inV4.value = (els.v4?.textContent || '').trim();
inL4.value = (els.l4?.textContent || '').trim();
inV5.value = (els.v5?.textContent || '').trim();
inL5.value = (els.l5?.textContent || '').trim();
inImg1.value = els.img1?.getAttribute('src') || '';
inImg2.value = els.img2?.getAttribute('src') || '';
inImg3.value = els.img3?.getAttribute('src') || '';
inImg4.value = els.img4?.getAttribute('src') || '';
inImg5.value = els.img5?.getAttribute('src') || '';
modal.hidden = false;
document.documentElement.classList.add('modal-open');
document.body.classList.add('modal-open');
inTitle.focus();
}
function closeModal() {
modal.hidden = true;
document.documentElement.classList.remove('modal-open');
document.body.classList.remove('modal-open');
ctx = null;
}
async function saveAll() {
if (!ctx) return;
const payloads = [];
if (ctx.keys.title) payloads.push({ key: ctx.keys.title, text: inTitle.value.trim() });
if (ctx.keys.v1) payloads.push({ key: ctx.keys.v1, text: inV1.value.trim() });
if (ctx.keys.l1) payloads.push({ key: ctx.keys.l1, text: inL1.value.trim() });
if (ctx.keys.v2) payloads.push({ key: ctx.keys.v2, text: inV2.value.trim() });
if (ctx.keys.l2) payloads.push({ key: ctx.keys.l2, text: inL2.value.trim() });
if (ctx.keys.v3) payloads.push({ key: ctx.keys.v3, text: inV3.value.trim() });
if (ctx.keys.l3) payloads.push({ key: ctx.keys.l3, text: inL3.value.trim() });
if (ctx.keys.v4) payloads.push({ key: ctx.keys.v4, text: inV4.value.trim() });
if (ctx.keys.l4) payloads.push({ key: ctx.keys.l4, text: inL4.value.trim() });
if (ctx.keys.v5) payloads.push({ key: ctx.keys.v5, text: inV5.value.trim() });
if (ctx.keys.l5) payloads.push({ key: ctx.keys.l5, text: inL5.value.trim() });
if (ctx.keys.img1) payloads.push({ key: ctx.keys.img1, text: inImg1.value.trim() });
if (ctx.keys.img2) payloads.push({ key: ctx.keys.img2, text: inImg2.value.trim() });
if (ctx.keys.img3) payloads.push({ key: ctx.keys.img3, text: inImg3.value.trim() });
if (ctx.keys.img4) payloads.push({ key: ctx.keys.img4, text: inImg4.value.trim() });
if (ctx.keys.img5) payloads.push({ key: ctx.keys.img5, text: inImg5.value.trim() });
setLoading(btnSave, true);
try {
const results = await Promise.all(payloads.map(pl =>
fetch(UPDATE_URL, {
method:'POST', headers:{'Content-Type':'application/json','Accept':'application/json'},
body: JSON.stringify(pl)
}).then(r=>r.json())
));
const bad = results.find(r => !r || r.ok === false);
if (bad) throw new Error(bad.error || 'Erreur inconnue');
if (ctx.els.title) ctx.els.title.textContent = inTitle.value.trim();
if (ctx.els.v1) ctx.els.v1.textContent = inV1.value.trim();
if (ctx.els.l1) ctx.els.l1.textContent = inL1.value.trim();
if (ctx.els.v2) ctx.els.v2.textContent = inV2.value.trim();
if (ctx.els.l2) ctx.els.l2.textContent = inL2.value.trim();
if (ctx.els.v3) ctx.els.v3.textContent = inV3.value.trim();
if (ctx.els.l3) ctx.els.l3.textContent = inL3.value.trim();
if (ctx.els.v4) ctx.els.v4.textContent = inV4.value.trim();
if (ctx.els.l4) ctx.els.l4.textContent = inL4.value.trim();
if (ctx.els.v5) ctx.els.v5.textContent = inV5.value.trim();
if (ctx.els.l5) ctx.els.l5.textContent = inL5.value.trim();
if (ctx.els.img1 && inImg1.value.trim()) ctx.els.img1.setAttribute('src', inImg1.value.trim());
if (ctx.els.img2 && inImg2.value.trim()) ctx.els.img2.setAttribute('src', inImg2.value.trim());
if (ctx.els.img3 && inImg3.value.trim()) ctx.els.img3.setAttribute('src', inImg3.value.trim());
if (ctx.els.img4 && inImg4.value.trim()) ctx.els.img4.setAttribute('src', inImg4.value.trim());
if (ctx.els.img5 && inImg5.value.trim()) ctx.els.img5.setAttribute('src', inImg5.value.trim());
closeModal();
} catch (err) {
location.reload();
//alert('Modification effectuée!');
} finally {
setLoading(btnSave, false);
}
}
document.addEventListener('DOMContentLoaded', () => {
modal = document.querySelector('#kpis-editor');
if (!modal) return;
closeEl = modal.querySelector('.cms-modal__close');
btnSave = modal.querySelector('#kpe-save');
btnCancel = modal.querySelector('#kpe-cancel');
inTitle = modal.querySelector('#kpe-title');
inV1 = modal.querySelector('#kpe-v1'); inL1 = modal.querySelector('#kpe-l1');
inV2 = modal.querySelector('#kpe-v2'); inL2 = modal.querySelector('#kpe-l2');
inV3 = modal.querySelector('#kpe-v3'); inL3 = modal.querySelector('#kpe-l3');
inV4 = modal.querySelector('#kpe-v4'); inL4 = modal.querySelector('#kpe-l4');
inV5 = modal.querySelector('#kpe-v5'); inL5 = modal.querySelector('#kpe-l5');
inImg1 = modal.querySelector('#kpe-img1');
inImg2 = modal.querySelector('#kpe-img2');
inImg3 = modal.querySelector('#kpe-img3');
inImg4 = modal.querySelector('#kpe-img4');
inImg5 = modal.querySelector('#kpe-img5');
document.addEventListener('click', (e) => {
const btn = e.target.closest('.cms-kpis-edit');
if (!btn) return;
const root = btn.closest('.chiffres__panel');
if (root) openEditor(root);
});
btnSave?.addEventListener('click', saveAll);
btnCancel?.addEventListener('click', closeModal);
closeEl?.addEventListener('click', closeModal);
modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(); });
document.addEventListener('keydown', (e) => { if (!modal.hidden && e.key === 'Escape') closeModal(); });
});
})();
</script>
{# --------------------------- TRUST (logos + headings) --------------------------- #}
<section class="trust">
<div class="container">
<p class="trust__kicker" style="font-size:20px;">ILS NOUS FONT CONFIANCE !</p>
{% set trustPartners = [
{ src: 'assets/img/mimethys.webp', alt: 'Mimethys' },
{ src: 'assets/img/pegase.webp', alt: 'Pegase Processus' },
{ src: 'assets/img/gepi.webp', alt: 'GEPI Conseil' },
{ src: 'assets/img/poste.png', alt: 'La Poste' }
] %}
{% set trustApprovals = [
{ src: 'assets/img/iprp.jpg', alt: 'IPRP' },
{ src: 'assets/img/agefiph.png', alt: 'Agefiph' }
] %}
<div class="trust__group">
<h3 class="trust__label" style="font-size:18px;" >Nos partenaires</h3>
<div class="trust__logos">
{% for logo in trustPartners %}
<img src="{{ asset(logo.src) }}" alt="{{ logo.alt }}" class="logo">
{% endfor %}
</div>
</div>
<div class="trust__group">
<h3 class="trust__label" style="font-size:18px;">Nos agréments et habilitations</h3>
<div class="trust__logos">
{% for logo in trustApprovals %}
<img src="{{ asset(logo.src) }}" alt="{{ logo.alt }}" class="logo">
{% endfor %}
</div>
</div>
</div>
</section>
{# ==== CHIFFRES CLÉS ==== #}
<section class="chiffres" aria-labelledby="chiffres-title">
<div class="container">
<div class="narrow">
<div class="chiffres__panel"
data-title-key="kpis_title"
data-v1-key="chiffre_cles_card1_value" data-l1-key="chiffre_cles_card1_label" data-img1-key="kpis_img1"
data-v2-key="chiffre_cles_card2_value" data-l2-key="chiffre_cles_card2_label" data-img2-key="kpis_img2"
data-v3-key="chiffre_cles_card3_value" data-l3-key="chiffre_cles_card3_label" data-img3-key="kpis_img3"
data-v4-key="chiffre_cles_card4_value" data-l4-key="chiffre_cles_card4_label" data-img4-key="kpis_img4"
data-v5-key="chiffre_cles_card5_value" data-l5-key="chiffre_cles_card5_label" data-img5-key="kpis_img5">
<button class="cms-kpis-edit" type="button" aria-label="Éditer les chiffres">✏️</button>
<h2 id="chiffres-title" class="chiffres__title">
{{ (kpis is defined and kpis.title is defined) ? kpis.title : ('Nos chiffres clés !'|trans) }}
</h2>
{% set items = [
{
img: 'pexels-fauxels-3182831.png',
value: (kpis is defined and kpis.items is defined and kpis.items[0] is defined) ? kpis.items[0].value : chiffre_cles_card1_value,
label: (kpis is defined and kpis.items is defined and kpis.items[0] is defined) ? kpis.items[0].label : chiffre_cles_card1_label
},
{
img: 'pexels-growthgal-3719037.png',
value: (kpis is defined and kpis.items is defined and kpis.items[1] is defined) ? kpis.items[1].value : chiffre_cles_card2_value,
label: (kpis is defined and kpis.items is defined and kpis.items[1] is defined) ? kpis.items[1].label : chiffre_cles_card2_label
},
{
img: 'pexels-shkrabaanthony-5292192.png',
value: (kpis is defined and kpis.items is defined and kpis.items[2] is defined) ? kpis.items[2].value : chiffre_cles_card3_value,
label: (kpis is defined and kpis.items is defined and kpis.items[2] is defined) ? kpis.items[2].label : chiffre_cles_card3_label
},
{
img: 'pexels-4.png',
value: (kpis is defined and kpis.items is defined and kpis.items[3] is defined) ? kpis.items[3].value : chiffre_cles_card4_value,
label: (kpis is defined and kpis.items is defined and kpis.items[3] is defined) ? kpis.items[3].label : chiffre_cles_card4_label
},
{
img: 'pexels-5.png',
value: (kpis is defined and kpis.items is defined and kpis.items[4] is defined) ? kpis.items[4].value : chiffre_cles_card5_value,
label: (kpis is defined and kpis.items is defined and kpis.items[4] is defined) ? kpis.items[4].label : chiffre_cles_card5_label
}
] %}
<ul class="chiffres__grid" role="list">
<li>
<figure class="chiffre">
<div class="chiffre__media">
<img src="{{ asset(kpis.items[0].img) }}" alt="">
<figcaption class="chiffre__content">
<span class="chiffre__value" id="chiffre1-value">{{ items[0].value }}</span>
<span class="chiffre__label" id="chiffre1-label">{{ items[0].label }}</span>
</figcaption>
</div>
</figure>
</li>
<li>
<figure class="chiffre">
<div class="chiffre__media">
<img src="{{ asset(kpis.items[1].img) }}" alt="">
<figcaption class="chiffre__content">
<span class="chiffre__value" id="chiffre2-value">{{ items[1].value }}</span>
<span class="chiffre__label" id="chiffre2-label">{{ items[1].label }}</span>
</figcaption>
</div>
</figure>
</li>
<li>
<figure class="chiffre">
<div class="chiffre__media">
<img src="{{ asset(kpis.items[2].img) }}" alt="">
<figcaption class="chiffre__content">
<span class="chiffre__value" id="chiffre3-value">{{ items[2].value }}</span>
<span class="chiffre__label" id="chiffre3-label">{{ items[2].label }}</span>
</figcaption>
</div>
</figure>
</li>
<li>
<figure class="chiffre">
<div class="chiffre__media">
<img src="{{ asset(kpis.items[3].img) }}" alt="">
<figcaption class="chiffre__content">
<span class="chiffre__value" id="chiffre4-value">{{ items[3].value }}</span>
<span class="chiffre__label" id="chiffre4-label">{{ items[3].label }}</span>
</figcaption>
</div>
</figure>
</li>
<li>
<figure class="chiffre">
<div class="chiffre__media">
<img src="{{ asset(kpis.items[4].img) }}" alt="">
<figcaption class="chiffre__content">
<span class="chiffre__value" id="chiffre5-value">{{ items[4].value }}</span>
<span class="chiffre__label" id="chiffre5-label">{{ items[4].label }}</span>
</figcaption>
</div>
</figure>
</li>
</ul>
<div class="only-mobile">
<div class="chiffres__carousel" aria-roledescription="carousel">
<div class="chiffres__viewport">
<ul class="chiffres__track" id="kpis-track" role="listbox" aria-label="Chiffres clés">
<li class="chiffres__slide" role="option" aria-selected="true">
<figure class="chiffre">
<div class="chiffre__media">
<img src="{{ asset(kpis.items[0].img) }}" alt="">
<figcaption class="chiffre__content">
<span class="chiffre__value">{{ items[0].value }}</span>
<span class="chiffre__label">{{ items[0].label }}</span>
</figcaption>
</div>
</figure>
</li>
<li class="chiffres__slide" role="option" aria-selected="false">
<figure class="chiffre">
<div class="chiffre__media">
<img src="{{ asset(kpis.items[1].img) }}" alt="">
<figcaption class="chiffre__content">
<span class="chiffre__value">{{ items[1].value }}</span>
<span class="chiffre__label">{{ items[1].label }}</span>
</figcaption>
</div>
</figure>
</li>
<li class="chiffres__slide" role="option" aria-selected="false">
<figure class="chiffre">
<div class="chiffre__media">
<img src="{{ asset(kpis.items[2].img) }}" alt="">
<figcaption class="chiffre__content">
<span class="chiffre__value">{{ items[2].value }}</span>
<span class="chiffre__label">{{ items[2].label }}</span>
</figcaption>
</div>
</figure>
</li>
<li class="chiffres__slide" role="option" aria-selected="false">
<figure class="chiffre">
<div class="chiffre__media">
<img src="{{ asset(kpis.items[3].img) }}" alt="">
<figcaption class="chiffre__content">
<span class="chiffre__value">{{ items[3].value }}</span>
<span class="chiffre__label">{{ items[3].label }}</span>
</figcaption>
</div>
</figure>
</li>
<li class="chiffres__slide" role="option" aria-selected="false">
<figure class="chiffre">
<div class="chiffre__media">
<img src="{{ asset(kpis.items[4].img) }}" alt="">
<figcaption class="chiffre__content">
<span class="chiffre__value">{{ items[4].value }}</span>
<span class="chiffre__label">{{ items[4].label }}</span>
</figcaption>
</div>
</figure>
</li>
</ul>
<button class="chiffres__arrow chiffres__arrow--prev" type="button" aria-label="Diapo précédente">‹</button>
<button class="chiffres__arrow chiffres__arrow--next" type="button" aria-label="Diapo suivante">›</button>
</div>
<div class="chiffres__dots" aria-label="Pagination"></div>
</div>
</div>
</div>
<div id="kpis-editor" class="cms-modal" hidden>
<div class="cms-modal__dialog" role="dialog" aria-modal="true" aria-labelledby="kpe-title-label">
<div class="cms-modal__header">
<strong id="kpe-title-label">Éditer les chiffres</strong>
<button type="button" class="cms-modal__close" aria-label="Fermer">×</button>
</div>
<div class="cms-modal__body">
<label class="cms-label">Titre
<input id="kpe-title" type="text" autocomplete="off">
</label>
<fieldset class="cms-fieldset">
<legend>Carte 1</legend>
<label class="cms-label">Valeur <input id="kpe-v1" type="text" autocomplete="off"></label>
<label class="cms-label">Label <input id="kpe-l1" type="text" autocomplete="off"></label>
</fieldset>
<fieldset class="cms-fieldset">
<legend>Carte 2</legend>
<label class="cms-label">Valeur <input id="kpe-v2" type="text" autocomplete="off"></label>
<label class="cms-label">Label <input id="kpe-l2" type="text" autocomplete="off"></label>
</fieldset>
<fieldset class="cms-fieldset">
<legend>Carte 3</legend>
<label class="cms-label">Valeur <input id="kpe-v3" type="text" autocomplete="off"></label>
<label class="cms-label">Label <input id="kpe-l3" type="text" autocomplete="off"></label>
</fieldset>
<fieldset class="cms-fieldset">
<legend>Carte 4</legend>
<label class="cms-label">Valeur <input id="kpe-v4" type="text" autocomplete="off"></label>
<label class="cms-label">Label <input id="kpe-l4" type="text" autocomplete="off"></label>
</fieldset>
<fieldset class="cms-fieldset">
<legend>Carte 5</legend>
<label class="cms-label">Valeur <input id="kpe-v5" type="text" autocomplete="off"></label>
<label class="cms-label">Label <input id="kpe-l5" type="text" autocomplete="off"></label>
</fieldset>
<fieldset class="cms-fieldset">
<legend>Images</legend>
<label class="cms-label">
Carte 1 — Image
<div class="cms-img-row">
<input id="kpe-img1" type="text" placeholder="ex: assets/img/visuel1.png">
<input id="kpe-file1" type="file" accept="image/png,image/jpeg,image/webp">
</div>
</label>
<label class="cms-label">
Carte 2 — Image
<div class="cms-img-row">
<input id="kpe-img2" type="text" placeholder="ex: assets/img/visuel2.png">
<input id="kpe-file2" type="file" accept="image/png,image/jpeg,image/webp">
</div>
</label>
<label class="cms-label">
Carte 3 — Image
<div class="cms-img-row">
<input id="kpe-img3" type="text" placeholder="ex: assets/img/visuel3.png">
<input id="kpe-file3" type="file" accept="image/png,image/jpeg,image/webp">
</div>
</label>
<label class="cms-label">
Carte 4 — Image
<div class="cms-img-row">
<input id="kpe-img4" type="text" placeholder="ex: assets/img/visuel4.png">
<input id="kpe-file4" type="file" accept="image/png,image/jpeg,image/webp">
</div>
</label>
<label class="cms-label">
Carte 5 — Image
<div class="cms-img-row">
<input id="kpe-img5" type="text" placeholder="ex: assets/img/visuel5.png">
<input id="kpe-file5" type="file" accept="image/png,image/jpeg,image/webp">
</div>
</label>
</fieldset>
</div>
<div class="cms-modal__footer">
<button type="button" class="btn btn-secondary" id="kpe-cancel">Annuler</button>
<button type="button" class="btn btn-primary" id="kpe-save">Enregistrer</button>
</div>
</div>
</div>
</div>
</div>
</section>
<style>
.only-desktop { display: none; }
.only-mobile { display: block; }
@media (min-width: 992px){
.only-desktop { display: block !important; }
.only-mobile { display: none !important; }
}
.only-mobile .chiffres__viewport{
width: min(620px, 96vw);
margin-inline:auto;
overflow:hidden;
position:relative;
}
.only-mobile .chiffres__track{
display:flex; gap:0; transition:.45s ease; will-change:transform;
}
.only-mobile .chiffres__slide{
flex:0 0 100%; min-width:100%;
display:flex; justify-content:center;
}
.only-mobile .chiffre__media{
width:92%; max-width:560px; aspect-ratio:4/5; border-radius:28px;
overflow:hidden; box-shadow:0 10px 30px rgba(0,0,0,.35);
}
.only-mobile .chiffre__media img{ width:100%; height:100%; object-fit:cover; }
.only-mobile .chiffre__media::after{
content:""; position:absolute; inset:0;
background:linear-gradient(180deg, rgba(0,0,0,.25) 20%, rgba(0,0,0,.55) 80%);
}
.only-mobile .chiffre__content{
position:absolute; left:18px; right:18px; bottom:18px; z-index:1; color:#fff;
text-shadow:0 2px 6px rgba(0,0,0,.45);
}
.only-mobile .chiffre__value{ display:block; font-weight:800; font-size:clamp(26px, 7vw, 34px); line-height:1.05; }
.only-mobile .chiffre__label{ display:block; font-size:13px; }
.only-mobile .chiffres__arrow{
position:absolute; width:44px; height:44px; border-radius:50%; border:0;
background:#fff; color:#1c2d4a; font-size:28px; display:grid; place-items:center;
box-shadow:0 8px 18px rgba(0,0,0,.25);
transform:translate(-50%, -50%);
z-index:3;
}
.only-mobile .chiffres__dots{ display:flex; justify-content:center; gap:8px; margin-top:10px; }
.only-mobile .chiffres__dots button{ width:8px; height:8px; border:0; border-radius:50%; background:#d9dde7; opacity:.7; }
.only-mobile .chiffres__dots button[aria-current="true"]{ opacity:1; background:#fff; transform:scale(1.15); }
.only-mobile .chiffres__cta{ display:flex; justify-content:center; margin-top:8px; }
.only-mobile .btn.btn-light{ background:#fff; color:#0a2a54; border:0; padding:8px 16px; border-radius:8px; font-weight:700; }
.only-desktop { display: none !important; }
.only-mobile { display: block !important; }
@media (min-width: 992px){
.only-desktop { display: grid !important; }
.only-mobile { display: none !important; }
}
@media (max-width: 991.98px){
.chiffres__panel > .chiffres__grid { display: none !important; }
}
@media (min-width: 992px){
.chiffres__panel .only-mobile { display: none !important; }
}
@media (max-width: 991.98px){
.chiffres .chiffres__title{
text-align: center !important;
margin: 0 auto 18px !important;
display: block;
width: 100%;
}
}
@media (max-width: 991.98px){
.catalogue .catalogue-grid{
display: flex;
flex-direction: column;
align-items: center;
}
.catalogue .catalogue-art{ order: -1; display:flex; justify-content:center; }
.catalogue .cat-card{
width: min(82vw, 320px);
aspect-ratio: 3 / 4;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 12px 30px rgba(0,0,0,.2);
position: relative;
}
.catalogue .cat-card {
aspect-ratio: 3 / 4;
}
.catalogue .catalogue-copy{
order: 0;
width: 100%;
}
.catalogue .catalogue-copy .catalogue-text-wrap{
max-width: 560px;
margin: 0 auto;
padding: 0 16px;
text-align: left;
}
.catalogue .catalogue-copy .sec-title{
color: #ff6a3d;
font-weight: 800;
line-height: 1.3;
font-size: clamp(18px, 4.8vw, 22px);
margin: 12px 0 10px;
text-transform: uppercase;
}
.catalogue .catalogue-copy .sec-text{
font-size: 14px;
line-height: 1.6;
margin: 0 0 10px;
}
.catalogue .catalogue-copy .btn-row{
display: flex;
justify-content: center;
align-items: center;
gap: 12px;
flex-wrap: wrap;
margin-top: 8px;
}
.catalogue .catalogue-grid > *{ margin: 0; }
}
@media (max-width: 991.98px){
.trust{
background:#fff;
padding: 18px 0 28px;
}
.trust .trust__kicker{
text-align:center;
margin: 0 0 20px;
font-weight:800;
font-size:14px;
color:#ff6a3d;
text-transform:uppercase;
letter-spacing:.2px;
}
.trust .trust__group + .trust__group{ margin-top:28px; }
.trust .trust__label{
margin:0 0 16px;
font-size:18px;
}
.trust .trust__logos{
display:grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
justify-items:center;
gap: 22px 36px;
max-width: 320px;
margin: 0 auto;
}
.trust .trust__logos .logo{
width: 72px;
height: auto;
object-fit: contain;
display:block;
}
}
@media (max-width: 991.98px){
.competences .panel--blue{
position: relative;
border-radius: 12px;
overflow: hidden;
padding: 24px 16px 18px;
}
.competences .panel--blue::after{
content:"";
position:absolute; inset:0;
background: rgba(13, 44, 88, .55);
pointer-events:none;
}
.competences .sec-title{
position: relative;
z-index: 1;
color:#fff;
text-align:center;
text-transform: uppercase;
letter-spacing:.02em;
font-weight:800;
font-size: 14px;
margin: 0 0 14px;
}
.competences .competences-grid{
position: relative; z-index: 1;
display: grid !important;
grid-template-columns: 1fr !important;
gap: 12px;
max-width: 340px;
margin: 0 auto;
}
.competences .comp-card{
display: grid;
grid-template-columns: 34px 1fr;
align-items: center;
column-gap: 12px;
padding: 10px 12px;
border-radius: 12px;
background: rgba(255,255,255,.08);
backdrop-filter: blur(1px);
}
.competences .comp-icon{
width: 28px; height: 28px; object-fit: contain;
filter: brightness(1.05);
}
.competences .comp-card p{
margin: 0;
color:#fff;
font-weight: 700;
font-size: 13px;
line-height: 1.45;
}
.competences .center{
position: relative; z-index: 1;
margin-top: 16px !important;
display: flex; justify-content: center;
}
.competences .btn.btn-secondary{
background:#fff; color:#0A2A45; border:0;
border-radius: 8px; padding: 8px 16px; font-weight: 700;
box-shadow: 0 6px 14px rgba(0,0,0,.18);
}
}
.prestations--overimg .presta-card{
position: relative;
isolation: isolate;
border-radius: 24px;
overflow: hidden;
padding: 24px;
background: var(--img) center/cover no-repeat;
background-clip: border-box;
}
.prestations--overimg .presta-card::after{
content:"";
position: absolute;
inset: 0;
z-index: 0;
pointer-events: none;
background:
linear-gradient(180deg,
rgba(10,43,88,0.15) 0%,
rgba(10,43,88,0.45) 40%,
rgba(10,43,88,0.78) 100%);
}
.prestations--overimg .presta-inner{
position: relative;
z-index: 1;
display:flex;
flex-direction:column;
justify-content:flex-start;
height:100%;
color: #fff;
text-shadow: 0 2px 6px rgba(0,0,0,.45);
}
.prestations--overimg .presta-inner h3,
.prestations--overimg .presta-inner p{ color:#fff; }
@media (min-width: 992px){
.chiffres__grid{
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 24px;
margin: 0;
padding: 6px;
list-style: none;
}
.chiffres__grid > li{
flex: 0 1 calc(33.333% - 16px);
}
}
@media (max-width: 991.98px){
.chiffres__grid{
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 24px;
margin: 0;
padding: 6px;
list-style: none;
}
.chiffres__grid > li{
flex: 0 0 100%;
}
}
</style>
<script>
(() => {
const panel = document.querySelector('.chiffres__panel');
if (!panel) return;
const mql = window.matchMedia('(max-width: 991.98px)');
let inited = false;
let viewport, track, slides, prevBtn, nextBtn, dotsWrap;
let index = 0;
function cacheEls(){
const mobileRoot = panel.querySelector('.only-mobile');
if (!mobileRoot) return false;
viewport = mobileRoot.querySelector('.chiffres__viewport');
track = mobileRoot.querySelector('.chiffres__track');
slides = [...mobileRoot.querySelectorAll('.chiffres__slide')];
prevBtn = mobileRoot.querySelector('.chiffres__arrow--prev');
nextBtn = mobileRoot.querySelector('.chiffres__arrow--next');
dotsWrap = mobileRoot.querySelector('.chiffres__dots');
return !!(viewport && track && slides.length && prevBtn && nextBtn && dotsWrap);
}
function buildDots(){
dotsWrap.innerHTML = '';
slides.forEach((_, i) => {
const b = document.createElement('button');
b.type = 'button';
b.setAttribute('aria-label', `Aller à la diapo ${i+1}`);
if (i === 0) b.setAttribute('aria-current', 'true');
b.addEventListener('click', () => goTo(i));
dotsWrap.appendChild(b);
});
}
function positionArrows(){
if (!viewport || !slides[index]) return;
const card = slides[index].querySelector('.chiffre__media');
if (!card) return;
const vp = viewport.getBoundingClientRect();
const cr = card.getBoundingClientRect();
const prevR = (prevBtn.getBoundingClientRect().width || 44) / 2;
const nextR = (nextBtn.getBoundingClientRect().width || 44) / 2;
const SPACING = 12; // gap from card edge to arrow OUTER edge
const desiredPrev = (cr.left - vp.left) - (prevR + SPACING);
const desiredNext = (cr.right - vp.left) + (nextR + SPACING);
const minPrev = prevR + 6, maxPrev = vp.width - prevR - 6;
const minNext = nextR + 6, maxNext = vp.width - nextR - 6;
const clampedPrev = Math.max(minPrev, Math.min(desiredPrev, maxPrev));
const clampedNext = Math.max(minNext, Math.min(desiredNext, maxNext));
const shiftRight = clampedPrev - desiredPrev;
const shiftLeft = desiredNext - clampedNext;
const symShift = Math.max(shiftRight, shiftLeft);
const prevCenter = desiredPrev + symShift;
const nextCenter = desiredNext - symShift;
const centerY = cr.top - vp.top + cr.height / 2;
prevBtn.style.left = prevCenter + 'px';
nextBtn.style.left = nextCenter + 'px';
prevBtn.style.top = centerY + 'px';
nextBtn.style.top = centerY + 'px';
prevBtn.style.visibility = nextBtn.style.visibility = 'visible';
}
function update(){
track.style.transform = `translateX(${-index * 100}%)`;
slides.forEach((li, i) => li.setAttribute('aria-selected', i === index ? 'true' : 'false'));
[...dotsWrap.children].forEach((d, i) => i === index ? d.setAttribute('aria-current','true') : d.removeAttribute('aria-current'));
prevBtn.disabled = (index === 0);
nextBtn.disabled = (index === slides.length - 1);
requestAnimationFrame(positionArrows);
}
function goTo(i){
index = Math.max(0, Math.min(slides.length - 1, i));
update();
}
function init(){
if (inited || !cacheEls()) return;
viewport.style.overflow = 'hidden';
viewport.style.position = 'relative';
track.style.display = 'flex';
track.style.gap = '0'; // <-- guard you asked for
track.style.transform = 'translateX(0)';
track.style.transition = track.style.transition || '.45s ease';
slides.forEach(s => {
s.style.minWidth = '100%';
s.style.flex = '0 0 100%';
s.style.display = 'flex';
s.style.justifyContent = 'center';
});
buildDots();
prevBtn.addEventListener('click', onPrev);
nextBtn.addEventListener('click', onNext);
track.addEventListener('transitionend', positionArrows);
viewport.tabIndex = 0;
viewport.addEventListener('keydown', onKey);
track.addEventListener('pointerdown', onPointerDown);
track.addEventListener('pointerup', onPointerEnd);
track.addEventListener('pointercancel', onPointerEnd);
track.addEventListener('pointerleave', onPointerEnd);
window.addEventListener('resize', positionArrows);
slides.forEach(sl => {
const img = sl.querySelector('img');
if (img && !img.complete) img.addEventListener('load', positionArrows, { once:true });
});
index = 0;
update();
inited = true;
}
function destroy(){
if (!inited) return;
prevBtn.removeEventListener('click', onPrev);
nextBtn.removeEventListener('click', onNext);
track.removeEventListener('transitionend', positionArrows);
viewport.removeEventListener('keydown', onKey);
track.removeEventListener('pointerdown', onPointerDown);
track.removeEventListener('pointerup', onPointerEnd);
track.removeEventListener('pointercancel', onPointerEnd);
track.removeEventListener('pointerleave', onPointerEnd);
window.removeEventListener('resize', positionArrows);
inited = false;
}
function onPrev(){ goTo(index - 1); }
function onNext(){ goTo(index + 1); }
function onKey(e){ if (e.key === 'ArrowRight') goTo(index + 1); if (e.key === 'ArrowLeft') goTo(index - 1); }
let startX = null, pointerId = null;
function onPointerDown(e){ startX = e.clientX; pointerId = e.pointerId; track.setPointerCapture(pointerId); }
function onPointerEnd(e){
if (startX === null) return;
const dx = e.clientX - startX;
const THRESH = 40;
if (dx > THRESH) goTo(index - 1);
if (dx < -THRESH) goTo(index + 1);
startX = null;
if (pointerId != null) { try { track.releasePointerCapture(pointerId); } catch {} pointerId = null; }
}
function handleMQ(e){ e.matches ? init() : destroy(); }
mql.addEventListener ? mql.addEventListener('change', handleMQ) : mql.addListener(handleMQ);
handleMQ(mql);
})();
</script>
<script>
(function () {
// --- Loader helper for the "Enregistrer" button ---
function setLoading(btn, isLoading, text = 'Enregistrement…') {
if (!btn) return;
if (isLoading) {
if (!btn.dataset._label) btn.dataset._label = btn.textContent;
btn.disabled = true;
btn.setAttribute('aria-busy', 'true');
btn.innerHTML =
'<svg width="16" height="16" viewBox="0 0 50 50" aria-hidden="true" focusable="false" style="vertical-align:-2px;margin-right:8px;">' +
'<circle cx="25" cy="25" r="20" fill="none" stroke="currentColor" stroke-width="6" stroke-opacity="0.25"></circle>' +
'<path d="M25 5 a20 20 0 0 1 0 40" stroke="currentColor" stroke-width="6" fill="none">' +
'<animateTransform attributeName="transform" type="rotate" from="0 25 25" to="360 25 25" dur="0.8s" repeatCount="indefinite"></animateTransform>' +
'</path>' +
'</svg>' + text;
} else {
btn.disabled = false;
btn.removeAttribute('aria-busy');
btn.textContent = btn.dataset._label || 'Enregistrer';
}
}
const UPDATE_URL = "{{ path('cms_text_update') }}";
const UPLOAD_URL = "{{ path('cms_page_media_upload') }}";
const ASSET_BASE = "{{ asset('/') }}";
const $ = (sel, root=document) => root.querySelector(sel);
let modal, closeEl, btnSave, btnCancel;
let inTitle, inV1, inL1, inV2, inL2, inV3, inL3, inV4, inL4, inV5, inL5;
let inImg1, inImg2, inImg3, inImg4, inImg5, file1, file2, file3, file4, file5;
let ctx = null;
function openEditor(root) {
const els = {
title: $('#chiffres-title', root),
v1: $('#chiffre1-value', root), l1: $('#chiffre1-label', root),
v2: $('#chiffre2-value', root), l2: $('#chiffre2-label', root),
v3: $('#chiffre3-value', root), l3: $('#chiffre3-label', root),
v4: $('#chiffre4-value', root), l4: $('#chiffre4-label', root),
v5: $('#chiffre5-value', root), l5: $('#chiffre5-label', root),
img1: $('li:nth-child(1) img', root),
img2: $('li:nth-child(2) img', root),
img3: $('li:nth-child(3) img', root),
img4: $('li:nth-child(4) img', root),
img5: $('li:nth-child(5) img', root),
};
const keys = {
title: root.dataset.titleKey || '',
v1: root.dataset.v1Key || '', l1: root.dataset.l1Key || '',
v2: root.dataset.v2Key || '', l2: root.dataset.l2Key || '',
v3: root.dataset.v3Key || '', l3: root.dataset.l3Key || '',
v4: root.dataset.v4Key || '', l4: root.dataset.l4Key || '',
v5: root.dataset.v5Key || '', l5: root.dataset.l5Key || '',
img1: root.dataset.img1Key || '',
img2: root.dataset.img2Key || '',
img3: root.dataset.img3Key || '',
img4: root.dataset.img4Key || '',
img5: root.dataset.img5Key || '',
};
ctx = { root, els, keys };
inTitle.value = (els.title?.textContent || '').trim();
inV1.value = (els.v1?.textContent || '').trim();
inL1.value = (els.l1?.textContent || '').trim();
inV2.value = (els.v2?.textContent || '').trim();
inL2.value = (els.l2?.textContent || '').trim();
inV3.value = (els.v3?.textContent || '').trim();
inL3.value = (els.l3?.textContent || '').trim();
inV4.value = (els.v4?.textContent || '').trim();
inL4.value = (els.l4?.textContent || '').trim();
inV5.value = (els.v5?.textContent || '').trim();
inL5.value = (els.l5?.textContent || '').trim();
inImg1.value = els.img1?.getAttribute('src')?.replace(ASSET_BASE, '') || '';
inImg2.value = els.img2?.getAttribute('src')?.replace(ASSET_BASE, '') || '';
inImg3.value = els.img3?.getAttribute('src')?.replace(ASSET_BASE, '') || '';
inImg4.value = els.img4?.getAttribute('src')?.replace(ASSET_BASE, '') || '';
inImg5.value = els.img5?.getAttribute('src')?.replace(ASSET_BASE, '') || '';
modal.hidden = false;
inTitle.focus();
}
function closeModal() {
modal.hidden = true;
ctx = null;
[file1, file2, file3, file4, file5].forEach(f => { if (f) f.value = ''; });
}
async function postText(key, text){
const r = await fetch(UPDATE_URL, {
method:'POST',
headers:{'Content-Type':'application/json','Accept':'application/json'},
body: JSON.stringify({ key, text })
});
const d = await r.json();
if (!d.ok) throw new Error(d.error || 'Erreur texte');
return d;
}
function buildPrefixedName(file, key) {
const map = {
'hero_slide1_bg': 'hero1-',
'hero_slide2_bg': 'hero2-',
'kpis_img1': 'c1-',
'kpis_img2': 'c2-',
'kpis_img3': 'c3-',
'kpis_img4': 'c4-',
'kpis_img5': 'c5-'
};
const prefix = map[key] || '';
const ext = (file.name.split('.').pop() || '').toLowerCase();
const base = file.name.replace(/\.[^.]+$/, '').toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-+|-+$/g,'') || 'image';
const stamp = new Date().toISOString().replace(/[-:TZ.]/g,'').slice(0,14);
const rand = Math.random().toString(36).slice(2,8);
return `${prefix}${base}-${stamp}-${rand}.${ext}`;
}
async function uploadImage(key, file){
const fd = new FormData();
fd.append('key', key);
fd.append('file', file, buildPrefixedName(file, key));
const r = await fetch('{{ path("cms_page_media_upload") }}', { method:'POST', body: fd });
const d = await r.json();
if (!d.ok) throw new Error(d.error || 'Erreur upload');
return d.path;
}
async function saveAll() {
if (!ctx) return;
const payloads = [];
if (ctx.keys.title) payloads.push(postText(ctx.keys.title, inTitle.value.trim()));
if (ctx.keys.v1) payloads.push(postText(ctx.keys.v1, inV1.value.trim()));
if (ctx.keys.l1) payloads.push(postText(ctx.keys.l1, inL1.value.trim()));
if (ctx.keys.v2) payloads.push(postText(ctx.keys.v2, inV2.value.trim()));
if (ctx.keys.l2) payloads.push(postText(ctx.keys.l2, inL2.value.trim()));
if (ctx.keys.v3) payloads.push(postText(ctx.keys.v3, inV3.value.trim()));
if (ctx.keys.l3) payloads.push(postText(ctx.keys.l3, inL3.value.trim()));
if (ctx.keys.v4) payloads.push(postText(ctx.keys.v4, inV4.value.trim()));
if (ctx.keys.l4) payloads.push(postText(ctx.keys.l4, inL4.value.trim()));
if (ctx.keys.v5) payloads.push(postText(ctx.keys.v5, inV5.value.trim()));
if (ctx.keys.l5) payloads.push(postText(ctx.keys.l5, inL5.value.trim()));
const imgUploads = {};
if (file1?.files?.[0] && ctx.keys.img1) imgUploads.img1 = uploadImage(ctx.keys.img1, file1.files[0]);
if (file2?.files?.[0] && ctx.keys.img2) imgUploads.img2 = uploadImage(ctx.keys.img2, file2.files[0]);
if (file3?.files?.[0] && ctx.keys.img3) imgUploads.img3 = uploadImage(ctx.keys.img3, file3.files[0]);
if (file4?.files?.[0] && ctx.keys.img4) imgUploads.img4 = uploadImage(ctx.keys.img4, file4.files[0]);
if (file5?.files?.[0] && ctx.keys.img5) imgUploads.img5 = uploadImage(ctx.keys.img5, file5.files[0]);
setLoading(btnSave, true);
try {
// Wait for texts and uploads (if any)
await Promise.all([
...payloads,
...Object.values(imgUploads)
]);
// Update DOM texts
if (ctx.els.title) ctx.els.title.textContent = inTitle.value.trim();
if (ctx.els.v1) ctx.els.v1.textContent = inV1.value.trim();
if (ctx.els.l1) ctx.els.l1.textContent = inL1.value.trim();
if (ctx.els.v2) ctx.els.v2.textContent = inV2.value.trim();
if (ctx.els.l2) ctx.els.l2.textContent = inL2.value.trim();
if (ctx.els.v3) ctx.els.v3.textContent = inV3.value.trim();
if (ctx.els.l3) ctx.els.l3.textContent = inL3.value.trim();
if (ctx.els.v4) ctx.els.v4.textContent = inV4.value.trim();
if (ctx.els.l4) ctx.els.l4.textContent = inL4.value.trim();
if (ctx.els.v5) ctx.els.v5.textContent = inV5.value.trim();
if (ctx.els.l5) ctx.els.l5.textContent = inL5.value.trim();
// Get upload results (promises already settled from the Promise.all above)
const settled = await Promise.allSettled(Object.values(imgUploads));
const keys = Object.keys(imgUploads);
settled.forEach((s, i) => {
if (s.status === 'fulfilled') {
const rel = s.value;
const abs = ASSET_BASE + rel;
if (keys[i] === 'img1') { inImg1.value = rel; ctx.els.img1?.setAttribute('src', abs); }
if (keys[i] === 'img2') { inImg2.value = rel; ctx.els.img2?.setAttribute('src', abs); }
if (keys[i] === 'img3') { inImg3.value = rel; ctx.els.img3?.setAttribute('src', abs); }
if (keys[i] === 'img4') { inImg4.value = rel; ctx.els.img4?.setAttribute('src', abs); }
if (keys[i] === 'img5') { inImg5.value = rel; ctx.els.img5?.setAttribute('src', abs); }
}
});
closeModal();
} catch (err) {
location.reload();
//alert('Modification effectuée');
} finally {
setLoading(btnSave, false);
}
}
document.addEventListener('DOMContentLoaded', () => {
modal = $('#kpis-editor');
if (!modal) return;
closeEl = $('.cms-modal__close', modal);
btnSave = $('#kpe-save', modal);
btnCancel = $('#kpe-cancel', modal);
inTitle = $('#kpe-title', modal);
inV1 = $('#kpe-v1', modal); inL1 = $('#kpe-l1', modal);
inV2 = $('#kpe-v2', modal); inL2 = $('#kpe-l2', modal);
inV3 = $('#kpe-v3', modal); inL3 = $('#kpe-l3', modal);
inV4 = $('#kpe-v4', modal); inL4 = $('#kpe-l4', modal);
inV5 = $('#kpe-v5', modal); inL5 = $('#kpe-l5', modal);
inImg1 = $('#kpe-img1', modal); inImg2 = $('#kpe-img2', modal); inImg3 = $('#kpe-img3', modal); inImg4 = $('#kpe-img4', modal); inImg5 = $('#kpe-img5', modal);
file1 = $('#kpe-file1', modal); file2 = $('#kpe-file2', modal); file3 = $('#kpe-file3', modal); file4 = $('#kpe-file4', modal); file5 = $('#kpe-file5', modal);
document.addEventListener('click', (e) => {
const btn = e.target.closest('.cms-kpis-edit');
if (!btn) return;
const root = btn.closest('.chiffres__panel');
if (root) openEditor(root);
});
btnSave?.addEventListener('click', saveAll);
btnCancel?.addEventListener('click', closeModal);
closeEl?.addEventListener('click', closeModal);
modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(); });
document.addEventListener('keydown', (e) => { if (!modal.hidden && e.key === 'Escape') closeModal(); });
});
})();
</script>
<div class="prefooter-gap" aria-hidden="true"></div>
<script>
(function whenReady(fn){
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', fn, { once: true });
} else { fn(); }
})(() => {
const $ = s => document.querySelector(s);
// --- Loader helper for the “Valider” button ---
function setLoading(btn, isLoading, text = 'Validation…') {
if (!btn) return;
if (isLoading) {
if (!btn.dataset._label) btn.dataset._label = btn.textContent;
btn.disabled = true;
btn.setAttribute('aria-busy', 'true');
btn.innerHTML =
'<svg width="16" height="16" viewBox="0 0 50 50" aria-hidden="true" focusable="false" style="vertical-align:-2px;margin-right:8px;">' +
'<circle cx="25" cy="25" r="20" fill="none" stroke="currentColor" stroke-width="6" stroke-opacity="0.25"></circle>' +
'<path d="M25 5 a20 20 0 0 1 0 40" stroke="currentColor" stroke-width="6" fill="none">' +
'<animateTransform attributeName="transform" type="rotate" from="0 25 25" to="360 25 25" dur="0.8s" repeatCount="indefinite"></animateTransform>' +
'</path>' +
'</svg>' + text;
} else {
btn.disabled = false;
btn.removeAttribute('aria-busy');
btn.textContent = btn.dataset._label || 'Valider';
}
}
const editBtn = $('.catalogue-edit-btn');
const modal = $('#catalogue-edit-modal');
const textarea = $('#catalogue-edit-textarea');
const cancelBtn = $('#catalogue-cancel');
const validateBtn = $('#catalogue-validate');
const textEl = $('#catalogue-text-left');
if (!editBtn || !modal || !textarea || !cancelBtn || !validateBtn || !textEl) {
console.warn('[catalogue] missing elements'); return;
}
const open = () => { textarea.value = editBtn.dataset.currentText || textEl.textContent.trim(); modal.hidden = false; textarea.focus(); };
const close = () => { modal.hidden = true; };
editBtn.addEventListener('click', open);
cancelBtn.addEventListener('click', close);
modal.addEventListener('click', e => { if (e.target === modal) close(); });
validateBtn.addEventListener('click', async () => {
const url = editBtn.dataset.url;
const newText = textarea.value.trim();
if (!newText) { alert('Le texte ne peut pas être vide.'); return; }
setLoading(validateBtn, true, 'Validation…');
try {
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify({ text: newText })
});
const ct = res.headers.get('content-type') || '';
const raw = await res.clone().text();
console.debug('[catalogue_text_update]', res.status, ct, raw);
if (!res.ok) {
alert('Échec (HTTP ' + res.status + '). Voir la console pour les détails.');
return;
}
if (!/application\/json/i.test(ct)) {
console.error('Non-JSON response:', raw);
alert('Réponse inattendue du serveur (non-JSON).');
return;
}
const data = JSON.parse(raw);
if (data && data.ok) {
textEl.textContent = data.text;
editBtn.dataset.currentText = data.text;
close();
} else {
alert(data?.error || 'Échec de la mise à jour.');
}
} catch (err) {
console.error(err);
alert('La mise à jour a échoué. Merci de réessayer.');
} finally {
setLoading(validateBtn, false);
}
});
});
</script>
<style>
.hero-slide .container.hero-content{
position: relative !important;
z-index: 1 !important;
height: 100% !important;
display: flex !important;
align-items: center !important;
justify-content: flex-start !important;
max-width: 1180px !important;
margin: 0 auto !important;
padding: 0 12px !important;
}
.hero-slide .hero-text-content {
width: 100% !important;
text-align: left !important;
display: flex !important;
flex-direction: column !important;
align-items: flex-start !important;
}
.hero-slide .hero-kicker {
text-align: left !important;
margin-bottom: 6px !important;
width: 100% !important;
}
.hero-slide .hero-title {
text-align: left !important;
margin: 10px 0 18px 0 !important;
width: 100% !important;
}
.hero-slide .hero-ctas {
text-align: left !important;
margin-top: 18px !important;
justify-content: flex-start !important;
width: 100% !important;
}
/* Style spécifique pour le titre "Nos prestations" */
.sec-title--orange {
color: #F14816 !important;
}
.hero-overlay{background:rgba(7,51,73,.55)}
.home-event-ribbon{box-shadow:0 3px 8px rgba(0,0,0,.15)}
.hero-ribbon-shell{position:absolute;top:calc(var(--topbar-h, 0px) + var(--nav-offset, 0px) + var(--pill-h, 0px) + 12px);left:0;right:0;z-index:4;display:flex;justify-content:center;padding:0 12px}
.hero-ribbon-shell .home-event-ribbon{margin:0;width:min(1180px,calc(100% - 24px));max-width:none}
.hero-content{position:relative;z-index:2;display:flex;align-items:center;justify-content:flex-start;height:100%;padding-top:max(calc(var(--topbar-h, 0px) + var(--nav-offset, 0px) + var(--pill-h, 0px) + var(--ribbon-h, 44px) + var(--gap, 40px)), 150px) !important}
.hero-copy{display:flex;flex-direction:column;align-items:flex-start;text-align:left;padding-top:clamp(12px,2vh,24px)}
.hero-title{font-family:'Inter',system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;font-weight:800;line-height:1.05;letter-spacing:.002em;font-size:clamp(28px,3.6vw,46px);color:#F14816;margin:10px 0 12px 0;max-width:26ch;white-space:normal;text-wrap:balance}
.hero-sub{max-width:700px;font-size:clamp(13px,1.2vw,16px);line-height:1.65;color:rgba(255,255,255,.92)}
.hero-ctas{margin-top:14px}
.hero-ctas .btn-primary{background:#F14816;color:#fff;box-shadow:0 6px 16px rgba(241,72,22,.25)}
.hero-dots{position:absolute;left:50%;bottom:24px;transform:translateX(-50%);z-index:4;display:flex;gap:8px}
.hero-dots button{width:7px;height:7px;border-radius:9999px;border:0;background:#fff;opacity:.55}
.hero-dots button[aria-selected="true"]{background:#F14816;opacity:1;width:9px;height:9px}
.hero-nav{background:rgba(141,156,165,.85);border:1px solid rgba(217,217,217,.30);box-shadow:0 2px 8px rgba(0,0,0,.20)}
:root{--hero-left:76px;}
.hero .container.hero-content{
max-width:100% !important;
margin:0 !important;
padding-left:var(--hero-left) !important;
padding-right:0 !important;
justify-content:flex-start !important;
}
.hero .hero-copy{
width:100% !important;
text-align:left !important;
align-items:flex-start !important;
}
</style>
<script>
(function () {
const track = document.querySelector('.hero-track');
if (!track) return;
const allSlides = Array.from(track.querySelectorAll('article[data-scope]'));
let slides = allSlides.filter(el =>
el.classList.contains('hero-slide') &&
!el.classList.contains('hero-slide-config')
);
if (!slides.length) {
slides = allSlides.filter(el => el.classList.contains('hero-slide'));
}
if (!slides.length) return;
const btnPrev = document.querySelector('.hero-nav.prev');
const btnNext = document.querySelector('.hero-nav.next');
const dotsWrap = document.querySelector('.hero-dots');
let dots = [];
if (dotsWrap) {
dotsWrap.innerHTML = '';
slides.forEach((_, i) => {
const dot = document.createElement('button');
dot.type = 'button';
dot.setAttribute('role', 'tab');
dot.setAttribute('aria-label', 'Aller à la diapo ' + (i + 1));
dot.addEventListener('click', () => goTo(i, true));
dotsWrap.appendChild(dot);
dots.push(dot);
});
}
let index = 0;
let timer = null;
const AUTOPLAY_DELAY = 6000;
function applyRibbonVisibility(slide) {
if (!slide) return;
const shell = slide.querySelector('.hero-ribbon-shell');
const rEl = slide.querySelector('.home-event-text');
if (!shell || !rEl) return;
const txt = (rEl.textContent || '').trim();
shell.style.display = (txt && txt !== '-') ? '' : 'none';
}
function update() {
slides.forEach((slide, i) => {
const active = i === index;
slide.classList.toggle('is-active', active);
slide.setAttribute('aria-hidden', active ? 'false' : 'true');
slide.style.display = active ? 'block' : 'none';
applyRibbonVisibility(slide);
});
if (dots.length) {
dots.forEach((dot, i) => {
dot.setAttribute('aria-selected', i === index ? 'true' : 'false');
});
}
}
function goTo(newIndex, fromUser) {
const max = slides.length;
index = ((newIndex % max) + max) % max;
update();
if (fromUser) restartAutoplay();
}
function next(fromUser) {
goTo(index + 1, fromUser);
}
function prev(fromUser) {
goTo(index - 1, fromUser);
}
function startAutoplay() {
if (timer || slides.length < 2) return;
timer = setInterval(() => next(false), AUTOPLAY_DELAY);
}
function stopAutoplay() {
if (!timer) return;
clearInterval(timer);
timer = null;
}
function restartAutoplay() {
stopAutoplay();
startAutoplay();
}
if (btnPrev) btnPrev.addEventListener('click', () => prev(true));
if (btnNext) btnNext.addEventListener('click', () => next(true));
const hero = document.querySelector('.hero');
if (hero) {
hero.addEventListener('mouseenter', stopAutoplay);
hero.addEventListener('mouseleave', startAutoplay);
hero.addEventListener('focusin', stopAutoplay);
hero.addEventListener('focusout', startAutoplay);
}
/******************************************************************/
(function bindSwipe() {
const mql = window.matchMedia('(max-width: 767.98px)');
let startX = 0, startY = 0, startT = 0;
let pointerId = null;
let tracking = false;
const THRESH_X = 45;
const THRESH_Y = 80;
const MAX_TIME = 600;
let blockClickUntil = 0;
hero.addEventListener('click', (e) => {
if (Date.now() < blockClickUntil) {
e.preventDefault();
e.stopPropagation();
}
}, true);
function shouldIgnoreTarget(e) {
return !!e.target.closest('a,button,input,textarea,select,label');
}
function onDown(e) {
if (!mql.matches) return;
if (e.pointerType === 'mouse') return;
if (shouldIgnoreTarget(e)) return;
tracking = true;
pointerId = e.pointerId;
startX = e.clientX;
startY = e.clientY;
startT = performance.now();
stopAutoplay();
try { hero.setPointerCapture(pointerId); } catch {}
}
function onUp(e) {
if (!tracking) return;
tracking = false;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
const dt = performance.now() - startT;
try { hero.releasePointerCapture(pointerId); } catch {}
pointerId = null;
if (Math.abs(dx) >= THRESH_X && Math.abs(dy) <= THRESH_Y && dt <= MAX_TIME) {
if (dx < 0) next(true);
else prev(true);
blockClickUntil = Date.now() + 400;
}
startAutoplay();
}
hero.addEventListener('pointerdown', onDown, { passive: true });
hero.addEventListener('pointerup', onUp, { passive: true });
hero.addEventListener('pointercancel', onUp, { passive: true });
})();
/*******************************************************************/
update();
startAutoplay();
})();
</script>
<style>
.hero,
.hero-track{
touch-action: pan-y;
-webkit-user-select: none;
user-select: none;
}
</style>
{% endblock %}