/* G4G — couche MOUVEMENT (« G4G en mouvement », chantier 2026-09).
 * ═══════════════════════════════════════════════════════════════════════════
 * Utilitaires GÉNÉRIQUES et OPT-IN du socle motion : reveals déclaratifs,
 * cascades, parallaxe, formes décoratives, fond duotone, grille de logos
 * rotative, compteurs, micro-interactions. Chargée globalement (juste APRÈS
 * base.css, cf. templates/base.html) mais AUCUNE règle ne prend effet tant qu'un
 * gabarit n'a pas posé l'attribut/classe correspondant → zéro effet sur
 * l'existant (Lot 0 = aucun changement visuel).
 *
 * SOURCE DES VALEURS : tokens.css (durées `--g4g-dur-*`, courbes `--g4g-ease-*`,
 * distances `--g4g-reveal-dist*`, `--g4g-stagger-step`). Rien en dur.
 *
 * AMÉLIORATION PROGRESSIVE (garde-fou du projet) : l'état initial MASQUÉ des
 * reveals n'existe QUE sous `html.g4g-motion`, classe posée par
 * static/js/motion.js UNIQUEMENT quand les animations sont actives (JS présent,
 * ni `prefers-reduced-motion`, ni réglage staff « Animations : sobres »). Sans
 * JS / en sobre / en mouvement réduit : rien n'est masqué, tout le rendu serveur
 * reste visible et lisible.
 *
 * CSP : aucune couleur littérale (garde-fou common/tests_tokens scanne ce
 * fichier) — tout par var(--g4g-*) ou color-mix() sur des tokens. Les décoratifs
 * sont hors flux (`position:absolute`, `aria-hidden`) et JAMAIS au-dessus d'un
 * texte ou d'un élément cliquable (z-index/isolation derrière, ou à distance).
 * Le garde-fou universel `@media (prefers-reduced-motion: reduce)` reste dans
 * base.css (ne pas le déplacer) ; ici on ne pose que les ceintures spécifiques.
 * ═══════════════════════════════════════════════════════════════════════════ */

/* ═══ 1. Reveals déclaratifs — data-anim ════════════════════════════════════
 * Pose `data-anim` sur un élément à révéler au défilement. Variantes :
 *   data-anim         montée (défaut, 44px)
 *   data-anim="left"  glissement depuis la gauche
 *   data-anim="right" glissement depuis la droite
 *   data-anim="scale" léger zoom
 *   data-anim="none"  fondu seul (aucun déplacement)
 * Cascade : mets `data-anim-group` sur le conteneur ; motion.js pose l'INDEX
 * `--anim-i` sur ses enfants `data-anim`, et le délai = index × `--anim-seq`
 * (pas de cascade du preset d'audience ; repli = --g4g-stagger-step). Un enfant est
 * révélé quand SON groupe reçoit `g4g-in` ; un élément autonome quand LUI-même
 * le reçoit. */
/* INDIRECTION PAR AUDIENCE : le reveal générique consomme des variables de preset
 * `--anim-*` (durée, courbe, distance, échelle, pas de cascade) avec REPLI sur les
 * tokens actuels → sans audience posée, comportement identique à avant (zéro
 * régression). Un `[data-audience]` ancêtre (body de page, ou section CMS ciblée)
 * redéfinit ces variables ; l'ancêtre le plus proche gagne. Voir §9. */
