/* Motion : la direction d'animation du site (16/09). Un seul vocabulaire pour toutes les sections :
   pas de fondu, pas de flou, pas de balayage, aucune opacité (Theo, 09/09 et 16/09) ; des masques et des déplacements le long de la grille, une cadence courte,
   tout se joue en transform (aucune reprise de mise en page). Trois durées, une courbe, une cadence : ce sont les seuls réglages ; une animation qui en sort doit être justifiée. */
:root {
  --m-vite: 220ms;        /* micro-interactions : survol, bascule d'état */
  --m-base: 440ms;        /* un bloc qui arrive */
  --m-lent: 640ms;        /* un mouvement sur une grande surface */
  --m-cadence: 70ms;      /* l'écart entre deux lignes ou deux cellules */
  --m-sortie: cubic-bezier(0.16, 1, 0.3, 1);    /* départ franc, arrivée nette (expo out) */
  --m-coupe: steps(1, end);                       /* la coupe : l'état change d'un coup */
  --m-souple: cubic-bezier(0.45, 0, 0.2, 1);      /* entrée et sortie douces, pour les mouvements lents (filets tracés, grandes surfaces) */
}

/* ---------- Les briques. Chaque animation se déclare une fois ici et se compose ensuite par des délais. ---------- */
@keyframes m-monter { from { transform: translateY(var(--m-de, 130%)); } to { transform: translateY(0); } }                    /* une pièce monte derrière son masque (--m-de : d'où elle part) */
@keyframes m-entrer-gauche { from { transform: translateX(calc(-100% - var(--marge))); } to { transform: translateX(0); } }   /* un contenu entre dans sa cellule par le filet de gauche */
@keyframes m-tracer-x { from { transform: scaleX(0); } to { transform: scaleX(1); } }                                          /* un filet se trace de gauche à droite */
@keyframes m-tracer-y { from { transform: scaleY(0); } to { transform: scaleY(1); } }                                          /* un filet se trace de haut en bas */
@keyframes m-filet { from { border-color: transparent; } to { border-color: var(--filet); } }                                  /* le vrai filet prend le relais du tracé, d'un coup (sections sombres) */
@keyframes m-filet-trait { from { border-color: transparent; } to { border-color: var(--trait); } }                            /* idem, sections claires */
@keyframes m-cacher { from { visibility: visible; } to { visibility: hidden; } }                                              /* le tracé s'efface d'un coup, à l'image exacte où le vrai filet revient : superposés, deux filets translucides scintillaient (Theo, 24/09) */

/* Enveloppes posées par le script le temps d'une entrée (i.m-int : le contenu d'une cellule ; i.m-ligne > i.m-int : une ligne réelle), puis HTML d'origine rendu.
   La balise i n'est qu'une enveloppe : jamais d'italique. */
i.m-int, i.m-ligne, i.m-mot { font-style: normal; font-weight: inherit; }
i.m-int { display: block; }
i.m-ligne { display: block; overflow: hidden; padding-bottom: 0.22em; margin-bottom: -0.22em; white-space: nowrap; } /* le masque déborde sous la ligne pour ne pas couper les descendantes (interlignage serré) ; nowrap : une ligne découpée ne se replie jamais (recomposée d'un seul tenant, elle peut dépasser sa mesure d'une fraction de pixel et passerait sur deux lignes : vu sur une citation des témoignages le 17/09) */

/* ================= HERO · entrée au chargement, « Lignes » retenue par Theo le 16/09 =================
   La bande est en place dès le départ : son entrée, c'est l'icône Meleï qui se forme et tient deux secondes. Seul le titre arrive, ligne par ligne,
   chaque ligne réelle montant derrière son masque. 780 ms. L'état caché du titre n'existe que si le script de tête a posé html[data-motion], et seulement
   tant que le hero n'a pas reçu sa variante : la page ne dépend jamais du JavaScript pour afficher son contenu. */
html[data-motion] .hero:not([data-entree]) h1 { opacity: 0; }
.hero[data-entree="lignes"] h1 i.m-ligne i.m-int { animation: m-monter 600ms cubic-bezier(0.3, 0, 0.2, 1) both; animation-delay: calc(var(--m-attente, 0ms) + 120ms + var(--i) * 90ms); } /* 24/09 au soir, Theo : « le tout un poil plus doux » : 600 ms au lieu de 520, courbe à entrée adoucie au lieu de l'expo out, 90 ms entre les lignes au lieu de 70 (hero seulement, les sections gardent la brique) */ /* --m-attente (24/09) : posée par la page sur .hero, le titre attend la bande pour que sa dernière ligne se pose quand l'icône se verrouille (« Titre calé sur l'icône », retenue par Theo le 24/09) */
.hero[data-entree="aucune"] h1 { opacity: 1; }

