/* assets/styles/_home.css */
/* Organismes de la page d'accueil (story 3-1, RF-1.1) : home-hero, home-verbs,
   home-method, home-proof (dont le wheel carousel des logos clients)
   et la FAQ (sortie dans _faq.css). Toutes les couleurs viennent des tokens --wts-* (cf. _variables.css) ;
   les sections alternent noir/blanc/orange comme la maquette Figma (nodes 23:343,
   26:575, 26:574, 26:582, 29:787, 32:1197). */

/* ── home-hero ── */
/* La marge NÉGATIVE annule, pour ce hero seulement, le bandeau que `#main-content` réserve
   en haut de page pour le header fixe (cf. app.css). Sans elle, le hero commençait 107px
   sous le haut de la fenêtre tout en faisant 100svh de haut : son centre tombait donc 107px
   sous le centre de l'écran, et toute la composition paraissait poussée vers le bas.
   Le hero occupe maintenant la fenêtre entière et le header flotte par-dessus — ce qui est
   de toute façon ce pour quoi il est dessiné : il est en `position: fixed`, sur fond
   transparent, avec sa propre capsule opaque. Le noir du hero et celui du body étant le
   même, le décalage ne se voit nulle part ailleurs que dans le centrage, qui est le but.
   Les 12vw de marge interne suffisent largement à dégager la capsule. */
.home-hero {
    position: relative;
    overflow: hidden;
    margin-top: calc(-1 * var(--wts-header-band));
    min-height: 100svh;
    display: flex;
    align-items: center;
    background-color: var(--wts-color-black);
    padding: clamp(5.5rem, 12vw, 12rem) 0;
}

/* Poussière de lumière ambiante (story 3-7, cf. hero_particles_controller.js). Invisible
   tant que le contrôleur ne l'a pas allumée : sans JS, en mouvement réduit ou sans Canvas,
   la toile reste transparente et rien ne change. Le fondu est long — elle doit s'installer
   sans qu'on la voie arriver. */
.home-hero__particles {
    position: absolute;
    inset: 0;
    display: block;
    width: 100%;
    height: 100%;
    pointer-events: none;
    opacity: 0;
    transition: opacity 2.4s var(--ease-out-soft);
}

.home-hero__particles.is-live {
    opacity: 1;
}

.home-hero__content {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: clamp(var(--wts-space-8), 5vw, var(--wts-space-16));
}

/* ── Signature de marque, en surtitre du manifeste ──
   Le hero ne nommait l'entreprise nulle part (cf. home-hero.html.twig). Monogramme + nom en
   capitales espacées, centrés entre deux filets qui se dessinent vers l'extérieur.

   La signature garde la gouttière PLEINE de la colonne (jusqu'à 4rem) : elle était jusqu'ici
   ramenée à 1,5rem du titre par une marge basse négative, pour se lire comme un surtitre
   collé à lui. Agrandie, elle n'en a plus besoin — elle tient toute seule et le rapproché
   l'écrasait contre le titre. Elle respire donc au même écart que le manifeste en bas de
   composition, et la colonne étant centrée verticalement, ces ~40px gagnés remontent aussi
   la signature d'autant de moitié. D'où l'absence de `margin` ici : c'est volontaire. */
.home-hero__signature {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: clamp(0.75rem, 2.5vw, 1.5rem);
    /* 34rem, comme la largeur du manifeste posé en bas de la composition : les deux bornes
       tombent au même endroit. Élargie en même temps que le lockup — à 32rem, le lockup
       agrandi mangeait les filets, qui n'auraient plus été que des moignons. */
    width: min(100%, 34rem);
    margin: 0;
}

/* Filets décoratifs : dégradés vers le silver plutôt qu'un trait plein, pour qu'ils
   s'éteignent au lieu de se couper net aux extrémités. */
.home-hero__signature::before,
.home-hero__signature::after {
    content: '';
    flex: 1;
    height: 1px;
    min-width: 1.5rem;
}

.home-hero__signature::before {
    background: linear-gradient(to right, transparent, var(--wts-color-silver-40));
}

.home-hero__signature::after {
    background: linear-gradient(to left, transparent, var(--wts-color-silver-40));
}

.home-hero__signature-lockup {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    flex-shrink: 0;
}

/* Monogramme et nom grossissent ENSEMBLE, dans la même proportion : le monogramme reste à
   ~2,8 fois la hauteur de capitale du nom, rapport sur lequel le lockup a été calé. Les
   agrandir séparément déséquilibrerait la signature. */
.home-hero__signature .icon {
    width: 2rem;
    height: 2rem;
    color: var(--wts-color-orange);
}

/* Nom en ORANGE, comme le monogramme qui le précède : les deux ne font qu'une seule marque,
   les séparer en deux couleurs les faisait lire comme deux éléments posés côte à côte.
   L'interlettrage ajoute une chasse APRÈS la dernière lettre : sans la marge négative
   correspondante, le nom paraît décalé d'un cran vers la gauche entre ses deux filets. */
.home-hero__signature-name {
    font-family: var(--wts-font-sans);
    font-size: var(--wts-font-size-base);
    font-weight: 600;
    line-height: 1;
    letter-spacing: 0.34em;
    margin-right: -0.34em;
    text-transform: uppercase;
    white-space: nowrap;
    color: var(--wts-color-orange);
}

/* La composition EST le geste visuel : display très large, interligne écrasé, casse
   haute. Purement décorative — le contenu réel vit dans le <h1> et la liste en .sr-only
   (cf. template), pour ne pas mêler les étiquettes au nom accessible du titre. */
.home-hero__composition {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.04em;
    font-family: var(--wts-font-display);
    font-size: clamp(2.25rem, 7.4vw, 6.5rem);
    line-height: 0.94;
    letter-spacing: -0.02em;
    text-transform: uppercase;
    color: var(--wts-color-white);
}

/* Une rangée par ligne : étiquette, morceau, forme, morceau, CTA — des frères de même
   hauteur. La forme se pose donc ENTRE les mots et ne peut jamais les recouvrir. */
.home-hero__line {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.18em;
}

.home-hero__line--accent {
    color: var(--wts-color-orange);
}

.home-hero__chunk {
    display: inline-flex;
    align-items: center;
    white-space: nowrap;
}

/* L'étiquette est SORTIE DU FLUX de la pile : sinon elle ajoute sa hauteur, et le morceau
   qu'elle accompagne se décale par rapport aux autres morceaux de la rangée. Elle flotte
   donc au-dessus (1re rangée) ou en dessous (3e). */
.home-hero__stack {
    position: relative;
    display: inline-flex;
}

.home-hero__label--trail {
    position: absolute;
    left: 0;
}

.home-hero__line--1 .home-hero__label--trail {
    bottom: calc(100% + 0.08em);
}

.home-hero__line--3 .home-hero__label--trail {
    top: calc(100% + 0.08em);
}

/* ── Formes intercalées, carrées et de la hauteur de la boîte de texte ── */
.home-hero__token {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 0.82em;
    height: 0.82em;
    /* Remonte optiquement la forme : centrée sur la BOÎTE de ligne, elle paraît trop
       basse, car cette boîte réserve sous la ligne de base une place pour les jambages
       que les capitales n'occupent pas. La marge basse décale le centrage vers le haut,
       pour aligner la forme sur la hauteur des capitales. */
    margin-bottom: 0.12em;
    /* Le wrapper ne porte QUE le trajet d'entrée (Web Animations, posé en JS) ; tout le
       visuel et l'animation continue vivent sur l'enfant — sinon les deux transforms
       s'écraseraient. */
    will-change: transform;
}

.home-hero__token-inner {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
}

/* Le monogramme de la marque, À NU : pas d'aplat, pas de cadre — le dessin lui-même, en
   orange, posé entre les mots. C'est le seul jeton qui n'est pas une forme abstraite, il
   n'a donc pas besoin d'un contenant pour exister.

   Il est aussi le seul à être AJOURÉ : à surface de boîte égale, une forme creuse pèse
   optiquement bien moins qu'un aplat, et le monogramme paraissait deux fois plus petit que
   ses voisins. Sa boîte est donc élargie à 1em (contre 0.82em pour les autres) pour que les
   trois jetons pèsent le même poids sur la ligne. */
.home-hero__token--mark {
    width: 1em;
    height: 1em;
}

.home-hero__token--mark .home-hero__token-inner {
    color: var(--wts-color-orange);
}

.home-hero__token--mark .icon {
    width: 100%;
    height: 100%;
}

.home-hero__token--cluster .home-hero__token-inner {
    color: var(--wts-color-white);
}

.home-hero__token--cluster .icon {
    width: 0.74em;
    height: 0.74em;
}

.home-hero__token--arrow .home-hero__token-inner {
    border-radius: var(--wts-radius-full);
    background-color: var(--wts-color-white);
    color: var(--wts-color-black);
}

.home-hero__token--arrow .icon {
    width: 0.4em;
    height: 0.4em;
}

/* Étiquettes et CTA vivent dans les rangées : ils doivent donc sortir de l'échelle `em`
   du titre, sans quoi ils suivraient sa taille démesurée. */
.home-hero__label {
    /* Largeur calée pour tenir en DEUX lignes : `balance` répartit ensuite les mots
       équitablement entre elles plutôt que de laisser un orphelin sur la dernière.
       `flex-shrink: 0` empêche la rangée de comprimer l'étiquette de tête, ce qui la
       ferait repasser sur trois lignes. */
    max-width: 14rem;
    flex-shrink: 0;
    text-wrap: balance;
    font-family: var(--wts-font-sans);
    font-size: var(--wts-font-size-xs);
    font-weight: 600;
    line-height: 1.35;
    letter-spacing: 0.08em;
    color: var(--wts-color-silver);
}

.home-hero__label--lead {
    text-align: right;
}

.home-hero__actions {
    display: inline-flex;
    margin-left: 1.5rem;
    font-size: 1rem;
    letter-spacing: normal;
    text-transform: none;
}

.home-hero__text {
    margin: 0;
    max-width: 34rem;
    font-size: var(--wts-font-size-lg);
    color: var(--wts-color-silver);
}


/* ── Entrée ──
   Conditionnée à `js-ready`, posée par l'amorce synchrone du <head> (assets/boot/boot.js) :
   sans JS, tout reste visible et rien n'est animé — même contrat que les `.reveal`
   (cf. _anim-tokens.css). Cette classe DOIT arriver avant la première peinture : sinon la
   page s'affiche d'abord dans son état final, puis retombe à l'état de départ quand elle
   arrive — l'ouverture semble alors se jouer deux fois. */
/* La signature entre TARD, et pas au chargement : comme les textes, elle attend `--settled`,
   donc la pose réelle des formes — et elle prend encore 900ms de plus par-dessus, ce qui la
   place après l'assemblage du titre, au moment où le manifeste monte. Elle ne s'annonce
   donc plus avant la composition, elle la signe une fois posée.
   Compter depuis `--settled` plutôt que depuis le chargement est aussi ce qui rend le
   décalage FIABLE : mesuré depuis le chargement, il dériverait avec le temps de chargement
   des polices et la signature pourrait repasser devant le titre sur une connexion lente. */
html.js-ready .home-hero__signature {
    opacity: 0;
    animation-name: hero-signature-in;
    animation-duration: 900ms;
    animation-delay: 900ms;
    animation-timing-function: var(--ease-out-soft);
    animation-fill-mode: forwards;
    animation-play-state: paused;
}

/* Les filets se dessinent vers l'EXTÉRIEUR depuis le nom : chacun prend donc son origine
   du côté du lockup. Ils partent après lui, pour qu'on lise le nom d'abord. */
html.js-ready .home-hero__signature::before,
html.js-ready .home-hero__signature::after {
    transform: scaleX(0);
    animation-name: hero-signature-rule;
    animation-duration: 1100ms;
    animation-delay: 1200ms;
    animation-timing-function: var(--ease-out-soft);
    animation-fill-mode: forwards;
    animation-play-state: paused;
}

html.js-ready .home-hero__signature::before {
    transform-origin: right center;
}

html.js-ready .home-hero__signature::after {
    transform-origin: left center;
}

@keyframes hero-signature-in {
    0% {
        opacity: 0;
        transform: translateY(-0.75rem);
    }
    100% {
        opacity: 1;
        transform: translateY(0);
    }
}

@keyframes hero-signature-rule {
    100% {
        transform: scaleX(1);
    }
}

html.js-ready .home-hero__token {
    /* Repris en main par le contrôleur, qui les groupe d'abord au centre. Masquées
       jusque-là : le contrôleur étant chargé en différé, elles apparaîtraient sinon une
       fraction de seconde à leur place définitive avant de sauter au centre. */
    opacity: 0;
}

/* L'entrée porte sur les ENFANTS de la rangée (morceaux, étiquettes, CTA) et surtout
   PAS sur la rangée elle-même : une opacité 0 sur le parent masquerait aussi les formes,
   qui sont ses enfants — elles voleraient invisibles et n'apparaîtraient qu'une fois
   posées, donnant l'impression que tout surgit en même temps.

   Le sens d'arrivée passe par une CLASSE (--from-left / --from-right) et non par une
   variable lue dans les keyframes, et le décalage par un `animation-delay` en ligne, en
   propriétés longues plutôt que par le raccourci `animation`. Ces deux mécanismes — var()
   dans un @keyframes, et var() dans un raccourci — sont les points fragiles : quand ils
   échouent, tout retombe silencieusement sur les valeurs par défaut, donc un départ
   uniforme depuis la droite et sans décalage. */
html.js-ready .home-hero__enter {
    opacity: 0;
    animation-duration: 950ms;
    animation-timing-function: var(--ease-in-out-soft);
    animation-fill-mode: forwards;
    /* C'est `--settled` qui libère les textes, donc au moment où les formes ont RÉELLEMENT
       fini leur trajet — et non à une heure fixe qui dériverait selon le temps de
       chargement des polices. */
    animation-play-state: paused;
}

html.js-ready .home-hero__enter--from-left {
    animation-name: hero-in-left;
}

html.js-ready .home-hero__enter--from-right {
    animation-name: hero-in-right;
}

html.js-ready .home-hero__text {
    opacity: 0;
    animation-name: hero-fade-up;
    animation-duration: 950ms;
    animation-timing-function: var(--ease-in-out-soft);
    animation-fill-mode: forwards;
    animation-play-state: paused;
}

/* Sélecteurs plus spécifiques que les règles en pause ci-dessus (qui portent `html`). */
html.js-ready .home-hero--settled .home-hero__enter,
html.js-ready .home-hero--settled .home-hero__text,
html.js-ready .home-hero--settled .home-hero__signature,
html.js-ready .home-hero--settled .home-hero__signature::before,
html.js-ready .home-hero--settled .home-hero__signature::after {
    animation-play-state: running;
}

/* L'opacité atteint 1 dès 30 % du temps, bien avant la fin du déplacement : sinon
   l'élément fait l'essentiel de son trajet en étant encore transparent, et on ne perçoit
   que sa fin de course — le sens d'arrivée devient illisible. L'easing est symétrique
   pour la même raison : une courbe « chargée à l'avant » avale la distance avant que
   l'œil ait vu quoi que ce soit. */
@keyframes hero-in-left {
    0% {
        opacity: 0;
        transform: translateX(clamp(-22rem, -20vw, -6rem));
    }
    30% {
        opacity: 1;
    }
    100% {
        opacity: 1;
        transform: translateX(0);
    }
}

@keyframes hero-in-right {
    0% {
        opacity: 0;
        transform: translateX(clamp(6rem, 20vw, 22rem));
    }
    30% {
        opacity: 1;
    }
    100% {
        opacity: 1;
        transform: translateX(0);
    }
}

/* Le manifeste monte depuis le bas — même montée d'opacité anticipée que les entrées
   latérales, pour que le déplacement soit visible et pas seulement sa fin de course. */
@keyframes hero-fade-up {
    0% {
        opacity: 0;
        transform: translateY(5rem);
    }
    30% {
        opacity: 1;
    }
    100% {
        opacity: 1;
        transform: translateY(0);
    }
}

/* ── Vie permanente ──
   L'entrée finie, les formes ne se figent pas : elles continuent de changer d'état par
   quarts de tour, avec des temps de pose, à des périodes non multiples entre elles pour
   que le trio ne retombe jamais en phase. */
.home-hero--settled .home-hero__token-inner {
    animation: hero-token-turn 11s var(--ease-in-out-soft) infinite;
}

/* Le monogramme, lui, BAT sans tourner. C'est un logo, pas une forme abstraite : les deux
   autres jetons peuvent pivoter parce qu'ils n'ont ni haut ni bas, alors que le monogramme
   a une orientation — le faire tourner, c'est présenter la marque à l'envers une fois sur
   deux.
   Mais lui couper l'animation tout court était pire : il se figeait à l'arrivée pendant que
   ses deux voisins continuaient de vivre, et il sortait visiblement du trio. Il garde donc
   sa place dans la boucle, exactement la même cadence à quatre temps et les mêmes temps de
   pose que les autres — on ne lui retire que la rotation (cf. hero-token-beat). */
.home-hero--settled .home-hero__token--mark .home-hero__token-inner {
    animation-name: hero-token-beat;
}

.home-hero--settled .home-hero__token--cluster .home-hero__token-inner {
    animation-duration: 17s;
    animation-direction: reverse;
}

.home-hero--settled .home-hero__token--arrow .home-hero__token-inner {
    animation-duration: 14s;
    animation-delay: -3s;
}

/* Le battement seul : rigoureusement les mêmes temps que `hero-token-turn` — mêmes paliers,
   mêmes temps de pose, même amplitude de 1.07 — la rotation en moins. C'est ce qui garde le
   monogramme dans le trio : il respire en phase avec ses voisins, il ne pivote simplement
   jamais. Toute retouche de la cadence doit être reportée dans les DEUX keyframes. */
@keyframes hero-token-beat {
    0%,
    18% {
        transform: scale(1);
    }
    26%,
    43% {
        transform: scale(1.07);
    }
    51%,
    68% {
        transform: scale(1);
    }
    76%,
    93% {
        transform: scale(1.07);
    }
    100% {
        transform: scale(1);
    }
}

@keyframes hero-token-turn {
    0%,
    18% {
        transform: rotate(0deg) scale(1);
    }
    26%,
    43% {
        transform: rotate(90deg) scale(1.07);
    }
    51%,
    68% {
        transform: rotate(180deg) scale(1);
    }
    76%,
    93% {
        transform: rotate(270deg) scale(1.07);
    }
    100% {
        transform: rotate(360deg) scale(1);
    }
}

/* ── CTA sous le titre dès que la 2e rangée ne peut plus le porter ──
   En desktop, le CTA vit au bout de la 2e rangée (« QUE VOTRE ▪ ACTIVITÉ ») et c'est lui qui
   la décale vers la gauche. Mais la rangée est en `nowrap` et le titre en 7.4vw : sous
   ~990px, il ne reste plus assez de place à droite. Élément flex rétrécissable, le CTA se
   comprimait alors jusqu'à passer son libellé sur trois lignes — pilule haute et
   disproportionnée — avant que la rangée ne finisse par déborder de l'écran.
   On le sort donc du flux pour le poser centré SOUS le titre, exactement comme en mobile,
   dont cette règle est l'extension : le même mécanisme vaut désormais de 1000px jusqu'en
   bas. La 2e rangée, libérée, se recentre avec les deux autres.
   Centré par un flex étalé sur toute la largeur, et non par un translateX : son animation
   d'entrée pilote justement `transform` et écraserait le centrage. */
@media (max-width: 1000px) {
    /* Les étiquettes de domaine partent au même seuil que le CTA, et pour la même raison :
       les rangées ne peuvent plus porter leurs accessoires. En desktop, les étiquettes des
       rangées 1 et 3 les décalent vers la droite, et le CTA décale la 2e vers la gauche —
       les deux s'équilibrent en zigzag. Le CTA parti, la 2e rangée se recentrait seule et
       toute la composition penchait à droite entre 900 et 1000px. */
    .home-hero__label {
        display: none;
    }

    .home-hero__composition {
        position: relative;
        /* Réserve la place du CTA, sorti du flux ci-dessous. */
        padding-bottom: 4.5rem;
    }

    .home-hero__actions {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        display: flex;
        justify-content: center;
        margin-left: 0;
    }
}

/* ── Mobile ──
   La composition en rangées ne tient plus : à cette largeur, la seule rangée
   « QUE VOTRE ▪ ACTIVITÉ » dépasse déjà l'écran. Comme les morceaux sont en
   `white-space: nowrap` dans une section `overflow: hidden`, le titre n'était pas
   redimensionné — il était ROGNÉ des deux côtés, sans même une barre de défilement pour
   le signaler.

   La composition redevient donc un bloc de texte centré qui coule : les rangées deviennent
   des lignes, les morceaux se coupent normalement, et les formes restent intercalées entre
   les mots. Le geste typographique survit, il cesse juste d'être une grille rigide. */
@media (max-width: 640px) {
    /* Le nom est en `nowrap` et le lockup en `flex-shrink: 0` : il ne peut ni se couper ni
       se comprimer, il déborde. Or à la taille desktop (1rem, 0.34em d'interlettrage) il
       fait à lui seul ~275px, et avec le monogramme il dépasse la largeur utile d'un écran
       de 320px. On redescend donc d'un cran de l'échelle, on resserre l'interlettrage, et on
       autorise les filets à se réduire à rien — ils s'effacent avant le nom.
       Conséquence assumée : la signature est un cran plus petite en dessous de 640px qu'au
       dessus. C'est le seul réglage qui tienne jusqu'à 320px sans casse. */
    .home-hero__signature {
        gap: 0.75rem;
    }

    .home-hero__signature::before,
    .home-hero__signature::after {
        min-width: 0;
    }

    .home-hero__signature .icon {
        width: 1.75rem;
        height: 1.75rem;
    }

    .home-hero__signature-name {
        font-size: var(--wts-font-size-sm);
        letter-spacing: 0.22em;
        margin-right: -0.22em;
    }

    /* Corps calé sur la LARGEUR DISPONIBLE, pour que chacune des trois lignes tienne en une
       seule : la plus longue (« QUE VOTRE ▪ ACTIVITÉ ») fait ~10,3em, jetons et espaces
       compris, et le diviseur 11 laisse la marge d'une barre de défilement de bureau.
       À 9.5vw, le corps était trop grand pour elle à TOUTES les largeurs mobiles : chaque
       ligne se cassait de son côté, et le titre tombait en fragments — « CE » seul sur sa
       ligne à 330px, « ACTIVITÉ » et « VRAIMENT. » rejetés à 390px. On retrouve ainsi en
       mobile la composition du desktop : trois lignes, un jeton au milieu de chacune.
       Interligne un peu plus ouvert qu'en desktop : à ce corps, 1.04 collait les capitales
       d'une ligne aux jetons de la suivante. */
    /* Le CTA, lui, est déjà sorti du flux et posé sous le titre par la règle ≤1000px
       (plus haut) : rien à redire ici. */
    .home-hero__composition {
        display: block;
        text-align: center;
        font-size: clamp(1.25rem, calc((100vw - 2 * var(--wts-space-6)) / 11), 3rem);
        line-height: 1.12;
    }

    /* Le manifeste suit le titre, centré lui aussi à cette largeur. En desktop, son bloc de
       34rem est centré dans la colonne et le texte aligné à gauche passe inaperçu ; ici le
       bloc prend toute la largeur, et un texte calé à gauche sous un titre centré se voyait
       tout de suite. */
    .home-hero__text {
        text-align: center;
    }

    /* Filet de sécurité : le titre est éditorial, un titre saisi plus long au back office
       peut encore dépasser. `balance` coupe alors la ligne en deux moitiés égales au lieu
       d'y laisser un mot orphelin. */
    .home-hero__line {
        display: block;
        text-wrap: balance;
    }

    .home-hero__chunk {
        display: inline;
        white-space: normal;
    }

    /* Plus de rangée flex pour centrer la forme : elle s'aligne désormais sur la ligne de
       texte, donc la compensation optique de la boîte flex n'a plus lieu d'être. */
    .home-hero__token {
        vertical-align: middle;
        margin-bottom: 0;
    }

}

@media (prefers-reduced-motion: reduce) {
    .home-hero__particles {
        display: none;
    }

    html.js-ready .home-hero__signature,
    html.js-ready .home-hero__signature::before,
    html.js-ready .home-hero__signature::after,
    html.js-ready .home-hero__token,
    html.js-ready .home-hero__enter,
    html.js-ready .home-hero__text,
    .home-hero--settled .home-hero__token-inner {
        opacity: 1;
        transform: none;
        animation: none;
    }
}

/* ── home-verbs ── */
/* Ce qu'on fait, en trois verbes (cf. home-verbs.html.twig et verbs_motion_controller.js).
   Toutes les valeurs pilotées au défilement ont ici leur valeur par défaut = état FINAL :
   sans JS ou en mouvement réduit, la feuille est plate, la phrase et les verbes écrits. */

/* Le noir derrière la feuille est celui du hero : c'est lui qu'on voit de part et d'autre du
   bord bombé tant que la feuille monte. */
.home-verbs {
    position: relative;
    background-color: var(--wts-color-black);
    color: var(--wts-color-black);
}

/* La feuille blanche. Son bord supérieur est une demi-ellipse aussi large qu'elle, dont la
   hauteur suit --rise : bombée en arrivant, plate une fois en place. */
.home-verbs__sheet {
    --dome: calc(var(--rise, 0) * clamp(3rem, 12vw, 12rem));
    padding-block: clamp(5rem, 11vw, 10rem) clamp(3.5rem, 7vw, 6rem);
    border-radius: 50% 50% 0 0 / var(--dome) var(--dome) 0 0;
    background-color: var(--wts-color-white);
}

/* ── Intro : titre, phrase qui s'écrit, illustration ── */
.home-verbs__intro {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: end;
    gap: var(--wts-space-8) var(--wts-space-12);
}

.home-verbs__lead {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--wts-space-6);
}

.home-verbs__eyebrow {
    display: inline-flex;
    align-items: center;
    gap: var(--wts-space-3);
    margin: 0;
    font-family: var(--wts-font-sans);
    font-weight: 600;
    font-size: var(--wts-font-size-sm);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--wts-color-orange);
}

.home-verbs__eyebrow::before {
    content: '';
    width: 2.5rem;
    height: 2px;
    background-color: currentColor;
}

/* La phrase d'intro, en grand : c'est une déclaration, pas un paragraphe. */
.home-verbs__statement {
    margin: 0;
    max-width: 24ch;
    font-family: var(--wts-font-sans);
    font-weight: 400;
    font-size: clamp(1.75rem, 3.6vw, 3.5rem);
    line-height: 1.15;
    letter-spacing: -0.015em;
}

/* Grisés tant que la lecture ne les a pas atteints — seulement une fois le contrôleur en
   place (`--armed`) : sans lui, la phrase reste entièrement noire. */
.home-verbs--armed .home-verbs__w {
    color: var(--wts-color-black-20);
    transition: color 450ms var(--ease-out-soft);
}

.home-verbs--armed .home-verbs__w.is-read {
    color: var(--wts-color-black);
}

/* L'illustration, dans une carte blanche au liseré discret, qui flotte à peine en traversant
   l'écran (--float) : un léger décalage vertical, sans rotation. */
.home-verbs__decor {
    width: clamp(12rem, 20vw, 20rem);
    padding: var(--wts-space-4);
    border: 1px solid var(--wts-color-silver-40);
    border-radius: var(--wts-radius-lg);
    background-color: var(--wts-color-white);
    box-shadow: 0 1.5rem 3rem -1.5rem var(--wts-color-black-20);
    transform: translate3d(0, calc((0.5 - var(--float, 0.5)) * 3rem), 0);
}

.home-verbs__decor-image {
    display: block;
    width: 100%;
    height: auto;
}

/* ── Les trois verbes ──
   Une liste numérotée entre filets fins. Chaque rangée se lit comme la phrase au-dessus :
   verbe gris → noir une fois atteint (.is-read), filet qui se remplit d'orange et
   pictogramme qui apparaît au fil de sa traversée (--read). */
.home-verbs__list {
    list-style: none;
    margin-block: clamp(3.5rem, 8vw, 6.5rem);
}

.home-verbs__row {
    position: relative;
    display: grid;
    grid-template-columns: clamp(2.5rem, 6vw, 5rem) minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--wts-space-6);
    padding-block: clamp(1.25rem, 2.5vw, 2.25rem);
    border-bottom: 1px solid var(--wts-color-silver-40);
}

.home-verbs__row:first-child {
    border-top: 1px solid var(--wts-color-silver-40);
}

/* Le filet orange, posé sur le filet gris du haut de la rangée. */
.home-verbs__row::before {
    content: '';
    position: absolute;
    top: -1px;
    left: 0;
    right: 0;
    height: 2px;
    background-color: var(--wts-color-orange);
    transform-origin: left center;
    transform: scaleX(var(--read, 1));
}

.home-verbs__index {
    align-self: start;
    padding-top: 0.5em;
    font-family: var(--wts-font-sans);
    font-weight: 600;
    font-size: var(--wts-font-size-sm);
    letter-spacing: 0.12em;
    color: var(--wts-color-orange);
}

.home-verbs__verb {
    margin: 0;
    font-family: var(--wts-font-display);
    font-size: clamp(1.75rem, 8vw, 6.5rem);
    line-height: 1;
    text-transform: uppercase;
}

.home-verbs--armed .home-verbs__verb {
    color: var(--wts-color-black-20);
    transition: color 600ms var(--ease-out-soft);
}

.home-verbs--armed .home-verbs__row.is-read .home-verbs__verb {
    color: var(--wts-color-black);
}

/* Pas de rotation : le premier pictogramme est le monogramme de la marque, il a un sens. */
.home-verbs__row .icon {
    width: clamp(2rem, 4vw, 3.5rem);
    height: clamp(2rem, 4vw, 3.5rem);
    color: var(--wts-color-orange);
    opacity: var(--read, 1);
    transform: scale(calc(0.6 + var(--read, 1) * 0.4));
}