html.g4g-motion [data-anim] {
  opacity: 0;
  transform: translateY(var(--anim-dist, var(--g4g-reveal-dist))) scale(var(--anim-scale, 1));
  transition:
    opacity var(--anim-dur, var(--g4g-dur-reveal)) var(--anim-ease, var(--g4g-ease-out)),
    transform var(--anim-dur, var(--g4g-dur-reveal)) var(--anim-ease, var(--g4g-ease-out));
  /* Cascade : motion.js pose l'INDEX `--anim-i` (entier) par enfant ; le pas vient du
     preset `--anim-seq` (repli = --g4g-stagger-step). delay = index × pas. */
  transition-delay: calc(var(--anim-i, 0) * var(--anim-seq, var(--g4g-stagger-step)));
  will-change: opacity, transform;
}
html.g4g-motion [data-anim="left"] {
  transform: translateX(calc(-1 * var(--g4g-reveal-dist-x)));
}
html.g4g-motion [data-anim="right"] {
  transform: translateX(var(--g4g-reveal-dist-x));
}
html.g4g-motion [data-anim="scale"] {
  transform: scale(0.94);
}
html.g4g-motion [data-anim="none"] {
  transform: none;
}
/* [Correctif Alexandre 2026-09-03] Les BANDES (sections pleine largeur) entrent par la
   GAUCHE (translateX) au lieu du bas — transition citoyen/entreprise + défilement. Les
   grilles/cartes internes gardent leur montée en cascade (data-anim par défaut). */
html.g4g-motion .g4g-section[data-anim] {
  transform: translateX(calc(-1 * var(--g4g-reveal-dist-x)));
}
html.g4g-motion [data-anim].g4g-in,
html.g4g-motion [data-anim-group].g4g-in [data-anim] {
  opacity: 1;
  transform: none;
}

/* Ceinture-bretelles : si le réglage OS « mouvement réduit » s'active APRÈS que
 * motion.js a posé `g4g-motion`, aucun contenu ne doit rester masqué. */