/* La grille des trois métiers (essai, 21/09) : ses filets se tracent pendant que le titre monte. Le filet du haut part de la gauche et vient rejoindre
   celui de la colonne des flèches, les deux filets entre les cellules descendent l'un après l'autre ; les vrais filets prennent le relais à la fin de l'entrée.
   Les libellés ne bougent pas (Theo, 21/09 : il n'aime pas leur montée). ~1 400 ms, fini avant la fin de l'icône de la bande. */
html[data-motion] .hero:not([data-entree]) .metiers, html[data-motion] .hero:not([data-entree]) .metiers span:not(:last-child) { border-color: transparent; }
.hero[data-entree="lignes"] .metiers { position: relative; border-color: transparent; }
.hero[data-entree="lignes"] .metiers span { position: relative; }
.hero[data-entree="lignes"] .metiers span:not(:last-child) { border-color: transparent; } /* les vrais filets restent transparents toute l'entrée : ils reviennent quand le hero repasse en « aucune », dans la même image où les tracés disparaissent. Les rendre avant faisait se superposer deux filets translucides, plus clairs un instant : un scintillement (Theo, 21/09) */
.hero[data-entree="lignes"] .metiers::before { content: ""; position: absolute; left: 0; right: 0; top: -1px; height: 1px; background: var(--filet); transform-origin: left; animation: m-tracer-x 1000ms var(--m-souple) both; animation-delay: calc(var(--m-attente, 0ms) + 250ms); pointer-events: none; }
.hero[data-entree="lignes"] .metiers span:not(:last-child)::after { content: ""; position: absolute; top: 0; bottom: 0; right: -1px; width: 1px; background: var(--filet); transform-origin: top; animation: m-tracer-y 700ms var(--m-souple) both; animation-delay: calc(var(--m-attente, 0ms) + 550ms + var(--i, 0) * 120ms); pointer-events: none; }
.hero[data-entree="lignes"] .metiers span:nth-child(2) { --i: 1; }
.hero[data-entree="lignes"] .metiers span:nth-child(3) { --i: 2; }
@media (max-width: 600px) { /* sur une colonne (téléphone ; la tablette garde la rangée depuis le 22/09), les filets entre cellules deviennent horizontaux : on ne trace que celui du haut, les autres sont là d'emblée */
  html[data-motion] .hero:not([data-entree]) .metiers span:not(:last-child), .hero[data-entree="lignes"] .metiers span:not(:last-child) { border-color: var(--filet); animation: none; }
  .hero[data-entree="lignes"] .metiers span::after { display: none; }
}

/* ================= PAGES PROJET (24/09) : les mêmes entrées au défilement que la home, ce que Theo veut garder partout : « au défilement du site, les animations sont les titres et les lignes qui apparaissent ».
   html[data-page="projet"] sur ces pages. Le titre h2 de chaque section monte ligne par ligne ; la bande des chiffres et la grille des chantiers tracent leurs filets comme les services de la home
   (le filet du haut part de la gauche, les verticaux descendent case après case, les vrais filets prennent le relais d'un coup). Les tracés sont posés exactement sur les vrais filets (dans la boîte, plus à -1 px)
   et s'effacent à l'image où ceux-ci reviennent (m-cacher) : décalés d'un pixel et gardés, ils scintillaient au relais (Theo, 24/09). Le pied suit les règles .fin de la home. ================= */