/* ── Conclusion et relance ── */
.home-verbs__outro {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--wts-space-8);
}

.home-verbs__text {
    margin: 0;
    max-width: 30rem;
    font-family: var(--wts-font-sans);
    font-size: var(--wts-font-size-lg);
    line-height: 1.5;
    color: var(--wts-color-black-80);
}

/* Pilule noire et pastille orange ; au survol, la pastille s'étend jusqu'à remplir toute la
   pilule, et la flèche se redresse vers le haut. */
.home-verbs__cta {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    display: inline-flex;
    align-items: center;
    gap: var(--wts-space-4);
    padding: var(--wts-space-2) var(--wts-space-2) var(--wts-space-2) var(--wts-space-8);
    border-radius: var(--wts-radius-full);
    background-color: var(--wts-color-black);
    color: var(--wts-color-white);
    font-weight: 600;
    font-size: var(--wts-font-size-base);
    text-decoration: none;
    transition: color 400ms var(--ease-out-soft);
}

.home-verbs__cta::before {
    content: '';
    position: absolute;
    z-index: -1;
    top: 50%;
    right: var(--wts-space-2);
    width: 3rem;
    height: 3rem;
    margin-top: -1.5rem;
    border-radius: 50%;
    background-color: var(--wts-color-orange);
    transform: scale(0);
    transition: transform 650ms var(--ease-out-soft);
}

.home-verbs__cta-icon {
    display: grid;
    place-items: center;
    width: 3rem;
    height: 3rem;
    border-radius: 50%;
    background-color: var(--wts-color-orange);
    color: var(--wts-color-black);
    transition: background-color 400ms var(--ease-out-soft), color 400ms var(--ease-out-soft);
}

.home-verbs__cta-icon .icon {
    transition: transform 500ms var(--ease-out-soft);
}

.home-verbs__cta:hover,
.home-verbs__cta:focus-visible {
    color: var(--wts-color-black);
}

.home-verbs__cta:hover::before,
.home-verbs__cta:focus-visible::before {
    transform: scale(9);
}

.home-verbs__cta:hover .home-verbs__cta-icon,
.home-verbs__cta:focus-visible .home-verbs__cta-icon {
    background-color: var(--wts-color-black);
    color: var(--wts-color-orange);
}

.home-verbs__cta:hover .home-verbs__cta-icon .icon,
.home-verbs__cta:focus-visible .home-verbs__cta-icon .icon {
    transform: rotate(-45deg);
}

@media (max-width: 900px) {
    .home-verbs__intro {
        grid-template-columns: 1fr;
    }

    .home-verbs__decor {
        display: none;
    }
}

/* Sur un téléphone, le verbe le plus long doit tenir entre son numéro et son pictogramme. */
@media (max-width: 640px) {
    .home-verbs__row {
        grid-template-columns: 1.75rem minmax(0, 1fr) auto;
        gap: var(--wts-space-3);
    }

    .home-verbs__row .icon {
        width: 1.75rem;
        height: 1.75rem;
    }
}

@media (prefers-reduced-motion: reduce) {
    .home-verbs__cta,
    .home-verbs__cta::before,
    .home-verbs__cta-icon,
    .home-verbs__cta-icon .icon {
        transition: none;
    }
}

/* ── home-method ── */
/* Le travelling de la méthode (cf. home-method.html.twig et method_scroll_controller.js).

   Deux mises en page. La PILE verticale ci-dessous est la base : écran étroit, mouvement
   réduit, et page sans JS. La PISTE horizontale épinglée n'est posée que plus bas, sous la
   même requête média que celle du contrôleur — les deux doivent rester identiques.

   Les valeurs pilotées au défilement arrivent en propriétés CSS (--frame, --progress,
   --enter, --fill, --drift, --rail, --seg). Toutes ont ici leur valeur par défaut = état
   FINAL : sans JS, ou avant la première mesure, rien n'est masqué ni à moitié dessiné. */
.home-method {
    /* Le blanc des sections voisines : c'est lui qu'on voit autour de la carte noire tant
       qu'elle est en retrait. */
    background-color: var(--wts-color-white);
    color: var(--wts-color-white);
}

/* La scène noire. Le retrait « carte » est un clip-path et non une marge : il ne touche pas
   à la mise en page, donc ni à la hauteur de la section ni aux mesures du contrôleur.
   --frame 0 → plein cadre, 1 → carte arrondie en retrait des bords. */
.home-method__stage {
    position: relative;
    overflow: clip;
    padding-block: clamp(5rem, 14vw, 9rem);
    background-color: var(--wts-color-black);
    clip-path: inset(
        0 calc(var(--frame, 0) * clamp(0.75rem, 4vw, 4rem))
        round calc(var(--frame, 0) * clamp(1.5rem, 3vw, 3rem))
    );
}

/* ── Décor ── */
/* Grille de points, à mi-vitesse de la piste : deux plans qui glissent l'un sur l'autre
   donnent la profondeur. Deux fois plus large que la scène pour ne jamais découvrir son bord. */
.home-method__dots {
    position: absolute;
    inset: 0 auto 0 0;
    width: 200%;
    background-image: radial-gradient(circle, var(--wts-color-white-20) 1px, transparent 1.6px);
    background-size: 2.25rem 2.25rem;
    opacity: 0.45;
    -webkit-mask-image: linear-gradient(to bottom, transparent, var(--wts-color-black) 22%, var(--wts-color-black) 78%, transparent);
    mask-image: linear-gradient(to bottom, transparent, var(--wts-color-black) 22%, var(--wts-color-black) 78%, transparent);
    transform: translate3d(calc(var(--progress, 0) * -25vw), 0, 0);
    pointer-events: none;
}

/* Halo orange qui accompagne la lecture : il traverse la scène de gauche à droite au fil de
   la piste. Un dégradé, sans `filter: blur` — trop coûteux à recomposer à chaque image. */
.home-method__glow {
    position: absolute;
    left: -25vw;
    bottom: -45vw;
    width: 75vw;
    aspect-ratio: 1;
    border-radius: 50%;
    background: radial-gradient(circle closest-side, rgba(255, 129, 70, 0.26), rgba(255, 129, 70, 0.08) 55%, transparent);
    transform: translate3d(calc(var(--progress, 0) * 85vw), 0, 0);
    pointer-events: none;
}

/* ── Piste (en pile par défaut) ── */
.home-method__track {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: clamp(4rem, 11vw, 7rem);
    max-width: 83.25rem;
    margin-inline: auto;
    padding-inline: var(--wts-space-6);
}

/* Fil, repère d'origine, indice de défilement et indicateur n'ont de sens que sur la piste
   horizontale : ils ne sont posés que dans le bloc épinglé. */
.home-method__rail,
.home-method__origin,
.home-method__hint,
.home-method__hud {
    display: none;
}

/* ── Intro ── */
.home-method__intro-head {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--wts-space-6);
}

.home-method__eyebrow {
    display: inline-flex;
    align-items: center;
    gap: var(--wts-space-3);
    margin: 0;
    font-family: var(--wts-font-sans);
    font-weight: 600;
    font-size: var(--wts-font-size-sm);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--wts-color-orange);
}

.home-method__eyebrow::before {
    content: '';
    width: 2.5rem;
    height: 2px;
    background-color: currentColor;
}

/* Frick n'a qu'une graisse : le gras par défaut d'un h2 serait un faux gras calculé. */
.home-method__title {
    --reveal-delay: 90ms;
    margin: 0;
    font-weight: 400;
    font-size: clamp(2.5rem, 9vw, 4.5rem);
    line-height: 0.95;
    text-transform: uppercase;
}

.home-method__title-line {
    display: block;
}

.home-method__title-line--accent {
    color: var(--wts-color-orange);
}

/* ── Étapes ── */
.home-method__steps {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    /* Une colonne en mobile, 2 × 2 au plus : à quatre de front (mouvement réduit sur grand
       écran), « Développement » débordait sur sa voisine. */
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 24rem), 1fr));
    gap: clamp(3.5rem, 9vw, 4.5rem) var(--wts-space-8);
}

.home-method__step {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: var(--wts-space-6);
    padding-top: var(--wts-space-6);
}

/* En pile, chaque étape est coiffée d'un filet qui se remplit en même temps que son numéro. */
.home-method__step::before,
.home-method__step::after {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 1px;
    background-color: var(--wts-color-white-20);
}

.home-method__step::after {
    height: 2px;
    background-color: var(--wts-color-orange);
    transform-origin: left center;
    transform: scaleX(var(--fill, 1));
}

/* La forme se colle à SON numéro : écartée au bout de l'étape, elle flottait entre deux
   numéros et se lisait comme appartenant au suivant.
   Parallaxe : c'est l'en-tête ENTIER (numéro + forme) qui glisse par rapport au texte, en
   retard sur la piste. Donner à chacun sa propre dérive horizontale les faisait converger
   d'un côté de l'écran jusqu'à se chevaucher. */
.home-method__step-head {
    display: flex;
    align-items: flex-end;
    gap: clamp(1.5rem, 3vw, 3rem);
    transform: translate3d(calc(var(--drift, 0) * -5vw), 0, 0);
}

/* Texte détouré qui se REMPLIT au passage du point de lecture (grands numéros, maître mot) :
   un dégradé à bord franc, deux fois plus large que le texte et découpé sur ses lettres,
   dont on fait glisser la frontière (--fill 0 → vide, 1 → plein).
   Interligne 1 et non plus serré : le fond découpé ne peint que dans la boîte de l'élément,
   un interligne écrasé aurait tronqué le remplissage en haut des lettres. */
.home-method__fill {
    font-family: var(--wts-font-display);
    line-height: 1;
    color: transparent;
    -webkit-text-stroke: 1.5px var(--wts-color-orange);
    background-image: linear-gradient(90deg, var(--wts-color-orange) 50%, transparent 50%);
    background-size: 200% 100%;
    background-repeat: no-repeat;
    background-position: calc((1 - var(--fill, 1)) * 100%) 0;
    -webkit-background-clip: text;
    background-clip: text;
}

/* Chasse condensée : à cette taille, la normale mangeait la moitié de la largeur de l'étape. */
.home-method__number {
    display: block;
    margin-bottom: -0.14em;
    font-stretch: condensed;
    font-size: clamp(5.5rem, 26vw, 9rem);
}

/* ── Formes, une par étape ──
   Chacune se construit à l'entrée de son étape (--enter) : deux cercles se rejoignent (se
   rencontrer), des ondes s'ouvrent (découvrir), des blocs s'assemblent (proposer), des lignes
   se tirent (construire), une flèche se trace et part (faire évoluer). En traversant l'écran,
   elles montent et pivotent légèrement (--drift) : un mouvement vertical, qui ne peut pas les
   faire buter contre leur numéro. */
.home-method__glyph {
    position: relative;
    flex: 0 0 auto;
    width: clamp(4.5rem, 7vw, 7.5rem);
    aspect-ratio: 1;
    color: var(--wts-color-orange);
    transform: translate3d(0, calc(var(--drift, 0) * 2.5rem), 0) rotate(calc(var(--drift, 0) * -18deg));
}

/* 01 — rencontre : deux cercles, le vôtre et le nôtre, arrivent de loin et se chevauchent ;
   un point bat à leur intersection. */
.home-method__glyph--1 span {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
    width: 62%;
    height: 62%;
    border: 1.5px solid currentColor;
    border-radius: 50%;
}

.home-method__glyph--1 span:nth-child(1) {
    border-color: var(--wts-color-white-60);
    opacity: clamp(0, calc(var(--enter, 1) * 1.5), 1);
    transform: translateX(calc(-19% - (1 - var(--enter, 1)) * 3rem));
}

.home-method__glyph--1 span:nth-child(2) {
    opacity: clamp(0, calc(var(--enter, 1) * 1.5), 1);
    transform: translateX(calc(19% + (1 - var(--enter, 1)) * 3rem));
}

.home-method__glyph--1 span:nth-child(3) {
    width: 13%;
    height: 13%;
    border: 0;
    background-color: currentColor;
    opacity: clamp(0, calc(var(--enter, 1) * 2 - 1), 1);
    animation: method-beat 2.4s var(--ease-in-out-soft) infinite;
}

.home-method__glyph--1 span:nth-child(4) {
    display: none;
}

/* 02 — ondes : trois cercles qui s'ouvrent, un point qui bat, une onde qui part en boucle. */
.home-method__glyph--2 span {
    position: absolute;
    inset: 0;
    margin: auto;
    border: 1.5px solid currentColor;
    border-radius: 50%;
}

.home-method__glyph--2 span:nth-child(1) {
    opacity: calc(var(--enter, 1) * 0.3);
    transform: scale(calc(0.2 + var(--enter, 1) * 0.8));
}

.home-method__glyph--2 span:nth-child(2) {
    opacity: calc(var(--enter, 1) * 0.6);
    transform: scale(calc(0.15 + var(--enter, 1) * 0.5));
}

.home-method__glyph--2 span:nth-child(3) {
    opacity: var(--enter, 1);
    transform: scale(calc(0.1 + var(--enter, 1) * 0.22));
}

.home-method__glyph--2 span:nth-child(4) {
    width: 16%;
    height: 16%;
    border: 0;
    background-color: currentColor;
    animation: method-beat 2.4s var(--ease-in-out-soft) infinite;
}

.home-method__glyph--2::after {
    content: '';
    position: absolute;
    inset: 0;
    border: 1px solid currentColor;
    border-radius: 50%;
    opacity: 0;
    animation: method-ripple 2.4s var(--ease-out-soft) infinite;
}

/* 03 — blocs : quatre carrés qui arrivent de loin en tournant et se rangent en 2 × 2. */
.home-method__glyph--3 {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 14%;
}

.home-method__glyph--3 span {
    border: 1.5px solid var(--wts-color-white-40);
    border-radius: 22%;
    opacity: clamp(0, calc(var(--enter, 1) * 1.5), 1);
    transform:
        translate(calc((1 - var(--enter, 1)) * var(--dx)), calc((1 - var(--enter, 1)) * var(--dy)))
        rotate(calc((1 - var(--enter, 1)) * 120deg));
}

.home-method__glyph--3 span:nth-child(1) { --dx: -2.5rem; --dy: -2rem; }
.home-method__glyph--3 span:nth-child(2) { --dx: 3rem; --dy: -3rem; }
.home-method__glyph--3 span:nth-child(3) { --dx: -3rem; --dy: 2.5rem; }

.home-method__glyph--3 span:nth-child(4) {
    --dx: 3.5rem;
    --dy: 3rem;
    border-color: currentColor;
    background-color: currentColor;
}

/* 04 — lignes : quatre traits tirés l'un après l'autre, comme des lignes de code. */
.home-method__glyph--4 {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 13%;
}

.home-method__glyph--4 span {
    height: clamp(0.4rem, 0.7vw, 0.65rem);
    border-radius: var(--wts-radius-full);
    background-color: var(--wts-color-white-40);
    transform-origin: left center;
    transform: scaleX(clamp(0, calc(var(--enter, 1) * 1.8 - var(--i) * 0.22), 1));
}

.home-method__glyph--4 span:nth-child(1) { --i: 0; width: 100%; }
.home-method__glyph--4 span:nth-child(2) { --i: 1; width: 62%; background-color: currentColor; }
.home-method__glyph--4 span:nth-child(3) { --i: 2; width: 84%; }
.home-method__glyph--4 span:nth-child(4) { --i: 3; width: 40%; background-color: var(--wts-color-white-20); }

/* 05 — flèche : tracée au trait (pathLength = 1), puis lancée vers le haut à droite. */
.home-method__glyph--5 svg {
    width: 100%;
    height: 100%;
    overflow: visible;
    transform: translate(calc((1 - var(--enter, 1)) * -1.5rem), calc((1 - var(--enter, 1)) * 1.5rem));
}

.home-method__glyph--5 path {
    stroke-dasharray: 1;
    stroke-dashoffset: calc(1 - var(--enter, 1));
}

/* Nom et texte glissent dans le champ avec leur étape. */
.home-method__step-body {
    opacity: clamp(0, calc(var(--enter, 1) * 1.7 - 0.45), 1);
    transform: translate3d(calc((1 - var(--enter, 1)) * 3rem), 0, 0);
}

.home-method__name {
    margin: 0 0 var(--wts-space-4);
    font-weight: 400;
    font-size: clamp(1.75rem, 7vw, 2.5rem);
    line-height: 1;
    text-transform: uppercase;
}

.home-method__text {
    margin: 0;
    max-width: 26rem;
    font-size: var(--wts-font-size-base);
    line-height: 1.6;
    color: var(--wts-color-white-60);
}

/* Puce de l'étape sur le fil (piste épinglée seulement). */
.home-method__node {
    display: none;
    position: relative;
    z-index: 1;
    width: 1rem;
    height: 1rem;
    border: 2px solid var(--wts-color-white-40);
    border-radius: 50%;
    background-color: var(--wts-color-black);
    transition:
        background-color 400ms var(--ease-out-soft),
        border-color 400ms var(--ease-out-soft),
        box-shadow 400ms var(--ease-out-soft),
        transform 400ms var(--ease-out-soft);
}

.home-method__step.is-reached .home-method__node {
    border-color: var(--wts-color-orange);
    background-color: var(--wts-color-orange);
    box-shadow: 0 0 0 0.4rem var(--wts-color-orange-20), 0 0 1.5rem var(--wts-color-orange-60);
    transform: scale(1.15);
}

/* ── Conclusion : le maître mot, puis la relance ──
   Le mot qui résume les cinq temps, détouré comme les numéros et rempli de la même façon
   quand le fil l'atteint : la lecture de la méthode aboutit sur lui. */
.home-method__outro {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--wts-space-8);
}

.home-method__motto-head {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--wts-space-4);
}

.home-method__motto {
    margin: 0;
    font-size: clamp(2.75rem, 13vw, 5rem);
    text-transform: uppercase;
}

.home-method__motto-text {
    margin: 0;
    max-width: 30rem;
    font-size: var(--wts-font-size-lg);
    line-height: 1.5;
    color: var(--wts-color-white-80);
}

/* Relance : un disque orange frappé de la flèche de marque, ceint d'un anneau de texte qui
   tourne. Le bouton grandit à mesure que le fil l'approche (--rail) : c'est là que mène la
   lecture. */
.home-method__cta {
    position: relative;
    align-self: center;
    display: grid;
    place-items: center;
    width: clamp(11rem, 16vw, 15rem);
    aspect-ratio: 1;
    border-radius: 50%;
    color: var(--wts-color-white);
    text-decoration: none;
    transform: scale(calc(0.7 + var(--rail, 1) * 0.3));
}

.home-method__cta-ring {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    animation: method-spin 24s linear infinite;
}

.home-method__cta-ring text {
    font-family: var(--wts-font-display);
    font-size: 13px;
    fill: currentColor;
}

.home-method__cta-disc {
    display: grid;
    place-items: center;
    width: 60%;
    aspect-ratio: 1;
    border-radius: 50%;
    background-color: var(--wts-color-orange);
    color: var(--wts-color-black);
    transition: transform 600ms var(--ease-out-soft), background-color var(--anim-fast) var(--ease-out-soft);
}

.home-method__cta-disc .icon {
    width: 34%;
    height: 34%;
    transition: transform 600ms var(--ease-out-soft);
}

.home-method__cta:hover .home-method__cta-disc,
.home-method__cta:focus-visible .home-method__cta-disc {
    background-color: var(--wts-color-orange-light);
    transform: scale(1.1);
}

.home-method__cta:hover .home-method__cta-disc .icon,
.home-method__cta:focus-visible .home-method__cta-disc .icon {
    transform: rotate(45deg);
}

@keyframes method-spin {
    to { transform: rotate(360deg); }
}

@keyframes method-beat {
    0%, 100% { transform: scale(1); }
    50% { transform: scale(1.35); }
}

@keyframes method-ripple {
    0% { opacity: 0.8; transform: scale(0.2); }
    100% { opacity: 0; transform: scale(1.15); }
}

@keyframes method-nudge {
    0%, 100% { transform: translateX(0); }
    50% { transform: translateX(0.5rem); }
}

/* ── Piste horizontale épinglée ──
   Même requête que PINNED_QUERY dans method_scroll_controller.js. Conditionnée à `js-ready`
   (posée par l'amorce avant la première peinture) : sans JS, la piste ne pourrait pas
   avancer, la pile reste donc en place. */
@media (min-width: 900px) and (min-height: 600px) and (prefers-reduced-motion: no-preference) {
    /* Une fenêtre de haut, plus la longueur de piste qui dépasse : c'est la course verticale
       convertie en course horizontale. Valeur estimée tant que le contrôleur n'a pas mesuré. */
    html.js-ready .home-method {
        height: calc(100vh + var(--method-shift, 250vw));
    }

    /* La scène colle au haut de la fenêtre le temps de la course. Elle réserve en haut la
       place du header fixe, qui flotte par-dessus. */
    /* Colonne fixée à la largeur de la scène : en `auto`, elle s'élargissait à la piste
       entière, et tout ce qui s'y rapporte (le retrait d'alignement de la piste, en %, et
       l'indicateur) se calculait sur des milliers de pixels. */
    html.js-ready .home-method__stage {
        position: sticky;
        top: 0;
        display: grid;
        grid-template-columns: 100%;
        grid-template-rows: minmax(0, 1fr) auto;
        height: 100vh;
        padding-block: var(--wts-header-band) 0;
    }

    html.js-ready .home-method__track {
        flex-direction: row;
        align-items: stretch;
        gap: 7vw;
        width: max-content;
        max-width: none;
        margin: 0;
        /* Aligné sur la colonne de contenu du site (cf. .container) au départ. */
        padding-inline: calc(max(0px, (100% - 83.25rem) / 2) + var(--wts-space-6)) 0;
        will-change: transform;
    }

    html.js-ready .home-method__steps {
        display: flex;
        gap: 7vw;
    }

    /* Tous les panneaux partagent la même grille : haut, ligne du fil, bas. Le fil passe donc
       à mi-hauteur de la piste, et chaque puce tombe pile dessus. */
    html.js-ready .home-method__panel {
        display: grid;
        grid-template-rows: minmax(0, 1fr) 1rem minmax(0, 1fr);
        flex: 0 0 auto;
        padding: 0;
    }

    html.js-ready .home-method__rail {
        display: block;
        position: absolute;
        top: 50%;
        height: 2px;
        margin-top: -1px;
        background-color: var(--wts-color-white-20);
    }

    html.js-ready .home-method__rail-fill {
        position: absolute;
        inset: 0;
        background: linear-gradient(90deg, var(--wts-color-orange-40), var(--wts-color-orange));
        box-shadow: 0 0 0.75rem var(--wts-color-orange-60);
        transform-origin: left center;
        transform: scaleX(var(--rail, 0));
    }

    /* Intro */
    html.js-ready .home-method__intro {
        width: min(46rem, 46vw);
    }

    html.js-ready .home-method__intro-head {
        grid-row: 1;
        align-self: end;
        padding-bottom: clamp(1.5rem, 4.5vh, 3rem);
    }

    html.js-ready .home-method__title {
        font-size: clamp(3rem, 5vw, 6.5rem);
    }

    html.js-ready .home-method__origin {
        display: block;
        grid-row: 2;
        align-self: center;
        position: relative;
        z-index: 1;
        width: 1rem;
        height: 1rem;
        border: 2px solid var(--wts-color-orange);
        border-radius: 50%;
        background-color: var(--wts-color-black);
    }

    html.js-ready .home-method__hint {
        --reveal-delay: 200ms;
        display: inline-flex;
        grid-row: 3;
        align-self: start;
        justify-self: start;
        align-items: center;
        gap: var(--wts-space-3);
        margin: clamp(1.5rem, 4.5vh, 3rem) 0 0;
        font-size: var(--wts-font-size-sm);
        font-weight: 600;
        letter-spacing: 0.14em;
        text-transform: uppercase;
        color: var(--wts-color-white-60);
    }

    /* L'indice s'efface dès les premiers pixels de course : il a fait son travail. */
    html.js-ready .home-method__hint > * {
        opacity: clamp(0, calc(1 - var(--progress, 0) * 12), 1);
    }

    html.js-ready .home-method__hint .icon {
        color: var(--wts-color-orange);
        animation: method-nudge 1.6s var(--ease-in-out-soft) infinite;
    }

    /* Étapes */
    html.js-ready .home-method__step {
        width: clamp(22rem, 34vw, 38rem);
    }

    html.js-ready .home-method__step::before,
    html.js-ready .home-method__step::after {
        display: none;
    }

    html.js-ready .home-method__step-head {
        grid-row: 1;
        align-self: end;
        padding-bottom: clamp(1.25rem, 3.5vh, 2.5rem);
    }

    html.js-ready .home-method__number {
        font-size: min(34vh, 15vw);
    }

    html.js-ready .home-method__node {
        display: block;
        grid-row: 2;
        align-self: center;
    }

    html.js-ready .home-method__step-body {
        grid-row: 3;
        align-self: start;
        padding-top: clamp(1.25rem, 3.5vh, 2.5rem);
    }

    html.js-ready .home-method__name {
        font-size: clamp(2rem, 3.2vw, 3.75rem);
    }

    /* Conclusion : le maître mot au-dessus du fil, son texte dessous, et le disque de relance
       à droite, centré sur la fin du fil. La marge droite laisse respirer le disque en bout
       de course, au lieu de le coller au bord de l'écran. */
    html.js-ready .home-method__outro {
        grid-template-columns: auto auto;
        column-gap: clamp(3rem, 6vw, 7rem);
        padding-right: 8vw;
    }

    html.js-ready .home-method__motto-head {
        grid-column: 1;
        grid-row: 1;
        align-self: end;
        padding-bottom: clamp(1.25rem, 3.5vh, 2.5rem);
    }

    html.js-ready .home-method__motto {
        font-size: min(6vw, 16vh);
    }

    html.js-ready .home-method__motto-text {
        grid-column: 1;
        grid-row: 3;
        align-self: start;
        padding-top: clamp(1.25rem, 3.5vh, 2.5rem);
    }

    html.js-ready .home-method__cta {
        grid-column: 2;
        grid-row: 1 / -1;
        justify-self: center;
    }

    /* Indicateur des étapes, en pied de scène, sur la colonne de contenu. */
    html.js-ready .home-method__hud {
        display: block;
        position: relative;
        z-index: 1;
        width: 100%;
        max-width: 83.25rem;
        margin-inline: auto;
        padding: 0 var(--wts-space-6) clamp(1.25rem, 3.5vh, 2.25rem);
    }
}

.home-method__hud-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    /* Une colonne par étape, quel que soit leur nombre. */
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
    gap: var(--wts-space-6);
}

.home-method__hud-step {
    display: flex;
    flex-direction: column;
    gap: var(--wts-space-3);
    width: 100%;
    padding: var(--wts-space-3) 0 0;
    border: 0;
    background: none;
    color: var(--wts-color-white-40);
    font-family: var(--wts-font-sans);
    font-weight: 600;
    font-size: var(--wts-font-size-xs);
    letter-spacing: 0.12em;
    text-align: left;
    text-transform: uppercase;
    cursor: pointer;
    transition: color var(--anim-fast) var(--ease-out-soft);
}

.home-method__hud-step:hover,
.home-method__hud-step[aria-current='step'] {
    color: var(--wts-color-white);
}

.home-method__hud-number {
    margin-right: var(--wts-space-2);
    color: var(--wts-color-orange);
}

.home-method__hud-bar {
    position: relative;
    height: 2px;
    overflow: hidden;
    border-radius: var(--wts-radius-full);
    background-color: var(--wts-color-white-20);
}

.home-method__hud-bar::after {
    content: '';
    position: absolute;
    inset: 0;
    background-color: var(--wts-color-orange);
    transform-origin: left center;
    transform: scaleX(var(--seg, 0));
}

/* Mouvement réduit : la pile, dans son état final (posé par le contrôleur), sans boucle. */
@media (prefers-reduced-motion: reduce) {
    .home-method__glyph--1 span:nth-child(3),
    .home-method__glyph--2 span:nth-child(4),
    .home-method__glyph--2::after,
    .home-method__cta-ring {
        animation: none;
    }

    .home-method__node,
    .home-method__cta-disc,
    .home-method__cta-disc .icon,
    .home-method__hud-step {
        transition: none;
    }
}

/* ── home-proof ── */
.home-proof {
    background-color: var(--wts-color-white);
    color: var(--wts-color-black);
    /* Padding bas réduit : la roue ci-dessous laisse déjà beaucoup de vide sous les
       derniers logos, inutile d'y ajouter un padding plein (cf. _faq.css). */
    padding: clamp(5.5rem, 12vw, 12rem) 0 clamp(1.5rem, 3vw, 3rem);
}

.home-proof__inner {
    display: flex;
    flex-direction: column;
    gap: var(--wts-space-12);
}

.home-proof__intro {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--wts-space-6);
}

.home-proof__title {
    margin: 0;
    font-size: clamp(2rem, 4.5vw, 3.5rem);
}

.home-proof__text {
    margin: 0;
    max-width: 42rem;
    font-size: var(--wts-font-size-lg);
    color: var(--wts-color-black-80);
}

.home-proof__stats {
    display: flex;
    flex-wrap: wrap;
    gap: var(--wts-space-8);
    margin: 0;
}

.home-proof__stat {
    display: flex;
    flex-direction: column;
    gap: var(--wts-space-1);
}

.home-proof__stat-value {
    margin: 0;
    font-family: var(--wts-font-display);
    font-size: clamp(2rem, 4vw, 3rem);
    color: var(--wts-color-orange);
}

.home-proof__stat-value::after {
    content: '+';
}