@media (prefers-reduced-motion: reduce) {
  html.g4g-motion [data-anim],
  html.g4g-motion [data-anim-group] [data-anim],
  html.g4g-motion [data-anim].g4g-in {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}

/* [2026-10-14] MOBILE : on COUPE les reveals au défilement (demande Alexandre : sur mobile
 * les éléments « apparaissent puis s'animent », effet trop agressif + léger flash). Tout est
 * visible d'emblée, sans montée ni fondu. Les autres animations (marquee, carrousels) restent.
 * Même mécanique que le garde-fou « mouvement réduit » ci-dessus. */
@media (max-width: 760px) {
  html.g4g-motion [data-anim],
  html.g4g-motion [data-anim-group] [data-anim],
  html.g4g-motion [data-anim].g4g-in,
  html.g4g-motion .g4g-section[data-anim] {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}

/* ═══ 2. Parallaxe — data-px (verticale) / data-px-x (horizontale) ══════════
 * motion.js écrit `transform: translate3d(...)` (rAF, transform SEUL → CLS 0).
 * Réservé aux décoratifs hors flux. `will-change` prépare la composition. */
html.g4g-motion [data-px],
html.g4g-motion [data-px-x] {
  transform: translate3d(var(--g4g-px-x, 0px), var(--g4g-px-y, 0px), 0);
  will-change: transform;
}
/* MOBILE : parallaxe désactivée (course de défilement courte + tactile = peu d'effet,
   et le décalage sortait les formes de l'écran sur les petits viewports). Les formes
   restent à leur position CSS ; motion.js écrit encore les variables (sans effet ici). */
@media (max-width: 760px) {
  html.g4g-motion [data-px],
  html.g4g-motion [data-px-x] { transform: none; }
}

/* ═══ 3. Formes décoratives (« formes du G-globe ») ═════════════════════════
 * Palette 4P d'abord (people/planet/prosperity/purpose) ; les couleurs ODD sont
 * réservées aux contextes ODD. L'ARCHE (demi-cercle haut) est REJETÉE — aucune
 * classe correspondante ici. Toujours `aria-hidden="true"`, hors flux, derrière
 * le contenu (z-index négatif ou isolation du parent) ou à distance. */
.g4g-shape {
  position: absolute;
  display: block;
  pointer-events: none;
  z-index: 0;
}
/* Anneau — cercle FERMÉ (le « globe »). Décision Alexandre 2026-09-01 : cercle complet,
   plus d'anneau entrouvert (le « quart effacé » se lisait comme interrompu). */
.g4g-shape--ring {
  border-radius: 50%;
  border: 2px solid var(--g4g-accent);
}
.g4g-shape--ring-people { border-color: var(--g4g-people); }
.g4g-shape--ring-prosperity { border-color: var(--g4g-prosperity); }
/* Roue 4P — conique des 4 piliers (jamais porteuse de texte). */
.g4g-shape--wheel {
  border-radius: 50%;
  background: conic-gradient(
    var(--g4g-people) 0 25%,
    var(--g4g-planet) 0 50%,
    var(--g4g-prosperity) 0 75%,
    var(--g4g-purpose) 0 100%
  );
}
/* Méridien — arc fin incliné à 23° (l'axe de la Terre), demi-anneau. */
.g4g-shape--meridian {
  border-radius: 50%;
  border: 2px solid var(--g4g-accent);
  border-right-color: transparent;
  border-bottom-color: transparent;
  transform: rotate(23deg);
}
/* Pastilles 4P — petits disques pleins (accents ponctuels). */
.g4g-shape--dot { border-radius: 50%; background: var(--g4g-accent); }
.g4g-shape--dot-people { border-radius: 50%; background: var(--g4g-people); }
.g4g-shape--dot-planet { border-radius: 50%; background: var(--g4g-planet); }
.g4g-shape--dot-prosperity { border-radius: 50%; background: var(--g4g-prosperity); }
.g4g-shape--dot-purpose { border-radius: 50%; background: var(--g4g-purpose); }
/* Pilule — capsule pleine douce. */
.g4g-shape--pill { border-radius: var(--g4g-radius-pill); background: var(--g4g-primary-soft); }
/* NB : le QUART DE CERCLE est REJETÉ (décision Alexandre 2026-09-01) — aucune classe
   de forme « quart » ici, à dessein. Remplacer par pilule / carré / pastille / anneau. */

/* Coin globe — cadre au galbe organique (border-radius 46 %). */
.g4g-coin-globe {
  border-radius: 46% 54% 52% 48% / 54% 46% 54% 46%;
  overflow: hidden;
}
/* Médaillon — cadre photo rond, ombré (portrait/illustration). */
.g4g-medaillon {
  border-radius: 50%;
  overflow: hidden;
  box-shadow: var(--g4g-shadow-md);
  background: var(--g4g-surface-alt);
}
.g4g-medaillon > img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ═══ 4. Fond DUOTONE — l'usage validé : DERRIÈRE une bande sombre, jamais sur
 * le texte. Photo en niveaux de gris + teinte accent→encre (mix color) + voile
 * dense. À coupler avec `data-px` pour la parallaxe. Le contenu de la bande doit
 * porter `position:relative; z-index:1`. */
.g4g-duotone {
  position: absolute;
  inset: -24% 0;               /* débord vertical pour la marge de parallaxe */
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}
.g4g-duotone__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(1) contrast(1.05);
}
.g4g-duotone__tint {
  position: absolute;
  inset: 0;
  background: linear-gradient(135deg, var(--g4g-accent), var(--g4g-ink));
  mix-blend-mode: color;
}
.g4g-duotone__veil {
  position: absolute;
  inset: 0;
  background: color-mix(in srgb, var(--g4g-ink) 82%, transparent);
}

/* (5. Grille de logos rotative : retirée le 2026-10-24 — plus aucun gabarit ne l'utilise ; les logos clients
   sont un ruban à 2 rangées, sections.css .g4g-cmarquee.) */

/* ═══ 6. Compteurs animés — data-count (texte égrené par motion.js) ═════════
 * Chiffres à chasse fixe pour éviter le « saut » de largeur pendant l'égrenage.
 * L'animation numérique (easing expo, une fois, via IntersectionObserver) est en
 * JS ; ici on ne fixe que la typographie. */
.g4g-count { font-variant-numeric: tabular-nums; }

/* ═══ 7. Micro-interactions génériques (opt-in) ════════════════════════════ */

/* Surlignage animé d'un mot/segment — trait jaune « purpose » à 38 %, qui grandit
 * de 0 → 100 % au chargement (uniquement en motion). Défaut : surlignage présent
 * (statique) pour sobre / mouvement réduit / sans JS. */
.g4g-hl {
  background-image: linear-gradient(
    color-mix(in srgb, var(--g4g-purpose) 38%, transparent),
    color-mix(in srgb, var(--g4g-purpose) 38%, transparent)
  );
  background-repeat: no-repeat;
  background-position: 0 88%;
  background-size: 100% 0.34em;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}
html.g4g-motion .g4g-hl {
  background-size: 0% 0.34em;
  animation: g4gMotionHighlight var(--g4g-dur-highlight) var(--g4g-ease-out) 0.9s forwards;
}
@keyframes g4gMotionHighlight {
  to { background-size: 100% 0.34em; }
}

/* Lien à soulignement animé qui grandit au survol. */
.g4g-linkfx {
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: left bottom;
  background-size: 0% 2px;
  transition: background-size var(--g4g-dur-base) var(--g4g-ease-out);
  padding-bottom: 3px;
}
.g4g-linkfx:hover,
.g4g-linkfx:focus-visible { background-size: 100% 2px; }

/* Léger soulèvement au survol (cartes). transform/opacity seul (CLS 0). */
.g4g-lift { transition: transform var(--g4g-dur-fast) var(--g4g-ease-out), box-shadow var(--g4g-dur-fast) var(--g4g-ease-out); }
.g4g-lift:hover { transform: translateY(-4px); box-shadow: var(--g4g-shadow-md); }

/* Zoom d'image confiné dans un cadre `overflow:hidden`. */
.g4g-zoom-frame { overflow: hidden; }
.g4g-zoom-frame > img { transition: transform var(--g4g-dur-base) var(--g4g-ease-out); }
.g4g-zoom-frame:hover > img { transform: scale(1.06); }

/* Apparition « pop » (léger rebond) — pour pastilles/jetons révélés en cascade.
 * Masqué uniquement en motion ; visible partout ailleurs. */
html.g4g-motion .g4g-pop {
  transform: scale(0);
  transition: transform 0.45s var(--g4g-ease-pop);
  /* Anim SPÉCIFIQUE (garde sa durée/courbe propres) ; seule la cascade suit l'index
     posé par motion.js, comme les reveals (repli = --g4g-stagger-step). */
  transition-delay: calc(var(--anim-i, 0) * var(--anim-seq, var(--g4g-stagger-step)));
}
html.g4g-motion .g4g-in .g4g-pop,
html.g4g-motion .g4g-pop.g4g-in { transform: scale(1); }
@media (prefers-reduced-motion: reduce) {
  html.g4g-motion .g4g-pop { transform: none !important; transition: none !important; }
}

/* ═══ 8. Rideau — transition d'AUDIENCE et de RÉGION (moment signature) ════
 * Voile bleu + formes 4P (depuis le 2026-10-20 ; avant : 4 bandes 4P en cascade).
 * NON gaté par `html.g4g-motion` : le rideau est piloté à part
 * (base.html au retour + rideau.js au départ), pas par les reveals au défilement.
 *   · ARRIVÉE : la couche `.g4g-rideau--cover` est rendue CÔTÉ SERVEUR (base.html,
 *     si `?rideau=1`) → retrait 100 % CSS → marche MÊME SANS JS (rideau.js ne fait
 *     que nettoyer l'URL + le DOM). Rendue serveur = zéro flash de contenu nu.
 *   · DÉPART : rideau.js pose `.g4g-rideau--enter` puis `--in` (le voile entre et
 *     couvre) avant de naviguer — avec filet : la navigation a TOUJOURS lieu.
 * Neutralisé en mouvement réduit / réglage « sobre » (le contenu apparaît direct).
 * Formes identifiées par `:nth-child` (JAMAIS en style inline → CSP).
 * z-index au-dessus de tout (header compris) le temps de la transition ; couleurs
 * 4P par tokens (garde-fou tokens). */
/* [2026-10-20 · demande Alexandre, démo approuvée] Nouveau visuel : un FOND BLEU (--g4g-primary) entre
   par la gauche et couvre l'écran ; 8 petites formes (couleurs 4P du site) entrent avec lui, DÉRIVENT
   LENTEMENT vers la droite pendant que la page se recharge, puis ressortent par la droite avec lui.
   Rythme : entrée 0,7 s (départ, rideau.js) · tenue avec dérive 0,5 s + sortie 0,7 s (arrivée, CSS).
   Mêmes mécanique et garde-fous qu'avant (rendu serveur à l'arrivée, filet de navigation). */
/* Canvas de la page = RÉPLIQUE STATIQUE du voile (bleu plein) pendant la transition : tout frame
   inter-page du rechargement est indiscernable du voile → zéro flash blanc. */
html.g4g-rideau-arriving { background: var(--g4g-primary); }
.g4g-rideau { position: fixed; inset: 0; z-index: 2000; pointer-events: none; overflow: hidden; }
.g4g-rideau__bg { position: absolute; inset: 0; background: var(--g4g-primary); will-change: transform; }
/* Formes : taille et dérive (--d) proportionnelles à l'écran, bornées (60 % → 100 % de la démo) ;
   petit décalage (--lag) = parallaxe. Identifiées par :nth-child (jamais de style inline → CSP).
   La DERNIÈRE a le plus grand décalage (rideau.js attend sa fin avant de naviguer). */
.g4g-rideau__f { position: absolute; box-sizing: border-box; border-radius: 50%; aspect-ratio: 1; will-change: transform; --d: 4vw; --lag: 0s; }
.g4g-rideau__f:nth-child(2) { left: 14%; top: 22%; width: clamp(29px, 4.8vw, 48px); background: var(--g4g-purpose); --d: clamp(36px, 6vw, 60px); }
.g4g-rideau__f:nth-child(3) { left: 66%; top: 68%; width: clamp(12px, 2vw, 20px); border-radius: 4px; rotate: 18deg; background: var(--g4g-purpose); --d: clamp(21px, 3.5vw, 35px); --lag: 0.04s; }
.g4g-rideau__f:nth-child(4) { left: 41%; top: 17%; width: clamp(8px, 1.2vw, 12px); background: var(--g4g-people); --d: clamp(48px, 8vw, 80px); --lag: 0.02s; }
.g4g-rideau__f:nth-child(5) { left: 30%; top: 60%; width: clamp(40px, 6.6vw, 66px); border: 4px solid var(--g4g-prosperity); --d: clamp(27px, 4.5vw, 45px); --lag: 0.045s; }
.g4g-rideau__f:nth-child(6) { left: 52%; top: 32%; width: clamp(48px, 8vw, 80px); height: clamp(9px, 1.5vw, 15px); aspect-ratio: auto; border-radius: 999px; background: var(--g4g-people); --d: clamp(33px, 5.5vw, 55px); --lag: 0.01s; }
.g4g-rideau__f:nth-child(7) { left: 84%; top: 56%; width: clamp(18px, 3vw, 30px); border: 4px solid var(--g4g-people); --d: clamp(18px, 3vw, 30px); --lag: 0.05s; }
.g4g-rideau__f:nth-child(8) { left: 21%; top: 80%; width: clamp(9px, 1.5vw, 15px); background: var(--g4g-prosperity); --d: clamp(45px, 7.5vw, 75px); --lag: 0.025s; }
.g4g-rideau__f:nth-child(9) { left: 58%; top: 84%; width: clamp(34px, 5.6vw, 56px); height: 4px; aspect-ratio: auto; border-radius: 4px; background: var(--g4g-prosperity); --d: clamp(30px, 5vw, 50px); --lag: 0.06s; }
/* ARRIVÉE (rendue serveur, 100 % CSS) : tenue 0,5 s (les formes dérivent), puis sortie 0,7 s à droite. */
.g4g-rideau--cover .g4g-rideau__bg { animation: g4gRideauBg 1.2s both; }
.g4g-rideau--cover .g4g-rideau__f { animation: g4gRideauF 1.2s both; animation-delay: var(--lag); }
@keyframes g4gRideauBg {
  0%, 41.7% { transform: translateX(0); animation-timing-function: var(--g4g-ease-sweep); }
  100% { transform: translateX(101%); }
}
@keyframes g4gRideauF {
  0% { transform: translateX(0); animation-timing-function: linear; }
  41.7% { transform: translateX(var(--d)); animation-timing-function: var(--g4g-ease-sweep); }
  100% { transform: translateX(calc(100vw + var(--d))); }
}
/* DÉPART (rideau.js) : fond et formes partent hors écran à gauche puis glissent en place en 0,7 s. */
.g4g-rideau--enter .g4g-rideau__bg { transform: translateX(-101%); transition: transform 0.7s var(--g4g-ease-sweep); }
.g4g-rideau--enter .g4g-rideau__f { transform: translateX(-100vw); transition: transform 0.7s var(--g4g-ease-sweep) var(--lag); }
.g4g-rideau--enter.g4g-rideau--in .g4g-rideau__bg,
.g4g-rideau--enter.g4g-rideau--in .g4g-rideau__f { transform: translateX(0); }
/* Mouvement réduit / sobre : aucune transition d'écran. */
@media (prefers-reduced-motion: reduce) { .g4g-rideau { display: none !important; } }
body.g4g-anim-sobre .g4g-rideau { display: none !important; }

/* ═══ 9. Presets d'animation PAR AUDIENCE — RÈGLE DU DESIGN SYSTEM ════════════
 * Le rythme des reveals dépend de l'AUDIENCE de la section/page (décision Alexandre,
 * maquette Communauté), appliqué EXHAUSTIVEMENT au site : un marqueur `data-audience`
 * est posé sur le <body> de chaque page (repli = défaut de site) ET sur chaque section
 * CMS ciblée (cf. content/templatetags/content_targeting.py). Ces règles ne posent QUE
 * des variables `--anim-*` (valeurs en TOKENS, cf. tokens.css) que les reveals
 * génériques consomment ; l'héritage CSS fait le reste (aucun câblage élément par
 * élément). L'ancêtre le plus proche gagne : une section citoyenne dans une page
 * entreprise adopte le rythme citoyen.
 *
 * EXCEPTION « animation spécifique » (§3 du brief) : compteurs (--g4g-dur-signature),
 * surlignage .g4g-hl (--g4g-dur-highlight), parallaxe [data-px], .g4g-pop (durée/courbe
 * propres), rideau 4P — AUCUN ne lit --anim-dur/ease/dist/scale → tous gardent leur
 * rythme quel que soit le preset de section. Un élément qui voudrait garder SON rythme
 * malgré le preset peut redéfinir les `--anim-*` sur lui-même (voir .g4g-anim-self).
 *
 * Non gaté par `html.g4g-motion` : ce ne sont que des déclarations de variables, sans
 * effet visuel tant qu'un reveal (lui, gaté) ne les consomme pas. */
[data-audience="entreprises"] {   /* posé · fondu lent · décélération · sans rebond */
  --anim-dur: var(--g4g-anim-dur-ent);
  --anim-ease: var(--g4g-anim-ease-ent);
  --anim-dist: var(--g4g-anim-dist-ent);
  --anim-scale: var(--g4g-anim-scale-ent);
  --anim-seq: var(--g4g-anim-seq-ent);
}
[data-audience="citoyens"] {      /* dynamique · rapide · léger rebond · pop d'échelle */
  --anim-dur: var(--g4g-anim-dur-cit);
  --anim-ease: var(--g4g-anim-ease-cit);
  --anim-dist: var(--g4g-anim-dist-cit);
  --anim-scale: var(--g4g-anim-scale-cit);
  --anim-seq: var(--g4g-anim-seq-cit);
}
/* Échappatoire explicite : un élément portant .g4g-anim-self garde le rythme POSÉ
 * (entreprise) quel que soit le preset de sa section — pour un reveal qui doit rester
 * calme dans une section citoyenne. Réinitialise les variables sur lui-même. */
.g4g-anim-self {
  --anim-dur: var(--g4g-anim-dur-ent);
  --anim-ease: var(--g4g-anim-ease-ent);
  --anim-dist: var(--g4g-anim-dist-ent);
  --anim-scale: var(--g4g-anim-scale-ent);
  --anim-seq: var(--g4g-anim-seq-ent);
}