html[data-page="projet"][data-motion] section[data-anim]:not([data-vu]) h2 i.m-ligne i.m-int { transform: translateY(130%); }
html[data-page="projet"] section[data-anim][data-vu] h2 i.m-ligne i.m-int { animation: m-monter 520ms var(--m-sortie) both; animation-delay: calc(120ms + var(--i) * var(--m-cadence)); }
/* Chiffres (bande sombre sous le hero) : filet du haut et verticaux entre les cases */
html[data-page="projet"] .chiffres[data-anim] { position: relative; }
html[data-page="projet"] .chiffres[data-anim] .grille > .c { position: relative; }
html[data-page="projet"][data-motion] .chiffres[data-anim]:not([data-vu]), html[data-page="projet"][data-motion] .chiffres[data-anim]:not([data-vu]) .grille > .c { border-color: transparent; }
html[data-page="projet"] .chiffres[data-anim][data-vu] { animation: m-filet 10ms var(--m-coupe) 1300ms both; }
html[data-page="projet"] .chiffres[data-anim][data-vu] .grille > .c { animation: m-filet 10ms var(--m-coupe) 1400ms both; }
html[data-page="projet"] .chiffres[data-anim][data-vu]::before { content: ""; position: absolute; left: 0; right: 0; top: 0; height: 1px; background: var(--filet); transform-origin: left; animation: m-tracer-x 1000ms var(--m-souple) 300ms both, m-cacher 10ms var(--m-coupe) 1300ms both; pointer-events: none; }
html[data-page="projet"] .chiffres[data-anim][data-vu] .grille > .c:not(:last-child)::after { content: ""; position: absolute; top: 0; bottom: 0; right: 0; width: 1px; background: var(--filet); transform-origin: top; animation: m-tracer-y 1000ms var(--m-souple) both, m-cacher 10ms var(--m-coupe) both; animation-delay: calc(400ms + var(--i, 0) * var(--m-cadence)), 1400ms; pointer-events: none; }
/* Chantiers (grille claire qui défile à l'horizontale) : le filet du haut de la section se trace quand la section entre, les verticaux des cartes quand la piste entre (ol, sous-cible) */
html[data-page="projet"] .chantiers[data-anim] { position: relative; }
html[data-page="projet"] .chantiers[data-anim] li { position: relative; }
html[data-page="projet"][data-motion] .chantiers[data-anim]:not([data-vu]) { border-top-color: transparent; }
html[data-page="projet"][data-motion] .chantiers[data-anim] ol:not([data-vu]) li { border-color: transparent; }
html[data-page="projet"] .chantiers[data-anim][data-vu] { animation: m-filet-trait 10ms var(--m-coupe) 1300ms both; }
html[data-page="projet"] .chantiers[data-anim] ol[data-vu] li { animation: m-filet-trait 10ms var(--m-coupe) 1400ms both; }
html[data-page="projet"] .chantiers[data-anim][data-vu]::before { content: ""; position: absolute; left: 0; right: 0; top: 0; height: 1px; background: var(--trait); transform-origin: left; animation: m-tracer-x 1000ms var(--m-souple) 300ms both, m-cacher 10ms var(--m-coupe) 1300ms both; pointer-events: none; }
html[data-page="projet"] .chantiers[data-anim] ol[data-vu] li::after { content: ""; position: absolute; top: 0; bottom: 0; right: 0; width: 1px; background: var(--trait); transform-origin: top; animation: m-tracer-y 1000ms var(--m-souple) both, m-cacher 10ms var(--m-coupe) both; animation-delay: calc(400ms + var(--i, 0) * var(--m-cadence)), 1400ms; pointer-events: none; }
html[data-page="projet"] .chantiers[data-anim] ol[data-vu] li:first-child::before { content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 1px; background: var(--trait); transform-origin: top; animation: m-tracer-y 1000ms var(--m-souple) 400ms both, m-cacher 10ms var(--m-coupe) 1400ms both; pointer-events: none; }
/* Sous 1100 px, les chiffres passent sur deux colonnes : plus de verticaux à tracer (les cases paires n'ont plus de filet droit), les filets du bas de chaque case viennent en douceur, comme les services de la home sur une colonne */
@keyframes m-filet-doux-sombre { from { border-color: transparent; } to { border-color: var(--filet); } }
@media (max-width: 1100px) {
  html[data-page="projet"] .chiffres[data-anim][data-vu] .grille > .c::after { display: none; }
  html[data-page="projet"] .chiffres[data-anim][data-vu] .grille > .c { animation: m-filet-doux-sombre 1000ms ease both; animation-delay: calc(300ms + var(--i, 0) * var(--m-cadence)); }
}
@media (prefers-reduced-motion: reduce) { html[data-page="projet"] section[data-anim] h2 i.m-int, html[data-page="projet"] .chiffres[data-anim], html[data-page="projet"] .chiffres[data-anim] .c, html[data-page="projet"] .chantiers[data-anim], html[data-page="projet"] .chantiers[data-anim] li { animation: none !important; transform: none !important; border-color: revert-layer; }
  html[data-page="projet"] .chiffres[data-anim]::before, html[data-page="projet"] .chiffres[data-anim] .c::after, html[data-page="projet"] .chantiers[data-anim]::before, html[data-page="projet"] .chantiers[data-anim] li::after, html[data-page="projet"] .chantiers[data-anim] li::before { display: none; } }

/* ================= CONTACT (24/09) : mêmes principes, html[data-page="contact"]. La tête (.tete.hero) entre comme un hero, le titre ligne par ligne ; le titre du créneau monte ; le pied comme sur la home, bureau seulement.
   Tracés posés sur les vrais filets et effacés à l'instant du relais. Les pages légales (html[data-page="legal"]) n'animent que leur h1, par la tête en hero (Theo, 24/09 : « anime uniquement les h1 »). ================= */