.home-proof__stat-label {
    margin: 0;
    color: var(--wts-color-black-80);
    font-size: var(--wts-font-size-sm);
}

/* Scène du wheel carousel + bloc "nom du client" côte à côte (maquette Figma node
   29:787 : la roue occupe la colonne gauche, le nom sélectionné la colonne droite). */
.home-proof__showcase {
    display: grid;
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
    align-items: center;
    gap: var(--wts-space-8);
}

/* Hauteur donnée par un ratio et non en absolu : le rayon étant indexé sur la largeur
   (cf. proof_wheel_controller.js), ce ratio est ce qui garantit que toute la course des
   logos tient dans le cadre, à n'importe quelle taille d'écran — d'où `overflow: visible`,
   rien n'est rogné. */
.proof-wheel {
    position: relative;
    aspect-ratio: 45 / 100;
    overflow: visible;
}

/* Le centre du cercle tombe HORS de la scène, à gauche (cf. proof_wheel_controller.js) :
   l'effet recherché est un arc qui émerge du bord, pas un cercle complet posé dans la page.

   Jusqu'ici cette illusion ne tenait que par accident : c'était le BORD GAUCHE DU VIEWPORT
   qui rognait la moitié cachée. Or `.container` plafonne à 1332px — au-delà, l'écran grandit
   mais pas le contenu, la colonne se recentre et s'éloigne du bord gauche. Le cercle, lui,
   garde sa taille : vers 2560px de large il n'est déjà plus rogné que de quelques pixels, et
   au-delà de ~2650px il tient entièrement dans l'écran. D'où l'impression de demi-cercle
   perdue sur très grand écran, et elle seulement là.

   Le dégradé de masque déplace ce rognage du viewport vers la scène elle-même : l'arc se
   dissout juste avant le bord gauche de la scène, quelle que soit la largeur de l'écran.
   La position du fondu est calculée à partir des mêmes variables que le cercle (et non en
   pourcentage figé) : elle reste juste même si le rayon change côté JS. Sur un écran
   ordinaire, toute cette zone est déjà hors champ — le rendu n'y bouge pas d'un pixel. */
.proof-wheel__guide {
    /* Distance du bord gauche de la scène, mesurée depuis celui du cercle. --proof-wheel-cx
       est négatif : la soustraction l'AJOUTE au rayon. */
    --proof-wheel-guide-edge: calc(var(--proof-wheel-r, 0px) - var(--proof-wheel-cx, 0px));

    position: absolute;
    left: calc(var(--proof-wheel-cx, 0px) - var(--proof-wheel-r, 0px));
    top: calc(var(--proof-wheel-cy, 0px) - var(--proof-wheel-r, 0px));
    width: calc(var(--proof-wheel-r, 0px) * 2);
    height: calc(var(--proof-wheel-r, 0px) * 2);
    border: 1px solid var(--wts-color-black-40);
    border-radius: var(--wts-radius-full);
    pointer-events: none;
    /* Préfixe conservé : la chaîne de build n'a pas d'autoprefixer, et `mask-image` sans
       préfixe n'existe que depuis Safari 15.4.

       `black` n'est pas une couleur de design et n'a donc pas à venir d'un token : un
       masque ne lit que la couche alpha de son dégradé, la teinte n'est jamais peinte.
       C'est l'opaque le plus court à écrire, rien de plus. */
    -webkit-mask-image: linear-gradient(
        to right,
        transparent calc(var(--proof-wheel-guide-edge) * 0.55),
        black var(--proof-wheel-guide-edge)
    );
    mask-image: linear-gradient(
        to right,
        transparent calc(var(--proof-wheel-guide-edge) * 0.55),
        black var(--proof-wheel-guide-edge)
    );
}

.proof-wheel__items {
    list-style: none;
    margin: 0;
    padding: 0;
    position: absolute;
    inset: 0;
}

/* Pas de transition sur l'opacité : le contrôleur la repose à chaque frame, une
   transition ne ferait que la retarder — et ferait scintiller une copie au moment où son
   tour est réaffecté (cf. proof_wheel_controller.js). */
.proof-wheel__item {
    position: absolute;
    top: 0;
    left: 0;
}

/* Emplacement carré posé sur l'arc : il ne sert qu'à donner une taille commune à des logos
   de rapports très différents, et à porter la zone cliquable. Sans liseré ni fond — la
   maquette (node 29:704) cerclait la pastille, mais un rond autour d'un logo déjà dessiné
   fait deux formes concurrentes ; c'est l'opacité seule qui distingue la sélection (posée
   en JS). Moins de rembourrage qu'avant, du coup : le logo occupe la place du cercle. */
.proof-wheel__pill {
    display: flex;
    align-items: center;
    justify-content: center;
    width: clamp(7rem, 13vw, 11rem);
    height: clamp(7rem, 13vw, 11rem);
    padding: clamp(0.75rem, 1.5vw, 1.5rem);
    border: none;
    background: none;
    cursor: pointer;
    /* Le grossissement est un ÉTAT, pas une courbe : il se déclenche quand le logo devient
       actif, et non au fil de sa progression sur l'arc. D'où sa place ici plutôt qu'en JS —
       le contrôleur ne fait que poser la classe, le navigateur s'occupe du reste.
       C'est bien la PASTILLE qui porte l'échelle et non l'item : celui-ci voit sa
       transformation réécrite à chaque frame pour tenir sa place sur le cercle, les deux
       s'écraseraient.
       La durée reste courte devant le pas de la roue (900 ms) : le grossissement doit se
       lire comme une réaction au changement d'état, pas comme une animation de plus qui
       courrait pendant tout le trajet. */
    transition: transform 400ms var(--ease-out-soft);
}

/* Échelle du logo actif. Le plafond n'est pas arbitraire : deux pastilles voisines sont
   séparées d'au moins MIN_GAP_RATIO (1,4) fois leur largeur (cf. proof_wheel_controller.js)
   et la voisine reste à l'échelle 1, donc la demi-largeur cumulée vaut `0,5 × ÉCHELLE + 0,5`.
   Le contact se produirait à 1,8 ; il reste ici 0,15 largeur de jeu dans le cas le plus
   serré, celui des petites scènes où c'est bien MIN_GAP_RATIO qui commande l'angle. */
.proof-wheel__item--selected .proof-wheel__pill {
    transform: scale(1.5);
}

@media (prefers-reduced-motion: reduce) {
    .proof-wheel__pill {
        transition: none;
    }
}

.proof-wheel__logo {
    width: 100%;
    height: 100%;
    object-fit: contain;
}

/* Curseur posé sur l'arc au point de sélection : repère fixe, les logos défilent devant
   lui (maquette Figma node 29:626). */
.proof-wheel__cursor {
    position: absolute;
    left: var(--proof-wheel-anchor-x, 0px);
    top: var(--proof-wheel-anchor-y, 0px);
    display: flex;
    color: var(--wts-color-black);
    transform: translate(-50%, -50%);
    pointer-events: none;
}

.proof-wheel__cursor .icon {
    width: 2.375rem;
    height: 0.875rem;
}

/* Fallback reduced-motion / sans logo : grille statique posée par le contrôleur JS
   (cf. proof_wheel_controller.js), aucune trig ni boucle rAF. */
.proof-wheel--static .proof-wheel {
    height: auto;
    overflow: visible;
}

.proof-wheel--static .proof-wheel__guide,
.proof-wheel--static .proof-wheel__cursor {
    display: none;
}

.proof-wheel--static .proof-wheel__items {
    position: static;
    display: flex;
    flex-wrap: wrap;
    gap: var(--wts-space-4);
}

.proof-wheel--static .proof-wheel__item {
    position: static;
    opacity: 1;
}

.proof-wheel--static .proof-wheel__item[data-proof-wheel-lap='-1'],
.proof-wheel--static .proof-wheel__item[data-proof-wheel-lap='1'] {
    display: none;
}

.proof-wheel--static .home-proof__nav-group {
    display: none;
}

.home-proof__name-block {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--wts-space-4);
}

.home-proof__nav-group {
    display: flex;
    gap: var(--wts-space-2);
}

.home-proof__nav {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    height: 2.75rem;
    border: 1px solid var(--wts-color-silver-40);
    border-radius: var(--wts-radius-full);
    background: none;
    color: var(--wts-color-black);
    cursor: pointer;
    transition: border-color var(--anim-fast) var(--ease-out-soft), color var(--anim-fast) var(--ease-out-soft);
}

.home-proof__nav:hover,
.home-proof__nav:focus-visible {
    border-color: var(--wts-color-orange);
    color: var(--wts-color-orange);
}

/* Nom, accroche et lien fondent ENSEMBLE : le fondu est porté par leur conteneur, et non
   par chacun d'eux. Trois transitions indépendantes se décalaient à l'œil, alors que les
   trois textes décrivent une seule et même sélection. */
.home-proof__client {
    /* Le bloc occupe la COLONNE, et non la largeur du plus long de ses textes : c'est ce qui
       donne au nom une largeur de référence stable à mesurer (cf. `fitName()` dans
       proof_wheel_controller.js). Ses enfants s'étirent donc aussi, sauf le lien, qui doit
       rester à la taille de son libellé. */
    align-self: stretch;
    display: flex;
    flex-direction: column;
    gap: var(--wts-space-3);
    transition: opacity 200ms var(--ease-out-soft), transform 200ms var(--ease-out-soft);
}

.home-proof__client--swapping {
    opacity: 0;
    transform: translateY(8px);
}

/* Le nom du client tient TOUJOURS sur une ligne. Les noms viennent du back office : une
   taille figée qui convient aux six marques d'aujourd'hui casserait au premier client au nom
   plus long. Le contrôleur mesure donc le texte une fois posé et pose ici le facteur de
   réduction qu'il faut — d'où le `calc()` plutôt qu'une taille écrite en dur.

   Le couple overflow/ellipsis n'est qu'un garde-fou, jamais atteint en fonctionnement
   normal : il évite qu'un nom démesuré — ou l'absence du JS qui fait vivre toute cette roue
   — ne pousse une barre de défilement horizontale sur la page entière. `scrollWidth` continue
   de rapporter la largeur réelle du texte, la mesure n'en est pas affectée. */
.home-proof__client-name {
    margin: 0;
    font-family: var(--wts-font-display);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: calc(clamp(2rem, 4.4vw, 4rem) * var(--client-name-fit, 1));
    line-height: 1.3;
    text-transform: uppercase;
}

/* Accroche du projet lié. Longueur bornée pour que le bloc ne s'étire pas d'un client à
   l'autre : la roue tourne à côté, et un bloc qui change de hauteur à chaque cran ferait
   respirer toute la section. */
.home-proof__client-tagline {
    margin: 0;
    max-width: 32rem;
    font-family: var(--wts-font-sans);
    font-weight: 300;
    font-size: var(--wts-font-size-base);
    line-height: 1.6;
    color: var(--wts-color-black-80);
}

/* Même geste que le lien de la section des verbes : pas un bouton, une relance discrète —
   la section raconte, elle ne vend pas. */
.home-proof__client-link {
    /* Le bloc parent s'étire sur la colonne : sans cela le lien s'étirerait avec lui, et sa
       zone de focus s'étalerait bien au-delà de son libellé. */
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    gap: var(--wts-space-2);
    color: inherit;
    text-decoration: none;
    font-weight: 700;
    font-size: var(--wts-font-size-base);
    transition: color var(--anim-fast) var(--ease-out-soft);
}

.home-proof__client-link:hover,
.home-proof__client-link:focus-visible {
    color: var(--wts-color-orange);
    text-decoration: underline;
}

/* Le `display` ci-dessus l'emporterait sur `[hidden] { display: none }` de la feuille du
   navigateur : sans cette règle, le lien resterait visible sur une marque sans projet. */
.home-proof__client-link[hidden] {
    display: none;
}

@media (prefers-reduced-motion: reduce) {
    .home-proof__client,
    .home-proof__client-link {
        transition: none;
    }
}

@media (max-width: 900px) {
    .home-proof__showcase {
        grid-template-columns: 1fr;
    }

    /* En colonne unique la roue prend toute la largeur : le ratio donnerait une hauteur
       démesurée. On repasse à une hauteur bornée, quitte à rogner la course des logos —
       le fondu par distance du contrôleur amortit la coupe. */
    .proof-wheel {
        aspect-ratio: auto;
        height: clamp(28rem, 95vw, 42rem);
        overflow: hidden;
    }
}

@media (prefers-reduced-motion: reduce) {
    .home-proof__nav {
        transition: none;
    }
}


/* assets/styles/_faq.css */
/* Module « questions fréquentes » — partagé par l'accueil, l'index des projets et la page
   Releases (cf. templates/components/organisms/faq-section.html.twig).

   Né sur l'accueil sous le nom `home-faq`, il vivait en trois exemplaires divergents : les
   deux pages de fin de parcours s'étaient écrit leur propre variante, plus pauvre (une
   colonne, sans le visuel ni la relance). Le style est donc sorti de _home.css et renommé,
   pour qu'il n'y ait plus qu'une seule FAQ à regarder — et une seule à corriger.
   Toutes les couleurs viennent des tokens --wts-* (cf. _variables.css). */

/* Halo coloré du fond : reprend les deux « decorator » de la maquette (nodes 32:1200 et
   32:1201) en dégradés radiaux plutôt qu'en images — rien à exporter côté design. */
/* Padding haut réduit : sur l'accueil, la roue de home-proof laisse déjà un grand vide sous
   les derniers logos (ils s'estompent bien avant le bord de son cadre), les paddings pleins
   des deux sections s'y cumuleraient. Le réglage vaut pour les trois pages : la FAQ y ferme
   toujours le contenu, juste avant le footer. */
.faq-section {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    background-color: var(--wts-color-white);
    color: var(--wts-color-black);
    padding: clamp(2rem, 4vw, 4rem) 0 clamp(5.5rem, 12vw, 12rem);
}

.faq-section::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    background:
        radial-gradient(38rem 30rem at 88% 42%, var(--wts-color-orange-20), transparent 70%),
        radial-gradient(22rem 22rem at 2% 88%, var(--wts-color-silver-40), transparent 70%);
    pointer-events: none;
}

/* Variante sombre — index des projets, page Releases. Ces pages sont noires de bout en bout :
   le module s'y efface au lieu d'y poser un bloc blanc. Fond et halo retirés plutôt que
   repeints en noir : c'est le fond de la page qui doit passer, sans qu'un aplat de plus ait
   à s'accorder avec lui. Seuls le fond, le halo et la couleur du chapô changent — la grille,
   le visuel et les cartes d'accordéon (blanches) sont ceux de l'accueil. */
.faq-section--dark {
    background-color: transparent;
    color: var(--wts-color-white);
}

.faq-section--dark::before {
    content: none;
}

/* Sur fond noir, le contour noir de la forme flottante disparaissait. Orange brûlé, couleur
   choisie par l'utilisateur : un trait sourd, qui détache la forme du fond sans venir
   concurrencer l'aplat orange qu'il entoure. Il reste discret sur le noir — c'est l'effet
   recherché, ce contour n'est pas un élément d'information.
   Le « ? », lui, reste noir dans les deux thèmes : il est posé DANS la forme orange, c'est
   sur elle qu'il doit contraster, pas sur la page. */
.faq-section--dark .faq-section__blob {
    color: var(--wts-color-orange-burnt);
}

.faq-section__inner {
    display: flex;
    flex-direction: column;
    gap: var(--wts-space-16);
}

.faq-section__title {
    margin: 0;
    text-align: center;
    font-size: clamp(1.75rem, 3.6vw, 3rem);
    text-transform: uppercase;
}

/* Chapô facultatif, hérité des variantes Projets et Releases où il est administré. Il était
   en silver sur fond noir ; la section est blanche, il passe donc au noir adouci — même
   intention (du texte secondaire), même contraste relatif. Le gap de 4rem de `__inner` est
   fait pour séparer des blocs, pas un titre de son chapô : la marge négative le recolle. */
.faq-section__lead {
    margin: calc(var(--wts-space-16) * -1 + var(--wts-space-4)) 0 0;
    max-width: 60ch;
    margin-inline: auto;
    color: var(--wts-color-black-60);
    text-align: center;
    line-height: 1.6;
}

/* Sur fond noir, le chapô reprend le silver qu'il avait dans les anciennes variantes. */
.faq-section--dark .faq-section__lead {
    color: var(--wts-color-silver);
}

.faq-section__layout {
    display: grid;
    grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
    align-items: center;
    gap: clamp(var(--wts-space-12), 8vw, 8rem);
}

.faq-section__list {
    display: flex;
    flex-direction: column;
}

.faq-section__aside {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--wts-space-12);
    text-align: center;
}

/* Le contour de la forme est le SEUL trait de faq-blob.svg peint en `currentColor` (l'aplat
   orange et le « ? » y sont écrits en dur) : régler la couleur de texte d'ici suffit donc à
   le changer, sans variable dédiée ni second fichier SVG (cf. la variante sombre plus haut).

   Animation « organique » : trois transformations lentes superposées (dérive, rotation,
   étirement) à des durées volontairement non multiples — leur composition ne retombe
   jamais en phase, ce qui évite l'effet de boucle mécanique. On reste sur des transforms
   (composées par le GPU) plutôt que sur un filtre feTurbulence, qui déformerait
   réellement le contour mais coûterait un rendu de filtre à chaque frame. */
.faq-section__blob {
    color: var(--wts-color-black);
    display: inline-flex;
    animation: faq-blob-drift 19s var(--ease-in-out-soft) infinite;
    will-change: transform;
}

.faq-section__blob .icon {
    width: clamp(12rem, 20vw, 18.5rem);
    height: auto;
    aspect-ratio: 301 / 275;
    animation: faq-blob-turn 23s var(--ease-in-out-soft) infinite;
}

.faq-section__blob svg {
    animation: faq-blob-squish 13s var(--ease-in-out-soft) infinite;
}

@keyframes faq-blob-drift {
    0%,
    100% {
        transform: translate3d(0, 0, 0);
    }
    25% {
        transform: translate3d(1.5%, -2.5%, 0);
    }
    50% {
        transform: translate3d(-1.5%, 1.5%, 0);
    }
    75% {
        transform: translate3d(-2%, -1%, 0);
    }
}

@keyframes faq-blob-turn {
    0%,
    100% {
        transform: rotate(-2.5deg);
    }
    35% {
        transform: rotate(1.5deg);
    }
    70% {
        transform: rotate(3deg);
    }
}

@keyframes faq-blob-squish {
    0%,
    100% {
        transform: scale(1, 1);
    }
    30% {
        transform: scale(1.035, 0.965);
    }
    65% {
        transform: scale(0.97, 1.045);
    }
}

@media (prefers-reduced-motion: reduce) {
    .faq-section__blob,
    .faq-section__blob .icon,
    .faq-section__blob svg {
        animation: none;
    }
}

.faq-section__aside-text {
    display: flex;
    flex-direction: column;
    gap: var(--wts-space-1);
}

.faq-section__aside-title {
    margin: 0;
    font-family: var(--wts-font-sans);
    font-weight: 600;
    font-size: clamp(1.5rem, 2.4vw, 2rem);
}

.faq-section__aside-note {
    margin: 0;
    font-family: var(--wts-font-sans);
    font-weight: 600;
    font-size: var(--wts-font-size-sm);
}

@media (max-width: 900px) {
    .faq-section__layout {
        grid-template-columns: 1fr;
    }

    /* Le visuel passe AVANT la liste en pile : sur mobile la relance de contact se perd
       en bas d'une liste d'accordéons potentiellement longue. */
    .faq-section__aside {
        order: -1;
    }
}


/* assets/styles/_projects.css */
/* Index des projets (story 3-2, RF-1.2) : en-tête de page et grille « lanterne » des tuiles
   projet (molecule project-tile, contrôleur projects-grid).

   Toute la page est dans la DA de WTS : noir, argent, orange, Frick / Outfit. Rien ici ne lit
   les variables --project-* — les couleurs d'un projet ne se voient qu'une fois entré dedans
   (dive-in). */

/* La marge NÉGATIVE remonte la section sous le bandeau que `#main-content` réserve en haut
   de page pour le header fixe (cf. app.css), et le `padding-top` lui rend exactement la même
   valeur. Rien ne bouge donc à l'écran — sauf le FOND, qui remonte lui jusqu'en haut de la
   fenêtre. Sans cela, la section commençait 107px plus bas : son halo aussi, et ces 107px de
   noir plat au-dessus se lisaient comme un bandeau rapporté en travers du haut de la page.
   Le header, lui, est fait pour flotter par-dessus (position: fixed, fond transparent, sa
   propre capsule opaque). */
/* Halo derrière l'en-tête (même idiome que le fond de la FAQ, cf. _faq.css : des dégradés
   radiaux, pas d'image à exporter) — l'orange monte du coin haut gauche, un voile argenté
   creuse la diagonale opposée pour que le bloc de titre ne flotte pas sur un noir plat. Il
   est peint dans le fond même de la section : ses deux pseudo-éléments servent à la trame. */
.projects-index {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    background:
        radial-gradient(42rem 28rem at 12% -6%, var(--wts-color-orange-20), transparent 66%),
        radial-gradient(26rem 22rem at 92% 8%, var(--wts-color-silver-20), transparent 70%),
        var(--wts-color-black);
    margin-top: calc(-1 * var(--wts-header-band));
    padding: calc(var(--wts-header-band) + clamp(3rem, 8vw, 7rem)) 0 clamp(5.5rem, 12vw, 12rem);
}

/* ── Trame de points (fond de toute la section) ──
   Deux calques de la même trame, sur la même boîte — donc exactement la même grille de
   points :
     ::after   la trame de base, toujours là, en sourdine ; elle s'efface en haut et en bas
               pour se fondre dans les sections voisines ;
     ::before  la même, plus lumineuse (points plus gros, blancs), visible seulement dans un
               disque autour du curseur : la lanterne du contrôleur projects-grid (--gx /
               --gy, repère de la section ; `is-lit` quand le curseur est dedans).
   Les masques ne lisent que l'alpha de leurs couleurs, d'où des rgb() noirs plutôt que des
   tokens. */
.projects-index::before,
.projects-index::after {
    content: '';
    position: absolute;
    inset: 0;
    background-size: 22px 22px;
    pointer-events: none;
}

.projects-index::after {
    z-index: -2;
    background-image: radial-gradient(circle, var(--wts-color-white-60) 1px, transparent 1.6px);
    mask-image: linear-gradient(
        to bottom,
        transparent,
        rgb(0 0 0 / 0.36) 16rem,
        rgb(0 0 0 / 0.36) calc(100% - 18rem),
        transparent
    );
}

.projects-index::before {
    z-index: -1;
    background-image: radial-gradient(circle, var(--wts-color-white) 1.3px, transparent 1.9px);
    mask-image: radial-gradient(
        19rem circle at var(--gx, 50%) var(--gy, 50%),
        rgb(0 0 0 / 0.7),
        rgb(0 0 0 / 0.24) 45%,
        transparent 100%
    );
    opacity: 0;
    transition: opacity 700ms var(--ease-out-soft);
}

.projects-index.is-lit::before {
    opacity: 1;
}

.projects-index__inner {
    display: flex;
    flex-direction: column;
    gap: clamp(var(--wts-space-12), 7vw, 6rem);
}

/* ── En-tête de page ──
   Titre à gauche, chapô et nombre de projets à droite, alignés sur leur pied.

   Attention aux tokens : l'échelle d'espacement est 1-2-3-4-6-8-12-16. Un `--wts-space-5`
   ou `--wts-space-10` n'existe pas, et une déclaration qui référence une variable absente
   est invalide en bloc — un `gap` retombe alors à `normal` (0) et un `padding` à 0, sans
   la moindre erreur visible. */
.projects-index__head {
    display: grid;
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
    align-items: end;
    gap: var(--wts-space-8) clamp(var(--wts-space-8), 6vw, 6rem);
}

.projects-index__heading {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--wts-space-6);
}

.projects-index__eyebrow {
    display: inline-flex;
    align-items: center;
    gap: var(--wts-space-3);
    margin: 0;
    font-weight: 600;
    font-size: var(--wts-font-size-sm);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--wts-color-orange);
}

.projects-index__eyebrow::before {
    content: '';
    width: 2.5rem;
    height: 2px;
    background-color: currentColor;
}

/* Graisse 400 assumée : Frick ne fournit qu'une seule graisse, le gras par défaut de <h1>
   ne serait qu'un faux gras synthétisé par le navigateur.
   Décalage d'apparition (--reveal-delay, ici et sur le bloc de droite) porté par la classe :
   le mécanisme --stagger de _anim-tokens.css passe par un style inline, que la CSP interdit. */
.projects-index__title {
    --reveal-delay: 80ms;

    margin: 0;
    max-width: 16ch;
    font-family: var(--wts-font-display);
    font-size: clamp(2.5rem, 6vw, 5.5rem);
    font-weight: 400;
    line-height: 0.95;
    letter-spacing: -0.01em;
    text-transform: uppercase;
    color: var(--wts-color-white);
}

.projects-index__aside {
    --reveal-delay: 160ms;

    display: flex;
    flex-direction: column;
    gap: var(--wts-space-6);
}

.projects-index__lead {
    margin: 0;
    font-size: var(--wts-font-size-lg);
    line-height: 1.55;
    color: var(--wts-color-white-80);
}

.projects-index__count {
    display: flex;
    align-items: baseline;
    gap: var(--wts-space-3);
    margin: 0;
    padding-top: var(--wts-space-6);
    border-top: 1px solid var(--wts-color-white-20);
}

.projects-index__count-value {
    font-family: var(--wts-font-display);
    font-size: clamp(2rem, 3vw, 2.75rem);
    line-height: 1;
    color: var(--wts-color-orange);
}

.projects-index__count-label {
    font-weight: 600;
    font-size: var(--wts-font-size-sm);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--wts-color-white-60);
}

/* ── Grille ──
   Grille éditoriale sur 12 colonnes : les rangées alternent 7/5 puis 5/7, de sorte que deux
   rangées voisines ne se répètent jamais. Un dernier projet seul sur sa rangée la prend en
   entier plutôt que de laisser un trou. Le motif tient pour n'importe quel nombre de
   projets : ajouter un projet au back office ne demande aucune retouche ici. */
.projects-index__list {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: clamp(var(--wts-space-3), 1.4vw, 1.25rem);
    list-style: none;
    margin: 0;
    padding: 0;
}

/* La tuile survolée grossit : elle doit passer PAR-DESSUS ses voisines, pas dessous. */
.projects-index__item {
    position: relative;
    grid-column: span 12;
    display: flex;
}

.projects-index__item:hover,
.projects-index__item:focus-within {
    z-index: 2;
}

/* Cascade d'apparition des premières tuiles, celles qui sont dans l'écran à l'arrivée. Au-
   delà, chaque tuile entre dans le viewport séparément (reveal_controller.js) : un délai n'y
   serait plus une cascade, juste une tuile lente à venir. */
.projects-index__item:nth-child(2) { --reveal-delay: 90ms; }
.projects-index__item:nth-child(3) { --reveal-delay: 180ms; }
.projects-index__item:nth-child(4) { --reveal-delay: 270ms; }

@media (min-width: 640px) {
    .projects-index__item {
        grid-column: span 6;
    }

    .projects-index__item:last-child:nth-child(odd) {
        grid-column: span 12;
    }
}

@media (min-width: 1024px) {
    .projects-index__item:nth-child(4n + 1),
    .projects-index__item:nth-child(4n) {
        grid-column: span 7;
    }

    .projects-index__item:nth-child(4n + 2),
    .projects-index__item:nth-child(4n + 3) {
        grid-column: span 5;
    }

    .projects-index__item:last-child:nth-child(odd) {
        grid-column: span 12;
    }
}

@media (max-width: 900px) {
    .projects-index__head {
        grid-template-columns: 1fr;
    }
}

.projects-index__empty {
    margin: 0;
    color: var(--wts-color-silver);
    font-size: var(--wts-font-size-lg);
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   Molecule project-tile (templates/components/molecules/project-tile.html.twig)

   Variables lues, toutes posées sur la <li> par le contrôleur projects-grid — et toutes
   optionnelles : sans elles (doigt, mouvement réduit, JS absent) la tuile est statique.
     --x, --y    position du curseur dans la tuile (px)
     --px, --py  la même, normalisée de -1 à 1 depuis le centre (parallaxe du logo)
     --near      proximité du curseur, de 0 (loin) à 1 (sur la tuile) : éveil du logo
   ═══════════════════════════════════════════════════════════════════════════════════════════ */
.project-tile {
    --tile-line: color-mix(in srgb, var(--wts-color-white) 9%, transparent);

    position: relative;
    isolation: isolate;
    display: grid;
    grid-template-rows: auto minmax(0, 1fr) auto;
    width: 100%;
    min-height: clamp(24rem, 35vw, 32rem);
    padding: clamp(var(--wts-space-6), 2.4vw, 2.25rem);
    border-radius: var(--wts-radius-lg);
    background-color: color-mix(in srgb, var(--wts-color-white) 3%, var(--wts-color-black));
    /* Trois ombres dès le repos, même vides : c'est ce qui permet au navigateur de les
       interpoler une à une vers celles du premier plan, au lieu de sauter. */
    box-shadow:
        inset 0 0 0 1px var(--tile-line),
        0 0 0 0 transparent,
        0 0 0 0 transparent;
    overflow: hidden;
    color: var(--wts-color-white);
    font-family: var(--wts-font-sans);
    transition:
        background-color 500ms var(--ease-out-soft),
        box-shadow 700ms var(--ease-out-soft),
        transform 700ms var(--ease-out-soft);
}

/* Lumière intérieure : un halo orange sous le curseur, sur la seule tuile survolée. */
.project-tile::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    background: radial-gradient(30rem circle at var(--x, 50%) var(--y, 0%), var(--wts-color-orange-20), transparent 65%);
    opacity: 0;
    transition: opacity 600ms var(--ease-out-soft);
    pointer-events: none;
}

/* Filet éclairé : la même lumière, réduite à l'épaisseur du bord (anneau d'1px découpé par
   masque). Allumé sur TOUTES les tuiles dès que le curseur est dans la section : comme chaque
   tuile reçoit la position du curseur dans son propre repère, la lueur déborde d'une tuile
   à ses voisines et se lit comme une seule source de lumière qui balaie la grille. */
.project-tile::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 1;
    padding: 1px;
    border-radius: inherit;
    background: radial-gradient(
        26rem circle at var(--x, 50%) var(--y, 50%),
        var(--wts-color-orange),
        var(--wts-color-orange-40) 30%,
        var(--wts-color-silver-20) 55%,
        transparent 80%
    );
    -webkit-mask: linear-gradient(var(--wts-color-black) 0 0) content-box, linear-gradient(var(--wts-color-black) 0 0);
    -webkit-mask-composite: xor;
    mask: linear-gradient(var(--wts-color-black) 0 0) content-box exclude, linear-gradient(var(--wts-color-black) 0 0);
    opacity: 0;
    transition: opacity 700ms var(--ease-out-soft);
    pointer-events: none;
}

.projects-index.is-lit .project-tile::after {
    opacity: 1;
}

/* ── Haut : repère et typologie ── */
.project-tile__top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--wts-space-4);
    font-size: var(--wts-font-size-xs);
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--wts-color-white-60);
    transition: opacity 500ms var(--ease-out-soft);
}

.project-tile__index {
    font-variant-numeric: tabular-nums;
    color: var(--wts-color-white-80);
}

.project-tile__total {
    color: var(--wts-color-white-40);
}

.project-tile__meta {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--wts-space-2);
    list-style: none;
    margin: 0;
    padding: 0;
}

/* Typologie et « Depuis <année> » en pastilles : un filet clair sur le fond de la tuile, qui
   s'avive quand la tuile passe au premier plan. */
.project-tile__meta li {
    padding: 0.45em 0.9em;
    border: 1px solid var(--wts-color-white-20);
    border-radius: var(--wts-radius-full);
    background-color: color-mix(in srgb, var(--wts-color-white) 4%, transparent);
    line-height: 1;
    white-space: nowrap;
    transition:
        border-color 400ms var(--ease-out-soft),
        color 400ms var(--ease-out-soft);
}

.project-tile:hover .project-tile__meta li,
.project-tile:focus-within .project-tile__meta li {
    border-color: var(--wts-color-white-40);
    color: var(--wts-color-white-80);
}

/* ── Marque ──
   Le logo est posé en absolu au centre de sa zone : il ne pèse jamais sur la hauteur de la
   tuile, et quand l'accroche se déploie sous le titre (la zone rétrécit), il remonte et se
   resserre avec elle au lieu de pousser la tuile — donc la grille — vers le bas.

   Au repos, silhouette monochrome : `brightness(0)` ramène toute couleur au noir, `invert(1)`
   au blanc. Quel que soit le logo déposé (vert, bleu, multicolore), il se lit dans la DA du
   site ; il lui faut seulement un fond transparent (cf. ProjectAssetType::Logo).

   Au premier plan, le logo reprend SES couleurs, directement sur la tuile (choix utilisateur :
   pas de cartouche clair derrière). Un logo sombre y reste donc sombre — à fournir en
   version claire au back office si on veut qu'il ressorte. */
.project-tile__mark {
    position: relative;
    min-height: clamp(7rem, 12vw, 10rem);
}

/* Socle : une nappe de lumière très sourde sous la marque, pour qu'elle soit posée quelque
   part plutôt que de flotter dans un aplat — elle s'intensifie quand la tuile passe devant. */
.project-tile__mark::before {
    content: '';
    position: absolute;
    inset: 0;
    background: radial-gradient(
        closest-side,
        color-mix(in srgb, var(--wts-color-white) 6%, transparent),
        transparent
    );
    opacity: calc(0.6 + 0.4 * var(--near, 0));
    transition: opacity 600ms var(--ease-out-soft);
}

.project-tile__logo,
.project-tile__wordmark {
    position: absolute;
    inset: 0;
    margin: auto;
    transform:
        translate(calc(var(--px, 0) * -10px), calc(var(--py, 0) * -8px))
        scale(var(--mark-scale, 1));
    transition:
        opacity 450ms var(--ease-out-soft),
        transform 900ms var(--ease-out-soft),
        color 450ms var(--ease-out-soft),
        filter 550ms var(--ease-out-soft);
}

/* Taille intrinsèque bornée (et non une boîte fixe en object-fit) : un élément remplacé en
   absolu, `inset: 0` + `margin: auto`, reste centré à la taille du logo réellement affiché. */
.project-tile__logo {
    width: auto;
    height: auto;
    max-width: min(60%, 21rem);
    max-height: min(clamp(4.5rem, 9vw, 8.5rem), 100% - var(--wts-space-4));
    filter: brightness(0) invert(1);
    opacity: calc(0.24 + 0.34 * var(--near, 0));
}

/* Projet sans logo : son nom, écrit au trait, tient la place de la marque. */
.project-tile__wordmark {
    display: grid;
    place-items: center;
    padding-inline: var(--wts-space-4);
    font-family: var(--wts-font-display);
    font-size: clamp(2.5rem, 5.5vw, 5rem);
    line-height: 0.9;
    text-align: center;
    text-transform: uppercase;
    overflow-wrap: anywhere;
    color: transparent;
    -webkit-text-stroke: 1px var(--wts-color-white-60);
    opacity: calc(0.45 + 0.4 * var(--near, 0));
}

/* ── Pied : titre, déploiement, flèche ──
   Surtout pas de `position` ici ni sur le titre : la zone cliquable du lien (::after) doit
   se caler sur la TUILE, premier ancêtre positionné. Posé sur le pied, seul le pied mène au
   projet. */
.project-tile__foot {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--wts-space-6);
}

.project-tile__text {
    flex: 1;
    min-width: 0;
}

.project-tile__title {
    margin: 0;
    font-family: var(--wts-font-display);
    font-size: clamp(1.5rem, 2.4vw, 2.25rem);
    font-weight: 400;
    line-height: 1;
    letter-spacing: -0.005em;
    text-transform: uppercase;
    color: var(--wts-color-white-80);
    transition: color 450ms var(--ease-out-soft), opacity 500ms var(--ease-out-soft);
}

.project-tile__title-sub {
    display: block;
    font-size: clamp(0.7rem, 1vw, 0.85rem);
    font-weight: 400;
    letter-spacing: 0;
    text-transform: none;
    opacity: 0.5;
    margin-top: 0.25em;
}

/* Un seul lien par tuile : sa zone cliquable couvre toute la tuile, au-dessus des calques de
   lumière — c'est aussi elle qui porte l'anneau de focus. */
.project-tile__link {
    color: inherit;
    text-decoration: none;
}

/* Elle porte aussi le reflet : une bande de lumière oblique qui traverse la tuile une fois,
   de gauche à droite, quand elle passe au premier plan. Hors écran aux deux bouts de sa
   course (110 % -> -10 %) ; le retour se fait sans transition, donc sans être vu. */
.project-tile__link::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 3;
    border-radius: var(--wts-radius-lg);
    background: linear-gradient(
        105deg,
        transparent 38%,
        color-mix(in srgb, var(--wts-color-white) 9%, transparent) 50%,
        transparent 62%
    ) no-repeat 110% 0 / 250% 100%;
}

.project-tile__link:focus-visible {
    outline: none;
}

.project-tile__link:focus-visible::after {
    outline: 2px solid var(--wts-color-orange);
    outline-offset: -3px;
}

/* Déploiement : ouvert par défaut (doigt, JS absent), replié au repos sur un écran à souris
   (cf. bloc hover plus bas). La hauteur passe de 0fr à 1fr : pas de max-height magique. */
.project-tile__details {
    display: grid;
    grid-template-rows: 1fr;
}

.project-tile__details-inner {
    min-height: 0;
    overflow: hidden;
}

.project-tile__summary {
    margin: var(--wts-space-4) 0 0;
    max-width: 44ch;
    font-size: var(--wts-font-size-base);
    line-height: 1.55;
    color: var(--wts-color-white-60);
}

.project-tile__stats {
    display: flex;
    flex-wrap: wrap;
    gap: var(--wts-space-4) var(--wts-space-8);
    margin: var(--wts-space-6) 0 0;
    padding-top: var(--wts-space-4);
    border-top: 1px solid var(--wts-color-white-20);
}

.project-tile__stat {
    display: flex;
    flex-direction: column;
    gap: var(--wts-space-1);
    min-width: 0;
    max-width: 11rem;
}

.project-tile__stat-value {
    font-family: var(--wts-font-display);
    font-size: var(--wts-font-size-xl);
    line-height: 1;
    font-variant-numeric: tabular-nums;
    color: var(--wts-color-white);
}

.project-tile__stat-suffix {
    margin-left: 0.1em;
    color: var(--wts-color-orange);
}

.project-tile__stat-label {
    margin: 0;
    font-size: var(--wts-font-size-xs);
    line-height: 1.35;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--wts-color-silver-60);
}

/* Flèche : penchée vers le haut au repos (« aller voir »), elle se redresse vers la droite
   et se remplit d'orange quand la tuile passe au premier plan. */
.project-tile__arrow {
    display: inline-grid;
    place-items: center;
    flex-shrink: 0;
    width: 3rem;
    height: 3rem;
    border: 1px solid var(--wts-color-white-20);
    border-radius: var(--wts-radius-full);
    color: var(--wts-color-white-80);
    transform: translate(0, 0) rotate(-45deg);
    transition:
        transform 600ms var(--ease-out-soft),
        background-color 350ms var(--ease-out-soft),
        border-color 350ms var(--ease-out-soft),
        color 350ms var(--ease-out-soft);
}

/* ── Premier plan ──
   Survol OU focus clavier : même mise en avant. */
.project-tile:hover,
.project-tile:focus-within {
    background-color: color-mix(in srgb, var(--wts-color-white) 5%, var(--wts-color-black));
    box-shadow:
        inset 0 0 0 1px var(--tile-line),
        0 2.5rem 4.5rem -1.5rem var(--wts-color-black),
        0 1.5rem 4rem -1rem var(--wts-color-orange-20);
}

.project-tile:hover::before,
.project-tile:focus-within::before {
    opacity: 1;
}

.project-tile:hover .project-tile__link::after,
.project-tile:focus-within .project-tile__link::after {
    background-position: -10% 0;
    transition: background-position 1100ms var(--ease-in-out-soft) 80ms;
}

/* Le logo reprend ses couleurs (même liste de fonctions qu'au repos, pour que le passage de
   la silhouette à la couleur soit interpolé et non instantané). */
.project-tile:hover .project-tile__logo,
.project-tile:focus-within .project-tile__logo {
    --mark-scale: 1.06;

    filter: brightness(1) invert(0);
    opacity: 1;
}

.project-tile:hover .project-tile__wordmark,
.project-tile:focus-within .project-tile__wordmark {
    --mark-scale: 1.04;

    color: var(--wts-color-white);
    opacity: 1;
}

.project-tile:hover .project-tile__title,
.project-tile:focus-within .project-tile__title {
    color: var(--wts-color-white);
}

.project-tile:hover .project-tile__arrow,
.project-tile:focus-within .project-tile__arrow {
    border-color: var(--wts-color-orange);
    background-color: var(--wts-color-orange);
    color: var(--wts-color-black);
    /* Aimantée : elle se laisse tirer de quelques pixels vers le curseur. */
    transform: translate(calc(var(--px, 0) * 6px), calc(var(--py, 0) * 6px)) rotate(0);
}

/* ── Écran à souris : repli au repos, focus sur la tuile survolée ── */
@media (hover: hover) and (pointer: fine) {
    .project-tile__details {
        grid-template-rows: 0fr;
        transition: grid-template-rows 650ms var(--ease-out-soft);
    }

    .project-tile__details-inner {
        opacity: 0;
        transform: translateY(0.75rem);
        transition:
            opacity 300ms var(--ease-out-soft),
            transform 650ms var(--ease-out-soft);
    }

    .project-tile:hover .project-tile__details,
    .project-tile:focus-within .project-tile__details {
        grid-template-rows: 1fr;
    }

    .project-tile:hover .project-tile__details-inner,
    .project-tile:focus-within .project-tile__details-inner {
        opacity: 1;
        transform: none;
        transition-delay: 120ms;
    }

    /* Les autres tuiles reculent pendant qu'on en regarde une : leur texte s'efface, leur
       logo retombe à ce que lui accorde la proximité du curseur. */
    .projects-index__list:has(.project-tile:hover) .project-tile:not(:hover) :is(.project-tile__top, .project-tile__title) {
        opacity: 0.4;
    }
}

/* ── Profondeur : la tuile survolée avance, les autres reculent ──
   Elle grossit et s'incline légèrement vers le curseur (--px / --py : ±2,5° au plus — assez
   pour sentir le volume, pas assez pour lire de biais). Au clavier, pas de curseur : elle
   grossit seulement. Mouvement réduit : rien de tout cela. */
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
    .project-tile:focus-within {
        transform: scale(1.025);
    }

    .project-tile:hover {
        transform:
            perspective(1400px)
            rotateX(calc(var(--py, 0) * -2deg))
            rotateY(calc(var(--px, 0) * 2.5deg))
            scale(1.03);
    }

    .projects-index__list:has(.project-tile:hover) .project-tile:not(:hover) {
        transform: scale(0.985);
    }
}

/* Doigt : pas de survol, donc pas d'éveil — les logos sont lisibles d'emblée. */
@media (hover: none) {
    .project-tile__logo {
        opacity: 0.72;
    }

    .project-tile__wordmark {
        opacity: 0.8;
    }
}

@media (max-width: 639px) {
    .project-tile {
        min-height: 24rem;
    }
}

/* Petit écran, ou doigt (tout y est déployé d'emblée) : la flèche remonte à côté du titre
   pour rendre toute la largeur au texte, et les chiffres se rangent en colonnes plutôt que
   de s'empiler. `display: contents` sur le bloc texte (une simple boîte de mise en page,
   sans rôle) laisse le titre et le déploiement se placer directement dans la grille du pied. */
@media (max-width: 639px), (hover: none) {
    .project-tile__foot {
        display: grid;
        grid-template-columns: minmax(0, 1fr) auto;
        grid-template-areas:
            'title arrow'
            'details details';
        align-items: center;
        gap: 0 var(--wts-space-4);
    }

    .project-tile__text {
        display: contents;
    }

    .project-tile__title {
        grid-area: title;
    }

    .project-tile__details {
        grid-area: details;
    }

    .project-tile__arrow {
        grid-area: arrow;
        width: 2.5rem;
        height: 2.5rem;
    }

    .project-tile__stats {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(5.5rem, 1fr));
        gap: var(--wts-space-4);
    }
}

@media (prefers-reduced-motion: reduce) {
    .project-tile,
    .project-tile::before,
    .project-tile::after,
    .project-tile__logo,
    .project-tile__wordmark,
    .project-tile__title,
    .project-tile__arrow,
    .project-tile__details,
    .project-tile__details-inner {
        transition: none;
    }

    .project-tile__logo,
    .project-tile__wordmark,
    .project-tile__details-inner {
        transform: none;
    }

    .project-tile__arrow,
    .project-tile:hover .project-tile__arrow,
    .project-tile:focus-within .project-tile__arrow {
        transform: none;
    }

    /* Pas de reflet qui traverse la tuile. */
    .project-tile__link::after {
        background: none;
    }
}

/* assets/styles/_font-pairs.css */
/* Paires typographiques des thèmes projet (FontPair, story 3-2).

   Une classe par paire embarquée, préconstruite ici au build : les noms de police d'un
   thème sont saisis au back office, on ne les injecte donc jamais dans du CSS généré
   (cf. App\Twig\ThemeExtension). L'extension ne fait que dériver `font-pair--<clé>` de la
   clé de la paire ; une clé sans classe correspondante retombe silencieusement sur le
   repli ci-dessous, jamais sur une police cassée.

   Ajouter une paire = ajouter sa classe ici (et ses @font-face dans _fonts.css si la
   police est réellement livrée). */

/* Repli : les polices du site. Déclaré avant les paires — même spécificité que
   `.font-pair--*`, c'est donc l'ordre qui tranche. */
[data-project-theme] {
    --project-font-heading: var(--wts-font-display);
    --project-font-body: var(--wts-font-sans);
}

/* Paire WTS, seule paire réellement embarquée (cf. _fonts.css). */
.font-pair--frick-outfit {
    --project-font-heading: 'Frick', 'Arial Black', sans-serif;
    --project-font-body: 'Outfit', system-ui, -apple-system, sans-serif;
}

/* Paire non embarquée : aucun .woff2 livré et `font-src 'self'` interdit d'aller chercher
   une police distante — on vise donc les piles système les plus proches. */
.font-pair--inter-jetbrains-mono {
    --project-font-heading: 'Inter', system-ui, -apple-system, sans-serif;
    --project-font-body: 'JetBrains Mono', ui-monospace, 'SFMono-Regular', monospace;
}

/* Les deux paires suivantes servent la story 3-3 : quatre projets de démonstration devaient
   rendre quatre univers RÉELLEMENT distincts (couleurs + fonts + hero), or deux paires ne
   suffisaient pas à départager quatre projets. Elles s'appuient sur une serif universellement
   disponible (Georgia) plutôt que sur une police à télécharger : `font-src 'self'` l'interdit,
   et aucune n'est embarquée. */

/* Serif de titre / sans embarquée en corps : registre « institutionnel ». */
.font-pair--georgia-outfit {
    --project-font-heading: 'Georgia', 'Times New Roman', serif;
    --project-font-body: 'Outfit', system-ui, -apple-system, sans-serif;
}

/* L'inverse : sans géométrique embarquée en titre, serif en corps — registre « magazine ». */
.font-pair--outfit-georgia {
    --project-font-heading: 'Outfit', system-ui, -apple-system, sans-serif;
    --project-font-body: 'Georgia', 'Times New Roman', serif;
}

/* assets/styles/_project-page.css */
/* Page projet (story 3-3, RF-1.3) — la « page-univers ».

   RÈGLE ABSOLUE DE CE FICHIER : aucun token du site principal. Pas un --wts-color-*, pas un
   --wts-space-*, pas une couleur en dur. Tout ce qui se voit ici descend des quatre variables
   du thème du projet — --project-primary, --project-secondary, --project-bg, --project-accent
   (servies par App\Theme\ProjectThemeCss) — et de sa paire typographique
   (--project-font-heading / --project-font-body, cf. _font-pairs.css). C'est ce qui fait que
   quatre projets aux thèmes différents rendent quatre univers réellement distincts sans qu'une
   ligne de CSS soit écrite pour l'un d'eux.

   Seule exception assumée : la classe utilitaire `.container` (largeur de colonne partagée avec
   le reste du site) et `.sr-only`. Ce sont des utilitaires de MISE EN PAGE et d'accessibilité,
   ils ne peignent rien.

   Les quatre couleurs du thème arrivent sur des rôles, définis une fois ci-dessous : le reste
   du fichier ne parle plus que d'encre, de filet, de surface. Un thème qui change ne demande
   donc jamais de retoucher une règle.

   RÈGLE DE CONTRASTE — L'ACCENT NE PORTE JAMAIS DE TEXTE.

   Les quatre couleurs sont saisies au back office : la page ne peut donc rien PRÉSUMER de leur
   contraste, elle doit rester lisible pour n'importe quel thème valide. Le seul couple sur
   lequel un thème s'engage est primary/bg — c'est lui qui fait la lisibilité de l'index projets
   comme de cette page (les quatre thèmes de fixtures y sont entre 8:1 et 11,8:1). L'accent, lui,
   est une couleur MOYENNE par nature : mesuré sur ces mêmes quatre thèmes, il plafonne à 4,9:1
   contre la couleur primaire et à 3,2:1 contre le fond — sous le seuil AA de 4,5:1 pour du texte
   courant.

   Conséquence tenue dans tout ce fichier : tout ce qui se LIT est peint avec le couple
   primary/bg (ou un color-mix des deux) ; l'accent ne sert qu'à des éléments GRAPHIQUES, où le
   contraste n'est pas une exigence de lecture — liseré du hero editorial, point du repère de
   défilement, halos, filet du bandeau final, anneau de focus. */

/* ── Le <body> de la page ──
   `.project-page` EST le <body> (cf. le bloc `body_attributes` de layouts/base.html.twig) et
   non un conteneur intérieur : c'est ce qui met le header et le footer — rendus par le layout,
   hors du contenu de la page — dans la portée des variables du thème. Une page-univers s'arrête
   aux bords de la fenêtre, pas au bord de son contenu. */

/* Le hero occupe le premier écran, header compris : sur cette page il passe DERRIÈRE le
   header fixe au lieu d'être poussé dessous. La réserve posée par #main-content pour tout le
   site est donc annulée ici — et uniquement ici. */
.project-page #main-content {
    padding-top: 0;
}

/* Rôles de couleur — le seul endroit du fichier où les variables du thème sont lues
   directement. La page suppose ce que suppose tout thème du site : --project-bg est la couleur
   claire, --project-primary la couleur profonde (cf. les fixtures et le formulaire de thème du
   back office). */
.project-page {
    /* --project-ink est facultative (cf. ProjectThemeCss) : sans elle, le texte s'écrit en
       primaire — le comportement de tous les thèmes dont la primaire est une encre lisible. */
    --pp-ink: var(--project-ink, var(--project-primary));
    /* 74 % est le plancher : en dessous, l'encre secondaire passe sous 4,5:1 sur le thème le
       moins contrasté des fixtures. C'est la teinte de tout le texte de second plan de la page
       — corps de chapitre, accroche, sur-titres. */
    --pp-ink-soft: color-mix(in srgb, var(--project-ink, var(--project-primary)) 74%, var(--project-bg));
    --pp-line: color-mix(in srgb, var(--project-primary) 16%, var(--project-bg));
    --pp-surface: color-mix(in srgb, var(--project-bg) 94%, var(--project-primary));
    --pp-shadow: color-mix(in srgb, var(--project-primary) 16%, transparent);

    /* Sur une surface peinte en --project-primary (hero immersive, bandeaux) les rôles
       s'inversent : c'est le fond clair du thème qui devient l'encre. */
    --pp-on-deep: var(--project-bg);
    /* Même plancher, dans l'autre sens (cf. --pp-ink-soft). */
    --pp-on-deep-soft: color-mix(in srgb, var(--project-bg) 76%, var(--project-primary));
}

.project-page {
    /* Rythme propre à la page : la page-univers ne partage pas l'échelle du site, elle a la
       sienne, respirée pour une lecture longue. */
    --pp-chapter-gap: clamp(2rem, 5vw, 5rem);
    --pp-chapter-pad: clamp(3.5rem, 8vw, 7rem);
    --pp-radius: 20px;
    --pp-radius-sm: 12px;
    --pp-duration: 260ms;
    --pp-ease: cubic-bezier(0.22, 1, 0.36, 1);
    /* Sortie plus longue et plus « posée » : celle des grands mouvements (rideaux, entrées). */
    --pp-ease-out: cubic-bezier(0.16, 1, 0.3, 1);

    position: relative;
    background-color: var(--project-bg);
    color: var(--pp-ink);
    font-family: var(--project-font-body);
}

/* Le focus clavier et la sélection sont peints par le site en orange de marque (cf. app.css).
   Dans une page-univers, ils appartiennent au projet — sinon la couleur de la maison
   réapparaît au premier coup de tabulation. */
.project-page :focus-visible {
    outline: 2px solid var(--project-accent);
    outline-offset: 3px;
}

.project-page ::selection {
    background-color: var(--project-accent);
    color: var(--project-primary);
}

/* Les chapitres reçoivent le focus quand on y saute depuis le sommaire (tabindex -1, cf.
   project-page#jump) : c'est un point de départ pour la tabulation, pas un élément à
   entourer. Et on s'y arrête sous le header fixe, pas derrière lui. */
.project-chapter {
    scroll-margin-top: 5rem;
}

.project-chapter:focus {
    outline: none;
}

/* ── Retour permanent à l'index (RF-1.4) ──
   Fixé au coin bas gauche : toujours atteignable, y compris au milieu d'un chapitre en bandeau
   plein, et hors du chemin du header fixe (z-index 50) comme de son bouton menu. */
.project-back {
    position: fixed;
    left: clamp(1rem, 3vw, 2rem);
    bottom: clamp(1rem, 3vw, 2rem);
    z-index: 40;
    display: inline-flex;
    align-items: center;
    gap: 0.7em;
    padding: 0.75em 1.2em 0.75em 1em;
    border: 1px solid color-mix(in srgb, var(--project-primary) 24%, transparent);
    border-radius: 9999px;
    background-color: color-mix(in srgb, var(--project-bg) 86%, transparent);
    backdrop-filter: blur(12px);
    color: var(--pp-ink);
    font-family: var(--project-font-body);
    font-size: 0.875rem;
    line-height: 1;
    text-decoration: none;
    box-shadow: 0 12px 32px var(--pp-shadow);
    transition:
        background-color var(--pp-duration) var(--pp-ease),
        border-color var(--pp-duration) var(--pp-ease),
        transform var(--pp-duration) var(--pp-ease);
}

/* Flèche dessinée en CSS (deux bords d'une boîte tournée à 45°) plutôt qu'un asset SVG :
   l'icône du design system est peinte en currentColor, mais son gabarit appartient au site. */
.project-back__arrow {
    width: 0.5em;
    height: 0.5em;
    border-left: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(45deg);
    transition: transform var(--pp-duration) var(--pp-ease);
}

.project-back:hover,
.project-back:focus-visible {
    border-color: color-mix(in srgb, var(--project-accent) 70%, transparent);
    background-color: var(--project-bg);
    transform: translateY(-2px);
}

.project-back:hover .project-back__arrow,
.project-back:focus-visible .project-back__arrow {
    transform: rotate(45deg) translate(-2px, 2px);
}

/* ── Avancement de la lecture ──
   Un filet d'accent en haut de l'écran, qui s'allonge avec --progress (posé sur l'univers par
   project-page). Sans JS, --progress n'existe pas : le filet reste à zéro, invisible. */
.project-progress {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 60;
    height: 3px;
    background-color: var(--project-accent);
    transform: scaleX(var(--progress, 0));
    transform-origin: 0 50%;
    pointer-events: none;
}

/* ── Sommaire latéral (project-rail) ──
   Collé au bord droit, replié en repères ; il se déplie au survol ou au focus. Il n'apparaît
   que pendant la lecture des chapitres (`is-reading`, posé par project-page) et seulement sur
   un écran assez large pour qu'il ne mange pas le contenu. Sa pastille a son propre fond,
   flouté : il reste lisible sur le fond clair comme sur un bandeau profond. */
.project-rail {
    display: none;
}

@media (min-width: 1360px) {
    .project-rail {
        display: block;
        position: fixed;
        top: 50%;
        right: 0.75rem;
        z-index: 40;
        opacity: 0;
        visibility: hidden;
        translate: 1.5rem -50%;
        transition:
            opacity 500ms var(--pp-ease),
            translate 700ms var(--pp-ease-out),
            visibility 0s linear 700ms;
    }

    .project-universe.is-reading .project-rail {
        opacity: 1;
        visibility: visible;
        translate: 0 -50%;
        transition:
            opacity 500ms var(--pp-ease),
            translate 700ms var(--pp-ease-out),
            visibility 0s;
    }
}

.project-rail__list {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.125rem;
    margin: 0;
    padding: 0.35rem;
    list-style: none;
    border: 1px solid color-mix(in srgb, var(--project-primary) 14%, transparent);
    border-radius: 1.25rem;
    background-color: color-mix(in srgb, var(--project-bg) 84%, transparent);
    backdrop-filter: blur(14px);
    box-shadow: 0 16px 40px var(--pp-shadow);
}

.project-rail__link {
    display: flex;
    flex-direction: row-reverse;
    align-items: center;
    gap: 0.6rem;
    padding: 0.45rem 0.3rem;
    border-radius: 0.8rem;
    color: var(--pp-ink-soft);
    font-size: 0.8125rem;
    line-height: 1;
    text-decoration: none;
    transition:
        color var(--pp-duration) var(--pp-ease),
        background-color var(--pp-duration) var(--pp-ease);
}

.project-rail__tick {
    flex-shrink: 0;
    width: 0.75rem;
    height: 2px;
    border-radius: 9999px;
    background-color: currentColor;
    opacity: 0.55;
    transition:
        width 500ms var(--pp-ease-out),
        background-color var(--pp-duration) var(--pp-ease),
        opacity var(--pp-duration) var(--pp-ease);
}

.project-rail__number,
.project-rail__label {
    max-width: 0;
    overflow: hidden;
    white-space: nowrap;
    opacity: 0;
    transition:
        max-width 500ms var(--pp-ease-out),
        opacity 300ms var(--pp-ease);
}

.project-rail__number {
    font-family: var(--project-font-heading);
    font-variant-numeric: tabular-nums;
}

.project-rail:hover :is(.project-rail__number, .project-rail__label),
.project-rail:focus-within :is(.project-rail__number, .project-rail__label) {
    max-width: 14rem;
    opacity: 1;
}

.project-rail__link:hover,
.project-rail__link:focus-visible {
    color: var(--pp-ink);
    background-color: color-mix(in srgb, var(--project-primary) 6%, transparent);
}

.project-rail__link[aria-current] {
    color: var(--pp-ink);
}

.project-rail__link[aria-current] .project-rail__tick {
    width: 1.25rem;
    background-color: var(--project-accent);
    opacity: 1;
}

/* ── Fiche d'identité, partagée par les deux heros ── */
.project-facts {
    display: flex;
    flex-wrap: wrap;
    gap: 0 clamp(1.5rem, 4vw, 3rem);
    margin: 0;
}