html[data-page="contact"][data-motion] section[data-anim]:not([data-vu]) h2 i.m-ligne i.m-int { transform: translateY(130%); }
html[data-page="contact"] section[data-anim][data-vu] h2 i.m-ligne i.m-int { animation: m-monter 520ms var(--m-sortie) both; animation-delay: calc(120ms + var(--i) * var(--m-cadence)); }
@media (min-width: 1101px) {
  html[data-page="contact"] .pied[data-anim], html[data-page="contact"] .pied[data-anim] > :is(.logo-wrap, .liens, .legal) { position: relative; }
  html[data-page="contact"][data-motion] .pied[data-anim]:not([data-vu]), html[data-page="contact"][data-motion] .pied[data-anim]:not([data-vu]) > * { border-color: transparent; }
  html[data-page="contact"] .pied[data-anim][data-vu] { animation: m-filet 10ms var(--m-coupe) 1200ms both; }
  html[data-page="contact"] .pied[data-anim][data-vu] > * { animation: m-filet 10ms var(--m-coupe) 1400ms both; }
  html[data-page="contact"] .pied[data-anim][data-vu]::before { content: ""; position: absolute; left: 0; right: 0; top: 0; height: 1px; background: var(--filet); transform-origin: left; animation: m-tracer-x 1000ms var(--m-souple) 200ms both, m-cacher 10ms var(--m-coupe) 1200ms both; pointer-events: none; }
  html[data-page="contact"] .pied[data-anim][data-vu] > :is(.logo-wrap, .liens, .legal)::before { content: ""; position: absolute; top: 0; bottom: 0; right: 0; width: 1px; background: var(--filet); transform-origin: top; animation: m-tracer-y 900ms var(--m-souple) both, m-cacher 10ms var(--m-coupe) both; animation-delay: calc(400ms + var(--i) * 90ms), 1400ms; pointer-events: none; }
}
@media (prefers-reduced-motion: reduce) {
  html[data-page="contact"] :is(section, .pied)[data-anim], html[data-page="contact"] .pied[data-anim] > *, html[data-page="contact"] [data-anim] h2 i.m-int { animation: none !important; transform: none !important; }
  html[data-page="contact"] .pied[data-anim]::before, html[data-page="contact"] .pied[data-anim] > *::before { display: none; }
}

/* Les flèches de défilement : une vague lente. Chaque flèche descend de quelques pixels en s'éclairant, puis remonte, en courbe souple et à la suite de la précédente.
   L'ancien signal allumait chaque flèche d'un coup (coupe) : trop « jeu vidéo » (Theo, 19/09). */
html[data-motion] .defiler span { animation: m-signal 2.4s var(--m-souple) infinite; opacity: 0.35; }
html[data-motion] .defiler span:nth-child(2) { animation-delay: 0.22s; }
html[data-motion] .defiler span:nth-child(3) { animation-delay: 0.44s; }
@keyframes m-signal { 0%, 100% { opacity: 0.35; transform: translateY(0); } 35% { opacity: 1; transform: translateY(5px); } 70% { opacity: 0.35; transform: translateY(0); } }

/* ================= Les sections suivantes : le script pose data-anim (la variante) puis data-vu="1" quand la section entre dans l'écran, une seule fois.
   Tant que data-vu n'est pas posé, les pièces attendent hors de leur masque (seulement sous html[data-motion] : sans script, rien n'est caché). ================= */

/* ===== Bande de logos · « Lignes » retenue par Theo le 16/09, version discrète : le masque n'est pas la case mais une boîte au ras du logo
   (.marque ramenée à la taille du logo, centrée par la propriété translate, qui n'entre pas en conflit avec l'animation de transform posée sur le logo ;
   spécificité au-dessus de la règle de la page, chargée après le module). Chaque logo monte de sa propre hauteur, case après case le long de la rangée ;
   le titre de la première case ne bouge pas. ~700 ms. --i est le rang de la case. ===== */
.clients .bande .case .marque { inset: auto; left: 50%; top: 50%; translate: -50% -50%; padding: 2px; overflow: hidden; }
.clients .case { --m-delai: calc(var(--i, 0) * var(--m-cadence)); }
html[data-motion] .clients[data-anim="lignes"]:not([data-vu]) .marque i { transform: translateY(120%); }
.clients[data-anim="lignes"][data-vu] .marque i { --m-de: 120%; animation: m-monter var(--m-base) var(--m-sortie) both; animation-delay: var(--m-delai); }
/* Relais (remis le 30/09, Theo : « remets l'animation de changement de logo dans les cellules ») : « Masque », retenue par Theo le 30/09 face à Compteur et Vague (retirées) :
   une case au hasard toutes les 2 s environ ; l'ancien logo sort par le haut de sa boîte au ras du logo, le nouveau monte dans la sienne juste derrière.
   Les sélecteurs passent au-dessus de la règle d'entrée (.clients[data-anim][data-vu] .marque i) : sans ça, l'ancien logo ne sortait pas et restait sous le nouveau (bug du 30/09).
   Un logo arrivé par le relais (.relais) ne rejoue pas l'entrée ; data-etat est retiré à la fin du geste. */
@keyframes m-partir { from { transform: translateY(0); } to { transform: translateY(-120%); } }
.clients[data-vu] .bande .marque.relais i { animation: none; }
.clients[data-vu] .bande .case .marque[data-etat="sortie"] i { animation: m-partir 360ms var(--m-souple) forwards; }
.clients[data-vu] .bande .case .marque[data-etat="entree"] i { --m-de: 120%; animation: m-monter var(--m-base) var(--m-sortie) 160ms both; }

/* ===== Services · « Lignes + filets tracés » retenue par Theo le 17/09 (titre seul et filets en fondu retirées). Theo : « juste ligne sur H2, et faire apparaître les lignes de façon smooth » : le titre de section monte ligne par ligne
   (lignes réelles découpées par le script), rien d'autre ne bouge dans le texte ; les filets de la grille des colonnes apparaissent en douceur, de deux façons possibles. ===== */