.project-facts__entry {
    display: flex;
    flex-direction: column;
    gap: 0.35em;
}

.project-facts__label {
    font-size: 0.75rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    opacity: 0.72;
}

.project-facts__value {
    margin: 0;
    font-family: var(--project-font-heading);
    font-size: 1.125rem;
    line-height: 1.2;
}

/* Logo du client, quand il en a fourni un (asset de type `logo`). Hauteur fixe et largeur
   libre : les logos arrivent dans tous les rapports, et c'est la hauteur qui doit s'aligner
   sur celle du texte des autres entrées. `contain` pour ne jamais rogner une marque.
   Aucun filtre, aucun aplat derrière : le fichier est servi tel qu'il a été déposé, à charge
   du back office de fournir une version lisible sur le fond du thème (le même fichier sert
   les deux variantes de hero — fond clair en editorial, couleur pleine en immersive). */
.project-facts__logo {
    display: block;
    height: 2rem;
    width: auto;
    max-width: 12rem;
    object-fit: contain;
}

/* Plusieurs logos clients côte à côte (ex : CMIP — 4 transporteurs). */
.project-facts__value--logos {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem 1.25rem;
}

.project-facts__value--logos .project-facts__logo {
    height: 1.5rem;
    max-width: 6rem;
}

/* Dans le hero immersif (fond sombre), les logos reçoivent une pilule givrée pour garder
   leurs couleurs d'origine lisibles sans filtre. */
.project-hero--immersive .project-facts__value--logos {
    background: rgba(255, 255, 255, 0.45);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border-radius: 2rem;
    padding: 0.6rem 1.5rem;
    gap: 0.75rem 1.75rem;
}

.project-hero--immersive .project-facts__value--logos .project-facts__logo {
    height: 2rem;
    max-width: 8rem;
}

/* ══════════════════════════════════════════════════════════════════════════
   HERO — COMMUN AUX DEUX VARIANTES
   ══════════════════════════════════════════════════════════════════════════ */

/* La toile vivante (project-shader) : sous le contenu, invisible tant qu'elle n'a pas peint
   sa première image — sans WebGL, le fond CSS de la variante reste seul. */
.project-hero__canvas {
    position: absolute;
    inset: 0;
    z-index: -1;
    display: block;
    width: 100%;
    height: 100%;
    opacity: 0;
    transition: opacity 1400ms var(--pp-ease);
}

.project-hero__canvas.is-live {
    opacity: 1;
}

/* Le titre est posé mot par mot dans des boîtes à débord masqué : c'est ce qui permet de
   faire LEVER chaque mot depuis sous sa ligne. Les marges négatives rendent à la ligne
   l'espace que la boîte ajoute pour ne pas rogner jambages et accents. */
.project-hero__word {
    display: inline-block;
    overflow: hidden;
    vertical-align: bottom;
    padding: 0.1em 0.05em;
    margin: -0.1em -0.05em;
}

.project-hero__word-inner {
    display: inline-block;
}

/* ── Entrée du hero ──
   Au départ (JS présent, `is-ready` pas encore posé par project-page) : mots sous leur ligne,
   le reste abaissé et transparent. `is-ready` arrive dès que l'écran est découvert — tout de
   suite, ou à la fin du dive-in. Les décalages sont portés par les classes : la CSP interdit
   les styles en ligne.

   Filet : si le contrôleur ne se connectait jamais, une animation remet tout en place au bout
   de 3 s. Sans JS du tout (pas de `js-ready`), rien de tout cela ne s'applique. Mouvement
   réduit : aucun état de départ, la page est posée d'emblée. */
@media (prefers-reduced-motion: no-preference) {
    .js-ready .project-hero .project-hero__word-inner {
        transition: translate 1100ms var(--pp-ease-out);
    }

    .js-ready .project-hero:not(.is-ready) .project-hero__word-inner {
        translate: 0 115%;
        animation: pp-intro-failsafe 600ms var(--pp-ease) 3s forwards;
    }

    .project-hero__word:nth-child(2) .project-hero__word-inner { transition-delay: 80ms; }
    .project-hero__word:nth-child(3) .project-hero__word-inner { transition-delay: 160ms; }
    .project-hero__word:nth-child(4) .project-hero__word-inner { transition-delay: 240ms; }
    .project-hero__word:nth-child(5) .project-hero__word-inner { transition-delay: 320ms; }
    .project-hero__word:nth-child(n + 6) .project-hero__word-inner { transition-delay: 400ms; }

    .js-ready .project-hero :is(.project-hero__eyebrow, .project-hero__tagline, .project-facts, .project-hero__figure, .project-hero__cue) {
        transition:
            opacity 900ms var(--pp-ease),
            translate 1200ms var(--pp-ease-out);
    }

    .js-ready .project-hero:not(.is-ready) :is(.project-hero__eyebrow, .project-hero__tagline, .project-facts, .project-hero__cue) {
        opacity: 0;
        translate: 0 1.25rem;
        animation: pp-intro-failsafe 600ms var(--pp-ease) 3s forwards;
    }

    .js-ready .project-hero:not(.is-ready) .project-hero__figure {
        opacity: 0;
        translate: 3rem 0;
        animation: pp-intro-failsafe 600ms var(--pp-ease) 3s forwards;
    }

    .project-hero .project-hero__tagline { transition-delay: 300ms; }
    .project-hero .project-facts { transition-delay: 420ms; }
    .project-hero .project-hero__figure { transition-delay: 180ms; }
    .project-hero .project-hero__cue { transition-delay: 900ms; }
}

@keyframes pp-intro-failsafe {
    to {
        opacity: 1;
        translate: 0 0;
    }
}

/* ══════════════════════════════════════════════════════════════════════════
   HERO — VARIANTE IMMERSIVE (clé `immersive`)
   Takeover : la couleur primaire prend tout l'écran, le titre est au centre, un champ de
   couleur vivant coule derrière. On entre DANS le projet.
   ══════════════════════════════════════════════════════════════════════════ */
.project-hero--immersive {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    display: grid;
    place-items: center;
    min-height: 100svh;
    padding: clamp(7rem, 16vh, 11rem) 0 clamp(5rem, 12vh, 8rem);
    background-color: var(--project-primary);
    color: var(--pp-on-deep);
    text-align: center;
}

/* L'asset de hero n'est qu'une MATIÈRE posée sur le champ : en luminosité seule, il garde
   les couleurs de la toile et n'apporte que son relief. */
.project-hero__backdrop {
    position: absolute;
    inset: 0;
    z-index: -1;
}

.project-hero__backdrop-image {
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0.4;
    filter: saturate(0.6);
    mix-blend-mode: luminosity;
}

/* Voile radial dans la couleur du thème : les bords se referment sur la couleur pleine, le
   titre garde son contraste quelle que soit l'image téléversée au back office. */
.project-hero__backdrop::after {
    content: '';
    position: absolute;
    inset: 0;
    background:
        radial-gradient(120% 85% at 50% 40%, transparent 40%, color-mix(in srgb, var(--project-primary) 65%, transparent) 95%),
        linear-gradient(to bottom, color-mix(in srgb, var(--project-primary) 45%, transparent), transparent 35%);
}

.project-hero--immersive .project-hero__inner {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: clamp(1.25rem, 3vw, 2rem);
}

/* Le sur-titre se lit : il est peint avec le couple primary/bg, pas avec l'accent (cf. la règle
   de contraste en tête de fichier). */
.project-hero--immersive .project-hero__eyebrow {
    margin: 0;
    color: var(--pp-on-deep-soft);
    font-size: 0.8125rem;
    letter-spacing: 0.22em;
    text-transform: uppercase;
}

/* Le nom du projet n'est PAS mis en capitales : c'est une marque, sa casse lui appartient.
   Graisse 400 assumée — plusieurs paires du site n'ont qu'une seule graisse, un <h1> gras par
   défaut n'y serait qu'un faux gras synthétisé par le navigateur. */
.project-hero--immersive .project-hero__title {
    margin: 0;
    font-family: var(--project-font-heading);
    font-size: clamp(3rem, 11vw, 9.5rem);
    font-weight: 400;
    line-height: 0.92;
    letter-spacing: -0.03em;
}

.project-hero__title-sub {
    display: block;
    font-size: clamp(0.9rem, 2vw, 1.5rem);
    font-weight: 400;
    letter-spacing: 0;
    line-height: 1.3;
    opacity: 0.6;
    margin-top: 0.4em;
}

.project-hero--immersive .project-hero__tagline {
    margin: 0;
    max-width: 46ch;
    color: var(--pp-on-deep-soft);
    font-size: clamp(1.0625rem, 1.6vw, 1.375rem);
    line-height: 1.6;
}

.project-hero--immersive .project-facts {
    justify-content: center;
    margin-top: clamp(0.5rem, 2vw, 1.5rem);
}

/* Repère de défilement : un filet qu'un point parcourt. La page fait un écran plein avant son
   premier chapitre, il faut dire qu'il y a une suite. */
.project-hero__cue {
    position: absolute;
    bottom: clamp(1.75rem, 5vh, 3rem);
    left: 50%;
    width: 1px;
    height: clamp(2.5rem, 6vh, 3.75rem);
    transform: translateX(-50%);
    background: linear-gradient(to bottom, transparent, color-mix(in srgb, var(--project-bg) 55%, transparent));
    overflow: hidden;
}

.project-hero__cue::after {
    content: '';
    position: absolute;
    left: 50%;
    width: 3px;
    height: 40%;
    margin-left: -1.5px;
    border-radius: 9999px;
    background-color: var(--project-accent);
    animation: project-hero-cue 2.4s var(--pp-ease) infinite;
}

@keyframes project-hero-cue {
    0% {
        top: -40%;
        opacity: 0;
    }

    35% {
        opacity: 1;
    }

    100% {
        top: 100%;
        opacity: 0;
    }
}

/* ══════════════════════════════════════════════════════════════════════════
   HERO — VARIANTE EDITORIAL (clé `editorial`)
   On présente le projet, on n'y plonge pas : fond clair, tout aligné à gauche sur une colonne
   de lecture, un liseré d'accent qui tient le bloc de titre, l'image posée à côté — en
   perspective, comme une pièce qu'on tourne vers soi.
   ══════════════════════════════════════════════════════════════════════════ */
.project-hero--editorial {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    display: grid;
    align-items: center;
    min-height: min(100svh, 62rem);
    padding: clamp(8rem, 18vh, 12rem) 0 clamp(4rem, 9vw, 7rem);
    background-color: var(--project-bg);
    color: var(--pp-ink);
}

/* La forme organique de la maquette, en dégradés radiaux : rien à exporter, et elle se reteinte
   toute seule d'un projet à l'autre. Repli de la toile quand WebGL n'est pas là. */
.project-hero__wash {
    position: absolute;
    inset: 0;
    z-index: -2;
    background:
        radial-gradient(58rem 42rem at -8% -24%, color-mix(in srgb, var(--project-secondary) 34%, transparent), transparent 62%),
        radial-gradient(32rem 28rem at 106% 8%, color-mix(in srgb, var(--project-accent) 20%, transparent), transparent 66%);
    pointer-events: none;
}

.project-hero--editorial .project-hero__inner {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
    align-items: center;
    gap: clamp(2rem, 4vw, 3.5rem);
}

/* Le liseré d'accent : la signature de la variante. */
.project-hero--editorial .project-hero__col {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: clamp(1rem, 2.5vw, 1.75rem);
    border-left: 3px solid var(--project-accent);
    padding-left: clamp(1.25rem, 2.5vw, 2rem);
}

.project-hero--editorial .project-hero__eyebrow {
    margin: 0;
    color: var(--pp-ink-soft);
    font-size: 0.8125rem;
    letter-spacing: 0.22em;
    text-transform: uppercase;
}

.project-hero--editorial .project-hero__title {
    margin: 0;
    font-family: var(--project-font-heading);
    font-size: clamp(2.75rem, 7vw, 6.5rem);
    font-weight: 400;
    line-height: 0.98;
    letter-spacing: -0.025em;
}

/* Le logo en guise de titre (cf. project-hero-editorial) : il occupe la place du texte,
   à une échelle qui tient le rang de <h1> sans envahir la colonne. */
.project-hero__title-logo {
    display: block;
    width: clamp(16rem, 28vw, 26rem);
    height: auto;
}

.project-hero--editorial .project-hero__tagline {
    margin: 0;
    max-width: 38ch;
    color: var(--pp-ink-soft);
    font-size: clamp(1.0625rem, 1.5vw, 1.3125rem);
    line-height: 1.6;
}

.project-hero__figure {
    position: relative;
    margin: 0;
    border: 1px solid var(--pp-line);
    border-radius: var(--pp-radius);
    overflow: hidden;
    background-color: var(--pp-surface);
    box-shadow:
        0 40px 80px -24px var(--pp-shadow),
        0 12px 24px -12px var(--pp-shadow);
}

/* Reflet : une lueur du fond clair glisse sur la pièce avec le curseur. */
.project-hero__figure::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(
        115deg,
        transparent 30%,
        color-mix(in srgb, var(--project-bg) 40%, transparent) 50%,
        transparent 70%
    ) no-repeat calc(50% + var(--mx, 0) * 40%) 0 / 250% 100%;
    mix-blend-mode: soft-light;
    pointer-events: none;
}

.project-hero__figure-image {
    width: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
}

/* Capture d'écran (PNG) : la pièce montrée s'adapte au format naturel de l'image au lieu
   de la recadrer en 4:3 — même parti que les slides du récit (cf. --screen). */
.project-hero__figure-image--screen {
    aspect-ratio: auto;
    height: auto;
    object-fit: unset;
}

/* ── Le hero en mouvement (JS présent, mouvement autorisé) ──
   Au défilement, le contenu s'éloigne (--exit, 0 -> 1 pendant que le hero sort par le haut) ;
   le curseur le décale à peine (--mx / --my). La pièce de l'éditorial, elle, arrive inclinée
   et se redresse en sortant. La transition lisse les à-coups de la molette. */
@media (prefers-reduced-motion: no-preference) {
    .project-universe.is-enhanced .project-hero__inner {
        transform:
            translate3d(calc(var(--mx, 0) * -10px), calc(var(--exit, 0) * -16vh + var(--my, 0) * -6px), 0)
            scale(calc(1 - var(--exit, 0) * 0.06));
        opacity: calc(1 - var(--exit, 0) * 1.4);
        transition: transform 450ms var(--pp-ease-out);
    }

    .project-universe.is-enhanced .project-hero__canvas {
        transform: scale(calc(1 + var(--exit, 0) * 0.14));
    }

    .project-universe.is-enhanced .project-hero--editorial .project-hero__figure {
        transform:
            perspective(1600px)
            rotateY(calc((1 - var(--exit, 0)) * -9deg + var(--mx, 0) * 6deg))
            rotateX(calc((1 - var(--exit, 0)) * 5deg + var(--my, 0) * -5deg));
        transition:
            opacity 900ms var(--pp-ease),
            translate 1200ms var(--pp-ease-out),
            transform 800ms var(--pp-ease-out);
    }
}

/* ══════════════════════════════════════════════════════════════════════════
   CHAPITRES NUMÉROTÉS (01 → 04)
   Lecture linéaire : c'est le rendu sur petit écran, sans JS, et celui du bandeau 04.
   ══════════════════════════════════════════════════════════════════════════ */
.project-chapter {
    position: relative;
    padding-block: var(--pp-chapter-pad);
}

.project-chapter__inner {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: center;
    gap: var(--pp-chapter-gap);
}

/* Alternance gauche/droite : c'est elle qui donne son rythme à la page. Le média est déclaré
   après le texte dans le HTML — l'ordre de LECTURE reste donc toujours « le texte, puis son
   illustration », quel que soit le côté où le média est peint. */
.project-chapter--reverse .project-chapter__text {
    order: 2;
}

/* Chapitre sans illustration (moins d'assets que de chapitres) : la colonne de texte se
   recentre au lieu de laisser un vide à côté d'elle. */
.project-chapter--text-only .project-chapter__inner {
    grid-template-columns: minmax(0, 68ch);
    justify-content: center;
}

.project-chapter__text {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
}

/* Le grand chiffre fantôme de la maquette : le titre remonte dedans (marge négative), il ne
   se pose donc pas comme une ligne de plus mais comme un filigrane derrière le chapitre. */
.project-chapter__number {
    margin: 0 0 -0.34em;
    font-family: var(--project-font-heading);
    font-size: clamp(4.5rem, 12vw, 9rem);
    font-weight: 400;
    line-height: 0.8;
    letter-spacing: -0.045em;
    color: color-mix(in srgb, var(--project-secondary) 22%, transparent);
    transition: color 700ms var(--pp-ease);
}

.project-chapter__title {
    position: relative;
    margin: 0 0 clamp(0.75rem, 2vw, 1.25rem);
    font-family: var(--project-font-heading);
    font-size: clamp(1.75rem, 3.4vw, 2.75rem);
    font-weight: 400;
    line-height: 1.1;
    letter-spacing: -0.01em;
    color: var(--pp-ink);
}

.project-chapter__body {
    --reveal-delay: 120ms;

    margin: 0;
    max-width: 58ch;
    color: var(--pp-ink-soft);
    font-size: clamp(1rem, 1.15vw, 1.0625rem);
    line-height: 1.75;
}

.project-chapter__media {
    margin: 0;
    border: 1px solid var(--pp-line);
    border-radius: var(--pp-radius);
    overflow: hidden;
    background-color: var(--pp-surface);
    box-shadow: 0 24px 60px var(--pp-shadow);
}

.project-chapter__image {
    width: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
}

/* Logo SVG en illustration : contain + padding pour ne pas rogner les bords. */
.project-chapter__image--logo {
    object-fit: contain;
    padding: 2.5rem;
    background-color: var(--pp-surface);
}

/* Screenshot UI/PNG : ratio naturel de l'image, sans bandes. */
.project-chapter__image--screen {
    aspect-ratio: auto;
    height: auto;
    object-fit: unset;
}

/* Rideau : l'illustration s'ouvre depuis un cadre resserré, l'image se pose en dézoomant. Se
   branche sur le contrôleur `reveal` du site (classe `is-visible`), mais sans son fondu ni son
   décalage : c'est la découpe qui porte le mouvement. */
.reveal.pp-reveal-curtain {
    opacity: 1;
    transform: none;
    clip-path: inset(14% 10% 14% 10% round var(--pp-radius));
    transition: clip-path 1200ms var(--pp-ease-out);
}

.reveal.pp-reveal-curtain.is-visible {
    clip-path: inset(0 round var(--pp-radius));
}

.reveal.pp-reveal-curtain .project-chapter__image {
    scale: 1.14;
    transition: scale 1600ms var(--pp-ease-out);
}

.reveal.pp-reveal-curtain.is-visible .project-chapter__image {
    scale: 1;
}

html:not(.js-ready) .pp-reveal-curtain {
    clip-path: none !important;
}

html:not(.js-ready) .pp-reveal-curtain .project-chapter__image {
    scale: 1 !important;
}

/* ── Chapitre en bandeau plein (04 Le résultat) ──
   Le seul chapitre peint sur la couleur primaire : la page marque une respiration là où le
   projet raconte ce qu'il a changé. Il arrive en carte arrondie, en retrait des bords, et
   s'ouvre à pleine largeur à mesure qu'il monte dans l'écran (--enter, posé par project-page ;
   sans lui, il est d'emblée à pleine largeur). */
.project-chapter--feature {
    --open: min(1, var(--enter, 1) * 1.7);

    /* Pas de marge dessous : le bandeau final suit directement, et une bande de fond clair
       coincée entre deux aplats profonds se lirait comme un oubli. Le filet d'accent du CTA
       suffit à les séparer. */
    margin-block: clamp(2rem, 5vw, 4rem) 0;
    padding-block: clamp(4.5rem, 10vw, 8rem);
    /* --project-cta est facultative (cf. ProjectThemeCss) : le bandeau du résultat partage
       la teinte de l'encart évaluateur quand le thème en a une, sinon la primaire. */
    background-color: var(--project-cta, var(--project-primary));
    color: var(--pp-on-deep);
    clip-path: inset(0 calc((1 - var(--open)) * 5vw) round calc((1 - var(--open)) * 2.5rem));
}

.project-chapter--feature .project-chapter__number {
    color: color-mix(in srgb, var(--project-bg) 26%, transparent);
    translate: 0 calc((1 - var(--open)) * 2.5rem);
}

.project-chapter--feature .project-chapter__title {
    color: var(--pp-on-deep);
}

.project-chapter--feature .project-chapter__body {
    color: var(--pp-on-deep-soft);
}

.project-chapter--feature .project-chapter__inner {
    grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
}

.project-chapter--feature .project-chapter__media {
    border-color: color-mix(in srgb, var(--project-bg) 24%, transparent);
    background-color: color-mix(in srgb, var(--project-bg) 8%, transparent);
    box-shadow: 0 40px 80px -30px color-mix(in srgb, var(--project-cta, var(--project-primary)) 80%, transparent);
}

/* Chiffres clés du projet, dans le bandeau du résultat : c'est là qu'ils racontent quelque
   chose. Le chiffre est du grand texte clair ; l'accent n'y est qu'un trait sous lui. */
.project-chapter__stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
    gap: 1.75rem 2rem;
    width: 100%;
    margin: clamp(2rem, 4vw, 3rem) 0 0;
    padding-top: clamp(1.25rem, 2.5vw, 1.75rem);
    border-top: 1px solid color-mix(in srgb, var(--project-bg) 22%, transparent);
}

.project-chapter__stat:nth-child(2) { --reveal-delay: 110ms; }
.project-chapter__stat:nth-child(3) { --reveal-delay: 220ms; }
.project-chapter__stat:nth-child(n + 4) { --reveal-delay: 330ms; }

.project-chapter__stat-value {
    font-family: var(--project-font-heading);
    font-size: clamp(2.5rem, 5vw, 4rem);
    font-weight: 400;
    font-variant-numeric: tabular-nums;
    line-height: 1;
    letter-spacing: -0.03em;
    color: var(--pp-on-deep);
}

.project-chapter__stat-value::after {
    content: '';
    display: block;
    width: 2rem;
    height: 3px;
    margin-top: 0.85rem;
    border-radius: 9999px;
    background-color: var(--project-accent);
}

.project-chapter__stat-suffix {
    margin-left: 0.08em;
    font-size: 0.5em;
    color: var(--pp-on-deep-soft);
}

.project-chapter__stat-label {
    margin: 0.75rem 0 0;
    font-size: 0.8125rem;
    line-height: 1.4;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--pp-on-deep-soft);
}

/* ══════════════════════════════════════════════════════════════════════════
   LE RÉCIT À SCÈNE COLLANTE (project-story)
   Grand écran + JS : le texte défile à gauche, la scène reste à droite et change
   d'illustration avec le chapitre en cours. Ailleurs : lecture linéaire (section précédente).
   ══════════════════════════════════════════════════════════════════════════ */
.project-story__stage {
    display: none;
}

@media (min-width: 1024px) {
    /* La largeur et la gouttière reprennent celles de `.container` (83,25rem / 1,5rem) : le
       récit devient lui-même la colonne, et les chapitres qu'il contient perdent la leur. */
    .project-universe.is-enhanced .project-story {
        --stage-h: min(68svh, 38rem);

        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
        gap: clamp(3rem, 6vw, 6rem);
        max-width: 83.25rem;
        margin-inline: auto;
        padding-inline: 1.5rem;
    }

    .project-universe.is-enhanced .project-story .project-chapter {
        display: flex;
        align-items: center;
        min-height: 72svh;
        padding-block: 8svh;
    }

    .project-universe.is-enhanced .project-story .project-chapter__inner {
        display: block;
        max-width: none;
        margin: 0;
        padding-inline: 0;
    }

    /* Le chapitre lu est au premier plan, les autres s'estompent. */
    .project-universe.is-enhanced .project-story .project-chapter__text {
        opacity: 0.22;
        transition: opacity 700ms var(--pp-ease);
    }

    .project-universe.is-enhanced .project-story .project-chapter.is-current .project-chapter__text {
        opacity: 1;
    }

    .project-universe.is-enhanced .project-story .project-chapter.is-current .project-chapter__number {
        color: color-mix(in srgb, var(--project-secondary) 48%, transparent);
    }

    /* L'illustration du chapitre ne quitte pas le document : elle ne sert plus qu'aux
       lecteurs d'écran (son `alt`), la scène en montre le doublon. */
    .project-universe.is-enhanced .project-story .project-chapter__media {
        position: absolute;
        width: 1px;
        height: 1px;
        margin: -1px;
        padding: 0;
        overflow: hidden;
        clip-path: inset(50%);
        border: 0;
        box-shadow: none;
    }

    .project-universe.is-enhanced .project-story__stage {
        display: block;
        position: sticky;
        top: calc(50svh - var(--stage-h) / 2);
        align-self: start;
        margin-block: 8svh;
        /* Conteneur de taille : les images-écran se bornent en cqw (largeur de colonne),
           ce qui permet au cadre de se dimensionner sur l'image sans circularité. */
        container-type: inline-size;
    }
}

.project-story__frame {
    position: relative;
    height: var(--stage-h);
    border: 1px solid var(--pp-line);
    border-radius: var(--pp-radius);
    overflow: hidden;
    background-color: var(--pp-surface);
    box-shadow:
        0 50px 100px -40px color-mix(in srgb, var(--project-primary) 45%, transparent),
        0 16px 32px -16px var(--pp-shadow);
    transform:
        perspective(1800px)
        rotateY(calc(var(--mx, 0) * 3.5deg))
        rotateX(calc(var(--my, 0) * -2.5deg));
    /* interpolate-size : anime le passage height fixe → auto (sinon, changement sec). */
    interpolate-size: allow-keywords;
    transition:
        transform 900ms var(--pp-ease-out),
        height 700ms var(--pp-ease-out),
        width 700ms var(--pp-ease-out),
        margin-left 700ms var(--pp-ease-out);
}

/* Quand la slide courante est un screenshot, le cadre s'adapte au format de l'image :
   la slide repasse dans le flux (position relative) et c'est la hauteur naturelle de
   l'image — quel que soit son ratio — qui dicte celle du cadre. Le min-height évite
   l'effondrement du cadre le temps que l'image charge. */
.project-story__frame:has(.project-story__slide--screen.is-current) {
    /* Le cadre épouse exactement l'image (fit-content) : pas de bande sur les côtés
       d'un portrait, et un paysage peut déborder de la colonne (--screen-bleed) pour
       gagner en taille. margin-inline auto le centre dans les deux cas. */
    --screen-bleed: clamp(1.5rem, 3vw, 4rem);

    height: auto;
    min-height: 12rem;
    width: fit-content;
    margin-inline: auto;
}

.project-story__slide--screen.is-current {
    position: relative;
    inset: auto;
}

/* Les illustrations sont empilées ; chacune monte en rideau par-dessus la précédente quand son
   chapitre arrive (`is-shown`), et redescend si l'on remonte. Celle du chapitre courant finit
   de se poser en dézoomant. */
.project-story__slide {
    position: absolute;
    inset: 0;
    display: grid;
    clip-path: inset(100% 0 0 0);
    transition: clip-path 1000ms var(--pp-ease-out);
}

.project-story__slide.is-shown {
    clip-path: inset(0);
}

.project-story__slide-image {
    width: 100%;
    height: 100%;
    object-fit: cover;
    scale: 1.14;
    transition: scale 1600ms var(--pp-ease-out);
}

.project-story__slide-image--logo {
    object-fit: contain;
    padding: 2.5rem;
    background-color: var(--pp-surface);
    scale: 1;
}

/* Slide contenant un screenshot : fond opaque pour masquer les slides adjacentes. */
.project-story__slide--screen {
    background-color: var(--pp-surface);
}

/* Screenshot UI/PNG : l'image garde son ratio naturel, bornée par la largeur du cadre
   ET par la hauteur de la scène (sinon un screenshot portrait rendrait le cadre immense).
   align-self/justify-self annulent le stretch du grid parent (sans ça, les bornes sont
   ignorées) ; un portrait plafonné se centre dans le cadre. */
.project-story__slide-image--screen {
    width: auto;
    /* 100cqw = largeur de la colonne (stage), pas du cadre : borne résoluble même
       quand le cadre est en fit-content sur l'image. */
    max-width: calc(100cqw + 2 * var(--screen-bleed, 0rem));
    height: auto;
    max-height: var(--stage-h);
    align-self: start;
    justify-self: center;
    scale: 1;
}

.project-story__slide.is-current .project-story__slide-image {
    scale: 1;
}

/* Chapitre sans illustration : son numéro, en très grand, sur la couleur du thème. */
.project-story__slide-number {
    display: grid;
    place-items: center;
    background:
        radial-gradient(90% 70% at 70% 20%, color-mix(in srgb, var(--project-secondary) 45%, transparent), transparent 70%),
        var(--project-primary);
    color: var(--pp-on-deep);
    font-family: var(--project-font-heading);
    font-size: clamp(8rem, 16vw, 15rem);
    line-height: 1;
    letter-spacing: -0.05em;
}

/* Reflet de vitre qui suit le curseur : la scène se lit comme un objet, pas comme une image. */
.project-story__sheen {
    position: absolute;
    inset: 0;
    z-index: 2;
    background: radial-gradient(
        60% 50% at calc(50% + var(--mx, 0) * 35%) calc(30% + var(--my, 0) * 25%),
        color-mix(in srgb, var(--project-bg) 30%, transparent),
        transparent 70%
    );
    mix-blend-mode: soft-light;
    pointer-events: none;
}

.project-story__steps {
    display: flex;
    gap: 0.5rem;
    margin: 1.25rem 0 0;
    padding: 0;
    list-style: none;
}

.project-story__step {
    width: 1.5rem;
    height: 3px;
    border-radius: 9999px;
    background-color: var(--pp-line);
    transition:
        width 600ms var(--pp-ease-out),
        background-color 600ms var(--pp-ease);
}

.project-story__step.is-current {
    width: 3.5rem;
    background-color: var(--project-accent);
}

/* ══════════════════════════════════════════════════════════════════════════
   CTA DE FIN
   C'est ici que l'accent est le plus présent — mais en GRAPHIQUE, jamais sous du texte : un
   filet franc en haut du bandeau et un halo qui monte du bas. Le bandeau lui-même est peint
   avec le couple primary/bg, le seul sur lequel un thème s'engage (cf. la règle de contraste en
   tête de fichier) ; la toile vivante dessous est bridée pour tenir ce contraste (cf.
   project_shader_controller.js).
   ══════════════════════════════════════════════════════════════════════════ */
.project-cta {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    padding-block: clamp(5rem, 11vw, 9rem);
    border-top: 4px solid var(--project-accent);
    background-color: var(--project-primary);
    color: var(--pp-on-deep);
    text-align: center;
}

.project-cta__canvas {
    position: absolute;
    inset: 0;
    z-index: -2;
    display: block;
    width: 100%;
    height: 100%;
    opacity: 0;
    transition: opacity 1400ms var(--pp-ease);
}

.project-cta__canvas.is-live {
    opacity: 1;
}

.project-cta::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    background: radial-gradient(46rem 30rem at 50% 120%, color-mix(in srgb, var(--project-accent) 45%, transparent), transparent 68%);
    pointer-events: none;
}

/* Matière de fond du projet (asset de type `texture`) : fondue en `soft-light` et à très
   faible opacité. Elle module la lumière du fond, elle ne le repeint pas. */
.project-cta__texture {
    position: absolute;
    inset: 0;
    z-index: -1;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0.16;
    mix-blend-mode: soft-light;
    pointer-events: none;
}

.project-cta__inner {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: clamp(1rem, 2.5vw, 1.75rem);
}

.project-cta__title {
    margin: 0;
    max-width: 18ch;
    font-family: var(--project-font-heading);
    font-size: clamp(2.5rem, 7vw, 5.5rem);
    font-weight: 400;
    line-height: 1.02;
    letter-spacing: -0.025em;
}

.project-cta__text {
    --reveal-delay: 120ms;

    margin: 0;
    max-width: 52ch;
    color: var(--pp-on-deep-soft);
    font-size: clamp(1rem, 1.3vw, 1.125rem);
    line-height: 1.65;
}

.project-cta__actions {
    --reveal-delay: 220ms;

    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.75rem;
    margin-top: clamp(0.5rem, 1.5vw, 1rem);
}

/* Boutons peints par le thème, pas par le design system : l'atom `button` du site est peint en
   orange de marque, hors sujet dans une page-univers. Leurs libellés sont du texte courant, ils
   restent donc eux aussi sur le couple primary/bg — un remplissage en accent les aurait mis à
   ~3:1.

   Aimantés (contrôleur magnetic) : --magnet-x / --magnet-y tirent le bouton vers le curseur,
   et son libellé un peu plus loin. Sans le contrôleur, ces variables valent 0. */
.project-cta__button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 48px;
    padding: 0.95em 1.9em;
    border: 1px solid color-mix(in srgb, var(--project-bg) 42%, transparent);
    border-radius: 9999px;
    color: var(--pp-on-deep);
    font-size: 1rem;
    line-height: 1;
    text-decoration: none;
    transform: translate(var(--magnet-x, 0px), var(--magnet-y, 0px));
    transition:
        background-color var(--pp-duration) var(--pp-ease),
        border-color var(--pp-duration) var(--pp-ease),
        color var(--pp-duration) var(--pp-ease),
        transform 500ms var(--pp-ease-out);
}

.project-cta__button-label {
    display: inline-block;
    transform: translate(calc(var(--magnet-x, 0px) * 0.4), calc(var(--magnet-y, 0px) * 0.4));
    transition: transform 500ms var(--pp-ease-out);
}

.project-cta__button--primary {
    border-color: var(--project-bg);
    background-color: var(--project-bg);
    color: var(--project-primary);
}

.project-cta__button:not(.project-cta__button--primary):hover,
.project-cta__button:not(.project-cta__button--primary):focus-visible {
    border-color: var(--project-bg);
    background-color: color-mix(in srgb, var(--project-bg) 12%, transparent);
}

.project-cta__button--primary:hover,
.project-cta__button--primary:focus-visible {
    background-color: color-mix(in srgb, var(--project-bg) 88%, var(--project-primary));
}

/* ══════════════════════════════════════════════════════════════════════════
   HEADER & FOOTER DU SITE, REPEINTS AUX COULEURS DU PROJET

   Le header et le footer restent — le footer porte les liens légaux, qui doivent rester
   atteignables depuis n'importe quelle page, et le header garde la navigation d'un document
   qui peut très bien être atteint directement depuis un moteur de recherche. Mais ils cessent
   d'être le chrome du SITE : sur une page projet, ils sont peints par le thème comme le reste.

   Toutes les règles sont préfixées de `.project-page` : elles ne s'appliquent nulle part
   ailleurs, et la spécificité (0,2,0) l'emporte sur les règles d'origine (0,1,0) d'app.css.

   Une seule chose reste en couleurs de marque : le mark WTS (assets SVG du logo et de
   LinkedIn). C'est l'identité de l'agence, pas celle du client — la repeindre en teal ou en
   violet selon le projet visité serait un contresens. Il joue ici exactement son rôle de
   signature.

   Contrainte de lisibilité propre au header : il flotte au-dessus des DEUX surfaces de la page
   (la primaire du hero immersif, le fond clair des chapitres). Chaque pastille reçoit donc un
   aplat sombre ET un liseré clair — c'est ce couple qui la détache sur l'une comme sur l'autre.
   C'est l'arbitrage déjà retenu par le site pour sa capsule noire (cf. app.css), transposé au
   thème.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Capsule de navigation ── */
.project-page .site-nav__capsule-shape {
    fill: var(--project-primary);
    stroke: color-mix(in srgb, var(--project-bg) 55%, transparent);
}

.project-page .site-nav__capsule-sheen-stop {
    stop-color: var(--project-bg);
}

.project-page .site-nav__capsule {
    color: var(--pp-on-deep);
}

.project-page .site-nav__capsule .nav-item-group .nav-item + .nav-item {
    border-left-color: color-mix(in srgb, var(--project-bg) 35%, transparent);
}

/* Sous 768px, la forme en haltère est abandonnée : le tracé SVG est masqué et la capsule
   devient un simple rond peint EN CSS sur `.site-nav__capsule` (cf. app.css). La règle
   ci-dessus, qui thème le tracé, ne l'atteint donc pas — le rond du logo doit être repris ici,
   avec l'aplat et le liseré du bouton menu qui lui fait face. */
@media (max-width: 768px) {
    .project-page .site-nav__capsule {
        border-color: color-mix(in srgb, var(--project-bg) 55%, transparent);
        background-color: var(--project-primary);
    }
}

/* ── Bouton Contact ──
   Aplat clair sur liseré sombre : l'exact inverse de la capsule, pour qu'il reste le point
   d'appel du header, et lisible sur les deux surfaces là encore. Son libellé est du texte
   courant, il reste donc sur le couple primary/bg (cf. la règle de contraste plus haut). */
.project-page .site-nav > .btn--primary {
    border-color: var(--project-primary);
    background-color: var(--project-bg);
    color: var(--project-primary);
}

/* L'anneau intérieur du bouton est dessiné en teinte de marque : il repasse sur le thème. */
.project-page .site-nav > .btn--primary::after {
    border-color: color-mix(in srgb, var(--project-primary) 30%, transparent);
}

/* La pulsation au survol est un halo orange de marque : elle repasse sur l'accent du projet. */
.project-page .site-nav > .btn--primary:hover:not(:disabled) {
    animation: none;
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--project-accent) 45%, transparent);
}

/* ── Bouton menu (mobile) ── */
.project-page .site-nav__toggle {
    border-color: color-mix(in srgb, var(--project-bg) 55%, transparent);
    background-color: var(--project-primary);
    color: var(--pp-on-deep);
}

/* ── Overlay de navigation mobile ── */
.project-page .mobile-menu {
    background-color: var(--project-bg);
}

.project-page .mobile-menu__item + .mobile-menu__item {
    border-top-color: color-mix(in srgb, var(--project-primary) 14%, transparent);
}

.project-page .mobile-menu__link {
    color: var(--pp-ink);
    font-family: var(--project-font-heading);
}

.project-page .mobile-menu__link-icon,
.project-page .mobile-menu__cta-icon {
    color: var(--project-accent);
}

.project-page .mobile-menu__link:hover,
.project-page .mobile-menu__link:focus-visible {
    color: var(--pp-ink-soft);
}

.project-page .mobile-menu__cta {
    background-color: var(--project-primary);
    color: var(--pp-on-deep);
}

.project-page .mobile-menu__cta-icon {
    color: var(--pp-on-deep);
}

/* ── Footer ──
   Noir de marque, comme partout ailleurs sur le site : la page-univers rend la main en
   arrivant au pied de page. Seuls le filet de séparation avec le bandeau CTA et les teintes
   de texte restent aux couleurs du projet. */
.project-page .site-footer {
    margin-top: 0;
    border-top: 1px solid color-mix(in srgb, var(--project-bg) 18%, transparent);
    background-color: var(--wts-color-black);
    color: var(--pp-on-deep);
}

.project-page .site-footer__social {
    color: var(--pp-on-deep-soft);
}

.project-page .site-footer__social:hover,
.project-page .site-footer__social:focus-visible {
    color: var(--pp-on-deep);
}

/* Les intitulés de colonne sont du texte courant : ils restent sur le couple primary/bg. */
.project-page .site-footer__nav-title {
    color: var(--pp-on-deep);
    font-family: var(--project-font-heading);
}

.project-page .site-footer__nav-group a {
    border-bottom-color: color-mix(in srgb, var(--project-bg) 35%, transparent);
    color: var(--pp-on-deep-soft);
}

.project-page .site-footer__nav-group a:hover {
    color: var(--pp-on-deep);
}

/* La baseline est le seul grand texte du bas de page : l'accent y passe le seuil des grands
   textes (3:1) sur les quatre thèmes, et c'est là qu'il peut donner sa couleur au projet. */
.project-page .site-footer__baseline {
    color: var(--project-accent);
    font-family: var(--project-font-heading);
}

.project-page .site-footer__about,
.project-page .site-footer__value-label {
    color: var(--pp-on-deep-soft);
}

/* Mêmes intitulés que les colonnes de liens : texte courant sur le couple primary/bg. */
.project-page .site-footer__contact-label {
    color: var(--pp-on-deep);
    font-family: var(--project-font-heading);
}

.project-page .site-footer__contact-value,
.project-page .site-footer__contact-value a {
    color: var(--pp-on-deep-soft);
}

.project-page .site-footer__contact-value a:hover,
.project-page .site-footer__contact-value a:focus-visible {
    color: var(--pp-on-deep);
}

.project-page .site-footer__value-icon {
    color: var(--project-accent);
}

/* Les filets internes reprennent celui qui sépare le footer du bandeau au-dessus. */
.project-page .site-footer__values,
.project-page .site-footer__bottom {
    border-top-color: color-mix(in srgb, var(--project-bg) 18%, transparent);
}

.project-page .site-footer__copyright {
    color: var(--pp-on-deep-soft);
}

/* ══════════════════════════════════════════════════════════════════════════
   ADAPTATIONS
   ══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 900px) {
    .project-hero--editorial .project-hero__inner,
    .project-chapter__inner {
        grid-template-columns: minmax(0, 1fr);
    }

    /* Une seule colonne : l'alternance n'a plus de sens, et le média doit rester APRÈS le
       texte qu'il illustre. */
    .project-chapter--reverse .project-chapter__text {
        order: 0;
    }

    .project-chapter--text-only .project-chapter__inner {
        grid-template-columns: minmax(0, 1fr);
    }

    .project-hero--editorial {
        min-height: 0;
    }
}

@media (max-width: 600px) {
    /* Le libellé du retour disparaît, la flèche reste : sur un petit écran la pastille
       mangerait le coin de la page, mais le chemin de sortie ne doit jamais disparaître. */
    .project-back {
        gap: 0;
        padding: 0.9em;
    }

    .project-back__label {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    .project-facts {
        gap: 1rem 2rem;
    }
}

/* Mouvement réduit : plus rien ne glisse, ne s'ouvre ni ne se déroule. Les ÉTATS restent (le
   chapitre courant du récit, l'illustration qui va avec, le sommaire) : ce sont des repères,
   ils changent simplement sans transition. */
@media (prefers-reduced-motion: reduce) {
    .project-back,
    .project-back__arrow,
    .project-cta__button,
    .project-cta__button-label,
    .project-rail,
    .project-rail__link,
    .project-rail__tick,
    .project-rail__number,
    .project-rail__label,
    .project-story__frame,
    .project-story__slide,
    .project-story__slide-image,
    .project-story__step,
    .project-hero__canvas,
    .project-cta__canvas {
        transition: none;
    }

    .project-hero__cue::after {
        animation: none;
        top: 30%;
    }

    .project-universe .project-story .project-chapter__text {
        transition: none;
    }

    .project-chapter--feature {
        clip-path: none;
    }

    .reveal.pp-reveal-curtain,
    .reveal.pp-reveal-curtain .project-chapter__image {
        clip-path: none;
        scale: 1;
        transition: none;
    }
}

/* ── FAQ + relance vers l'évaluation, en page-univers ──
   Ces deux modules sont partagés par tout le site et posés par le layout sur chaque page
   (cf. layouts/base.html.twig). Ils arrivent donc ici avec leurs couleurs de maison : bloc
   blanc, cartes blanches, aplat orange. Sur une page-univers, aucune de ces couleurs n'a le
   droit de réapparaître — c'est toute la promesse du fichier.

   Tout est donc repeint sur les rôles définis en haut de ce fichier. Rien n'est dupliqué de
   _faq.css ni de _maturity.css : seules les COULEURS sont reprises, la mise en page reste
   celle du module commun. Le sélecteur passe par `.faq-section--project`, posé par
   l'organism quand la page demande ce thème : un modificateur explicite plutôt qu'une
   surcharge à l'aveugle sur `.project-page .faq-section`, pour que le module dise lui-même
   qu'il connaît ce contexte. */
.project-page .faq-section--project {
    background-color: transparent;
    color: var(--pp-ink);
}

/* Le halo de la variante claire est fait de deux dégradés orange/gris : hors sujet ici, où
   c'est le fond du projet qui doit passer. */
.project-page .faq-section--project::before {
    content: none;
}

.project-page .faq-section--project .faq-section__lead {
    color: var(--pp-ink-soft);
}

/* Le contour de la forme flottante est le seul trait du SVG peint en `currentColor` : c'est
   un élément GRAPHIQUE, donc l'accent y est légitime (cf. la règle de contraste plus haut —
   il ne porte aucun texte). L'aplat orange et le « ? » sont écrits en dur dans le fichier et
   ne peuvent pas être thémés : la forme reste donc une note de la maison, assumée, comme le
   logo l'est dans le header. */
.project-page .faq-section--project .faq-section__blob {
    color: var(--project-accent);
}

/* Cartes d'accordéon : la surface du thème, pas du blanc. */
.project-page .faq-section--project .faq-item {
    background-color: var(--pp-surface);
    color: var(--pp-ink);
    border: 1px solid var(--pp-line);
    border-radius: var(--pp-radius-sm);
    box-shadow: 0 24px 32px -14px var(--pp-shadow);
}

.project-page .faq-section--project .faq-item__icon {
    background-color: var(--pp-ink);
    color: var(--project-bg);
}

.project-page .faq-section--project .faq-item__panel p {
    color: var(--pp-ink-soft);
}

/* ── L'encart d'évaluation ──
   Ailleurs sur le site c'est un aplat orange à texte noir. Ici il devient un aplat d'encre à
   texte clair : c'est le seul couple sur lequel un thème s'engage (primary/bg), donc le seul
   qui reste lisible quel que soit le projet. L'accent, lui, ne sert qu'au liseré. */
.project-page .faq-section--project .maturity-cta {
    /* --project-cta est facultative (cf. ProjectThemeCss) : sans elle, l'aplat d'encre. */
    background-color: var(--project-cta, var(--pp-ink));
    color: var(--pp-on-deep);
    border-radius: var(--pp-radius);
}

.project-page .faq-section--project .maturity-cta__eyebrow,
.project-page .faq-section--project .maturity-cta__text {
    color: var(--pp-on-deep-soft);
}

.project-page .faq-section--project .maturity-cta__title {
    font-family: var(--project-font-heading);
}

/* Les deux boutons gardent leur mise en page du design system, leurs couleurs viennent du
   thème : sur l'aplat d'encre, le bouton d'appel s'inverse (fond clair, libellé profond) et
   le second reste un contour. */
.project-page .faq-section--project .maturity-cta .btn--primary {
    background-color: var(--project-bg);
    color: var(--project-primary);
}

.project-page .faq-section--project .maturity-cta .btn--primary::after {
    border-color: color-mix(in srgb, var(--project-primary) 30%, transparent);
}

.project-page .faq-section--project .maturity-cta .btn--primary:hover:not(:disabled) {
    animation: none;
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--project-bg) 40%, transparent);
}

.project-page .faq-section--project .maturity-cta .btn--outline {
    border-color: color-mix(in srgb, var(--project-bg) 50%, transparent);
    color: var(--pp-on-deep);
}

.project-page .faq-section--project .maturity-cta .btn--outline:hover:not(:disabled) {
    border-color: var(--project-bg);
    color: var(--pp-on-deep);
    background-color: color-mix(in srgb, var(--project-bg) 14%, transparent);
}

/* L'anneau de focus de l'encart est écrit en noir par _maturity.css, pour tenir sur son aplat
   orange. Sur l'aplat d'encre du thème il disparaîtrait : il reprend donc l'accent du projet,
   comme partout ailleurs dans la page-univers. */
.project-page .faq-section--project .maturity-cta .btn:focus-visible {
    outline-color: var(--project-accent);
}

/* assets/styles/_dive.css */
/* Moteur dive-in (story 3-4, DCT §3.5) — l'overlay permanent et ses couches d'effet.

   Ce fichier ne décrit AUCUNE animation : toute la chorégraphie est jouée par la Web
   Animations API depuis `assets/dive/`, parce que la géométrie (rectangle de la carte, rayon
   de couverture) n'est connue qu'au clic et que la CSP du site — `style-src 'self'`, sans
   `unsafe-inline` ni nonce (cf. App\EventSubscriber\SecurityHeadersSubscriber) — interdit de
   la faire passer par un attribut `style`.

   Ce qui est ici, et qui n'a pas sa place en JS : le positionnement, l'empilement, et la
   couleur de repli, qui reste un token CSS comme partout ailleurs. */

/* ── L'overlay ──
   `position: fixed` + `inset: 0` : son repère est exactement le viewport, ce qui permet aux
   effets d'utiliser les coordonnées de `getBoundingClientRect()` sans conversion.

   Au repos il est vide, sans fond ni bordure : il ne peint rien, il n'attrape rien. Il reste
   quand même dans le DOM parce qu'il est `data-turbo-permanent` — c'est précisément ce qui
   fait la continuité visuelle d'une page à l'autre. */
.dive-overlay {
    position: fixed;
    inset: 0;
    /* Au-dessus de tout le chrome du site : header fixe (50) et bouton « Tous les projets »
       de la page projet (40). Une transition qui laisserait passer un bouton par-dessus ne
       serait plus une transition, ce serait un calque. */
    z-index: 200;
    overflow: hidden;
    pointer-events: none;
}

/* En cours de transition, l'overlay absorbe le pointeur : sans ça, un second clic pendant
   l'animation partirait sur un lien de la page qu'on est en train de quitter. Il reste
   `inert` et `aria-hidden` en permanence (cf. le template) — il n'a jamais rien à dire au
   clavier ni aux lecteurs d'écran. */
.dive-overlay.is-running {
    pointer-events: auto;
}

/* ── Les couches d'effet ──
   Créées et détruites par l'effet, jamais par le template. Plein viewport par défaut, et
   `transform-origin` au coin haut gauche : c'est l'hypothèse du FLIP de ZoomEffect, qui
   compose sa transformation inverse en coordonnées viewport directes.

   La couleur de repli suit la cascade des thèmes : sur une page projet, `--project-primary`
   est hérité du <body> et la couche est déjà à la bonne couleur avant même que le moteur ne
   la peigne ; ailleurs, on retombe sur le noir du site. Aucune couleur en dur, ici comme
   partout (cf. AGENTS.md §2). */
.dive-layer {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    transform-origin: 0 0;
    background-color: var(--project-primary, var(--wts-color-black));
    will-change: transform, opacity;
    backface-visibility: hidden;
}

/* Disque du FloodEffect : rayon de base 100px (cf. FloodEffect.BASE_RADIUS), centré sur son
   point d'ancrage par la marge négative — l'effet n'a plus qu'à le translater sur le centre
   du flot et à le mettre à l'échelle. Redimensionner un disque coûterait un repaint à chaque
   frame ; le mettre à l'échelle ne coûte qu'une composition. */
.dive-layer--disc {
    width: 200px;
    height: 200px;
    margin: -100px 0 0 -100px;
    border-radius: 50%;
    transform-origin: 50% 50%;
}

/* Voile du FadeEffect : le fond de la page dont on part, pas la couleur du projet — cf.
   l'en-tête de `fade_effect.js`. */
.dive-layer--veil {
    background-color: var(--wts-color-black);
}

/* Toile des effets dessinés (ParticleEffect, DissolveEffect — story 3-7) : ils peignent
   eux-mêmes chaque pixel, y compris les pixels transparents. Le fond de repli de
   `.dive-layer` les couvrirait d'un aplat dès la première image — la page qu'on quitte
   disparaîtrait avant que le front ne l'atteigne. La taille en pixels est portée par les
   attributs `width`/`height` de la toile ; ici, seulement sa taille à l'écran (100 %). */
.dive-layer--canvas {
    display: block;
    background-color: transparent;
}

/* ── Reduced motion ──
   Le court-circuit réel est en JS (le registre ne sert plus que FadeEffect, cf. DCT §3.5 f) :
   la règle ci-dessous n'est qu'une ceinture. Si une couche se retrouvait à l'écran malgré
   tout, elle n'aurait plus le droit de bouger. */
@media (prefers-reduced-motion: reduce) {
    .dive-layer {
        transform: none !important;
        will-change: auto;
    }
}

/* Page Contact (story 3-5).

   Un en-tête, puis le duo « coordonnées + formulaire ». Toute la mise en forme des champs
   eux-mêmes vient du design system (`.field-control`, `.form-field`, cf. _components.css) —
   le thème de formulaire public (templates/form/contact_theme.html.twig) n'existe que pour
   amener le composant Form sur ces classes-là. On ne trouvera donc ici aucune règle de
   champ, seulement de la mise en page.

   Aucune couleur en dur : tokens du site uniquement (les variables --project-* sont
   réservées aux pages projet). */

.contact-page {
    padding-block: clamp(2rem, 5vw, 4.5rem) 0;
}

.contact-page__inner {
    display: flex;
    flex-direction: column;
    gap: clamp(3rem, 6vw, 5.5rem);
}

/* ── En-tête de page ── */

.contact-page__head {
    max-width: 46rem;
}

.contact-page__eyebrow {
    margin: 0 0 var(--wts-space-3);
    color: var(--wts-color-orange);
    font-size: var(--wts-font-size-sm);
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.contact-page__title {
    margin: 0;
    font-size: clamp(2.5rem, 6vw, var(--wts-font-size-4xl));
    text-transform: uppercase;
}

.contact-page__lead {
    margin: var(--wts-space-6) 0 0;
    color: var(--wts-color-silver);
    font-size: var(--wts-font-size-lg);
    line-height: 1.6;
}

/* ── Duo coordonnées / formulaire ──
   Les coordonnées d'abord dans le DOM : c'est la sortie de secours pour qui ne veut pas
   remplir de formulaire, elle doit venir avant lui au clavier comme au lecteur d'écran. */

.contact-page__layout {
    display: grid;
    grid-template-columns: minmax(16rem, 22rem) 1fr;
    gap: clamp(var(--wts-space-8), 4vw, var(--wts-space-16));
    align-items: start;
}

.contact-details {
    position: sticky;
    top: calc(2 * var(--wts-space-6) + 59px + var(--wts-space-4));
    display: flex;
    flex-direction: column;
    gap: var(--wts-space-6);
    padding: var(--wts-space-8);
    border: 1px solid var(--wts-color-silver-20);
    border-radius: var(--wts-radius-lg);
}

.contact-details__title {
    margin: 0;
    font-size: var(--wts-font-size-xl);
}

.contact-details__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--wts-space-4);
}

.contact-details__item {
    display: flex;
    flex-direction: column;
    gap: var(--wts-space-1);
}

.contact-details__label {
    color: var(--wts-color-silver-60);
    font-size: var(--wts-font-size-xs);
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.contact-details__value {
    color: var(--wts-color-white);
    text-decoration: none;
    border-bottom: 1px dashed var(--wts-color-silver-40);
    padding-bottom: var(--wts-space-1);
    align-self: flex-start;
    word-break: break-word;
    transition: color var(--anim-fast) var(--ease-out-soft);
}

.contact-details__value:hover,
.contact-details__value:focus-visible {
    color: var(--wts-color-orange);
}

@media (prefers-reduced-motion: reduce) {
    .contact-details__value {
        transition: none;
    }
}

/* L'engagement de délai est la promesse qui décide le visiteur : il est traité comme une
   affirmation (orange, corps plus grand), pas comme une mention. */
.contact-details__delay {
    margin: 0;
    padding-top: var(--wts-space-6);
    border-top: 1px solid var(--wts-color-silver-20);
    color: var(--wts-color-orange);
    font-weight: 600;
    line-height: 1.5;
}

/* ── Formulaire ── */

.contact-form-panel {
    display: flex;
    flex-direction: column;
    gap: var(--wts-space-4);
}

.contact-form-panel__title {
    margin: 0;
    font-size: var(--wts-font-size-2xl);
}

.contact-form-panel__lead {
    margin: 0;
    color: var(--wts-color-silver);
    line-height: 1.6;
}

/* Confirmation d'envoi : vert de succès, en tête du panneau — elle apparaît après une
   redirection, donc sur une page qui a défilé en haut. */
.contact-form-panel__confirmation {
    margin: 0;
    padding: var(--wts-space-4) var(--wts-space-6);
    border-left: 3px solid var(--wts-color-success);
    border-radius: var(--wts-radius-sm);
    background-color: color-mix(in srgb, var(--wts-color-success) 12%, transparent);
    line-height: 1.6;
}

.contact-form {
    display: flex;
    flex-direction: column;
    gap: var(--wts-space-6);
    margin-top: var(--wts-space-2);
}

.contact-form__summary {
    margin: 0;
    padding: var(--wts-space-4) var(--wts-space-6);
    border-left: 3px solid var(--wts-color-error);
    border-radius: var(--wts-radius-sm);
    background-color: color-mix(in srgb, var(--wts-color-error) 12%, transparent);
    color: var(--wts-color-error);
}

.contact-form__grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--wts-space-6);
}

.contact-form__cell--full {
    grid-column: 1 / -1;
}

/* Honeypot : sorti du flux et rendu invisible, sans `display: none` ni
   `visibility: hidden` — les robots un peu sérieux ignorent les champs masqués ainsi, et le
   piège ne prendrait plus personne. Le champ reste donc dans la page, hors du parcours
   clavier (`tabindex="-1"`) et hors de l'arbre d'accessibilité (`aria-hidden` sur
   l'enveloppe, cf. le template). */
.contact-form__trap {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

.contact-form__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--wts-space-6);
}

.contact-form__legal {
    flex: 1 1 18rem;
    margin: 0;
    color: var(--wts-color-silver-60);
    font-size: var(--wts-font-size-xs);
    line-height: 1.5;
}

/* Les selects natifs n'héritent ni de la police ni de la couleur du champ sur tous les
   navigateurs, et leur flèche par défaut jure avec le reste : chevron dessiné en CSS pur
   (aucun asset, aucune image de fond encodée). */
.field-control--select {
    appearance: none;
    -webkit-appearance: none;
    padding-right: var(--wts-space-12);
    cursor: pointer;
}

.contact-form__cell {
    position: relative;
}

.contact-form__cell:has(.field-control--select)::after {
    content: '';
    position: absolute;
    right: var(--wts-space-6);
    bottom: 1.15rem;
    width: 0.5rem;
    height: 0.5rem;
    border-right: 2px solid var(--wts-color-silver);
    border-bottom: 2px solid var(--wts-color-silver);
    transform: rotate(45deg);
    pointer-events: none;
}

/* Les options s'affichent dans une popup peinte par le système, qui ne connaît pas notre
   fond sombre : sans cette règle, du texte blanc sur blanc sous Windows. */
.field-control--select option {
    background-color: var(--wts-color-black);
    color: var(--wts-color-white);
}

@media (max-width: 900px) {
    .contact-page__layout {
        grid-template-columns: 1fr;
    }

    /* Collée en haut, la carte de coordonnées volerait la moitié de l'écran au formulaire
       une fois les deux empilés. */
    .contact-details {
        position: static;
    }
}

@media (max-width: 640px) {
    .contact-form__grid {
        grid-template-columns: 1fr;
    }
}

/* assets/styles/_releases.css */
/* Page Releases (story 4-2, RG-1.1) : en-tête, barre de filtres par application et journal
   groupé (organisme release-feed). L'entrée elle-même — molecule release-item, ses notes
   Markdown et sa pagination — vit dans _components.css, avec les autres composants.

   Aucune couleur en dur : uniquement les tokens --wts-*. Cette page n'appartient à aucun
   thème projet, elle ne consomme donc jamais de variable --project-*. */