html[data-motion] .services[data-anim]:not([data-vu]) h2 i.m-ligne i.m-int { transform: translateY(130%); }
.services[data-anim][data-vu] h2 i.m-ligne i.m-int { animation: m-monter 520ms var(--m-sortie) both; animation-delay: calc(120ms + var(--i) * var(--m-cadence)); }
/* « Lignes » : le titre seul. 780 ms. */
/* Les deux variantes à filets : les vrais filets de la grille (haut et bas de la rangée, entre les colonnes) sont transparents tant que la grille n'est pas vue.
   Les filets se déclenchent sur la grille elle-même (.cartes[data-vu], sous-cible du script), pas sur la section : elle est plus bas que le titre, et Theo veut voir le trait partir
   (« déclenche le filet tracé un poil plus tard, qu'on puisse le voir apparaître », 16/09) ; un temps de 300 ms précède le premier trait. */
.services[data-anim^="filets"] .cartes, .services[data-anim^="filets"] .carte { position: relative; }
html[data-motion] .services[data-anim^="filets"] .cartes:not([data-vu]), html[data-motion] .services[data-anim^="filets"] .cartes:not([data-vu]) .carte { border-color: transparent; }
/* « Filets tracés » : chaque filet se trace lentement, d'une courbe souple (entrée et sortie douces, 1 s), tous ensemble : le haut et les verticaux d'abord, le bas suit. ~1 500 ms après le départ. */
.services[data-anim="filets-traces"] .cartes[data-vu] { animation: m-filet-trait 10ms var(--m-coupe) 1500ms both; }
.services[data-anim="filets-traces"] .cartes[data-vu] .carte { animation: m-filet-trait 10ms var(--m-coupe) 1400ms both; }
.services[data-anim="filets-traces"] .cartes[data-vu]::before, .services[data-anim="filets-traces"] .cartes[data-vu]::after { content: ""; position: absolute; left: 0; right: 0; height: 1px; background: var(--trait); transform-origin: left; animation: m-tracer-x 1000ms var(--m-souple) 300ms both; pointer-events: none; }
.services[data-anim="filets-traces"] .cartes[data-vu]::before { top: -1px; }
.services[data-anim="filets-traces"] .cartes[data-vu]::after { bottom: -1px; animation-delay: 500ms; }
.services[data-anim="filets-traces"] .cartes[data-vu] .carte::after { content: ""; position: absolute; top: 0; bottom: 0; right: -1px; width: 1px; background: var(--trait); transform-origin: top; animation: m-tracer-y 1000ms var(--m-souple) 400ms both; pointer-events: none; }
.services[data-anim="filets-traces"] .cartes[data-vu] .carte:last-child::after { display: none; }
@keyframes m-filet-doux { from { border-color: transparent; } to { border-color: var(--trait); } } /* sur une colonne seulement : le filet sous chaque carte vient en douceur */
@media (max-width: 1100px) { /* sur une colonne, il n'y a plus de grille à tracer : les filets sous chaque carte viennent en fondu dans les deux variantes */
  .services[data-anim="filets-traces"] .cartes[data-vu]::before, .services[data-anim="filets-traces"] .cartes[data-vu]::after, .services[data-anim="filets-traces"] .cartes[data-vu] .carte::after { display: none; }
  .services[data-anim="filets-traces"] .cartes[data-vu], .services[data-anim="filets-traces"] .cartes[data-vu] .carte { animation: m-filet-doux 1000ms ease both; animation-delay: calc(300ms + var(--i, 0) * var(--m-cadence)); }
}

/* ===== Projets (17/09). Entrée : le titre de section monte ligne par ligne, rien d'autre (Theo : « j'avais déjà mon animation au hover, j'aimerais juste améliorer
   l'entrée et la sortie sur le hover »). ===== */
html[data-motion] .projets[data-anim]:not([data-vu]) h2 i.m-ligne i.m-int { transform: translateY(130%); }
.projets[data-anim][data-vu] h2 i.m-ligne i.m-int { animation: m-monter 520ms var(--m-sortie) both; animation-delay: calc(120ms + var(--i) * var(--m-cadence)); }

/* ===== Projets · survol des cartes : « Posé » retenue par Theo le 17/09 (html[data-survol="pose"], posé par le script ; sans lui, les règles d'origine de la page s'appliquent).
   Principes communs : l'entrée et la sortie n'ont pas les mêmes réglages (les transitions de l'état survolé règlent l'entrée, celles de l'état de repos règlent la sortie) ;
   la sortie est plus courte que l'entrée et accélère (ease-in) au lieu de couper ; tout reste en transitions CSS, donc interruptible à tout moment (aller-retour rapide de la souris) ;
   le flou du voile n'est plus animé : il est posé une fois pour toutes sur le voile, et c'est l'opacité du voile qui le fait venir (beaucoup moins coûteux, plus fluide) ;
   côté script, la vidéo continue de jouer pendant la sortie et n'est remise à zéro qu'une fois cachée. ===== */