.releases {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    background-color: var(--wts-color-black);
    padding: clamp(3rem, 8vw, 7rem) 0 clamp(5.5rem, 12vw, 12rem);
}

/* Même idiome de halo que l'index des projets : deux dégradés radiaux, aucune image à
   exporter. La page Releases est la sœur de l'index projets — c'est le même type de page
   (un portail, pas une page de marque), elle doit s'ouvrir de la même façon. */
.releases::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    background:
        radial-gradient(42rem 28rem at 12% -6%, var(--wts-color-orange-20), transparent 66%),
        radial-gradient(26rem 22rem at 92% 8%, var(--wts-color-silver-20), transparent 70%);
    pointer-events: none;
}

.releases__inner {
    display: flex;
    flex-direction: column;
    gap: clamp(var(--wts-space-8), 5vw, var(--wts-space-16));
}

.releases__head {
    display: flex;
    flex-direction: column;
    gap: var(--wts-space-6);
}

/* Décalage d'apparition par élément. Le mécanisme --stagger de _anim-tokens.css passe par un
   style inline, que la CSP du site interdit : les valeurs sont donc portées par les classes
   (cf. la même construction dans _projects.css). */
.releases__head .reveal {
    transition-delay: var(--stagger, 0ms);
}

.releases__title {
    --stagger: 0ms;

    margin: 0;
    font-family: var(--wts-font-display);
    font-size: clamp(2rem, 4.4vw, 3.25rem);
    font-weight: 400;
    line-height: 1;
    letter-spacing: -0.01em;
    text-transform: uppercase;
    color: var(--wts-color-white);
}

.releases__lead {
    --stagger: 60ms;

    margin: 0;
    max-width: 60ch;
    color: var(--wts-color-silver);
}

/* ── Barre de filtres ──
   Même bandeau que l'index des projets, aux mêmes tokens : le visiteur qui vient de la page
   Projets doit reconnaître le geste, pas apprendre un second vocabulaire. */
.releases__filters {
    --stagger: 120ms;

    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--wts-space-3) var(--wts-space-6);
    margin-top: var(--wts-space-4);
    padding: var(--wts-space-4) var(--wts-space-6);
    border: 1px solid var(--wts-color-white-20);
    border-radius: var(--wts-radius-lg);
    background-color: color-mix(in srgb, var(--wts-color-white) 3%, transparent);
}

.releases__filters-label {
    margin: 0;
    font-size: var(--wts-font-size-xs);
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--wts-color-silver-60);
}

.releases__filters-list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--wts-space-2);
}

.releases__filter {
    display: inline-flex;
    align-items: center;
    gap: var(--wts-space-2);
    padding: var(--wts-space-2) var(--wts-space-3) var(--wts-space-2) var(--wts-space-4);
    border: 1px solid var(--wts-color-white-20);
    border-radius: var(--wts-radius-full);
    color: var(--wts-color-silver);
    font-size: var(--wts-font-size-sm);
    line-height: 1;
    text-decoration: none;
    transition:
        border-color var(--anim-fast) var(--ease-out-soft),
        background-color var(--anim-fast) var(--ease-out-soft),
        color var(--anim-fast) var(--ease-out-soft);
}

.releases__filter-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.5rem;
    padding: 0 var(--wts-space-1);
    border-radius: var(--wts-radius-full);
    background-color: var(--wts-color-white-20);
    font-size: var(--wts-font-size-xs);
    font-variant-numeric: tabular-nums;
    line-height: 1.5rem;
    color: var(--wts-color-silver);
    transition:
        background-color var(--anim-fast) var(--ease-out-soft),
        color var(--anim-fast) var(--ease-out-soft);
}

.releases__filter:hover {
    border-color: var(--wts-color-orange);
    color: var(--wts-color-white);
}

.releases__filter.is-current {
    border-color: var(--wts-color-orange);
    background-color: var(--wts-color-orange-20);
    color: var(--wts-color-orange);
}

.releases__filter:hover .releases__filter-count {
    background-color: var(--wts-color-white-40);
    color: var(--wts-color-white);
}

.releases__filter.is-current .releases__filter-count {
    background-color: var(--wts-color-orange-40);
    color: var(--wts-color-white);
}

.releases__filter:focus-visible {
    outline: 2px solid var(--wts-color-orange);
    outline-offset: 3px;
}

/* ── Le journal ── */
.release-feed {
    display: flex;
    flex-direction: column;
    gap: clamp(var(--wts-space-12), 8vw, var(--wts-space-16));
}

.release-feed__group {
    display: flex;
    flex-direction: column;
    gap: var(--wts-space-6);
}

/* En-tête de groupe : le nom de l'application, son effectif, et les actions qui la
   concernent. C'est lui qui porte le message de la page — « cette application-là est
   maintenue » —, d'où le filet supérieur qui le détache franchement du groupe précédent. */
.release-feed__group-head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--wts-space-4);
    padding-top: var(--wts-space-6);
    border-top: 1px solid var(--wts-color-orange-40);
}

.release-feed__group-identity {
    display: flex;
    flex-direction: column;
    gap: var(--wts-space-1);
}

.release-feed__group-title {
    margin: 0;
    font-family: var(--wts-font-display);
    font-size: clamp(1.5rem, 3vw, 2.25rem);
    font-weight: 400;
    line-height: 1.1;
    text-transform: uppercase;
    color: var(--wts-color-white);
}

/* « (suite) » : sans cette mention, un groupe qui reprend en haut d'une page se lirait comme
   le début du journal de l'application, et le visiteur croirait avoir remonté le temps. */
.release-feed__group-continued {
    margin-left: var(--wts-space-2);
    font-family: var(--wts-font-sans);
    font-size: var(--wts-font-size-sm);
    text-transform: none;
    color: var(--wts-color-silver-60);
}

.release-feed__group-count {
    margin: 0;
    font-size: var(--wts-font-size-sm);
    color: var(--wts-color-silver-60);
}

.release-feed__group-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--wts-space-3);
}

.release-feed__group-link {
    display: inline-flex;
    align-items: center;
    gap: var(--wts-space-2);
    color: var(--wts-color-silver);
    font-size: var(--wts-font-size-sm);
    text-decoration: none;
    transition: color var(--anim-fast) var(--ease-out-soft);
}

.release-feed__group-link:hover {
    color: var(--wts-color-orange);
}

.release-feed__group-link:focus-visible {
    outline: 2px solid var(--wts-color-orange);
    outline-offset: 3px;
}

.release-feed__group-link-icon {
    display: inline-flex;
}

.release-feed__list {
    list-style: none;
    margin: 0;
    padding: 0;
}

/* La dernière entrée d'un groupe n'a pas de filet bas : le filet haut de l'en-tête du groupe
   suivant fait déjà la séparation, et les deux traits se doubleraient. */
.release-feed__item:last-child .release-item {
    border-bottom: none;
}

.release-feed__empty {
    margin: 0;
    color: var(--wts-color-silver);
}

/* Questionnaire de maturité numérique (/evaluation).

   Structure reprise du questionnaire de FWC_SITE_VITRINE — en-tête, barre de progression,
   carte unique qui traverse ses douze étapes — mais habillée par la DA WTS : orange réservé
   aux titres et aux accents (jamais au corps de texte, cf. la règle de charte de
   _variables.css), pilules et rayons du design system. Aucune couleur en dur, tokens
   uniquement.

   ── Une page CLAIRE dans un site sombre (demande utilisateur) ──
   C'est la seule surface blanche de plein droit du site : la FAQ de l'accueil et les cartes
   d'accordéon le sont déjà, mais par section, pas par page. Le fond est retourné sur
   `#main-content` et NON sur le <body> : le footer n'a pas de fond à lui, il reprend celui
   du body et se retrouverait en texte blanc sur blanc. Le header, lui, n'a rien à changer —
   sa capsule est un aplat noir à liseré clair, déjà conçue pour traverser des sections
   noires, blanches et orange.
   Conséquence sur tout ce qui suit : les nuances de texte secondaire sont des opacités de
   NOIR (et non le silver du reste du site, pensé pour du blanc sur noir), et les quelques
   composants du design system utilisés ici — champs de saisie, boutons — sont dessinés pour
   le fond sombre : leurs couleurs sont donc reprises, en bas de fichier.

   Visibilité des étapes : l'attribut `hidden` seul ne suffit pas dès qu'une règle pose un
   `display`, qui l'emporte sur le `display: none` de la feuille du navigateur. Chaque
   conteneur masquable a donc sa règle `[hidden]` explicite. */

.maturity-surface #main-content {
    background-color: var(--wts-color-white);
    color: var(--wts-color-black);
}

.maturity-page {
    padding-block: clamp(2rem, 5vw, 4.5rem) clamp(3rem, 7vw, 6rem);
}

.maturity-page__inner {
    display: flex;
    flex-direction: column;
    gap: clamp(2.5rem, 5vw, 4rem);
}

/* ── En-tête de page ──
   Centré, et surtout : MÊME largeur et même axe que la carte du questionnaire (46rem,
   `margin-inline: auto`). L'en-tête était aligné à gauche du conteneur pendant que la carte,
   elle, était centrée : deux blocs de même largeur mais décalés l'un par rapport à l'autre,
   ce qui se voit immédiatement. Les deux valeurs vont ensemble — changer la largeur de la
   carte sans changer celle-ci ramènerait le décalage. */

.maturity-page__head {
    max-width: 46rem;
    margin-inline: auto;
    text-align: center;
}

.maturity-page__eyebrow {
    margin: 0 0 var(--wts-space-3);
    color: var(--wts-color-orange);
    font-size: var(--wts-font-size-sm);
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.maturity-page__title {
    margin: 0;
    font-size: clamp(2.25rem, 5.5vw, var(--wts-font-size-4xl));
    text-transform: uppercase;
}

.maturity-page__lead {
    margin: var(--wts-space-6) 0 0;
    color: var(--wts-color-black-60);
    font-size: var(--wts-font-size-lg);
    line-height: 1.6;
}

/* ── Le questionnaire ──
   Largeur volontairement bridée : une question et ses quatre réponses se lisent en une
   colonne, pas sur toute la largeur d'un 27 pouces. */

.maturity-quiz {
    width: 100%;
    max-width: 46rem;
    margin-inline: auto;
    /* La carte est ramenée en haut de l'écran à chaque changement d'étape : la marge
       compense le header fixe (59px) pour que le titre de l'étape ne passe pas dessous. */
    scroll-margin-top: calc(59px + 2 * var(--wts-space-6));
}

/* ── Barre de progression ── */

.maturity-quiz__progress {
    display: flex;
    flex-direction: column;
    gap: var(--wts-space-2);
    margin-bottom: var(--wts-space-6);
}

.maturity-quiz__progress[hidden] {
    display: none;
}

.maturity-quiz__progress-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--wts-space-4);
    font-size: var(--wts-font-size-sm);
}

.maturity-quiz__progress-text {
    font-weight: 600;
}

.maturity-quiz__progress-total {
    color: var(--wts-color-black-40);
}

.maturity-quiz__progress-track {
    height: 3px;
    border-radius: var(--wts-radius-full);
    background-color: var(--wts-color-black-20);
    overflow: hidden;
}

.maturity-quiz__progress-bar {
    display: block;
    width: 0;
    height: 100%;
    border-radius: var(--wts-radius-full);
    background-color: var(--wts-color-orange);
    transition: width var(--anim-slow) var(--ease-out-soft);
}

/* ── La carte ──
   Blanche sur blanc, détachée par une ombre portée et non par un filet : c'est déjà le
   traitement des cartes d'accordéon de la FAQ (cf. .faq-item dans _components.css), et
   c'est donc le vocabulaire du site pour une surface posée sur du clair. */

.maturity-quiz__card {
    padding: clamp(var(--wts-space-6), 4vw, var(--wts-space-12));
    border-radius: var(--wts-radius-lg);
    background-color: var(--wts-color-white);
    box-shadow: 0 24px 32px -14px rgba(149, 149, 149, 0.25);
}

.maturity-quiz__step {
    display: flex;
    flex-direction: column;
    gap: var(--wts-space-6);
}

.maturity-quiz__step[hidden] {
    display: none;
}

/* Le focus est déplacé sur l'étape à chaque changement (cf. le contrôleur Stimulus) :
   l'anneau serait ici un artefact, l'utilisateur n'a rien cliqué. Le contenu, lui, reste
   bien annoncé. */
.maturity-quiz__step:focus {
    outline: none;
}

.maturity-quiz__step-title {
    margin: 0;
    font-size: var(--wts-font-size-xl);
}

.maturity-quiz__step-lead,
.maturity-quiz__legal,
.maturity-quiz__nojs {
    margin: 0;
    color: var(--wts-color-black-60);
    line-height: 1.6;
}

.maturity-quiz__legal,
.maturity-quiz__nojs {
    font-size: var(--wts-font-size-sm);
}

.maturity-quiz__nojs {
    display: none;
    padding: var(--wts-space-3) var(--wts-space-4);
    border-left: 2px solid var(--wts-color-orange);
    border-radius: var(--wts-radius-sm);
    background-color: var(--wts-color-orange-20);
}

/* Le message n'apparaît que si le JS n'a pas pris la main (cf. app.js, qui pose
   `js-ready` sur <html>) : sans cette bascule, il s'afficherait une fraction de seconde
   à chaque chargement. */
html:not(.js-ready) .maturity-quiz__nojs {
    display: block;
}

.maturity-quiz__lead-form {
    display: flex;
    flex-direction: column;
    gap: var(--wts-space-4);
}

/* Les erreurs de champ du design system sont rendues dans un <div> par le thème de
   formulaire ; ici ce sont des <p>, d'où la remise à zéro des marges. */
.maturity-quiz .form-field__error {
    margin: 0;
}

.maturity-quiz .form-field__error[hidden] {
    display: none;
}

/* Honeypot : hors du champ de vision comme du parcours clavier, mais bien rendu — un
   champ masqué par `display: none` n'attire aucun robot. */
.maturity-quiz__trap {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* ── Une question ── */

.maturity-quiz__category {
    margin: 0;
    color: var(--wts-color-orange);
    font-size: var(--wts-font-size-xs);
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
}

.maturity-quiz__question {
    margin: 0;
    font-size: clamp(1.375rem, 3vw, var(--wts-font-size-2xl));
    line-height: 1.2;
}

.maturity-quiz__choices {
    display: flex;
    flex-direction: column;
    gap: var(--wts-space-3);
}

.maturity-quiz__choice {
    display: flex;
    align-items: flex-start;
    gap: var(--wts-space-4);
    padding: var(--wts-space-4);
    border: 1px solid var(--wts-color-black-20);
    border-radius: var(--wts-radius-md);
    cursor: pointer;
    user-select: none;
    transition:
        border-color var(--anim-fast) var(--ease-out-soft),
        background-color var(--anim-fast) var(--ease-out-soft);
}

/* Survol volontairement léger : sur du blanc, une opacité de noir devient vite un gris
   franc, et la réponse survolée se confondrait avec la réponse choisie. */
.maturity-quiz__choice:hover {
    border-color: var(--wts-color-black-40);
    background-color: color-mix(in srgb, var(--wts-color-black) 4%, transparent);
}

/* Le radio natif est conservé (clavier, lecteurs d'écran, groupe de radios) mais rendu
   invisible : c'est le libellé entier qui fait office de cible. L'anneau de focus est
   reporté sur la carte, sans quoi il n'y aurait aucun repère visuel au clavier. */
.maturity-quiz__radio {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
}

.maturity-quiz__choice:has(.maturity-quiz__radio:focus-visible) {
    outline: 2px solid var(--wts-color-orange);
    outline-offset: 2px;
}

.maturity-quiz__choice.is-selected {
    border-color: var(--wts-color-orange);
    background-color: var(--wts-color-orange-20);
}

.maturity-quiz__choice-letter {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 2rem;
    height: 2rem;
    border-radius: var(--wts-radius-md);
    background-color: color-mix(in srgb, var(--wts-color-black) 8%, transparent);
    font-family: var(--wts-font-display);
    font-size: var(--wts-font-size-sm);
    transition:
        background-color var(--anim-fast) var(--ease-out-soft),
        color var(--anim-fast) var(--ease-out-soft);
}

.maturity-quiz__choice.is-selected .maturity-quiz__choice-letter {
    background-color: var(--wts-color-orange);
    color: var(--wts-color-black);
}

.maturity-quiz__choice-text {
    line-height: 1.5;
}

@media (prefers-reduced-motion: reduce) {
    .maturity-quiz__progress-bar,
    .maturity-quiz__choice,
    .maturity-quiz__choice-letter,
    .maturity-result__bar {
        transition: none;
    }
}

/* ── Erreurs et navigation ── */

.maturity-quiz__error {
    margin: 0;
    padding: var(--wts-space-2) var(--wts-space-3);
    border-left: 2px solid var(--wts-color-error);
    border-radius: var(--wts-radius-sm);
    background-color: color-mix(in srgb, var(--wts-color-error) 12%, transparent);
    color: var(--wts-color-error);
    font-size: var(--wts-font-size-sm);
}

.maturity-quiz__error[hidden] {
    display: none;
}

.maturity-quiz__actions {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--wts-space-4);
    flex-wrap: wrap;
}

/* Une seule action : elle occupe toute la largeur plutôt que de flotter à gauche —
   c'est le geste attendu de l'étape. */
.maturity-quiz__actions--start .btn,
.maturity-quiz__actions--start {
    width: 100%;
}

.maturity-quiz__actions--result {
    justify-content: center;
}

.maturity-quiz__spinner {
    display: inline-flex;
    animation: maturity-spin 0.9s linear infinite;
}

.maturity-quiz__spinner[hidden] {
    display: none;
}

@keyframes maturity-spin {
    to {
        transform: rotate(1turn);
    }
}

@media (prefers-reduced-motion: reduce) {
    .maturity-quiz__spinner {
        animation: none;
    }
}

/* ── Écran de résultats ── */

.maturity-quiz__step--result {
    gap: var(--wts-space-8);
}

.maturity-result__score {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--wts-space-2);
    text-align: center;
}

.maturity-result__score-label {
    margin: 0;
    color: var(--wts-color-black-40);
    font-size: var(--wts-font-size-xs);
    letter-spacing: 0.12em;
    text-transform: uppercase;
}

.maturity-result__score-value {
    margin: 0;
    font-family: var(--wts-font-display);
    font-size: clamp(3rem, 12vw, 5rem);
    line-height: 1;
}

.maturity-result__score-total {
    color: var(--wts-color-orange);
    font-size: 0.4em;
}

.maturity-result__level {
    margin: 0;
    color: var(--wts-color-orange);
    font-weight: 600;
    letter-spacing: 0.08em;
}

.maturity-result__track {
    width: 100%;
    max-width: 22rem;
    height: 4px;
    margin-top: var(--wts-space-2);
    border-radius: var(--wts-radius-full);
    background-color: var(--wts-color-black-20);
    overflow: hidden;
}

.maturity-result__bar {
    display: block;
    width: 0;
    height: 100%;
    border-radius: var(--wts-radius-full);
    background-color: var(--wts-color-orange);
    transition: width var(--anim-slow) var(--ease-out-soft);
}

.maturity-result__block {
    display: flex;
    flex-direction: column;
    gap: var(--wts-space-3);
}

/* La recommandation est la seule prise de parole commerciale du site : elle est posée sur
   un aplat orange, comme les sections d'affirmation de l'accueil. */
.maturity-result__block--recommendation {
    padding: var(--wts-space-6);
    border-radius: var(--wts-radius-lg);
    background-color: var(--wts-color-orange);
    color: var(--wts-color-black);
}

.maturity-result__title {
    margin: 0;
    font-size: var(--wts-font-size-lg);
    text-transform: uppercase;
}

.maturity-result__text {
    margin: 0;
    color: var(--wts-color-black-60);
    line-height: 1.65;
}

.maturity-result__block--recommendation .maturity-result__text {
    color: var(--wts-color-black);
}

/* ── Mobile ── */

@media (max-width: 599px) {
    .maturity-quiz__actions .btn {
        flex: 1 1 auto;
        justify-content: center;
    }

    .maturity-quiz__choice {
        padding: var(--wts-space-3);
        gap: var(--wts-space-3);
    }
}

/* ── Composants du design system, repris pour la surface claire ──
   `.field-control` et les variantes de bouton sont dessinés pour le fond noir du site : le
   champ est un verre clair à texte blanc, l'outline a un libellé blanc, la secondaire un
   aplat blanc translucide. Aucun des trois n'existe sur du blanc. On ne touche pas au design
   system pour autant — seules leurs couleurs sont reprises dans le contexte de cette page,
   la forme, les rayons et les transitions restent les siens. */

.maturity-page .field-control {
    border-color: var(--wts-color-black-20);
    background-color: var(--wts-color-white);
    color: var(--wts-color-black);
    /* Le flou d'arrière-plan ne sert que l'effet de verre du thème sombre : sur un aplat
       blanc il ne produit rien et coûte une couche de composition. */
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    box-shadow: none;
}

.maturity-page .field-control::placeholder {
    color: var(--wts-color-black-40);
}

.maturity-page .field-control:hover:not(:disabled):not(:focus) {
    border-color: var(--wts-color-black-40);
}

/* L'anneau de focus orange est conservé : c'est le repère du site, et il contraste
   aussi bien sur blanc que sur noir. */
.maturity-page .field-control:focus {
    border-color: var(--wts-color-orange);
    background-color: var(--wts-color-white);
    box-shadow: 0 0 0 3px var(--wts-color-orange-20);
}

/* « Précédent », « Refaire le questionnaire » : action de retrait, elle reste en retrait. */
.maturity-page .btn--outline {
    border-color: var(--wts-color-black-20);
    color: var(--wts-color-black);
}

/* « Suivant » : l'action qui fait avancer neuf questions sur dix. Aplat noir plutôt que
   translucide — visible sur le blanc, et sans venir concurrencer l'orange du bouton final,
   qui doit rester le seul point d'appel de la carte. */
.maturity-page .btn--secondary {
    background-color: var(--wts-color-black);
    color: var(--wts-color-white);
}

.maturity-page .btn--secondary:hover:not(:disabled) {
    background-color: var(--wts-color-black-80);
}

/* ─────────────────────────────────────────────────────────────────────────────
   Relance vers le questionnaire, posée au-dessus de chaque module FAQ
   (cf. templates/components/organisms/maturity-cta.html.twig).

   Aplat orange et texte noir : le traitement que le site réserve déjà à ses prises de
   parole (home-method, la recommandation de l'écran de résultats). C'est ce qui la rend
   lisible aussi bien sur la bande blanche de la FAQ de l'accueil que sur le noir des pages
   Projets et Releases — l'encart n'a donc pas de variante de thème, il est identique
   partout, comme la FAQ qu'il précède.

   Les deux boutons gardent les classes du design system ; seules leurs couleurs sont
   reprises ici, parce qu'aucune des variantes existantes n'est lisible sur un aplat orange
   (le primaire est orange, l'outline est prévu pour du texte blanc). Même procédé que le
   thème des pages projet, qui repeint déjà `.btn--primary` dans son contexte.
   ───────────────────────────────────────────────────────────────────────────── */

.maturity-cta {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--wts-space-3);
    max-width: 52rem;
    margin: 0 auto clamp(var(--wts-space-12), 7vw, var(--wts-space-16));
    padding: clamp(var(--wts-space-8), 5vw, var(--wts-space-12)) clamp(var(--wts-space-6), 4vw, var(--wts-space-12));
    border-radius: var(--wts-radius-lg);
    background-color: var(--wts-color-orange);
    color: var(--wts-color-black);
    text-align: center;
}

.maturity-cta__eyebrow {
    margin: 0;
    font-size: var(--wts-font-size-xs);
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    /* Pas de blanc ni de gris ici : sur l'orange, seule une nuance de noir garde le
       contraste exigé par la charte. */
    color: var(--wts-color-black-60);
}

/* Un <p> et non un titre : l'encart est un aside, il ne doit pas entrer dans le plan de
   titres de la page. La famille display lui donne le poids d'un titre sans en être un. */
.maturity-cta__title {
    margin: 0;
    max-width: 30ch;
    font-family: var(--wts-font-display);
    font-size: clamp(1.375rem, 3vw, var(--wts-font-size-2xl));
    line-height: 1.15;
    text-transform: uppercase;
}

.maturity-cta__text {
    margin: 0;
    max-width: 46rem;
    line-height: 1.6;
    color: var(--wts-color-black-80);
}

.maturity-cta__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: var(--wts-space-3);
    margin-top: var(--wts-space-4);
}

/* Bouton d'appel : l'inverse de la pilule habituelle — aplat noir, libellé blanc. */
.maturity-cta .btn--primary {
    background-color: var(--wts-color-black);
    color: var(--wts-color-white);
}

.maturity-cta .btn--primary::after {
    border-color: var(--wts-color-white-40);
}

/* La pulsation au survol est un halo orange : invisible sur un fond orange. Elle repasse
   en halo noir, même geste, même durée. */
.maturity-cta .btn--primary:hover:not(:disabled) {
    animation: none;
    box-shadow: 0 0 0 4px var(--wts-color-black-40);
}

.maturity-cta .btn--outline {
    border-color: var(--wts-color-black-40);
    color: var(--wts-color-black);
}

.maturity-cta .btn--outline:hover:not(:disabled) {
    border-color: var(--wts-color-black);
    color: var(--wts-color-black);
    background-color: var(--wts-color-white-40);
}

/* Le focus visible du site est orange : illisible sur l'aplat orange de l'encart. */
.maturity-cta .btn:focus-visible {
    outline-color: var(--wts-color-black);
}

/* Pages annexes (story 3-6) : mentions légales et politique de confidentialité.

   Une colonne de lecture, rien d'autre : ce sont des textes de référence. L'en-tête reprend
   le rythme de celui de la page Contact (surtitre orange, titre en capitales) pour que ces
   pages appartiennent au même site, sans en copier les classes — les deux évolueront
   séparément.

   Aucune couleur en dur : tokens du site uniquement. */

.legal-page {
    padding-block: clamp(2rem, 5vw, 4.5rem) clamp(3rem, 8vw, 6rem);
}

.legal-page__inner {
    display: flex;
    flex-direction: column;
    gap: clamp(2.5rem, 5vw, 4rem);
    max-width: 48rem;
}

/* ── En-tête ── */