html[data-survol]:not([data-survol="actuel"]) .visuel-video video { will-change: transform; box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45); }
html[data-survol]:not([data-survol="actuel"]) .projet .visuel img { transition: transform 0.55s cubic-bezier(0.4, 0, 0.6, 1); }
html[data-survol]:not([data-survol="actuel"]) .projet:is(:hover, .en-vue) .visuel img { transition: transform 0.9s var(--m-sortie); }
html[data-survol]:not([data-survol="actuel"]) .carte-projet::before { transition: opacity 0.45s ease; }
html[data-survol]:not([data-survol="actuel"]) .carte-projet .fleche { transition: transform 0.45s var(--m-sortie), opacity 0.3s ease; }

/* « Posé » : ton animation, affinée. Même montée inclinée en 3D qui se pose à plat ; à l'entrée le voile précède la vidéo de 50 ms ; à la sortie la vidéo s'enfonce
   en accélérant pendant qu'elle s'efface (300 ms), au lieu de disparaître d'un coup, et le voile se retire juste après. */
html[data-survol="pose"] .visuel-video .voile { backdrop-filter: blur(2px); transition: opacity 0.4s ease 0.06s; }
html[data-survol="pose"] .projet:is(:hover, .en-vue) .visuel-video .voile { transition: opacity 0.35s cubic-bezier(0.25, 0, 0.25, 1); }
html[data-survol="pose"] .visuel-video video { transition: opacity 0.3s cubic-bezier(0.4, 0, 1, 1), transform 0.55s cubic-bezier(0.5, 0, 0.75, 0); }
html[data-survol="pose"] .projet:is(:hover, .en-vue) .visuel-video video { transition: opacity 0.25s cubic-bezier(0.25, 0, 0.25, 1) 0.05s, transform 0.9s var(--m-sortie) 0.05s; }

/* (Montée et Latéral, essayées, sont retirées : Posé retenue par Theo le 17/09.) */

/* ===== Témoignages (17/09, en cours). Le titre de section monte ligne par ligne ; le reste se joue carte par carte, quand la carte elle-même entre dans l'écran
   (.temoignage[data-vu], sous-cible du script ; --i : le rang de la carte, pour la cadence quand plusieurs entrent ensemble). ===== */
html[data-motion] .temoignages[data-anim]:not([data-vu]) h2 i.m-ligne i.m-int { transform: translateY(130%); }
.temoignages[data-anim][data-vu] h2 i.m-ligne i.m-int { animation: m-monter 520ms var(--m-sortie) both; animation-delay: calc(120ms + var(--i) * var(--m-cadence)); }
/* L'entrée de la section : le titre seul (les essais cadres tracés et portraits sont retirés avec les anciennes compositions, 17/09). Le changement d'avis du sélecteur
   a sa propre transition, dans /temoignages-versions.css. */

/* ===== Bénéfices (18/09) : la même entrée que les services, validée par Theo le 17/09, sur la même structure (une rangée de colonnes bord à bord séparées par des filets).
   Le titre de section monte ligne par ligne pendant que le filet vertical de la tête se trace de haut en bas ; quand la grille des trois cibles entre à son tour dans l'écran (.cibles[data-vu],
   sous-cible du script), ses filets se tracent lentement, d'une courbe souple : le haut de gauche à droite, les deux verticaux de haut en bas, puis le bas ; les deux filets décoratifs
   qui descendent sous le bloc ont leur propre déclenchement, plus bas. Les vrais filets sont transparents jusqu'à ce que le tracé les rejoigne. Les cartes ne bougent pas. Bureau seulement : sur une colonne il n'y a plus de grille à tracer. ===== */
html[data-motion] .benefices[data-anim]:not([data-vu]) h2 i.m-ligne i.m-int { transform: translateY(130%); }
.benefices[data-anim][data-vu] h2 i.m-ligne i.m-int { animation: m-monter 520ms var(--m-sortie) both; animation-delay: calc(120ms + var(--i) * var(--m-cadence)); }
@media (min-width: 1101px) {
  html[data-motion] .benefices[data-anim]:not([data-vu]) .tete::after { transform: scaleY(0); }
  .benefices[data-anim] .tete::after { transform-origin: top; }
  .benefices[data-anim][data-vu] .tete::after { animation: m-tracer-y 1000ms var(--m-souple) 120ms both; }
  .benefices[data-anim] .cibles, .benefices[data-anim] .cible { position: relative; }
  html[data-motion] .benefices[data-anim] .cibles:not([data-vu]), html[data-motion] .benefices[data-anim] .cibles:not([data-vu]) .cible { border-color: transparent; }
  .benefices[data-anim] .cibles[data-vu] { animation: m-filet-trait 10ms var(--m-coupe) 1500ms both; }
  .benefices[data-anim] .cibles[data-vu] .cible { animation: m-filet-trait 10ms var(--m-coupe) 1400ms both; }
  .benefices[data-anim] .cibles[data-vu]::before, .benefices[data-anim] .cibles[data-vu]::after { content: ""; position: absolute; left: 0; right: 0; height: 1px; background: var(--trait); transform-origin: left; animation: m-tracer-x 1000ms var(--m-souple) 300ms both; pointer-events: none; }
  .benefices[data-anim] .cibles[data-vu]::before { top: -1px; }
  .benefices[data-anim] .cibles[data-vu]::after { bottom: -1px; animation-delay: 500ms; }
  .benefices[data-anim] .cibles[data-vu] .cible::after { content: ""; position: absolute; top: 0; bottom: 0; right: -1px; width: 1px; background: var(--trait); transform-origin: top; animation: m-tracer-y 1000ms var(--m-souple) 400ms both; pointer-events: none; }
  .benefices[data-anim] .cibles[data-vu] .cible:last-child::after { display: none; }
  /* Les deux filets du bas ne se tracent que lorsqu'ils sont eux-mêmes à l'écran (Theo, 18/09) : ce sont des pseudo-éléments, donc le script pose à leur place un témoin invisible (.m-bas),
     observé comme une sous-cible, qui reçoit data-vu quand la moitié de sa hauteur est visible. Avant, ils suivaient la grille avec un délai et se traçaient souvent hors écran. */
  .benefices .m-bas { position: absolute; left: 0; right: 0; bottom: 0; height: 160px; pointer-events: none; }
  html[data-motion] .benefices[data-anim]:not(:has(.m-bas[data-vu])) .in::before, html[data-motion] .benefices[data-anim]:not(:has(.m-bas[data-vu])) .in::after { transform: scaleY(0); }
  .benefices[data-anim] .in::before, .benefices[data-anim] .in::after { transform-origin: top; }
  .benefices[data-anim]:has(.m-bas[data-vu]) .in::before, .benefices[data-anim]:has(.m-bas[data-vu]) .in::after { animation: m-tracer-y 800ms var(--m-souple) 120ms both; }
  .benefices[data-anim]:has(.m-bas[data-vu]) .in::after { animation-delay: 220ms; }
}

/* ===== CMS (18/09), sur la composition « mosaïque » validée : le titre de section monte ligne par ligne. Le visuel, déjà animé en boucle, ne bouge pas. Quand la grille entre à son tour
   dans l'écran (.corps-cms[data-vu], sous-cible du script), ses deux grands filets, celui du haut puis celui du bas, se tracent d'un bord à l'autre de l'écran, lentement et d'une courbe souple,
   comme ceux des services et des bénéfices ; les filets entre les cellules sont en place. Bureau seulement : sur une colonne, seul le titre monte. ===== */
html[data-motion] .cms[data-anim]:not([data-vu]) h2 i.m-ligne i.m-int { transform: translateY(130%); }
.cms[data-anim][data-vu] h2 i.m-ligne i.m-int { animation: m-monter 520ms var(--m-sortie) both; animation-delay: calc(120ms + var(--i) * var(--m-cadence)); }
@media (min-width: 1101px) {
  html[data-motion] .cms[data-anim] .corps-cms:not([data-vu]) { border-top-color: transparent; border-bottom-color: transparent; }
  .cms[data-anim] .corps-cms[data-vu] { animation: m-filet-hb 10ms var(--m-coupe) 1500ms both; }
  @keyframes m-filet-hb { from { border-top-color: transparent; border-bottom-color: transparent; } to { border-top-color: var(--filet); border-bottom-color: var(--filet); } }
  .cms[data-anim] .corps-cms[data-vu]::before, .cms[data-anim] .corps-cms[data-vu]::after { content: ""; position: absolute; left: 0; right: 0; height: 1px; background: var(--filet); transform-origin: left; animation: m-tracer-x 1000ms var(--m-souple) 300ms both; pointer-events: none; z-index: 1; }
  .cms[data-anim] .corps-cms[data-vu]::before { top: -1px; }
  .cms[data-anim] .corps-cms[data-vu]::after { bottom: -1px; animation-delay: 500ms; }
}

/* ===== À propos (18/09). Seul le récit, qui tient lieu de titre (le h2 est masqué dans cette section), monte ligne par ligne, comme les titres des autres sections. Il est composé en text-wrap: wrap
   et non « pretty » : le découpage en lignes doit tomber comme le texte au repos (leçon des témoignages). Les chiffres qui montaient derrière leur masque et les portraits qui montaient
   dans leur cadre, posés le même jour, sont retirés : Theo n'aimait pas ces animations. ===== */
html body .apropos .corps-apropos .recit, html body .apropos .recit { text-wrap: wrap; }
html[data-motion] .apropos[data-anim]:not([data-vu]) .recit i.m-ligne i.m-int { transform: translateY(130%); }
.apropos[data-anim][data-vu] .recit i.m-ligne i.m-int { animation: m-monter 520ms var(--m-sortie) both; animation-delay: calc(120ms + var(--i) * var(--m-cadence)); }