.legal-page__eyebrow {
    margin: 0 0 var(--wts-space-3);
    color: var(--wts-color-orange);
    font-size: var(--wts-font-size-sm);
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.legal-page__title {
    margin: 0;
    font-size: clamp(2.25rem, 5.5vw, var(--wts-font-size-4xl));
    text-transform: uppercase;
}

.legal-page__updated {
    margin: var(--wts-space-4) 0 0;
    color: var(--wts-color-silver-60);
    font-size: var(--wts-font-size-sm);
}

/* ── Corps ── */

.legal-page__body {
    display: flex;
    flex-direction: column;
    gap: var(--wts-space-12);
}

.legal-page__section {
    display: flex;
    flex-direction: column;
    gap: var(--wts-space-4);
    padding-top: var(--wts-space-8);
    border-top: 1px solid var(--wts-color-silver-20);
    color: var(--wts-color-silver);
    line-height: 1.7;
}

.legal-page__section h2 {
    margin: 0;
    color: var(--wts-color-white);
    font-size: var(--wts-font-size-xl);
}

.legal-page__section h3 {
    margin: var(--wts-space-4) 0 0;
    color: var(--wts-color-white);
    font-size: var(--wts-font-size-lg);
}

.legal-page__section p,
.legal-page__section ul {
    margin: 0;
}

.legal-page__section ul {
    display: flex;
    flex-direction: column;
    gap: var(--wts-space-2);
    padding-left: var(--wts-space-6);
}

.legal-page__section strong {
    color: var(--wts-color-white);
    font-weight: 600;
}

.legal-page__section a {
    color: var(--wts-color-white);
    text-decoration: underline;
    text-decoration-color: var(--wts-color-orange);
    text-underline-offset: 0.2em;
}

.legal-page__section a:hover,
.legal-page__section a:focus-visible {
    color: var(--wts-color-orange);
}

/* Fiche « intitulé : valeur » (éditeur, hébergeur). */
.legal-page__facts {
    display: grid;
    grid-template-columns: minmax(10rem, max-content) 1fr;
    gap: var(--wts-space-2) var(--wts-space-6);
    margin: 0;
}

.legal-page__facts dt {
    color: var(--wts-color-silver-60);
}

.legal-page__facts dd {
    margin: 0;
    color: var(--wts-color-white);
}

/* Information légale encore inconnue (cf. _legal_layout.html.twig) : volontairement voyante,
   pour ne jamais passer inaperçue en recette. */
.legal-page__todo {
    padding: 0 var(--wts-space-1);
    border-radius: var(--wts-radius-sm);
    background: var(--wts-color-orange-20);
    color: var(--wts-color-orange);
    -webkit-box-decoration-break: clone;
    box-decoration-break: clone;
}

@media (max-width: 640px) {
    .legal-page__facts {
        grid-template-columns: 1fr;
        gap: 0;
    }

    .legal-page__facts dd + dt {
        margin-top: var(--wts-space-3);
    }
}

/* assets/styles/app.css */

/* 0. Tokens d'animation */

/* 1. Tokens de design (couleurs, typo, espacements, rayons) */

/* 1.bis. Composants (atoms & molecules, story 1-3) */

/* 1.ter. Organismes de la page d'accueil (story 3-1) */

/* 1.ter.bis. Module FAQ — partagé par l'accueil, l'index des projets et la page Releases */

/* 1.quater. Index des projets (story 3-2) */

/* 1.quinquies. Paires typographiques des thèmes projet (story 3-2) */

/* 1.sexies. Page projet — la page-univers (story 3-3) */

/* 1.septies. Moteur dive-in — overlay permanent et couches d'effet (story 3-4) */

/* 1.octies. Page Contact (story 3-5) */

/* 1.nonies. Page Releases — le journal de bord public (story 4-2) */

/* 1.decies. Questionnaire de maturité numérique (/evaluation) */

/* 1.undecies. Pages annexes — mentions légales, confidentialité (story 3-6) */

/* 2. Polices auto-hébergées — Frick (titres/accents) + Outfit (corps) */

/* 3. Reset minimal */
*,
*::before,
*::after {
    box-sizing: border-box;
}

/* Échelle fluide au-delà de 1440px.

   Tout le design system est exprimé en rem — corps de texte, espacements, rayons, largeurs
   de clamp (cf. _variables.css). Le rem, lui, valait 16px quelle que soit la dalle : passé
   ~1440px de large, plus rien ne grandissait, et comme `.container` plafonne aussi, le site
   devenait un îlot de taille fixe au milieu d'un écran de plus en plus grand. D'où
   l'impression de textes minuscules et de proportions fausses sur 27/32 pouces.

   Un seul réglage suffit donc à remettre l'ensemble à l'échelle : 16px jusqu'à 1440px
   (rien ne bouge sur les écrans courants, c'est volontaire), puis montée linéaire jusqu'à
   20px à 2560px, plafonnée à 21px.

   Le terme fixe est en rem et non en px : c'est ce qui préserve la taille de police choisie
   par l'utilisateur dans son navigateur, et le zoom (qui réduit la largeur du viewport en
   px CSS, donc la part `vw`, pendant que la part `rem`, elle, grandit). */
html {
    font-size: clamp(1rem, 0.6786rem + 0.3571vw, 1.3125rem);
    -webkit-text-size-adjust: 100%;
}

body {
    margin: 0;
}

img,
picture,
svg,
video {
    display: block;
    max-width: 100%;
}

/* 4. Base — thème sombre (fond noir, texte blanc), typographie */
body {
    background-color: var(--wts-color-black);
    color: var(--wts-color-white);
    font-family: var(--wts-font-sans);
    font-size: var(--wts-font-size-base);
    line-height: 1.5;
}

h1,
h2,
h3,
h4,
h5,
h6 {
    font-family: var(--wts-font-display);
    line-height: 1.15;
}

/* Règle charte : l'orange n'habille jamais le corps de texte, seulement
   titres/accents (cf. docs/DCT_WTS_SITE.md §7.2). */
a {
    color: inherit;
}

/* 5. Sélection & focus visible */
::selection {
    background-color: var(--wts-color-orange);
    color: var(--wts-color-black);
}

:focus-visible {
    outline: 2px solid var(--wts-color-orange);
    outline-offset: 2px;
}

/* 6. Accessibilité — utilitaires */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.skip-link {
    position: absolute;
    top: -100%;
    left: 0;
    z-index: 100;
    padding: var(--wts-space-2) var(--wts-space-4);
    background-color: var(--wts-color-orange);
    color: var(--wts-color-black);
    border-radius: 0 0 var(--wts-radius-sm) 0;
}

.skip-link:focus {
    top: 0;
}

/* Largeur de contenu partagée par les sections de page (accueil, story 3-1 et suivantes) :
   1332px reprend la largeur du header (cf. maquette Figma, node 29:958, 66px de marge de
   chaque côté sur 1440px). */
/* 83.25rem = les mêmes 1332px qu'avant tant que le rem vaut 16px. Exprimée en rem, la
   largeur suit l'échelle fluide ci-dessus au lieu de figer le contenu : la colonne grandit
   dans la même proportion que le texte qu'elle contient, donc la longueur de ligne — le
   nombre de caractères — reste celle qui a été réglée. */
.container {
    max-width: 83.25rem;
    margin-inline: auto;
    padding-inline: var(--wts-space-6);
}

/* 7. Header / Navigation (organism) — fixe en haut, toujours visible au scroll.
   pointer-events: none sur le header et le flex .site-nav : leur boîte couvre toute la
   largeur (y compris l'espace vide entre le logo et le bouton menu, agrandi par
   justify-content: space-between) et intercepterait sinon les clics destinés au contenu
   en dessous (ex. accordéon FAQ visible sous le header). Réactivé explicitement sur les
   seuls éléments cliquables. */
.site-header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 50;
    padding: var(--wts-space-6);
    pointer-events: none;
    transition: opacity 700ms var(--ease-out-soft);
}

/* Séquence d'ouverture de l'accueil. `hero-intro-pending` est posée dès le HTML par la page
   (cf. home/index.html.twig) et retirée par hero_intro_controller.js à la fin de la séquence.
   Les deux règles ci-dessous sont conditionnées à `js-ready` : sans JS la classe reste, et
   rien ne doit alors être ni masqué ni bloqué. Elles sont aussi limitées à
   `no-preference` — en mouvement réduit il n'y a pas de séquence à protéger. */
@media (prefers-reduced-motion: no-preference) {
    /* Le header n'entre qu'une fois la séquence terminée. `visibility` en plus de l'opacité :
       un header invisible restait cliquable, ce qui se voyait d'autant plus qu'il est
       désormais masqué dès la première peinture. Non transitionnée, elle repasse donc à
       `visible` immédiatement au retrait de la classe, pendant que l'opacité, elle, fond. */
    html.js-ready.hero-intro-pending .site-header {
        opacity: 0;
        visibility: hidden;
    }

    /* Tant que l'ouverture n'est pas finie, la page ne défile pas : l'animation dure quelques
       secondes et pouvait jusqu'ici être passée d'un coup de molette. Sur les deux éléments,
       car selon le navigateur l'un ou l'autre porte le défilement du document.
       Conséquence assumée : au rechargement, l'accueil repart du haut plutôt que de restaurer
       la position précédente — c'est la seule position d'où la séquence a un sens. */
    html.js-ready.hero-intro-pending,
    html.js-ready.hero-intro-pending body {
        overflow: hidden;
    }
}

@media (prefers-reduced-motion: reduce) {
    .site-header {
        transition: none;
    }
}

/* Compense la hauteur du header fixe pour que le contenu ne parte pas dessous
   (2 × padding vertical du header + hauteur de la capsule/bouton, 59px). */
#main-content {
    padding-top: var(--wts-header-band);
}

.site-nav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--wts-space-4);
    pointer-events: none;
}

.site-nav__capsule,
.site-nav__toggle,
.site-nav > .btn--primary {
    pointer-events: auto;
}

/* Capsule "en haltère" : tracé Bézier de la maquette Figma (pincement concave entre le logo
   et les liens, impossible à reproduire avec un simple border-radius). Le <svg> de fond porte
   le fill/stroke/ombre, le logo et les liens sont de vrais éléments HTML positionnés en absolu
   par-dessus (cf. header.html.twig). */
/* La seule zone du site qui ne suit PAS l'échelle fluide (cf. html, plus haut) : la capsule
   est un gabarit calqué sur un tracé Bézier, et son `clip-path` est écrit en coordonnées px
   absolues. Elle ne peut donc pas être redimensionnée par le seul rem — ni son contenu, qui
   déborderait de la forme. La typo interne est donc épinglée en px : sans cela, les libellés
   à 20px sortaient du lobe droit. */
/* Deux largeurs, une par nombre de liens (cf. le calcul détaillé dans header.html.twig) :
   310px pour Projets + Évaluation, 389px pour ces deux-là plus Releases une fois rouverte.
   Le 244×59 de la maquette d'origine ne portait que deux libellés plus courts, et son lobe
   gauche était un disque : le nouveau logo (lockup 3,56:1) a imposé de l'étirer en stade,
   d'où les +52px sur les deux largeurs. */
.site-nav__capsule {
    position: relative;
    width: 310px;
    height: 59px;
    flex-shrink: 0;
    font-size: 16px;
}

.site-nav__capsule--wide {
    width: 389px;
}

/* Capsule OPAQUE, sans effet de verre. L'effet glass (backdrop-filter, filtre SVG de
   réfraction) a été retiré : la page traverse des fonds noir, blanc et orange, et un
   verre translucide n'offre de contraste stable sur aucun des trois — il disparaissait
   selon la section survolée au scroll. Un aplat noir + liseré clair reste lisible partout,
   y compris sur le hero noir où c'est le liseré qui détache la forme.
   Le reflet directionnel (.site-nav__capsule-sheen, -45°) est conservé : il donne du
   relief sans dépendre de ce qu'il y a derrière. */
/* Le clip-path ne peut pas être calculé côté Twig (la CSP interdit le style inline) : les
   deux tracés sont donc dupliqués ici, et doivent rester rigoureusement identiques aux
   `nav_capsule_path` du gabarit — sinon l'ombre portée découperait une forme et le <path>
   en dessinerait une autre. */
.site-nav__capsule-bg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    clip-path: path('M0 29.5C0 45.7924 13.1838 59 29.4469 59H81.4469C91.2278 59 99.8948 54.2228 105.2501 46.8705C105.6415 46.3332 106.259 46 106.9238 46H112.7483C113.4113 46 114.0274 46.3315 114.4194 46.8663C117.3759 50.899 121.3636 54.1531 125.9671 56.2627C129.7278 57.9861 133.944 58.2373 138.08 58.2262L280.88 57.88C286.002 57.8662 291.27 57.4352 295.662 54.8001C304.26 49.641 310.012 40.2124 310 29.4419C309.988 18.6901 304.236 9.288 295.652 4.1417C291.266 1.5125 286.006 1.0805 280.892 1.0646L138.092 0.6624C133.947 0.6495 129.722 0.8972 125.9587 2.6332C121.3437 4.7622 117.3731 8.0517 114.4188 12.1275C114.0275 12.6673 113.4084 13.0022 112.7417 13.002L106.9234 13.0003C106.2589 13.0001 105.6416 12.6669 105.2503 12.1297C99.8951 4.7773 91.2279 0 81.4469 0H29.4469C13.1838 0 0 13.2076 0 29.5Z');
    filter: drop-shadow(0 4px 14px rgba(0, 0, 0, 0.35));
    pointer-events: none;
}

.site-nav__capsule--wide .site-nav__capsule-bg {
    clip-path: path('M0 29.5C0 45.7924 13.1838 59 29.4469 59H81.4469C91.2278 59 99.8948 54.2228 105.2501 46.8705C105.6415 46.3332 106.259 46 106.9238 46H112.7483C113.4113 46 114.0274 46.3315 114.4194 46.8663C117.3759 50.899 121.3636 54.1531 125.9671 56.2627C129.7278 57.9861 133.944 58.2373 138.08 58.2262L359.88 57.88C365.002 57.8662 370.27 57.4352 374.662 54.8001C383.26 49.641 389.012 40.2124 389 29.4419C388.988 18.6901 383.236 9.288 374.652 4.1417C370.266 1.5125 365.006 1.0805 359.892 1.0646L138.092 0.6624C133.947 0.6495 129.722 0.8972 125.9587 2.6332C121.3437 4.7622 117.3731 8.0517 114.4188 12.1275C114.0275 12.6673 113.4084 13.0022 112.7417 13.002L106.9234 13.0003C106.2589 13.0001 105.6416 12.6669 105.2503 12.1297C99.8951 4.7773 91.2279 0 81.4469 0H29.4469C13.1838 0 0 13.2076 0 29.5Z');
}

.site-nav__capsule-shape {
    fill: var(--wts-color-black);
    stroke: var(--wts-color-silver-40);
    stroke-width: 2px;
}

/* Couleur des stops du reflet (light -45°) — en classes CSS externes, pas en style=
   inline (CSP style-src 'self' stricte, cf. header.html.twig). */
.site-nav__capsule-sheen-stop {
    stop-color: var(--wts-color-black);
    stop-opacity: 0;
}

.site-nav__capsule-sheen-stop--start {
    stop-opacity: 0.28;
}

.site-nav__capsule-sheen {
    mix-blend-mode: screen;
}

/* Posé dans le lobe gauche, devenu un stade de 111px de large (cf. header.html.twig).
   Coordonnées en px comme tout le reste de la capsule, et pour la même raison : la forme
   ne suit pas l'échelle fluide, un logo en rem en sortirait sur grand écran.
   85px de large → 23,9px de haut (rapport 3,56 du lockup), donc 13px de marge à gauche,
   13px à droite et 17,5px en haut/bas : le logo est centré dans son lobe. */
.site-nav__logo {
    position: absolute;
    left: 13px;
    top: 17.5px;
    display: inline-flex;
    align-items: center;
    color: var(--wts-color-orange);
}

.site-nav__logo .icon {
    width: 85px;
    height: auto;
}

.site-nav__logo .icon svg {
    width: 100%;
    height: auto;
    display: block;
}

/* La capsule est volontairement compacte (59px de haut) : le touch-target accessible de
   44px et les paddings/marges cumulés du molecule .nav-item (cf. _components.css)
   débordent largement de la forme. Plutôt que de positionner la pilule à des coordonnées
   pixel devinées à la main (fragile : toute variation de largeur de texte rendu la
   décale), on définit un "slot" rectangulaire dans le lobe droit (inset : 120px à gauche —
   les 68px d'origine plus les 52px d'allongement du lobe du logo —, 10px en haut/bas, 14px
   à droite, soit toujours 176px de slot) et on laisse le flexbox centrer le
   contenu dedans automatiquement, quelle que soit la largeur réelle du texte. Uniquement
   dans ce contexte précis — la variante mobile (dropdown, media query plus bas) garde le
   touch-target 44px standard. */
.site-nav__capsule .nav-item-group {
    position: absolute;
    inset: 10px 14px 10px 120px;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0;
    /* Padding en px comme les gouttières plus bas, et pour la même raison : en rem, il
       grandissait avec l'échelle fluide au-delà de 1440px (jusqu'à ~16px de chaque côté)
       dans une forme, elle, restée à sa largeur fixe. Les 12px sont exactement ceux sur
       lesquels les deux largeurs de capsule ont été calculées (cf. header.html.twig) : les
       laisser varier, c'est faire déborder les libellés sur les grands écrans. */
    padding: 0 12px;
}

.site-nav__capsule .nav-item-group .nav-item {
    min-height: 0;
    height: 100%;
    padding: 0;
    line-height: 1;
    white-space: nowrap;
}

/* Gouttières en px comme le reste de la capsule : en rem, elles auraient élargi le groupe
   de liens sur grand écran, dans une forme restée fixe. */
.site-nav__capsule .nav-item-group .nav-item + .nav-item {
    height: 60%;
    border-left: 1px solid var(--wts-color-silver-20);
    margin-left: 8px;
    padding-left: 8px;
}

/* z-index: le bouton reste au-dessus de l'overlay .mobile-menu (cf. plus bas) une fois
   ouvert, comme le logo — CSS pur, se transforme en croix via ses barres ::before/::after. */
/* Même traitement que la capsule du logo, et pour la même raison : la page traverse des
   fonds noir, blanc et orange. Un burger en `currentColor` sur fond transparent était
   simplement invisible dès que le header passait devant une section blanche (home-proof,
   la FAQ). L'aplat noir + liseré clair, lui, est lisible sur les trois fonds — c'est déjà
   l'arbitrage retenu pour .site-nav__capsule. Le disque fait 58px, comme elle : les deux
   extrémités du header se répondent. */
.site-nav__toggle {
    position: relative;
    z-index: 2;
    display: none;
    align-items: center;
    justify-content: center;
    width: 58px;
    height: 58px;
    border-radius: var(--wts-radius-full);
    background-color: var(--wts-color-black);
    border: 1px solid var(--wts-color-silver-40);
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
    /* Orange de marque : le burger (et la croix qu'il devient) sont en `currentColor`, ils le
       prennent tous les deux. Il répond ainsi au logo, orange lui aussi, à l'autre bout du
       header mobile. */
    color: var(--wts-color-orange);
    cursor: pointer;
}

.site-nav__burger {
    display: block;
    width: 24px;
    height: 2px;
    background-color: currentColor;
    position: relative;
    transition: background-color var(--anim-fast) var(--ease-out-soft);
}

.site-nav__burger::before,
.site-nav__burger::after {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 24px;
    height: 2px;
    background-color: currentColor;
    transition: transform var(--anim-fast) var(--ease-out-soft);
}

.site-nav__burger::before {
    transform: translateY(-7px);
}

.site-nav__burger::after {
    transform: translateY(7px);
}

.site-nav__toggle[aria-expanded='true'] .site-nav__burger {
    background-color: transparent;
}

.site-nav__toggle[aria-expanded='true'] .site-nav__burger::before {
    transform: translateY(0) rotate(45deg);
}

.site-nav__toggle[aria-expanded='true'] .site-nav__burger::after {
    transform: translateY(0) rotate(-45deg);
}

@media (prefers-reduced-motion: reduce) {
    .site-nav__burger,
    .site-nav__burger::before,
    .site-nav__burger::after {
        transition: none;
    }
}

/* Overlay mobile plein écran, dans l'esprit de la nav mobile de Mistral AI (logo + croix
   en haut, liste de liens à chevrons, CTA final en pleine largeur) — adapté à notre thème
   sombre et à nos liens. Caché par défaut (y compris hors mobile, garde-fou si la classe
   is-open est togglée par erreur en desktop) ; affiché uniquement dans le breakpoint
   mobile ci-dessous. */
.mobile-menu {
    display: none;
}

body.has-mobile-menu-open {
    overflow: hidden;
}

@media (max-width: 768px) {
    .site-nav__toggle {
        display: flex;
    }

    /* Mobile : la forme en haltère (tracé SVG) ne sert plus — on la referme en simple
       pilule autour du logo (CSS pur, plus besoin d'adaptabilité vu qu'il n'y a plus qu'un
       seul élément dedans) et on retire la pilule de liens (elle devient l'overlay
       .mobile-menu, cf. plus bas — la garder hors de .site-nav__capsule reste préférable,
       cf. Dev Notes 1-3). Le bouton menu se retrouve seul à droite via le
       justify-content: space-between existant de .site-nav (cf. header.html.twig).

       Une PILULE et non plus un rond : le lockup fait 3,56 fois sa hauteur, un rond de 58px
       l'aurait réduit à ~11px de haut. La pilule garde la hauteur 58px du bouton menu (les
       deux extrémités du header continuent de se répondre) et prend en largeur ce qu'il
       faut : 76px de logo + 2×16px, soit ~108px. Avec le bouton (58px) et la gouttière du
       flex, l'ensemble tient dès 320px de large. */
    .site-nav__capsule-bg,
    .site-nav__capsule .nav-item-group {
        display: none;
    }

    .site-nav__capsule {
        position: relative;
        z-index: 2;
        width: auto;
        height: 58px;
        display: flex;
        align-items: center;
        justify-content: center;
        padding-inline: 16px;
        border-radius: var(--wts-radius-full);
        background-color: var(--wts-color-black);
        border: 1px solid var(--wts-color-silver-40);
        box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
    }

    .site-nav__logo {
        position: static;
    }

    .site-nav__logo .icon {
        width: 76px;
    }

    .site-nav > .btn--primary {
        display: none;
    }

    .mobile-menu {
        position: fixed;
        inset: 0;
        z-index: 1;
        display: flex;
        flex-direction: column;
        padding-top: calc(2 * var(--wts-space-6) + 59px);
        background-color: var(--wts-color-white);
        opacity: 0;
        visibility: hidden;
        pointer-events: none;
        transition: opacity var(--anim-base) var(--ease-out-soft);
    }

    .mobile-menu.is-open {
        opacity: 1;
        visibility: visible;
        pointer-events: auto;
    }

    /* Menu ouvert : le bouton garde son disque noir, la croix reste donc blanche. C'est le
       disque qui assure le contraste sur l'overlay clair — inutile (et contre-productif)
       de noircir la croix, elle disparaîtrait dans le disque. */

    .mobile-menu__list {
        list-style: none;
        margin: 0;
        padding: 0 var(--wts-space-6);
        flex: 1;
        overflow-y: auto;
    }

    .mobile-menu__item + .mobile-menu__item {
        border-top: 1px solid color-mix(in srgb, var(--wts-color-black) 12%, transparent);
    }

    .mobile-menu__link {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: var(--wts-space-4);
        min-height: 44px;
        padding: var(--wts-space-6) 0;
        color: var(--wts-color-black);
        text-decoration: none;
        font-family: var(--wts-font-display);
        font-size: var(--wts-font-size-2xl);
        transition: color var(--anim-fast) var(--ease-out-soft);
    }

    /* Icônes "en pointillé" (chevrons/flèches façon pixel-art de la maquette Mistral) :
       même tracé arrow-right/arrow-diagonal, juste segmenté en tirets courts via CSS
       (stroke-dasharray), sans dupliquer d'asset SVG. */
    .mobile-menu__link-icon svg,
    .mobile-menu__cta-icon svg {
        stroke-dasharray: 1.5 2.5;
    }

    .mobile-menu__link-icon {
        display: inline-flex;
        flex-shrink: 0;
        color: var(--wts-color-orange);
        transition: transform var(--anim-fast) var(--ease-out-soft);
    }

    .mobile-menu__link:hover,
    .mobile-menu__link:focus-visible {
        color: var(--wts-color-orange);
    }

    .mobile-menu__link:hover .mobile-menu__link-icon,
    .mobile-menu__link:focus-visible .mobile-menu__link-icon {
        transform: translateX(4px);
    }

    /* CTA final pleine largeur, même emplacement que "Try Le Chat" chez Mistral — fond
       orange et flèche noire (plutôt que noir/orange) pour rester dans notre charte. */
    .mobile-menu__cta {
        display: flex;
        align-items: center;
        justify-content: center;
        gap: var(--wts-space-2);
        padding: var(--wts-space-6);
        background-color: var(--wts-color-orange);
        color: var(--wts-color-white);
        text-decoration: none;
        font-weight: 600;
        font-size: var(--wts-font-size-lg);
    }

    .mobile-menu__cta-icon {
        display: inline-flex;
        color: var(--wts-color-black);
    }

    @media (prefers-reduced-motion: reduce) {
        .mobile-menu,
        .mobile-menu__link,
        .mobile-menu__link-icon {
            transition: none;
        }
    }
}

/* 8. Footer (organism).
   Structure reprise à l'identique du footer FWC (cf. en-tête du twig), habillée aux
   couleurs WTS : marque + coordonnées à gauche, deux groupes de liens à droite, bande
   de 4 valeurs pictogrammées, puis barre copyright centrée.

   Le bandeau « Parlons de votre projet » a été retiré (demande utilisateur) : avec lui
   partent le texte géant rogné à mi-hauteur, sa flèche, et le filet qui le traversait
   (l'ancien ::before du footer). Le footer n'a donc plus de bord haut dessiné — c'est la
   marge qui le détache du contenu de la page. */
/* La marge haute était dimensionnée pour le bandeau « Parlons de votre projet », qui
   sortait du footer par le haut : elle lui réservait sa place. Le bandeau parti, il ne
   restait qu'un grand vide — d'où la réduction, ici et sur le padding haut. */
.site-footer {
    position: relative;
    margin-top: clamp(4rem, 7vw, 7rem);
    padding: var(--wts-space-12) var(--wts-space-6) var(--wts-space-8);
}

/* ── Bloc principal : marque à gauche, navigation à droite ── */
.site-footer__main {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--wts-space-16);
    align-items: start;
}

/* Le bloc de marque porte sa propre échelle, et ses icônes sont dimensionnées en `em` :
   un seul réglage commande donc le couple logo + LinkedIn, qui garde son rapport de taille
   quoi qu'il arrive.

   Pourquoi il lui en faut une à lui : le logo fait 562×158, il est donc trois fois et demie
   plus large que haut. Dimensionné en largeur comme les autres icônes, il ne mesure presque
   rien de haut — moins que l'icône LinkedIn posée dessous. L'échelle fluide globale le
   multipliait bien par 1,25 comme le reste, mais partant d'aussi bas, ça ne se voyait pas :
   il restait le seul élément du footer à paraître figé.

   Calé pour ne rien changer jusqu'à 1440px (la borne basse l'emporte), puis monter jusqu'à
   ~128px de large à 2560px, au lieu des 104px du seul facteur global. */
.site-footer__brand {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--wts-space-6);
    font-size: clamp(1rem, 0.3125rem + 0.7143vw, 1.3rem);
}

/* Logo et baseline côte à côte, comme sur FWC. */
.site-footer__brand-head {
    display: flex;
    align-items: center;
    gap: var(--wts-space-4);
}

.site-footer__logo {
    display: inline-flex;
    color: var(--wts-color-orange);
}

/* Dimensionné en HAUTEUR utile, pas en largeur : le logo doit mener le bloc, donc dépasser
   l'icône LinkedIn de 2em posée sous lui. On vise les mêmes ~2,2em de haut qu'avant ; le
   nouveau lockup faisant 3,56 fois sa hauteur (contre 2 pour l'ancien mark), cela donne
   7.8em de large — c'est la même présence visuelle, pas un agrandissement. */
.site-footer__logo .icon {
    width: 7.8em;
    height: auto;
    flex-shrink: 0;
}

.site-footer__logo .icon svg {
    width: 100%;
    height: auto;
    display: block;
}

/* Baseline : accent orange, réservé aux titres par la charte. */
.site-footer__baseline {
    color: var(--wts-color-orange);
    font-weight: 700;
    font-size: var(--wts-font-size-base);
    line-height: 1.3;
    margin: 0;
}

.site-footer__about {
    color: var(--wts-color-silver-80);
    font-size: var(--wts-font-size-sm);
    font-style: italic;
    line-height: 1.6;
    margin: 0;
    max-width: 42ch;
}

.site-footer__socials {
    display: flex;
    align-items: center;
    gap: var(--wts-space-4);
}

/* Couleur plus sourde que le blanc par défaut pour se distinguer du logo, et s'éclaircit
   au survol/focus pour indiquer que c'est cliquable (icône linkedin.svg en currentColor). */
.site-footer__social {
    display: inline-flex;
    color: var(--wts-color-silver-60);
    transition: color var(--anim-fast) var(--ease-out-soft);
}

.site-footer__social:hover,
.site-footer__social:focus-visible {
    color: var(--wts-color-white);
}

@media (prefers-reduced-motion: reduce) {
    .site-footer__social {
        transition: none;
    }
}

.site-footer__social .icon {
    width: 2em;
    height: 2em;
}

.site-footer__social .icon svg {
    width: 100%;
    height: 100%;
}

/* ── Coordonnées : deux colonnes, intitulé orange au-dessus de la valeur ── */
.site-footer__contact {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--wts-space-4) var(--wts-space-6);
    margin: 0;
    width: 100%;
    max-width: 32rem;
}

.site-footer__contact-label {
    color: var(--wts-color-orange);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    font-size: var(--wts-font-size-xs);
    margin: 0 0 var(--wts-space-1);
}

.site-footer__contact-value {
    color: var(--wts-color-silver);
    font-size: var(--wts-font-size-sm);
    line-height: 1.4;
    margin: 0;
}

/* L'élément <address> penche en italique par défaut : on veut ici le même corps de
   texte que les autres coordonnées. */
.site-footer__contact-value address {
    font-style: normal;
}

.site-footer__contact-value a {
    color: var(--wts-color-silver);
    text-decoration: none;
    word-break: break-word;
    transition: color var(--anim-fast) var(--ease-out-soft);
}

.site-footer__contact-value a:hover,
.site-footer__contact-value a:focus-visible {
    color: var(--wts-color-orange);
}

@media (prefers-reduced-motion: reduce) {
    .site-footer__contact-value a {
        transition: none;
    }
}

/* ── Navigation : deux groupes côte à côte, alignés à droite ── */
.site-footer__nav {
    display: flex;
    justify-content: flex-end;
    gap: var(--wts-space-12);
}

.site-footer__nav-title {
    color: var(--wts-color-orange);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    font-size: var(--wts-font-size-sm);
    margin: 0 0 var(--wts-space-4);
}

.site-footer__nav-group ul {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--wts-space-3);
}

.site-footer__nav-group a {
    color: var(--wts-color-silver);
    text-decoration: none;
    text-transform: uppercase;
    font-size: var(--wts-font-size-sm);
    border-bottom: 1px dashed var(--wts-color-silver-60);
    padding-bottom: var(--wts-space-1);
}

.site-footer__nav-group a:hover {
    color: var(--wts-color-orange);
}

/* ── Bande valeurs : quatre pictogrammes légendés, séparés par un filet ── */
.site-footer__values {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--wts-space-8);
    border-top: 1px solid var(--wts-color-silver-20);
    margin-top: var(--wts-space-12);
    padding-top: var(--wts-space-12);
}

.site-footer__value {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: var(--wts-space-3);
}

.site-footer__value-icon {
    display: inline-flex;
    color: var(--wts-color-orange);
}

.site-footer__value-icon .icon {
    width: 2.5rem;
    height: 2.5rem;
}

.site-footer__value-label {
    color: var(--wts-color-silver-80);
    font-size: var(--wts-font-size-xs);
    line-height: 1.4;
}

/* ── Barre copyright ── */
.site-footer__bottom {
    border-top: 1px solid var(--wts-color-silver-20);
    margin-top: var(--wts-space-12);
    padding-top: var(--wts-space-6);
}

.site-footer__copyright {
    color: var(--wts-color-silver-60);
    font-size: var(--wts-font-size-xs);
    text-align: center;
    margin: 0;
}

@media (max-width: 768px) {
    .site-footer__main {
        grid-template-columns: 1fr;
        gap: var(--wts-space-12);
    }

    .site-footer__nav {
        justify-content: flex-start;
    }

    .site-footer__values {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (max-width: 480px) {
    .site-footer__brand-head {
        flex-direction: column;
        align-items: flex-start;
    }

    .site-footer__contact {
        grid-template-columns: 1fr;
    }

    .site-footer__values {
        grid-template-columns: 1fr;
    }
}