/* ===== Process et dernier appel (18/09) : le titre monte ligne par ligne, comme partout. Le process n'a rien d'autre : son panneau a déjà sa mécanique et sa transition.
   Le dernier appel ferme la page avec le geste des services : quand le pied entre dans l'écran (.pied[data-vu], sous-cible du script), son filet du haut se trace d'un bord à l'autre,
   puis les trois filets entre ses colonnes de haut en bas, l'un après l'autre, lentement et d'une courbe souple. Les vrais filets sont transparents jusqu'à ce que le tracé les rejoigne.
   Le texte, le bouton et les liens ne bougent pas. Filets du pied : bureau seulement. ===== */
html[data-motion] :is(.process, .fin)[data-anim]:not([data-vu]) h2 i.m-ligne i.m-int { transform: translateY(130%); }
:is(.process, .fin)[data-anim][data-vu] h2 i.m-ligne i.m-int { animation: m-monter 520ms var(--m-sortie) both; animation-delay: calc(120ms + var(--i) * var(--m-cadence)); }
@media (min-width: 1101px) {
  html[data-pied="colonnes"] .fin[data-anim] .pied, html[data-pied="colonnes"] .fin[data-anim] .pied > :is(.logo-wrap, .liens, .legal) { position: relative; }
  html[data-motion][data-pied="colonnes"] .fin[data-anim] .pied:not([data-vu]), html[data-motion][data-pied="colonnes"] .fin[data-anim] .pied:not([data-vu]) > * { border-color: transparent; }
  html[data-pied="colonnes"] .fin[data-anim] .pied[data-vu] { animation: m-filet 10ms var(--m-coupe) 1200ms both; }
  html[data-pied="colonnes"] .fin[data-anim] .pied[data-vu] > * { animation: m-filet 10ms var(--m-coupe) 1400ms both; }
  /* Tracés posés sur les vrais filets et effacés à l'image du relais (m-cacher), comme sur le contact : décalés d'un pixel et gardés, ils doublaient les filets, plus clairs (Theo, 29/09) */
  html[data-pied="colonnes"] .fin[data-anim] .pied[data-vu]::before { content: ""; position: absolute; left: 0; right: 0; top: 0; height: 1px; background: var(--filet); transform-origin: left; animation: m-tracer-x 1000ms var(--m-souple) 200ms both, m-cacher 10ms var(--m-coupe) 1200ms both; pointer-events: none; }
  html[data-pied="colonnes"] .fin[data-anim] .pied[data-vu] > :is(.logo-wrap, .liens, .legal)::before { content: ""; position: absolute; top: 0; bottom: 0; right: 0; width: 1px; background: var(--filet); transform-origin: top; animation: m-tracer-y 900ms var(--m-souple) both, m-cacher 10ms var(--m-coupe) both; animation-delay: calc(400ms + var(--i) * 90ms), 1400ms; pointer-events: none; }
}

/* ---------- Mouvement réduit : tout est visible d'emblée, rien ne se joue. ---------- */
@media (prefers-reduced-motion: reduce) {
  html[data-motion] .hero h1, .hero i.m-int { animation: none !important; opacity: 1 !important; transform: none !important; }
  .hero .metiers, .hero .metiers span, .hero .metiers::before, .hero .metiers span::after { animation: none !important; }
  .hero .metiers::before, .hero .metiers span::after { display: none; }
  html[data-motion] .defiler span { animation: none; opacity: 1; }
  .projets i.m-int { animation: none !important; transform: none !important; }
  .process i.m-int, .fin i.m-int, .fin .pied, .fin .pied > *, .fin .pied::before, .fin .pied > *::before { animation: none !important; transform: none !important; }
  .fin .pied, .fin .pied > * { border-color: var(--filet) !important; }
  .fin .pied::before, .fin .pied > *::before { display: none; }
  .apropos i.m-int { animation: none !important; transform: none !important; }
  .cms i.m-int, .cms .corps-cms, .cms .corps-cms::before, .cms .corps-cms::after { animation: none !important; transform: none !important; }
  .cms .corps-cms { border-top-color: var(--filet) !important; border-bottom-color: var(--filet) !important; }
  .benefices i.m-int, .benefices .tete::after, .benefices .in::before, .benefices .in::after, .benefices .cibles, .benefices .cible, .benefices .cibles::before, .benefices .cibles::after, .benefices .cible::after { animation: none !important; transform: none !important; border-color: var(--trait) !important; }
  .temoignages i.m-int, .temoignages .temo-curseur { animation: none !important; transform: none !important; transition: none !important; }
  .clients .marque i, .services i.m-int, .services .cartes, .services .carte, .services .cartes::before, .services .cartes::after, .services .carte::after { animation: none !important; transform: none !important; border-color: var(--trait) !important; }
}
