/* ============================== GÉNÉRÉ ==============================
   Ce fichier est concaténé depuis src/styles/portfolio/*.css par
   scripts/build-styles.mjs — NE PAS ÉDITER À LA MAIN.
   ==================================================================== */
:root {
    --white: #ffffff;
    --black: #0a0a0a;
    --dark-1: #0d0d0d;
    --dark-2: #111111;
    --dark-3: #1a1a1a;
    --dark-4: #222222;
    --gray-1: rgba(255, 255, 255, 0.85);
    --gray-2: rgba(255, 255, 255, 0.55);
    --gray-3: rgba(255, 255, 255, 0.35);
    --gray-4: rgba(255, 255, 255, 0.12);
    --gray-5: rgba(255, 255, 255, 0.07);
    --accent: #0066ff;
    --accent-light: #60a5fa;
    /* L'accent porte deux rôles opposés : FOND de bouton (sous du blanc) et
       COULEUR DE TEXTE (sur un aplat). Une seule valeur ne peut pas satisfaire
       les deux — démontré en tâche 6 pour --accent-light, et vérifié à nouveau
       ici pour --accent quand les couloirs l'ont déplacé (.section-label est
       tombé de 5,4:1 à 3,8:1). --accent-texte est la variante LISIBLE, dérivée
       de --accent (jamais saisie à la main) : voir 132-couloirs-jetons.css.
       Hors couloir, elle vaut l'accent lui-même : rien ne change. */
    --accent-texte: var(--accent);
    --green: #4ade80;
    --display: "Syne", sans-serif;
    --mono: "JetBrains Mono", "Courier New", monospace;
    --font: "Onest", sans-serif;
    /* ── L'ÉCHELLE. Six pas, pas un de plus. La page en comptait dix-sept,
       dont dix serrées entre 10 et 19 px : aucun œil ne lit ça comme une
       intention. Un pas = un usage, et l'usage est écrit à côté. ── */
    --pas-affirmation: clamp(2.6rem, 6vw, 4.4rem);   /* le hero, uniquement */
    --pas-titre:       clamp(1.9rem, 3.4vw, 2.6rem); /* titres de section */
    --pas-sous-titre:  1.25rem;                      /* intertitres */
    --pas-lecture:     1.0625rem;                    /* TOUT le corps de texte */
    --pas-appui:       0.875rem;                     /* légendes, notes */
    --pas-etiquette:   0.75rem;                      /* mono capitales */
    /* Le logotype est une marque, pas du texte : il a son échelle propre et
       n'entre pas dans le décompte des six pas. */
    --pas-signature:   clamp(4rem, 12vw, 9rem);
    /* Un chiffre d'affichage (preuve chiffrée d'un case study : "150%",
       "3 sem."…) n'est ni un titre ni du corps de texte : l'enfermer dans
       --pas-titre le gonfle, dans --pas-sous-titre il l'aplatit. Sa propre
       échelle, mesurée sur .case-stat-num (seul élément vivant du site dans
       cette catégorie ; .metric-num en est écarté — voir son commentaire
       dans 025-hero-showcase… — car il est mort et sa plage ne recouvre pas
       celle-ci : les forcer sur un même jeton aurait dégradé l'un des deux
       à toutes les largeurs). */
    --pas-chiffre:     clamp(2.4rem, 3.2vw, 3.4rem);

    /* La mesure de lecture : ~65 caractères. La page montait à 119, quand la
       lisibilité tient entre 45 et 75. */
    --mesure:       34rem;
    --mesure-large: 52rem;   /* listes, tableaux */
    --nav-h: 64px;
    --max-w: 1160px;
    --gap: clamp(1.5rem, 4vw, 3rem);
    --radius: 10px;
    --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
}
*,
::after,
::before {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}
html {
    /* scroll-behavior: auto pour instant snap aux anchors;
       JS gère le smooth scroll plus rapidement que le natif (300ms vs 700ms) */
    scroll-behavior: auto;
    font-size: 16px;
    overflow-x: clip;
}
@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
}
body {
    font-family: var(--font);
    background: var(--black);
    color: var(--gray-1);
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
    overflow-x: clip;
}
img {
    display: block;
    max-width: 100%;
}
a {
    color: inherit;
    text-decoration: none;
}
ol,
ul {
    list-style: none;
}
button {
    cursor: pointer;
    border: none;
    background: 0 0;
    font-family: inherit;
}
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    border: 0;
}
:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
}
#progress-bar {
    display: none;
}
.reveal-wrap {
    overflow: hidden;
    display: block;
}
.reveal-line {
    display: block;
    transform: translateY(110%);
    transition: transform 0.9s var(--ease-out-expo);
}
.reveal-wrap.visible .reveal-line {
    transform: translateY(0);
}
.fade-in {
    opacity: 0;
    transform: translateY(36px);
    transition:
        opacity 0.7s var(--ease-out-expo),
        transform 0.7s var(--ease-out-expo);
    will-change: opacity, transform;
}
.fade-in.visible {
    opacity: 1;
    transform: none;
    will-change: auto;
}
.stagger-1 {
    transition-delay: 0.1s;
}
.stagger-2 {
    transition-delay: 0.2s;
}
.stagger-3 {
    transition-delay: 0.3s;
}
.stagger-4 {
    transition-delay: 0.4s;
}
.cursor-dot {
    position: fixed;
    width: 8px;
    height: 8px;
    background: #fff;
    border-radius: 50%;
    pointer-events: none;
    z-index: 9999;
    transform: translate(-50%, -50%);
    mix-blend-mode: difference;
    will-change: left, top;
}
.cursor-ring {
    position: fixed;
    width: 36px;
    height: 36px;
    border: 1.5px solid rgba(130, 130, 130, 0.4);
    border-radius: 50%;
    pointer-events: none;
    z-index: 9998;
    transform: translate(-50%, -50%);
    transition:
        width 0.3s var(--ease-out-expo),
        height 0.3s var(--ease-out-expo),
        border-color 0.3s,
        background 0.3s;
    will-change: left, top;
}
.cursor-ring.hover {
    width: 56px;
    height: 56px;
    border-color: var(--accent);
    background: rgba(0, 102, 255, 0.07);
}
.cursor-ring.clicked {
    width: 24px;
    height: 24px;
    background: rgba(0, 102, 255, 0.2);
}
@media (hover: none) {
    .cursor-dot,
    .cursor-ring {
        display: none;
    }
}
.side-nav {
    position: fixed;
    right: 1.75rem;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    flex-direction: column;
    gap: 0.875rem;
    z-index: 50;
}
.side-dot {
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background:
        radial-gradient(circle, rgba(255, 255, 255, 0.18) 3.5px, transparent 4px);
    transition:
        background 0.3s,
        transform 0.3s,
        box-shadow 0.3s;
    position: relative;
    display: block;
}
.side-dot::before {
    content: attr(data-label);
    position: absolute;
    right: calc(100% + 0.75rem);
    top: 50%;
    transform: translateY(-50%) translateX(6px);
    font-size: 0.75rem;
    font-weight: 600;
    color: #fff;
    background: var(--dark-4);
    border: 1px solid var(--gray-4);
    padding: 0.3rem 0.65rem;
    border-radius: 4px;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition:
        opacity 0.2s,
        transform 0.2s;
}
.side-dot:hover::before {
    opacity: 1;
    transform: translateY(-50%) translateX(0);
}
.side-dot.active {
    background:
        radial-gradient(circle, var(--accent) 5px, transparent 5.5px);
}
.side-dot.active::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 50%;
    box-shadow: 0 0 12px rgba(0, 102, 255, 0.6);
    pointer-events: none;
}
.side-dot:hover {
    background:
        radial-gradient(circle, var(--accent-light) 4px, transparent 4.5px);
}
@media (max-width: 900px) {
    .side-nav {
        display: none;
    }
}
#header {
    position: fixed;
    /* top:0 — la barre de progression de 3px a été retirée (dé-IA), plus de
       décalage à réserver en haut. */
    top: 0;
    left: 0;
    right: 0;
    height: var(--nav-h);
    background: rgba(10, 10, 10, 0.92);
    border-bottom: 1px solid var(--gray-5);
    z-index: 100;
    transition: box-shadow 0.3s;
}
#header.scrolled {
    box-shadow: 0 4px 30px rgba(0, 0, 0, 0.4);
}
#header nav {
    max-width: var(--max-w);
    margin: 0 auto;
    padding: 0 var(--gap);
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
}
.logo {
    font-size: 1.125rem;
    font-weight: 700;
    letter-spacing: 0.01em;
    font-family: var(--mono);
    color: #fff;
}
.logo-cursor {
    color: var(--accent);
    animation: cursor-blink 0.75s step-end infinite;
}
.nav-links {
    display: flex;
    gap: 2rem;
    align-items: center;
}
.nav-link {
    font-size: 0.875rem;
    font-weight: 500;
    color: var(--gray-3);
    transition: color 0.2s;
    position: relative;
}
.nav-link::after {
    content: "";
    position: absolute;
    bottom: -3px;
    left: 0;
    right: 0;
    height: 1.5px;
    background: var(--accent-light);
    border-radius: 2px;
    transform: scaleX(0);
    transition: transform 0.25s var(--ease-out-expo);
    transform-origin: left;
}
.nav-link.active::after,
.nav-link:hover::after {
    transform: scaleX(1);
}
.nav-link.active,
.nav-link:hover {
    color: #fff;
}
.nav-cta {
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--black);
    background: linear-gradient(135deg, var(--accent-light), var(--green));
    padding: 0.45rem 1.25rem;
    border-radius: 6px;
    transition:
        opacity 0.2s,
        transform 0.2s;
}
.nav-cta:hover {
    opacity: 0.85;
    transform: translateY(-1px);
}
.hamburger {
    display: none;
    flex-direction: column;
    align-items: center;
    justify-content: center;  /* les 3 traits centrés DANS le bouton (44px cible
                                 tactile) — sinon flex-start les colle en haut, décalés du logo */
    gap: 5px;
    padding: 4px;
    position: relative;
    z-index: 101;
}
.logo {
    position: relative;
    z-index: 101;
}
.hamburger-line {
    width: 22px;
    height: 2px;
    background: var(--gray-2);
    border-radius: 2px;
    transition: all 0.3s;
}
.hamburger.open .hamburger-line {
    background: #f8fafc;
}
.hamburger.open .hamburger-line:first-child {
    transform: translateY(7px) rotate(45deg);
}
.hamburger.open .hamburger-line:nth-child(2) {
    opacity: 0;
}
.hamburger.open .hamburger-line:nth-child(3) {
    transform: translateY(-7px) rotate(-45deg);
}
.container {
    width: 100%;
    max-width: var(--max-w);
    margin: 0 auto;
    padding: 0 var(--gap);
}
section {
    padding: clamp(5rem, 10vw, 8rem) 0;
}
.section-label {
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--gray-3);
    margin-bottom: 0.875rem;
}
.section-title {
    font-size: clamp(2rem, 5vw, 3.25rem);
    font-weight: 800;
    letter-spacing: -0.03em;
    line-height: 1.1;
    color: var(--white);
    margin-bottom: 1.25rem;
}
.section-sub {
    font-size: 1.0625rem;
    color: var(--gray-2);
    line-height: 1.7;
}
#home {
    min-height: 100vh;
    display: flex;
    align-items: center;
    padding-top: var(--nav-h);
    background-color: var(--black);
    background-image: radial-gradient(rgba(255, 255, 255, 0.032) 1px, transparent 1px);
    background-size: 28px 28px;
    position: relative;
    overflow: hidden;
}
#home::before {
    content: "";
    position: absolute;
    top: -180px;
    left: -120px;
    width: 680px;
    height: 680px;
    border-radius: 50%;
    background: radial-gradient(
        circle,
        rgba(0, 102, 255, 0.28) 0,
        rgba(0, 102, 255, 0.05) 55%,
        transparent 75%
    );
    
    pointer-events: none;
    z-index: 0;
}
#home::after {
    content: "";
    position: absolute;
    bottom: -80px;
    right: -80px;
    width: 520px;
    height: 520px;
    border-radius: 50%;
    background: radial-gradient(
        circle,
        rgba(74, 222, 128, 0.22) 0,
        rgba(0, 102, 255, 0.05) 55%,
        transparent 75%
    );
    
    pointer-events: none;
    z-index: 0;
}
@keyframes blob-drift {
    0%,
    100% {
        transform: translate(0, 0) scale(1);
    }
    33% {
        transform: translate(70px, 50px) scale(1.08);
    }
    66% {
        transform: translate(-45px, 80px) scale(0.93);
    }
}
.hero-inner {
    max-width: var(--max-w);
    margin: 0 auto;
    padding: 0 var(--gap);
    width: 100%;
    position: relative;
    z-index: 1;
    display: grid;
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 0.85fr);
    gap: clamp(2rem, 4vw, 4rem);
    align-items: center;
}
.hero-content,
.hero-visual { min-width: 0; }
.hero-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.8125rem;
    font-weight: 500;
    color: var(--gray-3);
    border: 1px solid var(--gray-4);
    background: rgba(255, 255, 255, 0.04);
    border-radius: 6px;
    padding: 0.35rem 1rem;
    margin-bottom: 2rem;
}
.hero-badge-dot {
    width: 7px;
    height: 7px;
    background: #22c55e;
    border-radius: 50%;
    animation: blink 2s infinite;
}
@keyframes blink {
    0%,
    100% {
        opacity: 1;
    }
    50% {
        opacity: 0.35;
    }
}
.hero-dual {
    display: flex;
    gap: 0.75rem;
    align-items: center;
    margin-bottom: 1.75rem;
    flex-wrap: wrap;
}
.hero-dual-tag {
    font-size: 0.875rem;
    font-weight: 700;
    padding: 0.45rem 1.1rem;
    border-radius: 6px;
}
.hero-dual-tag.code {
    background: rgba(74, 222, 128, 0.1);
    color: var(--green);
    font-family: var(--mono);
    border: 1px solid rgba(74, 222, 128, 0.22);
}
.hero-dual-tag.design {
    background: rgba(0, 102, 255, 0.15);
    color: #93c5fd;
    border: 1px solid rgba(0, 102, 255, 0.28);
}
.hero-dual-sep {
    font-size: 1.5rem;
    color: rgba(255, 255, 255, 0.15);
    font-weight: 200;
}
.hero-title {
    font-size: clamp(3rem, 8.5vw, 7rem);
    font-weight: 900;
    letter-spacing: -0.045em;
    line-height: 0.97;
    color: #fff;
    margin-bottom: 1.75rem;
}
.typewriter-wrap {
    display: inline-block;
    min-width: 4ch;
}
.typewriter-text {
    color: var(--accent-light, #60a5fa);
    }
.typewriter-cursor {
    display: inline-block;
    width: 3px;
    height: 0.82em;
    background: var(--green);
    margin-left: 3px;
    vertical-align: baseline;
    animation: cursor-blink 0.75s step-end infinite;
    border-radius: 1px;
    transform: translateY(0.05em);
}
@keyframes cursor-blink {
    0%,
    100% {
        opacity: 1;
    }
    50% {
        opacity: 0;
    }
}
.hero-sub {
    font-size: clamp(1rem, 1.8vw, 1.2rem);
    color: var(--gray-2);
    line-height: 1.75;
    margin-bottom: 3rem;
}
.hero-actions {
    display: flex;
    gap: 1rem;
    flex-wrap: wrap;
    align-items: center;
    margin-bottom: 4rem;
}
.btn-primary {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.9375rem;
    font-weight: 700;
    color: var(--black);
    background: linear-gradient(135deg, var(--accent-light), var(--green));
    padding: 0.875rem 2rem;
    border-radius: 6px;
    transition:
        opacity 0.25s,
        transform 0.25s,
        box-shadow 0.35s;
}
.btn-primary:hover {
    opacity: 0.9;
    transform: translateY(-2px);
    box-shadow: none;
}
.btn-primary svg {
    width: 16px;
    height: 16px;
    transition: transform 0.3s;
}
.btn-primary:hover svg {
    transform: translateX(3px);
}
.btn-secondary {
    display: inline-flex;
    align-items: center;
    font-size: 0.9375rem;
    font-weight: 600;
    color: var(--gray-2);
    padding: 0.875rem 0;
    border-bottom: 1.5px solid var(--gray-4);
    transition:
        color 0.2s,
        border-color 0.2s;
}
.btn-secondary:hover {
    color: #fff;
    border-color: rgba(255, 255, 255, 0.5);
}
.hero-stats {
    display: flex;
    gap: 3rem;
    flex-wrap: wrap;
    padding-top: 2.5rem;
    border-top: 1px solid var(--gray-5);
}
.hero-stat-num {
    font-size: 2rem;
    font-weight: 800;
    letter-spacing: -0.03em;
    color: #fff;
}
.hero-stat-label {
    font-size: 0.8125rem;
    color: var(--gray-3);
    margin-top: 0.125rem;
}
.scroll-indicator {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    margin-top: 3rem;
    color: rgba(255, 255, 255, 0.18);
    animation: scroll-bounce 2s ease-in-out infinite;
}
.scroll-indicator svg {
    width: 20px;
    height: 20px;
}
.scroll-indicator span {
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
}
@keyframes scroll-bounce {
    0%,
    100% {
        transform: translateY(0);
    }
    50% {
        transform: translateY(8px);
    }
}
.marquee-section {
    overflow: hidden;
    background: var(--dark-2);
    border-top: 1px solid var(--gray-5);
    border-bottom: 1px solid var(--gray-5);
    padding: 1.5rem 0;
    position: relative;
}
.marquee-section::after,
.marquee-section::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    width: 140px;
    z-index: 2;
    pointer-events: none;
}
.marquee-section::before {
    left: 0;
    background: linear-gradient(to right, var(--dark-2), transparent);
}
.marquee-section::after {
    right: 0;
    background: linear-gradient(to left, var(--dark-2), transparent);
}
.marquee-track {
    display: flex;
    gap: 1rem;
    width: max-content;
    animation: marquee-scroll 35s linear infinite;
}
.marquee-track:hover {
    animation-play-state: paused;
}
.marquee-img {
    height: 120px;
    width: auto;
    border-radius: 6px;
    object-fit: cover;
    flex-shrink: 0;
    opacity: 0.7;
    filter: grayscale(15%);
    transition:
        transform 0.4s var(--ease-out-expo),
        opacity 0.4s,
        filter 0.4s;
}
.marquee-img:hover {
    transform: scale(1.07) translateY(-2px);
    opacity: 1;
    filter: none;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
}
@keyframes marquee-scroll {
    0% {
        transform: translateX(0);
    }
    100% {
        transform: translateX(-50%);
    }
}
#about {
    /* Harmonisation : --dark-2 → --dark-1 pour réduire à 2 nuances. */
    background: var(--dark-1);
}
.about-grid {
    display: grid;
    grid-template-columns: minmax(260px, 0.9fr) minmax(0, 1.2fr);
    gap: clamp(3rem, 6vw, 6rem);
    align-items: start;
}
@media (min-width: 900px) {
    .about-img-wrap {
        position: sticky;
        top: 6rem;
    }
}
.about-img-wrap {
    position: relative;
    aspect-ratio: 4/5;
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--dark-3);
    box-shadow:
        0 20px 60px rgba(0, 0, 0, 0.5),
        0 0 0 1px var(--gray-5);
}
.about-img-wrap img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: top;
    transition: transform 0.8s var(--ease-out-expo);
}
.about-img-wrap:hover img {
    transform: scale(1.03);
}
.about-img-badge {
    position: absolute;
    bottom: 1rem;
    left: 1rem;
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
}
.about-img-badge span {
    font-size: 0.75rem;
    font-weight: 700;
    padding: 0.3rem 0.75rem;
    border-radius: 6px;
}
.about-img-badge .b-dev {
    background: rgba(74, 222, 128, 0.15);
    color: var(--green);
    font-family: var(--mono);
    border: 1px solid rgba(74, 222, 128, 0.25);
    backdrop-filter: blur(8px);
}
.about-img-badge .b-design {
    background: rgba(0, 102, 255, 0.18);
    color: #93c5fd;
    backdrop-filter: blur(8px);
    border: 1px solid rgba(0, 102, 255, 0.3);
}
.about-content .section-title {
    margin-bottom: 1.25rem;
}
.about-text {
    font-size: 1rem;
    color: var(--gray-2);
    line-height: 1.8;
    margin-bottom: 1.25rem;
}
.about-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 2rem;
}
/* Liste inline éditoriale (dé-IA) : fini le mur de chips en boîtes —
   texte mono séparé par des puces, comme un colophon de magazine. */
.about-tag {
    font-family: var(--mono);
    font-size: 0.8rem;
    font-weight: 500;
    letter-spacing: 0.02em;
    color: var(--gray-2);
    transition: color 0.2s;
}
.about-tag:not(:last-child)::after {
    content: "·";
    margin-left: 0.5rem;
    color: var(--gray-4);
}
.about-tag:hover {
    color: #93c5fd;
}
#work {
    background: var(--dark-1);
}
.work-header {
    margin-bottom: 2.5rem;
}
.bento-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: 280px 280px 220px;
    grid-auto-rows: 220px;
    gap: 0.875rem;
}
.bento-card {
    position: relative;
    overflow: hidden;
    border-radius: var(--radius);
    border: 1px solid var(--gray-5);
    display: block;
    color: inherit;
    text-decoration: none;
    background: var(--dark-3);
    transition:
        box-shadow 0.4s ease,
        border-color 0.4s;
    transform-style: preserve-3d;
    will-change: transform;
}
.bento-card:hover {
    box-shadow: 0 28px 70px rgba(0, 0, 0, 0.6);
    border-color: var(--gray-4);
}
.bento-card img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform 0.6s var(--ease-out-expo);
    display: block;
}
.bento-card:hover img {
    transform: scale(1.07);
}
.bento-card--featured {
    grid-column: 1/3;
    grid-row: 1/3;
}
.bento-card--wide {
    grid-column: 2/4;
}
.bento-overlay {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    padding: 1.5rem 1.25rem 1.25rem;
    background: linear-gradient(
        to top,
        rgba(0, 0, 0, 0.93) 0,
        rgba(0, 0, 0, 0.35) 55%,
        transparent 100%
    );
    color: #fff;
    transform: translateY(6px);
    opacity: 0.9;
    transition:
        transform 0.4s var(--ease-out-expo),
        opacity 0.4s;
}
.bento-card:hover .bento-overlay {
    transform: translateY(0);
    opacity: 1;
}
.bento-overlay-top {
    position: absolute;
    top: 1rem;
    right: 1rem;
}
.bento-num {
    font-size: 0.7rem;
    font-weight: 700;
    color: rgba(255, 255, 255, 0.4);
    letter-spacing: 0.08em;
    display: block;
    margin-bottom: 0.35rem;
}
.bento-title {
    font-size: 1.0625rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    line-height: 1.3;
    margin-bottom: 0.25rem;
}
.bento-card--featured .bento-title {
    font-size: 1.5rem;
}
.bento-desc {
    font-size: 0.8125rem;
    color: rgba(255, 255, 255, 0.65);
    line-height: 1.4;
}
.bento-badge {
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    padding: 0.3rem 0.75rem;
    border-radius: 6px;
    backdrop-filter: blur(12px);
}
.bento-badge.design {
    background: rgba(0, 102, 255, 0.45);
    color: #bfdbfe;
    border: 1px solid rgba(0, 102, 255, 0.55);
}
.bento-badge.dev {
    background: rgba(74, 222, 128, 0.45);
    color: #bbf7d0;
    border: 1px solid rgba(74, 222, 128, 0.5);
}
#skills {
    /* Harmonisation : --dark-2 → --dark-1. */
    background: var(--dark-1);
}
.skills-intro {
    margin-bottom: 3rem;
}
.skills-dual {
    display: grid;
    grid-template-columns: 1fr 1fr;
    border-radius: var(--radius);
    overflow: hidden;
    border: 1px solid var(--gray-5);
    box-shadow: 0 8px 40px rgba(0, 0, 0, 0.3);
}
.skills-dev {
    background: #0a0a14;
    padding: 2.5rem;
}
.skills-dev-label {
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--green);
    font-family: var(--mono);
    margin-bottom: 1.75rem;
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.skills-dev-label::before {
    content: "//";
    color: rgba(255, 255, 255, 0.15);
}
.dev-skill-group {
    margin-bottom: 1.75rem;
}
.dev-skill-group:last-child {
    margin-bottom: 0;
}
.dev-skill-group-title {
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.22);
    font-family: var(--mono);
    margin-bottom: 0.75rem;
}
.dev-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}
.dev-tag {
    font-size: 0.8rem;
    font-weight: 500;
    color: rgba(255, 255, 255, 0.65);
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 4px;
    padding: 0.275rem 0.625rem;
    font-family: var(--mono);
    transition:
        border-color 0.2s,
        color 0.2s,
        transform 0.2s,
        box-shadow 0.2s;
}
.dev-tag:hover {
    border-color: var(--green);
    color: var(--green);
    transform: translateY(-2px);
    box-shadow: none;
}
.skills-design {
    background: #0a100a;
    padding: 2.5rem;
    border-left: 1px solid var(--gray-5);
}
.skills-design-label {
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--accent-light);
    margin-bottom: 1.75rem;
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.skills-design-label::before {
    content: "[ ";
    color: rgba(255, 255, 255, 0.15);
}
.skills-design-label::after {
    content: " ]";
    color: rgba(255, 255, 255, 0.15);
}
.design-skill-group {
    margin-bottom: 1.75rem;
}
.design-skill-group:last-child {
    margin-bottom: 0;
}
.design-skill-group-title {
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.22);
    margin-bottom: 0.75rem;
}
.design-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}
.design-tag {
    font-size: 0.875rem;
    font-weight: 500;
    color: rgba(255, 255, 255, 0.65);
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 4px;
    padding: 0.275rem 0.625rem;
    transition:
        border-color 0.2s,
        color 0.2s,
        transform 0.2s,
        box-shadow 0.2s;
}
.design-tag:hover {
    border-color: var(--accent-light);
    color: #93c5fd;
    transform: translateY(-2px);
    box-shadow: none;
}
#contact {
    background: var(--black);
    background-image: radial-gradient(rgba(255, 255, 255, 0.025) 1px, transparent 1px);
    background-size: 28px 28px;
    position: relative;
    overflow: hidden;
}
#contact::before {
    content: "";
    position: absolute;
    bottom: -150px;
    left: 30%;
    width: 500px;
    height: 500px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(0, 102, 255, 0.16) 0, transparent 70%);
    pointer-events: none;
    animation: blob-drift 20s ease-in-out infinite;
}
.contact-layout {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: clamp(3rem, 6vw, 6rem);
    margin-top: 3rem;
}
.contact-info {
    display: flex;
    flex-direction: column;
    gap: 2.5rem;
}
.contact-item-label {
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--gray-3);
    margin-bottom: 0.5rem;
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.contact-item-val {
    font-size: 1.0625rem;
    font-weight: 500;
    color: var(--gray-1);
    transition: color 0.2s;
    display: inline-block;
}
.contact-item-val:hover {
    color: #93c5fd;
}
.contact-socials {
    display: flex;
    gap: 1.25rem;
    margin-top: 1rem;
    flex-wrap: wrap;
}
.social-link {
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--gray-3);
    border-bottom: 1.5px solid transparent;
    transition:
        color 0.2s,
        border-color 0.2s;
    padding-bottom: 2px;
}
.social-link:hover {
    color: #fff;
    border-color: rgba(255, 255, 255, 0.4);
}
.contact-form {
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
}
.form-group {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.form-group label {
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--gray-3);
}
.form-group input,
.form-group textarea {
    font-family: var(--font);
    font-size: 0.9375rem;
    color: #fff;
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid var(--gray-4);
    border-radius: 6px;
    padding: 0.875rem 1rem;
    width: 100%;
    transition:
        border-color 0.25s,
        box-shadow 0.25s;
}
.form-group input::placeholder,
.form-group textarea::placeholder {
    color: rgba(255, 255, 255, 0.2);
}
.form-group input:focus,
.form-group textarea:focus {
    outline: 0;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.15);
}
.form-group textarea {
    resize: vertical;
    min-height: 140px;
}
.btn-submit {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.9375rem;
    font-weight: 700;
    color: var(--black);
    background: linear-gradient(135deg, var(--accent-light), var(--green));
    border: none;
    border-radius: 6px;
    padding: 0.875rem 2rem;
    cursor: pointer;
    transition:
        opacity 0.2s,
        transform 0.2s,
        box-shadow 0.3s;
    width: 100%;
}
.btn-submit:hover {
    opacity: 0.88;
    transform: translateY(-2px);
    box-shadow: none;
}
.form-status {
    font-size: 0.875rem;
    color: var(--gray-3);
    min-height: 1.25rem;
}
footer {
    border-top: 1px solid var(--gray-5);
    padding: 2rem 0;
    background: var(--black);
}
.footer-inner {
    max-width: var(--max-w);
    margin: 0 auto;
    padding: 0 var(--gap);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1.5rem;
    flex-wrap: wrap;
}
.footer-copy {
    font-size: 0.8125rem;
    color: var(--gray-3);
}
.footer-socials {
    display: flex;
    gap: 1.5rem;
}
.footer-social-link {
    font-size: 0.8125rem;
    font-weight: 500;
    color: var(--gray-3);
    transition: color 0.2s;
}
.footer-social-link:hover {
    color: #fff;
}
.hero-visual {
    position: relative;
    height: 440px;
    display: flex;
    align-items: center;
    justify-content: center;
}
.hero-win {
    position: absolute;
    border-radius: 10px;
    border: 1px solid rgba(255, 255, 255, 0.08);
    overflow: hidden;
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);
}
.hero-win-code {
    width: 300px;
    top: 20px;
    left: 0;
    background: #0d1117;
    animation: float-bob-code 5s ease-in-out infinite;
    z-index: 1;
}
.hero-win-design {
    width: 260px;
    bottom: 20px;
    right: 0;
    background: #111827;
    animation: float-bob-design 4s ease-in-out infinite 2.5s;
    z-index: 2;
}
.hero-win-bar {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 10px 14px;
    background: rgba(255, 255, 255, 0.04);
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.wdot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    flex-shrink: 0;
}
.wd-r {
    background: #ff5f57;
}
.wd-y {
    background: #febc2e;
}
.wd-g {
    background: #28c840;
}
.hero-win-name {
    font-size: 0.7rem;
    color: rgba(255, 255, 255, 0.3);
    font-family: var(--mono);
    margin-left: 8px;
}
.hero-code-body {
    padding: 14px 16px;
    font-family: var(--mono);
    font-size: 0.75rem;
    line-height: 1.9;
}
.hc-line {
    color: rgba(255, 255, 255, 0.65);
}
.hc-i {
    padding-left: 16px;
}
.hc-kw {
    color: #c792ea;
}
.hc-fn {
    color: #82aaff;
}
.hc-var {
    color: #f07178;
}
.hc-str {
    color: #c3e88d;
}
.hc-p {
    color: rgba(255, 255, 255, 0.4);
}
.hc-op {
    color: #89ddff;
}
.hero-design-body {
    padding: 14px 16px;
}
.hd-palette {
    display: flex;
    gap: 6px;
    margin-bottom: 14px;
}
.hd-swatch {
    width: 26px;
    height: 26px;
    border-radius: 6px;
}
.hd-shapes {
    display: flex;
    gap: 10px;
    align-items: center;
    margin-bottom: 14px;
}
.hd-circle {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background: linear-gradient(135deg, #60a5fa, #4ade80);
}
.hd-rect {
    width: 38px;
    height: 26px;
    border-radius: 4px;
    background: #06f;
}
.hd-tri {
    width: 0;
    height: 0;
    border-left: 14px solid transparent;
    border-right: 14px solid transparent;
    border-bottom: 26px solid #f59e0b;
}
.hd-typo {
    border-top: 1px solid rgba(255, 255, 255, 0.06);
    padding-top: 10px;
    display: flex;
    align-items: baseline;
    gap: 8px;
}
.hd-typo-big {
    font-size: 1.5rem;
    font-weight: 800;
    color: #fff;
    letter-spacing: -0.04em;
}
.hd-typo-small {
    font-size: 0.65rem;
    color: rgba(255, 255, 255, 0.3);
    font-family: var(--mono);
}
.hero-badge-float {
    position: absolute;
    font-size: 0.7rem;
    font-weight: 700;
    padding: 0.35rem 0.875rem;
    border-radius: 6px;
    backdrop-filter: blur(12px);
    z-index: 3;
}
.hbf-1 {
    top: 22%;
    right: -10px;
    background: rgba(74, 222, 128, 0.15);
    color: var(--green);
    border: 1px solid rgba(74, 222, 128, 0.3);
    animation: float-bob 3.5s ease-in-out infinite 1s;
}
.hbf-2 {
    bottom: 26%;
    left: -10px;
    background: rgba(0, 102, 255, 0.15);
    color: var(--accent-light);
    border: 1px solid rgba(0, 102, 255, 0.3);
    animation: float-bob 4.5s ease-in-out infinite 0.5s;
}
.hbf-3 {
    top: -15px;
    left: 44%;
    background: rgba(245, 158, 11, 0.15);
    color: #fbbf24;
    border: 1px solid rgba(245, 158, 11, 0.3);
    animation: float-bob 5s ease-in-out infinite 3s;
}
@keyframes float-bob-code {
    0%,
    100% {
        transform: rotate(-2deg) translateY(0);
    }
    50% {
        transform: rotate(-2deg) translateY(-8px);
    }
}
@keyframes float-bob-design {
    0%,
    100% {
        transform: rotate(1.5deg) translateY(0);
    }
    50% {
        transform: rotate(1.5deg) translateY(-8px);
    }
}
@keyframes float-bob {
    0%,
    100% {
        transform: translateY(0);
    }
    50% {
        transform: translateY(-8px);
    }
}
.tech-stack {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(78px, 1fr));
    gap: 0.75rem;
    margin-bottom: 3rem;
    align-items: stretch;
}
.tech-icon-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    padding: 0.875rem;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: 8px;
    min-width: 64px;
    cursor: default;
    transition:
        background 0.2s,
        transform 0.25s var(--ease-out-expo),
        border-color 0.2s;
}
.tech-icon-item:hover {
    background: rgba(255, 255, 255, 0.08);
    transform: translateY(-2px);
    border-color: rgba(255, 255, 255, 0.14);
}
.tech-icon-item img {
    width: 36px;
    height: 36px;
    object-fit: contain;
    pointer-events: none;
}
.tech-icon-item span {
    font-size: 0.65rem;
    color: rgba(255, 255, 255, 0.4);
    font-family: var(--mono);
    white-space: nowrap;
}
.tech-sep {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    justify-content: center;
    color: rgba(255, 255, 255, 0.18);
    font-size: 1rem;
    font-weight: 200;
    margin: 0.5rem 0;
    position: relative;
}
.tech-sep::before,
.tech-sep::after {
    content: "";
    flex: 1;
    height: 1px;
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.08), transparent);
    margin: 0 1rem;
}
.c-icon {
    width: 14px;
    height: 14px;
    flex-shrink: 0;
    opacity: 0.55;
}
.s-icon {
    width: 14px;
    height: 14px;
    flex-shrink: 0;
}
.social-link {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
}
.footer-social-link {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
}
@media (max-width: 900px) {
    .hero-inner {
        grid-template-columns: 1fr;
        gap: 2.5rem;
    }
    .hero-visual {
        height: 360px;
        justify-content: center;
    }
    .hero-win-code {
        width: 240px;
        left: 5px;
    }
    .hero-win-design {
        width: 210px;
        right: 5px;
    }
    .bento-grid {
        grid-template-columns: 1fr 1fr;
        grid-template-rows: 240px 240px 240px;
        grid-auto-rows: 240px;
    }
    .bento-card--featured {
        grid-column: 1/3;
        grid-row: 1/2;
    }
    .bento-card--wide {
        grid-column: 1/3;
    }
    .skills-dual {
        grid-template-columns: 1fr;
    }
    .skills-design {
        border-left: none;
        border-top: 1px solid var(--gray-5);
    }
}
@media (max-width: 768px) {
    .hero-visual {
        display: block;
        width: 100%;
        margin-top: 1.5rem;
        position: relative;
        transform: none !important;
    }
    .hero-showcase {
        grid-template-columns: 1fr !important;
        grid-template-areas:
            "status"
            "testimonial" !important;
        grid-template-rows: auto auto !important;
        gap: 0.85rem !important;
        padding: 0 !important;
        max-width: 100%;
    }
    .nav-links {
        display: flex !important;
        flex-direction: column;
        gap: 0;
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        width: 100vw;
        height: 100vh;
        height: 100dvh;
        background: linear-gradient(160deg, #050811, #0a0e1a);
        /* backdrop-filter retiré (charte : pas de blur sur élément animé) ;
           le dégradé sombre quasi-opaque suffit. */
        padding: calc(var(--nav-h) + 3rem) 1.5rem 2rem;
        justify-content: flex-start;
        align-items: stretch;
        z-index: 99;
        opacity: 0;
        pointer-events: none;
        /* visibility:hidden retire le menu fermé de l'ordre de tabulation et des
           lecteurs d'écran (sinon liens fantômes focusables/annoncés). */
        visibility: hidden;
        transform: translateY(-12px);
        transition: opacity 0.4s var(--ease-out-expo), transform 0.5s var(--ease-out-expo), visibility 0s linear 0.5s;
        overflow-y: auto;
    }
    .nav-links.open {
        opacity: 1;
        pointer-events: auto;
        visibility: visible;
        transform: translateY(0);
        transition: opacity 0.4s var(--ease-out-expo), transform 0.5s var(--ease-out-expo), visibility 0s;
    }
    .nav-link, .nav-cta {
        opacity: 0;
        transform: translateY(14px);
        transition: opacity 0.5s var(--ease-out-expo), transform 0.5s var(--ease-out-expo);
    }
    .nav-links.open .nav-link,
    .nav-links.open .nav-cta {
        opacity: 1;
        transform: translateY(0);
    }
    .nav-links.open .nav-link:nth-child(1) { transition-delay: 0.08s; }
    .nav-links.open .nav-link:nth-child(2) { transition-delay: 0.14s; }
    .nav-links.open .nav-link:nth-child(3) { transition-delay: 0.20s; }
    .nav-links.open .nav-cta { transition-delay: 0.28s; }

    .nav-link {
        font-family: 'Syne', system-ui, sans-serif;
        font-size: 2.4rem;
        font-weight: 700;
        letter-spacing: -0.02em;
        padding: 0.65rem 0;
        border-bottom: 1px solid rgba(255, 255, 255, 0.06);
        width: 100%;
        text-align: left;
        position: relative;
        color: rgba(255, 255, 255, 0.92);
    }
    .nav-link::before {
        content: counter(nav-step, decimal-leading-zero) "  ";
        font-family: 'JetBrains Mono', monospace;
        font-size: 0.72rem;
        color: var(--accent);
        position: absolute;
        right: 0;
        top: 1.2rem;
        font-weight: 500;
        letter-spacing: 0.1em;
        opacity: 0.7;
    }
    .nav-links { counter-reset: nav-step; }
    .nav-link { counter-increment: nav-step; }
    .nav-link:active {
        color: var(--accent);
        transform: translateX(8px);
    }
    .nav-cta {
        margin-top: auto;
        padding: 1.1rem 1.5rem !important;
        border-radius: 6px !important;
        text-align: center;
        font-size: 1rem;
        font-weight: 600;
        background: linear-gradient(115deg, var(--accent), var(--accent-light)) !important;
        /* PAS de box-shadow. Ce bouton portait ici un halo de 40px que le
           balayage de bureau ne pouvait pas voir : la règle ne vit que sous
           768px, donc il fallait ouvrir le menu burger pour le rencontrer.
           Il a d'abord été teint en accent, puis éteint — le MÊME bouton
           n'a aucune ombre au-dessus de 768px, il n'y a pas de raison qu'il
           luise en dessous, et la charte bannit les halos. Le module 100
           nomme la famille et garde la porte fermée. */
    }
    /* Sub-label "navigation" en haut */
    .nav-links::before {
        content: 'NAVIGATION';
        font-family: 'JetBrains Mono', monospace;
        font-size: 0.7rem;
        color: rgba(255, 255, 255, 0.4);
        letter-spacing: 0.18em;
        margin-bottom: 1.5rem;
        opacity: 0;
        transition: opacity 0.5s var(--ease-out-expo) 0.05s;
    }
    .nav-links.open::before { opacity: 1; }

    /* Footer mini avec le malagasy easter egg */
    .nav-links::after {
        content: 'Tongasoa · Antananarivo';
        font-family: 'JetBrains Mono', monospace;
        font-size: 0.7rem;
        color: rgba(255, 255, 255, 0.3);
        letter-spacing: 0.05em;
        margin-top: 1.5rem;
        text-align: center;
        opacity: 0;
        transition: opacity 0.5s var(--ease-out-expo) 0.4s;
    }
    .nav-links.open::after { opacity: 1; }

    /* Bloque le scroll sous le menu quand ouvert */
    body:has(.nav-links.open) {
        overflow: hidden;
    }
    /* Header bg solide quand menu mobile ouvert
       (sinon les watermarks/contenu se voient à travers le header transparent) */
    body:has(.nav-links.open) header {
        background: linear-gradient(160deg, #050811, #0a0e1a);
        border-bottom-color: transparent;
    }
    .hamburger {
        display: flex;
    }
    .about-grid {
        grid-template-columns: 1fr;
    }
    .about-img-wrap {
        max-width: 300px;
        aspect-ratio: 1/1;
    }
    .bento-grid {
        grid-template-columns: 1fr;
        grid-template-rows: auto;
    }
    .bento-card {
        height: 240px;
    }
    .bento-card--featured {
        grid-column: 1;
        grid-row: auto;
        height: 300px;
    }
    .bento-card--wide {
        grid-column: 1;
    }
    .contact-layout {
        grid-template-columns: 1fr;
    }
    .hero-stats {
        gap: 2rem;
    }
    .scroll-indicator {
        display: none;
    }
}
@media (max-width: 480px) {
    .hero-title {
        font-size: clamp(2.5rem, 13vw, 4rem);
    }
    .hero-stats {
        flex-direction: column;
        gap: 1.5rem;
    }
    .hero-dual {
        gap: 0.5rem;
    }
}
.grain {
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 9997;
    opacity: 0.04;
    background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.75' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
.hero-title {
    font-family: var(--display);
    font-weight: 800;
    letter-spacing: -0.06em;
}
.section-title {
    font-family: var(--display);
    font-weight: 800;
}
.hero-stat-num {
    font-family: var(--display);
}
.logo {
    font-family: var(--display);
    letter-spacing: -0.04em;
    font-size: 1.2rem;
}
.hero-watermark {
    position: absolute;
    bottom: 5%;
    left: 50%;
    transform: translateX(-50%);
    font-family: var(--display);
    font-size: clamp(3rem, 11vw, 8.5rem);
    font-weight: 800;
    letter-spacing: -0.08em;
    color: transparent;
    -webkit-text-stroke: 1px rgba(255, 255, 255, 0.038);
    pointer-events: none;
    user-select: none;
    z-index: 0;
    white-space: nowrap;
    line-height: 1;
}
.section-label {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    letter-spacing: 0.16em;
    color: var(--gray-3);
}
.section-label::before { display: none; }
.skill-levels {
    display: flex;
    flex-direction: column;
    gap: 1.5rem;
    margin-bottom: 2.5rem;
}
.skill-level-group {
    display: flex;
    align-items: flex-start;
    gap: 1.25rem;
}
.skill-level-label {
    font-size: 0.65rem;
    font-weight: 700;
    font-family: var(--mono);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    padding: 0.3rem 0.7rem;
    border-radius: 6px;
    white-space: nowrap;
    flex-shrink: 0;
    margin-top: 0.1rem;
}
.level-advanced {
    background: rgba(34, 197, 94, 0.12);
    color: #22c55e;
    border: 1px solid rgba(34, 197, 94, 0.25);
}
.level-current {
    background: rgba(96, 165, 250, 0.1);
    color: #60a5fa;
    border: 1px solid rgba(96, 165, 250, 0.2);
}
.level-learning {
    background: rgba(168, 85, 247, 0.08);
    color: #a855f7;
    border: 1px solid rgba(168, 85, 247, 0.18);
}
.skill-level-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}
.skill-tag {
    font-size: 0.75rem;
    font-weight: 500;
    padding: 0.3rem 0.75rem;
    border-radius: 6px;
    font-family: var(--mono);
}
.skill-tag.tag-dev {
    background: rgba(255, 255, 255, 0.04);
    color: rgba(255, 255, 255, 0.7);
    border: 1px solid rgba(255, 255, 255, 0.08);
}
.skill-tag.tag-design {
    background: rgba(96, 165, 250, 0.06);
    color: rgba(147, 197, 253, 0.85);
    border: 1px solid rgba(96, 165, 250, 0.12);
}
.bento-card:hover {
    box-shadow:
        0 28px 70px rgba(0, 0, 0, 0.65),
        0 0 0 1px rgba(96, 165, 250, 0.12);
    border-color: rgba(96, 165, 250, 0.15);
}
.btn-primary:hover {
    box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.12);
}
.about-stats {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1.5rem;
    margin-top: 2.25rem;
    padding-top: 2rem;
    border-top: 1px solid var(--gray-5);
}
.about-stat-val {
    font-size: 1.75rem;
    font-weight: 800;
    font-family: var(--display);
    letter-spacing: -0.04em;
    color: var(--white);
    line-height: 1;
}
.about-stat-label {
    font-size: 0.75rem;
    color: var(--gray-3);
    margin-top: 0.3rem;
}
.hero-badge-dot {
    box-shadow: 0 0 8px rgba(34, 197, 94, 0.5);
}
@media (max-width: 768px) {
    .skill-bars {
        grid-template-columns: 1fr;
    }
    .hero-watermark {
        font-size: clamp(2rem, 8vw, 4.5rem);
    }
}
@media (max-width: 900px) {
    .skills-design,
    .skills-dev {
        padding: 1.75rem;
    }
    .skill-bars {
        gap: 0.75rem 2.5rem;
    }
}
@media (max-width: 768px) {
    .hero-sub {
        max-width: 100%;
    }
    .hero-badge {
        font-size: 0.75rem;
        padding: 0.3rem 0.875rem;
    }
    .nav-cta {
        border-radius: 6px !important;
        margin: 0.5rem var(--gap);
        width: calc(100% - 2 * var(--gap)) !important;
    }
    .about-img-wrap {
        margin: 0 auto;
    }
    .about-stats {
        gap: 1rem;
    }
    .skills-design,
    .skills-dev {
        padding: 1.5rem;
    }
    .form-group input,
    .form-group textarea {
        font-size: 1rem;
    }
    .footer-inner {
        flex-direction: column;
        align-items: flex-start;
        gap: 1rem;
    }
}
@media (max-width: 480px) {
    .hero-actions {
        flex-direction: column;
        align-items: stretch;
        gap: 0.75rem;
    }
    .btn-primary {
        width: 100%;
        justify-content: center;
    }
    .about-stats {
        gap: 0.875rem;
    }
    .about-stat-val {
        font-size: 1.375rem;
    }
    .skills-design,
    .skills-dev {
        padding: 1.25rem;
    }
    .tech-icon-item {
        padding: 0.75rem;
        min-width: 56px;
    }
    .tech-icon-item img {
        width: 30px;
        height: 30px;
    }
    .contact-socials {
        gap: 0.875rem;
    }
    section {
        padding: clamp(3.5rem, 8vw, 5rem) 0;
    }
    .bento-card {
        height: 200px;
    }
    .bento-card--featured {
        height: 260px;
    }
}
#services {
    background: var(--black);
    padding: clamp(4rem, 8vw, 7rem) 0;
    border-top: 1px solid var(--gray-5);
}
.services-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    background: var(--gray-5);
    border: 1px solid var(--gray-5);
    border-radius: var(--radius);
    overflow: hidden;
    margin-top: 2.5rem;
}
.service-card {
    background: var(--dark-1);
    padding: 2.75rem 2.25rem 2.5rem;
    position: relative;
    overflow: hidden;
    transition: background 0.3s;
}
.service-card:hover {
    background: #0f0f0f;
}
.service-card::before {
    content: "";
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    height: 2px;
    background: linear-gradient(90deg, var(--accent), var(--green));
    transform: scaleX(0);
    transform-origin: left;
    transition: transform 0.5s var(--ease-out-expo);
}
.service-card:hover::before {
    transform: scaleX(1);
}
.service-num {
    display: block;
    font-family: var(--display);
    font-size: clamp(3rem, 6vw, 5rem);
    font-weight: 800;
    color: transparent;
    -webkit-text-stroke: 1px rgba(255, 255, 255, 0.08);
    line-height: 1;
    margin-bottom: 2rem;
    letter-spacing: -0.06em;
    transition: -webkit-text-stroke 0.3s;
}
.service-card:hover .service-num {
    -webkit-text-stroke: 1px rgba(255, 255, 255, 0.18);
}
.service-card-title {
    font-family: var(--display);
    font-size: 1.25rem;
    font-weight: 700;
    color: var(--white);
    letter-spacing: -0.02em;
    margin-bottom: 0.875rem;
}
.service-card-desc {
    font-size: 0.9rem;
    color: var(--gray-2);
    line-height: 1.7;
    margin-bottom: 1.5rem;
}
.service-card-list {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    list-style: none;
}
.service-card-list li {
    font-size: 0.75rem;
    font-family: var(--mono);
    color: var(--gray-3);
    letter-spacing: 0.04em;
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.service-card-list li::before {
    content: "→";
    color: var(--accent);
    font-size: 0.65rem;
    flex-shrink: 0;
}
@media (min-width: 769px) {
    .hero-content {
        position: relative;
        padding-left: 1.75rem;
    }
    .hero-content::before {
        content: "";
        position: absolute;
        left: 0;
        top: 8%;
        height: 65%;
        width: 2px;
        background: linear-gradient(
            to bottom,
            transparent,
            var(--accent) 30%,
            var(--green) 70%,
            transparent
        );
        opacity: 0.35;
        border-radius: 2px;
    }
}
.about-quote {
    font-size: 1.0625rem;
    color: var(--gray-1);
    line-height: 1.8;
    border-left: 2px solid var(--accent);
    padding-left: 1.25rem;
    margin: 1.5rem 0;
    font-style: italic;
}
#contact {
    background: #060a12;
}
#contact .section-title {
    font-size: clamp(2.5rem, 7vw, 5rem);
    max-width: none;
    margin-bottom: 1rem;
    letter-spacing: -0.055em;
}
#contact .section-sub {
    font-size: 1.0625rem;
}
.contact-title-accent {
    color: var(--accent-light, #60a5fa);
    }
.work-sub {
    font-size: 1rem;
    color: var(--gray-3);
    margin-top: 0.625rem;
    font-family: var(--mono);
    letter-spacing: 0.02em;
}
@media (max-width: 900px) {
    .services-grid {
        grid-template-columns: 1fr 1fr;
    }
    .service-card:last-child {
        grid-column: 1/-1;
        border-top: 1px solid var(--gray-5);
    }
    .service-card:nth-child(2) {
        border-left: 1px solid var(--gray-5);
    }
}
@media (max-width: 768px) {
    .services-grid {
        grid-template-columns: 1fr;
    }
    .service-card:nth-child(2) {
        border-left: none;
        border-top: 1px solid var(--gray-5);
    }
    .service-card:last-child {
        grid-column: auto;
        border-top: 1px solid var(--gray-5);
    }
    #contact .section-title {
        font-size: clamp(2rem, 9vw, 3.5rem);
    }
    .hero-content {
        padding-left: 0;
    }
}
@media (max-width: 480px) {
    .service-card {
        padding: 2rem 1.5rem;
    }
    .service-num {
        font-size: 2.5rem;
        margin-bottom: 1.5rem;
    }
}

/* ==========================================================
   DESIGN ELEVATION — v2
   ========================================================== */

/* --- Typography --- */

.section-title {
    font-size: var(--pas-titre);
    letter-spacing: -0.04em;
}

.section-label {
    font-size: var(--pas-etiquette);
}

/* (was: section-label::before bar — retiré) */

/* --- Hero — stat numbers gradient --- */

.hero-stat-num {
    color: var(--white, #fff);
    font-size: var(--pas-titre);
    }

/* --- Hero — watermark slightly more visible --- */

.hero-watermark {
    -webkit-text-stroke: 1px rgba(255, 255, 255, 0.052);
}

/* --- Hero — left accent line bolder --- */

@media (min-width: 769px) {
    .hero-content::before {
        opacity: 0.55;
        width: 3px;
    }
}

/* --- Hero — floating windows larger + colored glow --- */

.hero-win {
    box-shadow:
        0 28px 70px rgba(0, 0, 0, 0.75),
        0 0 60px rgba(0, 102, 255, 0.12),
        0 0 0 1px rgba(255, 255, 255, 0.1);
}

.hero-win-code {
    width: 320px;
}

.hero-win-design {
    width: 282px;
}

.hero-visual {
    height: 500px;
}

/* --- Hero badge dot — animated glow pulse --- */

@keyframes glow-pulse {
    0%, 100% { box-shadow: 0 0 6px rgba(34, 197, 94, 0.5); }
    50%       { box-shadow: 0 0 16px rgba(34, 197, 94, 0.95); }
}

.hero-badge-dot {
    animation: glow-pulse 2s ease-in-out infinite;
}

/* --- Services — more dramatic numbers --- */

.service-num {
    font-size: var(--pas-signature);
    -webkit-text-stroke: 1px rgba(255, 255, 255, 0.13);
    margin-bottom: 1.5rem;
}

.service-card:hover .service-num {
    -webkit-text-stroke: 1px rgba(255, 255, 255, 0.28);
}

.service-card::before {
    height: 3px;
}

.service-card-title {
    font-size: var(--pas-sous-titre);
}

/* --- Services — subtle top-right glow per section --- */

#services {
    position: relative;
    overflow: hidden;
}

#services::after {
    content: "";
    position: absolute;
    top: -15%;
    right: -8%;
    width: 550px;
    height: 550px;
    background: radial-gradient(circle, rgba(0, 102, 255, 0.07) 0%, transparent 65%);
    pointer-events: none;
    z-index: 0;
}

.services-grid {
    position: relative;
    z-index: 1;
}

/* --- About — photo glow + gradient border --- */

.about-img-wrap {
    box-shadow:
        0 28px 80px rgba(0, 0, 0, 0.65),
        0 0 0 1px rgba(0, 102, 255, 0.25),
        0 0 90px rgba(0, 102, 255, 0.08);
}

/* --- About — blockquote editorial --- */

.about-quote {
    font-size: var(--pas-sous-titre);
    font-style: normal;
    font-weight: 500;
    color: rgba(255, 255, 255, 0.88);
    border-left: 3px solid var(--accent);
    padding: 0.25rem 0 0.25rem 1.625rem;
    margin: 2rem 0;
    line-height: 1.7;
    letter-spacing: -0.01em;
}

/* --- About — gradient stat numbers --- */

.about-stat-val {
    color: var(--white, #fff);
    font-size: var(--pas-titre);
    }

/* --- About — section ambient glow --- */

#about {
    position: relative;
    overflow: hidden;
}

#about::after {
    content: "";
    position: absolute;
    bottom: -15%;
    left: -8%;
    width: 600px;
    height: 600px;
    background: radial-gradient(circle, rgba(74, 222, 128, 0.05) 0%, transparent 65%);
    pointer-events: none;
    z-index: 0;
}

.about-grid {
    position: relative;
    z-index: 1;
}

/* --- Work — subtle dot grid + ambient glow --- */

#work {
    position: relative;
    overflow: hidden;
    background-image: radial-gradient(rgba(255, 255, 255, 0.02) 1px, transparent 1px);
    background-size: 28px 28px;
    background-color: var(--dark-1);
}

#work::after {
    content: "";
    position: absolute;
    bottom: -10%;
    right: -8%;
    width: 500px;
    height: 500px;
    background: radial-gradient(circle, rgba(96, 165, 250, 0.06) 0%, transparent 65%);
    pointer-events: none;
    z-index: 0;
}

.work-header,
.bento-grid {
    position: relative;
    z-index: 1;
}

/* --- Work — taller bento grid rows --- */

.bento-grid {
    grid-template-rows: 320px 320px 250px;
}

.bento-card--featured .bento-title {
    font-size: var(--pas-sous-titre); /* 28px→20px : titre de carte bento, "intertitre" (tâche 4, arbitré) */
}

/* Richer bento overlay */
.bento-overlay {
    background: linear-gradient(
        to top,
        rgba(0, 0, 0, 0.97) 0,
        rgba(0, 0, 0, 0.55) 50%,
        transparent 100%
    );
    padding: 2rem 1.5rem 1.5rem;
}

/* --- Skills — thicker bars with glow --- */

.skill-bar-bg {
    height: 5px;
    background: rgba(255, 255, 255, 0.1);
    border-radius: 100px;
}

.skill-bar-fill.dev-bar {
    box-shadow: 0 0 14px rgba(34, 197, 94, 0.55);
}

.skill-bar-fill.des-bar {
    box-shadow: 0 0 14px rgba(96, 165, 250, 0.55);
}

.skill-bar-pct {
    font-size: var(--pas-etiquette);
    color: rgba(255, 255, 255, 0.42);
}

.skill-bar-name {
    font-size: var(--pas-appui);
}

/* --- Skills — dual panel darker + deeper contrast --- */

.skills-dev {
    background: #07070f;
}

.skills-design {
    background: #070f07;
}

.skills-dual {
    box-shadow:
        0 8px 40px rgba(0, 0, 0, 0.5),
        0 0 0 1px rgba(255, 255, 255, 0.08);
}

/* --- Skills — ambient glow center --- */

#skills {
    position: relative;
    overflow: hidden;
}

#skills::before {
    content: "";
    position: absolute;
    top: 40%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 800px;
    height: 500px;
    background: radial-gradient(circle, rgba(0, 102, 255, 0.04) 0%, transparent 65%);
    pointer-events: none;
    z-index: 0;
}

.skills-intro,
.skill-bars,
.tech-stack,
.skills-dual {
    position: relative;
    z-index: 1;
}

/* --- Contact — massive display title --- */

#contact .section-title {
    font-size: var(--pas-titre);
    letter-spacing: -0.07em;
    line-height: 0.95;
    margin-bottom: 1.25rem;
    display: block;
}

#contact .section-title .reveal-wrap {
    display: block;
}

/* --- Contact — second ambient glow --- */

#contact::after {
    content: "";
    position: absolute;
    top: -5%;
    right: -10%;
    width: 600px;
    height: 400px;
    background: radial-gradient(circle, rgba(74, 222, 128, 0.04) 0%, transparent 70%);
    pointer-events: none;
    z-index: 0;
}

.contact-layout {
    position: relative;
    z-index: 1;
}

/* --- Contact — form inputs more refined --- */

.form-group input,
.form-group textarea {
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.1);
    padding: 1rem 1.125rem;
    border-radius: 8px;
}

.form-group input:focus,
.form-group textarea:focus {
    background: rgba(0, 102, 255, 0.04);
    border-color: rgba(0, 102, 255, 0.6);
    box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.1);
}

/* --- Contact — submit button pill shape --- */

.btn-submit {
    border-radius: 6px;
    font-size: var(--pas-lecture);
    letter-spacing: 0.01em;
    padding: 1rem 2.5rem;
}

/* --- Marquee — slightly taller images --- */

.marquee-img {
    height: 148px;
}

/* --- Text marquee strip --- */

.text-marquee {
    overflow: hidden;
    background: var(--dark-1);
    border-top: 1px solid var(--gray-5);
    border-bottom: 1px solid var(--gray-5);
    padding: 1.125rem 0;
    position: relative;
}

.text-marquee::before,
.text-marquee::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    width: 120px;
    z-index: 2;
    pointer-events: none;
}

.text-marquee::before {
    left: 0;
    background: linear-gradient(to right, var(--dark-1), transparent);
}

.text-marquee::after {
    right: 0;
    background: linear-gradient(to left, var(--dark-1), transparent);
}

.text-marquee-track {
    display: flex;
    gap: 2.5rem;
    width: max-content;
    align-items: center;
    animation: marquee-scroll 28s linear infinite;
}

.text-marquee-track span {
    font-family: var(--display);
    font-size: var(--pas-etiquette);
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.3);
    white-space: nowrap;
}

.tmq-sep {
    color: var(--accent) !important;
    opacity: 0.5;
    font-size: var(--pas-etiquette) !important;
    letter-spacing: 0 !important;
}

/* --- Footer — gradient top border --- */

footer {
    border-top: none;
    background-image: linear-gradient(
        to right,
        transparent 0%,
        rgba(0, 102, 255, 0.25) 30%,
        rgba(74, 222, 128, 0.18) 70%,
        transparent 100%
    );
    background-size: 100% 1px;
    background-repeat: no-repeat;
    background-position: top;
    padding-top: 2rem;
}

/* --- Responsive overrides for new sizes --- */

@media (max-width: 900px) {
    .bento-grid {
        grid-template-rows: 260px 260px 260px;
    }
    .hero-visual {
        height: 380px;
    }
}

@media (max-width: 768px) {
    #contact .section-title {
        font-size: var(--pas-titre);
        line-height: 1;
    }
    .hero-stat-num {
        /* var(--pas-titre) vaut 30,4px à cette largeur (clamp) : quasi
           identique au 30px d'origine, cohérent avec sa variante desktop
           (tâche 4, arbitré). */
        font-size: var(--pas-titre);
    }
    .about-stat-val {
        font-size: var(--pas-sous-titre); /* 26px→20px : réduction mobile assumée (tâche 4, arbitré) */
    }
}

@media (max-width: 480px) {
    #contact .section-title {
        font-size: var(--pas-titre);
    }
    .skill-bar-bg {
        height: 4px;
    }
}

/* ================================================
   DESIGN ELEVATION v3 — Dramatic Details
   ================================================ */

/* --- Custom scrollbar --- */
::-webkit-scrollbar {
    width: 5px;
}
::-webkit-scrollbar-track {
    background: var(--dark-1);
}
::-webkit-scrollbar-thumb {
    background: rgba(255,255,255,0.12);
    border-radius: 100px;
}
::-webkit-scrollbar-thumb:hover {
    background: rgba(255,255,255,0.22);
}

/* --- Selection branding --- */
::selection {
    background: rgba(0, 102, 255, 0.35);
    color: #fff;
}
::-moz-selection {
    background: rgba(0, 102, 255, 0.35);
    color: #fff;
}

/* --- Hero title sized to fit hero-content column without overflowing the grid --- */
.hero-title {
    font-size: var(--pas-affirmation);
    letter-spacing: -0.05em;
    hyphens: manual;
}

/* --- Hero aurora more dramatic --- */
#home::before {
    background: radial-gradient(
        circle,
        rgba(0, 102, 255, 0.36) 0,
        rgba(0, 102, 255, 0.07) 55%,
        transparent 75%
    );
}
#home::after {
    background: radial-gradient(
        circle,
        rgba(74, 222, 128, 0.30) 0,
        rgba(0, 102, 255, 0.07) 55%,
        transparent 75%
    );
}

/* --- Hero dual tags slightly bigger --- */
.hero-dual-tag {
    font-size: var(--pas-appui);
    padding: 0.5rem 1.25rem;
}

/* --- Service card hover sweep + children above it --- */
.service-card::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(
        135deg,
        rgba(0, 102, 255, 0.05) 0%,
        rgba(74, 222, 128, 0.03) 100%
    );
    opacity: 0;
    transition: opacity 0.45s ease;
    pointer-events: none;
}
.service-card:hover::after {
    opacity: 1;
}
.service-card > * {
    position: relative;
    z-index: 1;
}

/* --- Service numbers gradient fill --- */
.service-num {
    color: var(--white, #fff);
    -webkit-text-stroke: 0;
}
.service-card:hover .service-num {
    color: var(--white, #fff);
    }

/* --- Section watermarks --- */
#about::before {
    content: "ABOUT";
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-family: var(--display);
    font-size: var(--pas-signature);
    font-weight: 900;
    letter-spacing: -0.05em;
    color: transparent;
    -webkit-text-stroke: 1px rgba(255, 255, 255, 0.04);
    pointer-events: none;
    white-space: nowrap;
    z-index: 0;
    user-select: none;
    line-height: 1;
}
#work::before {
    content: "WORK";
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-family: var(--display);
    font-size: var(--pas-signature);
    font-weight: 900;
    letter-spacing: -0.05em;
    color: transparent;
    -webkit-text-stroke: 1px rgba(255, 255, 255, 0.04);
    pointer-events: none;
    white-space: nowrap;
    z-index: 0;
    user-select: none;
    line-height: 1;
}
#skills::after {
    content: "SKILLS";
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-family: var(--display);
    font-size: var(--pas-signature);
    font-weight: 900;
    letter-spacing: -0.05em;
    color: transparent;
    -webkit-text-stroke: 1px rgba(255, 255, 255, 0.04);
    pointer-events: none;
    white-space: nowrap;
    z-index: 0;
    user-select: none;
    line-height: 1;
}

/* --- About photo gradient border --- */
.about-img-wrap {
    border: 2px solid transparent;
    background:
        linear-gradient(var(--dark-3), var(--dark-3)) padding-box,
        linear-gradient(145deg, rgba(0, 102, 255, 0.6) 0%, rgba(74, 222, 128, 0.4) 100%) border-box;
    box-shadow: 0 28px 80px rgba(0, 0, 0, 0.65), 0 0 80px rgba(0, 102, 255, 0.1);
}

/* --- Featured bento glow on hover --- */
.bento-card--featured:hover {
    border-color: rgba(0, 102, 255, 0.35);
    box-shadow:
        0 0 0 1px rgba(0, 102, 255, 0.15),
        0 0 60px rgba(0, 102, 255, 0.12),
        0 24px 60px rgba(0, 0, 0, 0.5);
}

/* --- Contact availability chip --- */
.contact-avail {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-family: var(--mono);
    font-size: var(--pas-etiquette);
    font-weight: 500;
    color: var(--green);
    border: 1px solid rgba(74, 222, 128, 0.22);
    background: rgba(74, 222, 128, 0.06);
    border-radius: 6px;
    padding: 0.375rem 1rem 0.375rem 0.75rem;
    margin-bottom: 1.5rem;
    letter-spacing: 0.01em;
}
.contact-avail-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--green);
    flex-shrink: 0;
    animation: glow-pulse 2s ease-in-out infinite;
}

/* --- v3 responsive --- */
@media (max-width: 768px) {
    .hero-title {
        font-size: var(--pas-affirmation);
    }
    #about::before,
    #work::before,
    #skills::after {
        font-size: var(--pas-signature);
    }
}

/* ================================================
   DESIGN ELEVATION v4 — Motion & Interaction
   ================================================ */

/* --- Fix mobile: hide hero watermark (overlaps stats) --- */
@media (max-width: 768px) {
    .hero-watermark {
        display: none;
    }
}

/* --- Animated film grain --- */
@keyframes grain-animate {
    0%   { transform: translate(0, 0); }
    10%  { transform: translate(-10px, -10px); }
    20%  { transform: translate(8px, 8px); }
    30%  { transform: translate(-8px, 10px); }
    40%  { transform: translate(10px, -8px); }
    50%  { transform: translate(-6px, 0); }
    60%  { transform: translate(6px, 0); }
    70%  { transform: translate(0, -10px); }
    80%  { transform: translate(0, 8px); }
    90%  { transform: translate(-8px, -8px); }
    100% { transform: translate(0, 0); }
}
.grain {
    inset: unset;
    top: -20px;
    left: -20px;
    width: calc(100% + 40px);
    height: calc(100% + 40px);
    animation: grain-animate 0.7s steps(1) infinite;
    will-change: transform;
}

/* --- Cursor VIEW label (appears on project card hover) --- */
.cursor-view {
    position: fixed;
    top: 0;
    left: 0;
    width: 82px;
    height: 82px;
    background: #fff;
    color: #000;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--display);
    font-size: var(--pas-etiquette);
    font-weight: 800;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    pointer-events: none;
    z-index: 9998;
    transform: translate(-50%, -50%) scale(0);
    transition:
        transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1),
        opacity 0.3s ease;
    opacity: 0;
    will-change: transform;
    user-select: none;
}
.cursor-view.visible {
    transform: translate(-50%, -50%) scale(1);
    opacity: 1;
}
@media (hover: none) {
    .cursor-view { display: none; }
}

/* --- Featured bento card shimmer sweep on hover --- */
.bento-card--featured::after {
    content: "";
    position: absolute;
    top: 0;
    left: -100%;
    width: 50%;
    height: 100%;
    background: linear-gradient(
        90deg,
        transparent 0%,
        rgba(255, 255, 255, 0.06) 50%,
        transparent 100%
    );
    transform: skewX(-15deg);
    transition: left 0.75s ease;
    pointer-events: none;
    z-index: 5;
}
.bento-card--featured:hover::after {
    left: 180%;
}

/* --- Service card tilt: smooth reset transition --- */
.service-card {
    transition: background 0.3s, transform 0.5s cubic-bezier(0.23, 1, 0.32, 1);
    will-change: transform;
}

/* --- Hero visual: smooth parallax transition on leave --- */
.hero-visual {
    transition: transform 0.8s cubic-bezier(0.23, 1, 0.32, 1);
    will-change: transform;
}

/* ================================================
   MOBILE RESPONSIVE — Comprehensive fixes
   ================================================ */

/* 1. Section title: fluid scaling so long words don't overflow on small screens */
@media (max-width: 540px) {
    .section-title {
        font-size: var(--pas-titre);
        letter-spacing: -0.03em;
    }
}

/* 3. Contact title keeps its own large display size but stays safe */
@media (max-width: 540px) {
    #contact .section-title {
        font-size: var(--pas-titre);
        letter-spacing: -0.05em;
    }
}

/* 4. Touch devices: neutralise JS-driven transforms that can freeze on tap */
@media (hover: none) {
    .service-card {
        transform: none !important;
        will-change: auto;
    }
    .hero-visual {
        transform: none !important;
        will-change: auto;
    }
}

/* 5. Watermarks: clip safely, no contribution to scroll width */
#about, #work, #skills {
    overflow: hidden;
}

/* 6. About image: gradient border looks heavy at very small size — simplify */
@media (max-width: 480px) {
    .about-img-wrap {
        border-width: 1.5px;
    }
}

/* 7. Bento grid: single column on very small screens */
@media (max-width: 400px) {
    .bento-grid {
        grid-template-columns: 1fr;
        grid-template-rows: auto;
    }
    .bento-card--featured,
    .bento-card--wide {
        grid-column: auto;
        grid-row: auto;
        height: 220px;
    }
}

/* 8. Contact layout: form stacks below info on mobile (already exists but reinforce) */
@media (max-width: 768px) {
    .contact-avail {
        font-size: var(--pas-etiquette);
    }
}

/* 9. Hero dual tags: don't overflow on very small phones */
@media (max-width: 380px) {
    .hero-dual-tag {
        font-size: var(--pas-etiquette);
        padding: 0.4rem 0.875rem;
    }
    .hero-dual {
        gap: 0.375rem;
    }
}

/* 10. Section sub text: readable on mobile */
@media (max-width: 480px) {
    .section-sub {
        font-size: var(--pas-appui);
    }
    .about-quote {
        font-size: var(--pas-lecture);
    }
}

/* ==========================================================
   DESIGN ELEVATION — v5  (index full upgrade)
   ========================================================== */

/* ── HERO v5 ─────────────────────────────────────────────── */

/* Stronger aurora: bigger blobs, more saturated */
#home::before {
    background:
        radial-gradient(ellipse 80% 60% at 60% 20%, rgba(0,102,255,0.18) 0%, transparent 65%),
        radial-gradient(ellipse 50% 40% at 20% 80%, rgba(74,222,128,0.10) 0%, transparent 60%);
    filter: blur(0px);
}

/* Hero stat numbers → gradient text */
.hero-stat-num {
    color: var(--white, #fff);
    font-size: var(--pas-titre);
}

/* Hero stats row: more contrast */
.hero-stats {
    border-top: 1px solid rgba(255,255,255,0.08);
    gap: 2.5rem;
}

/* Hero badge: glow ring */
.hero-badge {
    box-shadow: 0 0 0 1px rgba(74,222,128,0.2), 0 0 20px rgba(74,222,128,0.08);
}

/* Horizontal accent line between tags row and title */
.hero-dual {
    margin-bottom: 1.75rem;
    padding-bottom: 1.75rem;
    border-bottom: 1px solid rgba(255,255,255,0.06);
    position: relative;
}
.hero-dual::after {
    content: "";
    position: absolute;
    bottom: -1px;
    left: 0;
    width: 120px;
    height: 1px;
    background: linear-gradient(90deg, var(--accent), var(--green));
}

/* ── SERVICES v5 ─────────────────────────────────────────── */

/* Switch from merged grid to spaced-glass individual cards */
.services-grid {
    background: transparent;
    border: none;
    gap: 1.25rem;
    border-radius: 0;
    overflow: visible;
}

.service-card {
    background: rgba(255,255,255,0.025);
    border: 1px solid rgba(255,255,255,0.08);
    border-radius: 20px;
    overflow: hidden;
    box-shadow: 0 4px 24px rgba(0,0,0,0.35), inset 0 1px 0 rgba(255,255,255,0.04);
    transition: border-color 0.3s, transform 0.35s var(--ease-out-expo), box-shadow 0.35s;
}
.service-card:hover {
    background: rgba(255,255,255,0.04);
    border-color: rgba(255,255,255,0.14);
    transform: translateY(-2px);
    box-shadow: 0 16px 48px rgba(0,0,0,0.5), 0 0 0 1px rgba(0,102,255,0.15), inset 0 1px 0 rgba(255,255,255,0.07);
}

/* Gradient top border on each card */
.service-card::after {
    content: "";
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 1px;
    background: linear-gradient(90deg, transparent, rgba(0,102,255,0.6) 35%, rgba(74,222,128,0.45) 65%, transparent);
    opacity: 0;
    transition: opacity 0.4s;
}
.service-card:hover::after {
    opacity: 1;
}

/* Left accent sweep → move to top border reveal (done via ::after above) */
/* Bottom sweep → keep for visual continuity, but tweak */
.service-card::before {
    height: 1px;
    top: 0;
    bottom: auto;
    background: linear-gradient(90deg, var(--accent), var(--green));
    transform: scaleX(0);
    transform-origin: left;
    transition: transform 0.55s var(--ease-out-expo);
    z-index: 2;
}
.service-card:hover::before {
    transform: scaleX(1);
}

/* Ghost number: bigger, more transparent */
.service-num {
    font-size: var(--pas-signature);
    -webkit-text-stroke: 1px rgba(255,255,255,0.06);
    margin-bottom: 1.5rem;
    letter-spacing: -0.08em;
}
.service-card:hover .service-num {
    color: var(--white, #fff);
    -webkit-text-stroke: 1px rgba(255,255,255,0.22);
    }

/* Service title bigger */
.service-card-title {
    font-size: var(--pas-sous-titre);
    letter-spacing: -0.03em;
}

/* Category chip */
.service-chip {
    display: inline-block;
    font-size: var(--pas-etiquette);
    font-family: var(--mono);
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    padding: 0.25rem 0.625rem;
    border-radius: 6px;
    margin-bottom: 2.5rem;
    border: 1px solid;
}
.service-chip.chip-dev {
    color: var(--green);
    background: rgba(74,222,128,0.08);
    border-color: rgba(74,222,128,0.22);
}
.service-chip.chip-design {
    color: #93c5fd;
    background: rgba(0,102,255,0.1);
    border-color: rgba(0,102,255,0.28);
}
.service-chip.chip-brand {
    color: #f9a8d4;
    background: rgba(236,72,153,0.08);
    border-color: rgba(236,72,153,0.22);
}

/* ── ABOUT v5 ────────────────────────────────────────────── */

/* About stat cards: glass treatment */
.about-stats {
    gap: 1rem;
    padding-top: 1.5rem;
}

.about-stat-item {
    background: rgba(255,255,255,0.03);
    border: 1px solid rgba(255,255,255,0.08);
    border-radius: 14px;
    padding: 1.25rem 1.5rem 1.5rem;
    position: relative;
    overflow: hidden;
    transition: border-color 0.3s, transform 0.3s;
}
.about-stat-item::before {
    content: "";
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 2px;
    background: linear-gradient(90deg, var(--accent), var(--green));
    opacity: 0.6;
}
.about-stat-item:hover {
    border-color: rgba(255,255,255,0.14);
    transform: translateY(-2px);
}

/* About stat values → gradient */
.about-stat-val {
    color: var(--white, #fff);
    font-size: var(--pas-titre);
    }

/* Stronger about quote */
.about-quote {
    border-left-color: var(--accent);
    border-left-width: 3px;
    background: rgba(0,102,255,0.04);
    border-radius: 0 8px 8px 0;
    padding: 1.25rem 1.5rem;
    margin: 1.75rem 0;
}

/* About tags: gradient border on hover */
.about-tag:hover {
    background: rgba(0,102,255,0.08);
    border-color: rgba(0,102,255,0.3);
    color: #93c5fd;
}

/* ── WORK / BENTO v5 ─────────────────────────────────────── */

/* Bento cards: animated gradient border on hover */
.bento-card {
    transition: transform 0.45s var(--ease-out-expo), box-shadow 0.4s;
}
.bento-card:hover {
    transform: translateY(-2px) scale(1.005);
    box-shadow: 0 24px 60px rgba(0,0,0,0.65), 0 0 0 1px rgba(255,255,255,0.1);
}

/* Featured card: glowing ring */
.bento-card--featured:hover {
    box-shadow: 0 24px 60px rgba(0,0,0,0.65), 0 0 0 1px rgba(0,102,255,0.25), 0 0 40px rgba(0,102,255,0.08);
}

/* Section header: accent rule */
.work-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    margin-bottom: 2.5rem;
    padding-bottom: 1.5rem;
    border-bottom: 1px solid rgba(255,255,255,0.06);
    position: relative;
}
.work-header::after {
    content: "";
    position: absolute;
    bottom: -1px;
    left: 0;
    width: 80px;
    height: 1px;
    background: linear-gradient(90deg, var(--accent), var(--green));
}

/* ── SKILLS v5 ───────────────────────────────────────────── */

/* Skill bars: green stays, enhance bg */
.skill-bar-bg {
    height: 6px;
    background: rgba(255,255,255,0.07);
    border-radius: 100px;
    overflow: hidden;
}

/* Tech grid items: glassmorphism */
.tech-item {
    background: rgba(255,255,255,0.03);
    border: 1px solid rgba(255,255,255,0.08);
    border-radius: 10px;
    transition: background 0.25s, border-color 0.25s, transform 0.3s;
}
.tech-item:hover {
    background: rgba(255,255,255,0.07);
    border-color: rgba(255,255,255,0.15);
    transform: translateY(-2px);
}

/* Skills dual panels: gradient top border */
.skills-dev {
    position: relative;
    overflow: hidden;
}
.skills-dev::before {
    content: "";
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 2px;
    background: linear-gradient(90deg, var(--green), rgba(0,102,255,0.5));
    opacity: 0.7;
}
.skills-design {
    position: relative;
    overflow: hidden;
}
.skills-design::before {
    content: "";
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 2px;
    background: linear-gradient(90deg, var(--accent), #c084fc);
    opacity: 0.7;
}

/* ── SECTION LABELS v5 ───────────────────────────────────── */

/* Animated underline reveal on section labels */
.section-label {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 0.625rem;
}
.section-label::after {
    content: "";
    display: inline-block;
    width: 28px;
    height: 1px;
    background: linear-gradient(90deg, var(--accent), var(--green));
    vertical-align: middle;
    transition: width 0.5s var(--ease-out-expo);
}
.section-label:hover::after {
    width: 56px;
}

/* ── CONTACT v5 ──────────────────────────────────────────── */

/* Gradient divider above contact */
#contact::before {
    content: "";
    position: absolute;
    top: 0; left: 8%; right: 8%;
    height: 1px;
    background: linear-gradient(90deg, transparent, rgba(0,102,255,0.5) 35%, rgba(74,222,128,0.35) 65%, transparent);
}

/* ── MOBILE v5 ───────────────────────────────────────────── */
@media (max-width: 768px) {
    .services-grid {
        gap: 0.875rem;
    }
    .service-card {
        border-radius: 16px;
    }
    .about-stat-item {
        padding: 1rem 1.125rem 1.25rem;
    }
    .about-stat-val {
        font-size: var(--pas-sous-titre); /* 26px→20px : réduction mobile assumée (tâche 4, arbitré) */
    }
    /* Work header: stack vertically on mobile */
    .work-header {
        flex-direction: column;
        align-items: flex-start;
        gap: 0.25rem;
    }
}

/* ================================================
   PERFORMANCE — scroll & motion optimizations
   ================================================ */

/* Sections off-screen: skip rendering until near viewport */
#services, #about, #work, #skills, #contact {
    content-visibility: auto;
    contain-intrinsic-size: 1px 800px;
}

/* Reduced-motion: kill parallax, grain, marquees */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
    .grain,
    .marquee-track,
    .text-marquee-track,
    .hero-win-code,
    .hero-win-design {
        animation: none !important;
    }
    .hero-visual {
        transform: none !important;
    }
}

/* Promote animated elements to their own layer (cheaper repaints) */
.grain,
.cursor-dot,
.cursor-ring,
.cursor-view,
.marquee-track,
.text-marquee-track {
    transform: translateZ(0);
    backface-visibility: hidden;
}

/* ================================================
   ELEVATION — Premium animations & micro-interactions
   ================================================ */

/* --- Bento images: clip-path reveal staggered --- */
.bento-card img {
    clip-path: inset(0 100% 0 0);
    transition: clip-path 1.05s var(--ease-out-expo);
}
.bento-grid.fade-in.visible .bento-card img { clip-path: inset(0 0 0 0); }
.bento-grid.fade-in.visible .bento-card:nth-child(1) img { transition-delay: 0.10s; }
.bento-grid.fade-in.visible .bento-card:nth-child(2) img { transition-delay: 0.22s; }
.bento-grid.fade-in.visible .bento-card:nth-child(3) img { transition-delay: 0.34s; }
.bento-grid.fade-in.visible .bento-card:nth-child(4) img { transition-delay: 0.46s; }
.bento-grid.fade-in.visible .bento-card:nth-child(5) img { transition-delay: 0.58s; }

/* Bento overlay text: lift slightly on hover (subtle premium) */
.bento-card .bento-overlay {
    transition: transform 0.5s var(--ease-out-expo);
}
.bento-card:hover .bento-overlay {
    transform: translateY(-2px);
}

/* --- Tech icons: stagger float-in with bounce --- */
.tech-stack.fade-in .tech-icon-item {
    opacity: 0;
    transform: translateY(20px) scale(0.85);
    transition:
        opacity 0.5s var(--ease-out-expo),
        transform 0.7s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.tech-stack.fade-in.visible .tech-icon-item {
    opacity: 1;
    transform: translateY(0) scale(1);
}
.tech-stack.fade-in.visible .tech-icon-item:nth-child(1)  { transition-delay: 0.04s; }
.tech-stack.fade-in.visible .tech-icon-item:nth-child(2)  { transition-delay: 0.08s; }
.tech-stack.fade-in.visible .tech-icon-item:nth-child(3)  { transition-delay: 0.12s; }
.tech-stack.fade-in.visible .tech-icon-item:nth-child(4)  { transition-delay: 0.16s; }
.tech-stack.fade-in.visible .tech-icon-item:nth-child(5)  { transition-delay: 0.20s; }
.tech-stack.fade-in.visible .tech-icon-item:nth-child(6)  { transition-delay: 0.24s; }
.tech-stack.fade-in.visible .tech-icon-item:nth-child(7)  { transition-delay: 0.28s; }
.tech-stack.fade-in.visible .tech-icon-item:nth-child(8)  { transition-delay: 0.32s; }
.tech-stack.fade-in.visible .tech-icon-item:nth-child(9)  { transition-delay: 0.36s; }
.tech-stack.fade-in.visible .tech-icon-item:nth-child(10) { transition-delay: 0.40s; }
.tech-stack.fade-in.visible .tech-icon-item:nth-child(11) { transition-delay: 0.44s; }
.tech-stack.fade-in.visible .tech-icon-item:nth-child(12) { transition-delay: 0.48s; }
.tech-stack.fade-in.visible .tech-icon-item:nth-child(13) { transition-delay: 0.52s; }
.tech-stack.fade-in.visible .tech-icon-item:nth-child(14) { transition-delay: 0.56s; }
.tech-stack.fade-in.visible .tech-icon-item:nth-child(15) { transition-delay: 0.60s; }
.tech-stack.fade-in.visible .tech-icon-item:nth-child(16) { transition-delay: 0.64s; }

/* Tech icon hover: lift + colorize border (already exists, refined) */
.tech-icon-item {
    will-change: auto;
}
.tech-icon-item img {
    transition: transform 0.4s var(--ease-out-expo), filter 0.3s ease;
    filter: grayscale(0.3) brightness(0.95);
}
.tech-icon-item:hover img {
    transform: scale(1.12) rotate(-3deg);
    filter: grayscale(0) brightness(1.05);
}

/* --- Marquee: pause on hover --- */
.marquee-section:hover .marquee-track,
.text-marquee:hover .text-marquee-track {
    animation-play-state: paused;
}

/* --- Service card: number glow + shadow on hover --- */
.service-card:hover .service-num {
    -webkit-text-stroke: 1px rgba(255, 255, 255, 0.45);
    text-shadow: 0 0 32px rgba(0, 102, 255, 0.22);
    transform: translateX(-4px);
}
.service-num {
    transition:
        -webkit-text-stroke-color 0.4s,
        text-shadow 0.4s,
        transform 0.6s var(--ease-out-expo);
}

/* --- Btn primary: gradient sweep on hover --- */
.btn-primary {
    position: relative;
    overflow: hidden;
    isolation: isolate;
}
.btn-primary::after {
    content: "";
    position: absolute;
    top: 0;
    left: -120%;
    width: 80%;
    height: 100%;
    background: linear-gradient(
        90deg,
        transparent 0%,
        rgba(255, 255, 255, 0.22) 50%,
        transparent 100%
    );
    transition: left 0.8s var(--ease-out-expo);
    pointer-events: none;
    z-index: 1;
}
.btn-primary:hover::after {
    left: 130%;
}

/* --- Hero dual-tags: hover lift + shadow --- */
.hero-dual-tag {
    transition:
        transform 0.4s var(--ease-out-expo),
        box-shadow 0.4s,
        background 0.4s,
        border-color 0.4s;
    cursor: default;
    will-change: transform;
}
.hero-dual-tag.code:hover {
    transform: translateY(-2px) scale(1.03);
    box-shadow: none;
    border-color: rgba(74, 222, 128, 0.6);
}
.hero-dual-tag.design:hover {
    transform: translateY(-2px) scale(1.03);
    box-shadow: none;
    border-color: rgba(0, 102, 255, 0.6);
}

/* (Section label line decoration retirée — trop bruyant) */

/* --- Bento overlay top: badge fade-in delayed --- */
.bento-card .bento-overlay-top {
    opacity: 0;
    transform: translateY(-6px);
    transition: opacity 0.5s ease 0.4s, transform 0.5s var(--ease-out-expo) 0.4s;
}
.bento-grid.fade-in.visible .bento-card .bento-overlay-top {
    opacity: 1;
    transform: translateY(0);
}
.bento-grid.fade-in.visible .bento-card:nth-child(1) .bento-overlay-top { transition-delay: 0.5s; }
.bento-grid.fade-in.visible .bento-card:nth-child(2) .bento-overlay-top { transition-delay: 0.62s; }
.bento-grid.fade-in.visible .bento-card:nth-child(3) .bento-overlay-top { transition-delay: 0.74s; }
.bento-grid.fade-in.visible .bento-card:nth-child(4) .bento-overlay-top { transition-delay: 0.86s; }
.bento-grid.fade-in.visible .bento-card:nth-child(5) .bento-overlay-top { transition-delay: 0.98s; }

/* --- Hero badge dot: stronger glow --- */
.hero-badge {
    transition: transform 0.4s, box-shadow 0.4s;
}
.hero-badge:hover {
    transform: translateY(-2px);
    box-shadow: 0 10px 24px rgba(34, 197, 94, 0.18);
}

/* --- Reduced-motion: kill heavy animations --- */
@media (prefers-reduced-motion: reduce) {
    .bento-card img { clip-path: none !important; transition: none !important; }
    .tech-stack.fade-in .tech-icon-item {
        opacity: 1 !important;
        transform: none !important;
        transition: none !important;
    }
    .btn-primary::after { display: none !important; }
}

/* ================================================
   ELEVATION v2 — High-level cinematic
   ================================================ */

/* === SplitText: char-by-char drop-in with rotate === */
.split-char {
    display: inline-block;
    transform: translateY(110%) rotate(8deg);
    transition: transform 1s cubic-bezier(0.22, 1, 0.36, 1);
    transition-delay: calc(var(--i, 0) * 0.025s);
    will-change: transform;
}
/* Empêche le break mid-word: chaque mot est inline-block atomique,
   les retours à la ligne ne peuvent se faire qu'aux espaces entre .split-word */
.split-word {
    display: inline-block;
    white-space: nowrap;
}
.is-split.visible .split-char,
.fade-in.is-split.visible .split-char,
.reveal-wrap.visible .split-char,
.reveal-wrap.visible .split-char {
    transform: translateY(0) rotate(0);
}
/* When chars take over, kill reveal-line's own translate */
.is-split .reveal-line,
.is-split.visible .reveal-line {
    transform: none !important;
    transition: none !important;
}
/* Keep reveal-wrap as overflow:hidden mask for chars */
.section-title .reveal-wrap,
.proj-title {
    overflow: hidden;
    display: inline-block;
}
.section-title { display: block; }

/* === Hero scroll cinematic — content fades & lifts === */
.hero-content {
    will-change: transform, opacity;
}

/* === Bento immersive hover — premium === */
.bento-card {
    isolation: isolate;
}
.bento-card img {
    transition:
        transform 0.9s cubic-bezier(0.22, 1, 0.36, 1),
        filter 0.6s ease,
        clip-path 1.05s var(--ease-out-expo);
}
.bento-card:hover img {
    transform: scale(1.07) rotate(-0.6deg);
    filter: brightness(1.08) saturate(1.18) contrast(1.05);
}
/* Gradient border glow on hover */
.bento-card::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    padding: 1.5px;
    background: linear-gradient(135deg, var(--accent), var(--green), var(--accent));
    background-size: 200% 200%;
    -webkit-mask:
        linear-gradient(#000 0 0) content-box,
        linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask-composite: exclude;
    opacity: 0;
    transition: opacity 0.5s ease;
    pointer-events: none;
    z-index: 3;
    /* l'animation ne tourne QU'AU SURVOL (règle plus bas). Elle animait
       background-position — propriété non composite, donc repaint à chaque
       image — en permanence sur les 11 cartes, alors que ce contour est à
       opacity 0 tant qu'on ne survole pas. On payait du repaint continu
       pour un effet invisible. Rien ne change à l'écran. */
}
.bento-card:hover::after { opacity: 0.85; }
.bento-card:hover::after { animation: gradient-shift 4s linear infinite; }
@keyframes gradient-shift {
    0%   { background-position: 0% 50%; }
    100% { background-position: 200% 50%; }
}
/* Title underline draw on hover */
.bento-title {
    position: relative;
    display: inline-block;
    padding-bottom: 4px;
}
.bento-title::after {
    content: "";
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    height: 1.5px;
    background: linear-gradient(90deg, var(--accent), var(--green));
    transform: scaleX(0);
    transform-origin: left;
    transition: transform 0.65s cubic-bezier(0.22, 1, 0.36, 1);
}
.bento-card:hover .bento-title::after { transform: scaleX(1); }
/* Badge slide-in on hover (when card revealed) */
.bento-card:hover .bento-badge {
    transform: translateX(-2px) scale(1.04);
    box-shadow: 0 8px 18px rgba(0, 102, 255, 0.25);
}
.bento-badge {
    transition: transform 0.4s var(--ease-out-expo), box-shadow 0.4s;
}

/* === Text marquee: stroke outline → fill on hover === */
.text-marquee-track > span:not(.tmq-sep) {
    -webkit-text-stroke: 1px rgba(255, 255, 255, 0.32);
    color: transparent;
    transition:
        color 0.6s ease,
        -webkit-text-stroke-color 0.6s ease;
}
.text-marquee:hover .text-marquee-track > span:not(.tmq-sep) {
    color: rgba(255, 255, 255, 0.92);
    -webkit-text-stroke-color: transparent;
}
/* Separator stays the way it was */
.text-marquee-track > .tmq-sep {
    color: rgba(255, 255, 255, 0.18);
}

/* === Section chapter mark — vertical line drawing === */
section[id] {
    position: relative;
}
.chapter-mark {
    position: absolute;
    left: clamp(1rem, 3vw, 2.5rem);
    top: clamp(4rem, 8vw, 7rem);
    width: 1px;
    height: 90px;
    background: linear-gradient(
        180deg,
        transparent 0%,
        var(--accent) 50%,
        transparent 100%
    );
    transform: scaleY(0);
    transform-origin: top;
    transition: transform 1.2s cubic-bezier(0.22, 1, 0.36, 1) 0.1s;
    pointer-events: none;
    z-index: 5;
    opacity: 0.75;
}
section.in-view .chapter-mark {
    transform: scaleY(1);
}
@media (max-width: 900px) {
    .chapter-mark { display: none; }
}

/* === Service-num: heavier presence on hover === */
.service-card:hover .service-num {
    transform: translateX(-6px) scale(1.05);
}

/* === Cursor word morph zones === */
.cursor-view {
    text-transform: uppercase;
    font-size: var(--pas-etiquette);
    letter-spacing: 0.14em;
}

/* === Reduced-motion overrides === */
@media (prefers-reduced-motion: reduce) {
    .split-char {
        transform: none !important;
        transition: none !important;
    }
    .chapter-mark {
        transform: scaleY(1) !important;
        transition: none !important;
    }
    .bento-card:hover img {
        transform: none !important;
        filter: none !important;
    }
}

/* ================================================
   LOCATION SECTION — 3D Globe Madagascar
   ================================================ */
#location {
    position: relative;
    padding: clamp(4rem, 8vw, 8rem) 0; /* harmonisé 128px max */
    background: linear-gradient(180deg, var(--dark-1) 0%, var(--black) 100%);
    overflow: hidden;
}
#location::before {
    content: "";
    position: absolute;
    top: 50%;
    left: -10%;
    width: 700px;
    height: 700px;
    transform: translateY(-50%);
    background: radial-gradient(circle, rgba(0, 102, 255, 0.08) 0%, transparent 65%);
    pointer-events: none;
}
.location-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: clamp(2rem, 5vw, 5rem);
    align-items: center;
    position: relative;
    z-index: 1;
}
.location-content .section-title {
    font-size: var(--pas-titre);
    line-height: 1.05;
    letter-spacing: -0.04em;
    margin-bottom: 1.5rem;
}
.location-accent {
    color: var(--accent-light, #60a5fa);
    }
.location-content .section-sub {
    font-size: var(--pas-lecture);
    color: var(--gray-2);
    line-height: 1.7;
    max-width: 480px;
    margin-bottom: 2.5rem;
}
.location-meta {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1.5rem;
    padding-top: 1.75rem;
    border-top: 1px solid var(--gray-5);
    max-width: 480px;
}
.location-meta-item {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}
.location-meta-label {
    font-family: var(--mono);
    font-size: var(--pas-etiquette);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.13em;
    color: var(--gray-3);
}
.location-meta-val {
    font-family: var(--mono);
    font-size: var(--pas-appui);
    font-weight: 500;
    color: var(--gray-1);
}

/* Globe canvas container */
.location-globe {
    position: relative;
    width: 100%;
    aspect-ratio: 1;
    max-width: 540px;
    margin: 0 auto;
}
.location-globe canvas {
    width: 100% !important;
    height: 100% !important;
    contain: layout paint;
    cursor: grab;
    opacity: 0;
    transition: opacity 0.8s var(--ease-out-expo);
}
.location-globe.loaded canvas { opacity: 1; }
.location-globe canvas:active { cursor: grabbing; }

/* Floating pin label — position computed in JS per frame to follow Madagascar */
.location-pin-label {
    position: absolute;
    /* top/left set in JS each frame from 3D projection */
    top: 50%;
    left: 50%;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    background: rgba(10, 10, 10, 0.94);
    border: 1px solid rgba(74, 222, 128, 0.4);
    border-radius: 6px;
    padding: 0.4rem 0.9rem;
    font-family: var(--mono);
    font-size: var(--pas-etiquette);
    font-weight: 600;
    color: #fff;
    pointer-events: none;
    opacity: 0;
    transform: translate(-50%, calc(-100% - 14px));
    z-index: 2;
    white-space: nowrap;
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.04);
}
/* Connector line entre label et marker */
.location-pin-label::after {
    content: "";
    position: absolute;
    left: 50%;
    bottom: -10px;
    width: 1px;
    height: 10px;
    background: linear-gradient(180deg, rgba(74, 222, 128, 0.6), transparent);
    transform: translateX(-50%);
}
.location-pin-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--green);
    box-shadow: 0 0 12px var(--green);
    flex-shrink: 0;
}

/* Loading skeleton (avant que cobe charge) */
.location-globe::before {
    content: "";
    position: absolute;
    inset: 0;
    margin: auto;
    width: 70%;
    height: 70%;
    border-radius: 50%;
    border: 1px solid rgba(255, 255, 255, 0.06);
    background: radial-gradient(circle at 30% 30%, rgba(0, 102, 255, 0.18), rgba(74, 222, 128, 0.04) 60%, transparent 75%);
    opacity: 1;
    transition: opacity 0.6s ease;
    pointer-events: none;
}
.location-globe.loaded::before { opacity: 0; }

/* Mobile: stack vertically */
@media (max-width: 880px) {
    .location-grid { grid-template-columns: 1fr; gap: 3rem; }
    .location-content .section-title { font-size: var(--pas-titre); }
    .location-content .section-sub { max-width: none; }
    .location-meta { max-width: none; }
    .location-globe { max-width: 380px; }
    .location-pin-label { font-size: var(--pas-etiquette); padding: 0.3rem 0.7rem; }
}
@media (max-width: 480px) {
    .location-meta { grid-template-columns: 1fr 1fr; }
    .location-meta-item:last-child { grid-column: 1 / -1; }
}
@media (prefers-reduced-motion: reduce) {
    .location-pin-dot { animation: none; }
}

/* === Location title: prevent mid-word break des split-chars === */
#location .section-title .reveal-wrap {
    white-space: nowrap;
}
#location .section-title {
    font-size: var(--pas-titre);
    letter-spacing: -0.04em;
}
@media (max-width: 880px) {
    #location .section-title {
        font-size: var(--pas-titre);
    }
}
/* Fix mobile : « Antananarivo, » (un seul mot, insécable) faisait 396px à ~37px
   et débordait du viewport → coupé par le masque overflow (titre ET phrase
   sous-titre). On force une taille fluide qui tient sur écran étroit (le
   sous-titre se remet en page une fois le conteneur dans le viewport).
   !important : un bloc .section-title plus bas (override navy) gagnait la cascade. */
@media (max-width: 480px) {
    #location .section-title { font-size: var(--pas-titre) !important; }
}
/* Desktop/tablette (grille 2 colonnes ≥881px) : « Antananarivo, » (insécable)
   débordait de sa demi-colonne autour de 880–1290px et était coupé par le
   masque overflow:hidden de .reveal-wrap. On plafonne la taille du titre pour
   que le mot tienne dans la colonne à toutes ces largeurs.
   !important : un bloc .section-title plus bas (navy) gagnait la cascade. */
@media (min-width: 881px) {
    #location .section-title { font-size: var(--pas-titre) !important; letter-spacing: -0.045em; }
}
/* Le masque overflow:hidden de .reveal-wrap est calé sur la largeur d'AVANCE du
   mot, mais le dernier glyphe (« , » + italique) déborde ~3px de cette avance →
   rogné. On élargit la boîte du masque à droite (padding) pour laisser passer
   ce débord, sans toucher au masquage vertical du reveal. */
#location .section-title .reveal-wrap { padding-right: 0.2em; }

/* === Fix: re-apply gradient sur split-chars dans location-accent === */
.location-accent .split-char {
    color: var(--accent-light, #60a5fa);
    }

/* === Live clock — ticking dot + monospace tabular nums === */
.location-clock {
    font-variant-numeric: tabular-nums;
    position: relative;
    padding-left: 1.1rem;
    color: var(--green);
    letter-spacing: 0.05em;
}
.location-clock::before {
    content: "";
    position: absolute;
    left: 0;
    top: 50%;
    width: 6px;
    height: 6px;
    background: var(--green);
    border-radius: 50%;
    transform: translateY(-50%);
    animation: clock-pulse 1s steps(1, end) infinite;
    box-shadow: 0 0 8px rgba(74, 222, 128, 0.7);
}
@keyframes clock-pulse {
    0%, 50%   { opacity: 1; }
    50.01%, 100% { opacity: 0.25; }
}
@media (prefers-reduced-motion: reduce) {
    .location-clock::before { animation: none; }
}

/* === Pin label dynamic swap (cycle entre Antananarivo / Tana / Iarivo / Madagascar) === */
.location-pin-text {
    display: inline-block;
    transition:
        transform 0.28s cubic-bezier(0.4, 0, 0.2, 1),
        opacity 0.28s ease,
        filter 0.28s ease;
    will-change: transform, opacity;
}
.location-pin-text.swap-out {
    transform: translateY(-6px);
    opacity: 0;
    filter: blur(2px);
}
.location-pin-text.swap-in {
    animation: pin-name-in 0.35s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
@keyframes pin-name-in {
    0%   { transform: translateY(8px); opacity: 0; filter: blur(2px); }
    100% { transform: translateY(0);   opacity: 1; filter: blur(0); }
}
@media (prefers-reduced-motion: reduce) {
    .location-pin-text { transition: none !important; animation: none !important; }
}

/* === Caption fixe en bas du globe (remplace le pin tracking instable) === */
.location-globe-caption {
    position: absolute;
    bottom: -2.25rem;
    left: 50%;
    transform: translateX(-50%);
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    background: rgba(10, 10, 10, 0.94);
    border: 1px solid rgba(74, 222, 128, 0.32);
    border-radius: 6px;
    padding: 0.45rem 1rem;
    font-family: var(--mono);
    font-size: var(--pas-etiquette);
    font-weight: 500;
    color: var(--gray-1);
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.04);
    pointer-events: none;
    white-space: nowrap;
    z-index: 2;
    opacity: 0;
    transition: opacity 0.7s ease 0.4s;
}
.location-globe.loaded .location-globe-caption { opacity: 1; }
.location-globe-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--green);
    box-shadow: 0 0 10px rgba(74, 222, 128, 0.85);
    flex-shrink: 0;
}
.location-globe-coords {
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.04em;
}
@media (max-width: 480px) {
    .location-globe-caption { font-size: var(--pas-etiquette); padding: 0.35rem 0.8rem; bottom: -2rem; }
}

/* ================================================
   HERO MESH GRADIENT — WebGL fluid background
   ================================================ */
#hero-mesh {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    z-index: 0;
    opacity: 0;
    transition: opacity 1.5s ease;
    /* Couleurs brand pour whatamesh — bleu/navy dominant, cyan/vert en touches */
    --gradient-color-1: #0a0e1a;
    --gradient-color-2: #0066ff;
    --gradient-color-3: #0a5f8c;
    --gradient-color-4: #1c7a50;
}
#hero-mesh.loaded { opacity: 0.9; }
#home::before, #home::after { z-index: 1; }
.hero-watermark, .hero-inner { z-index: 2; }

/* Atténuation au scroll out (déjà géré par hero parallax) */
@media (prefers-reduced-motion: reduce) {
    #hero-mesh { display: none; }
}
@media (max-width: 600px) {
    #hero-mesh { opacity: 0.65; }
}

/* ================================================
   ACTIVITY SECTION — Live terminal
   ================================================ */
#activity {
    position: relative;
    padding: clamp(4rem, 8vw, 8rem) 0; /* harmonisé 128px max */
    background: var(--dark-1);
    overflow: hidden;
}
#activity::before {
    content: "";
    position: absolute;
    top: -10%;
    right: -10%;
    width: 600px;
    height: 600px;
    background: radial-gradient(circle, rgba(74, 222, 128, 0.06) 0%, transparent 65%);
    pointer-events: none;
}
.activity-grid {
    display: grid;
    grid-template-columns: 1fr 1.15fr;
    gap: clamp(2rem, 5vw, 5rem);
    align-items: center;
    position: relative;
    z-index: 1;
}
.activity-content .section-title {
    font-size: var(--pas-titre);
    line-height: 1.05;
    letter-spacing: -0.04em;
    margin-bottom: 1.5rem;
}
.activity-content .section-title .reveal-wrap { white-space: nowrap; }
.activity-accent {
    color: var(--accent-light, #60a5fa);
    }
.activity-accent .split-char {
    color: var(--accent-light, #60a5fa);
    }
.activity-content .section-sub {
    font-size: var(--pas-lecture);
    color: var(--gray-2);
    line-height: 1.7;
    max-width: 480px;
    margin-bottom: 2rem;
}
.activity-meta {
    display: flex;
    gap: 1.5rem;
    flex-wrap: wrap;
    font-family: var(--mono);
    font-size: var(--pas-etiquette);
    color: rgba(255, 255, 255, 0.62); /* a11y : contraste AA */
}
.activity-meta-item {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
}
.activity-meta-item strong {
    color: var(--gray-1);
    font-weight: 500;
}
.activity-pulse {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--green);
    box-shadow: 0 0 10px rgba(74, 222, 128, 0.85);
    flex-shrink: 0;
}

/* Terminal */
.activity-terminal {
    background: #0a0e14;
    border: 1px solid rgba(255, 255, 255, 0.07);
    border-radius: 12px;
    overflow: hidden;
    box-shadow:
        0 28px 80px rgba(0, 0, 0, 0.5),
        0 0 0 1px rgba(74, 222, 128, 0.1) inset;
}
.terminal-bar {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.75rem 1rem;
    background: rgba(255, 255, 255, 0.02);
    border-bottom: 1px solid rgba(255, 255, 255, 0.05);
    font-family: var(--mono);
    font-size: var(--pas-etiquette);
    color: var(--gray-3);
}
.terminal-bar .wdot {
    width: 11px;
    height: 11px;
    border-radius: 50%;
    margin-right: 0.25rem;
}
.terminal-bar .wd-r { background: #ff5f56; }
.terminal-bar .wd-y { background: #ffbd2e; }
.terminal-bar .wd-g { background: #27c93f; }
.terminal-name {
    margin-left: 0.5rem;
    flex: 1;
    text-align: center;
    color: rgba(255, 255, 255, 0.62); /* a11y : contraste AA */
    letter-spacing: 0.04em;
}
.terminal-rec {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    color: rgba(255, 95, 86, 0.85);
    font-weight: 600;
    font-size: var(--pas-etiquette);
    letter-spacing: 0.18em;
}
.terminal-rec-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #ff5f56;
    box-shadow: 0 0 8px rgba(255, 95, 86, 0.7);
    animation: rec-blink 1.4s ease-in-out infinite;
}
@keyframes rec-blink {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.3; }
}
.terminal-body {
    padding: 1.25rem 1.5rem 1.5rem;
    height: 320px;
    overflow: hidden;
    font-family: var(--mono);
    font-size: var(--pas-etiquette);
    line-height: 1.7;
    color: rgba(255, 255, 255, 0.78);
    position: relative;
    mask-image: linear-gradient(180deg, transparent 0%, #000 12%, #000 100%);
    -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 12%, #000 100%);
}
.terminal-line {
    display: flex;
    gap: 0.75rem;
    align-items: flex-start;
    margin-bottom: 0.4rem;
    opacity: 0;
    transform: translateY(6px);
    animation: line-in 0.35s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
@keyframes line-in {
    to { opacity: 1; transform: translateY(0); }
}
.terminal-time {
    color: var(--gray-3);
    flex-shrink: 0;
    width: 5.5em;
}
.terminal-icon {
    flex-shrink: 0;
    width: 1em;
    text-align: center;
    color: var(--green);
}
.terminal-icon.icon-blue { color: var(--accent-light); }
.terminal-icon.icon-yellow { color: #fbbf24; }
.terminal-icon.icon-pink { color: #f472b6; }
.terminal-msg {
    color: rgba(255, 255, 255, 0.85);
    flex: 1;
    word-break: break-word;
}
.terminal-msg .tk-key { color: var(--accent-light); }
.terminal-msg .tk-str { color: var(--green); }
.terminal-msg .tk-num { color: #fbbf24; }
.terminal-msg .tk-cmd { color: #f472b6; font-style: italic; }
.terminal-msg .tk-mute { color: var(--gray-3); }
.terminal-cursor-line {
    display: inline-block;
    width: 7px;
    height: 0.95em;
    background: var(--green);
    vertical-align: -0.15em;
    margin-left: 0.25rem;
    animation: term-blink 0.9s steps(1, end) infinite;
}
@keyframes term-blink {
    0%, 50%   { opacity: 1; }
    50.01%, 100% { opacity: 0; }
}

/* Mobile */
@media (max-width: 880px) {
    .activity-grid { grid-template-columns: 1fr; gap: 3rem; }
    .activity-content .section-sub { max-width: none; }
    .terminal-body { height: 280px; font-size: var(--pas-etiquette); }
}
@media (prefers-reduced-motion: reduce) {
    .terminal-line { animation: none; opacity: 1; transform: none; }
    .terminal-rec-dot { animation: none; }
    .terminal-cursor-line { animation: none; }
}

/* === Fix gradient "ensemble." sur split-chars (contact title) === */
.contact-title-accent .split-char {
    color: var(--accent-light, #60a5fa);
    }

/* ================================================
   SOUS-PAGES — Élévations parallèles aux index
   ================================================ */

/* === Proj-gallery: clip-path reveal staggered (comme bento) === */
.proj-gallery-item img {
    clip-path: inset(0 100% 0 0);
    transition:
        clip-path 1.05s var(--ease-out-expo),
        transform 0.9s cubic-bezier(0.22, 1, 0.36, 1),
        filter 0.6s ease;
}
.proj-gallery.fade-in.visible .proj-gallery-item img,
.fade-in.visible .proj-gallery-item img {
    clip-path: inset(0 0 0 0);
}
.proj-gallery.fade-in.visible .proj-gallery-item:nth-child(1) img { transition-delay: 0.10s, 0s, 0s; }
.proj-gallery.fade-in.visible .proj-gallery-item:nth-child(2) img { transition-delay: 0.22s, 0s, 0s; }
.proj-gallery.fade-in.visible .proj-gallery-item:nth-child(3) img { transition-delay: 0.34s, 0s, 0s; }
.proj-gallery.fade-in.visible .proj-gallery-item:nth-child(4) img { transition-delay: 0.46s, 0s, 0s; }
.proj-gallery.fade-in.visible .proj-gallery-item:nth-child(5) img { transition-delay: 0.58s, 0s, 0s; }
.proj-gallery.fade-in.visible .proj-gallery-item:nth-child(6) img { transition-delay: 0.70s, 0s, 0s; }
.proj-gallery.fade-in.visible .proj-gallery-item:nth-child(7) img { transition-delay: 0.82s, 0s, 0s; }
.proj-gallery.fade-in.visible .proj-gallery-item:nth-child(8) img { transition-delay: 0.94s, 0s, 0s; }

/* === Proj-gallery item: immersive hover (image scale + tilt + brightness) === */
.proj-gallery-item {
    isolation: isolate;
    position: relative;
}
.proj-gallery-item:hover img {
    transform: scale(1.06) rotate(-0.5deg);
    filter: brightness(1.08) saturate(1.15) contrast(1.04);
}
.proj-gallery-item::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    padding: 1.5px;
    background: linear-gradient(135deg, var(--accent), var(--green));
    background-size: 200% 200%;
    -webkit-mask:
        linear-gradient(#000 0 0) content-box,
        linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask-composite: exclude;
    opacity: 0;
    transition: opacity 0.5s ease;
    pointer-events: none;
    z-index: 3;
    
}
.proj-gallery-item:hover::after { opacity: 0.75; }

/* === Proj-overview-img: same clip-path reveal === */
.proj-overview-img img {
    clip-path: inset(0 100% 0 0);
    transition: clip-path 1.05s var(--ease-out-expo) 0.1s, transform 0.6s ease;
}
.proj-overview-aside.fade-in.visible .proj-overview-img img {
    clip-path: inset(0 0 0 0);
}

/* === Proj-tags: stagger float-in bouncy === */
.proj-tags .proj-tag {
    opacity: 0;
    transform: translateY(15px) scale(0.92);
    transition:
        opacity 0.5s var(--ease-out-expo),
        transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.fade-in.visible .proj-tags .proj-tag {
    opacity: 1;
    transform: translateY(0) scale(1);
}
.fade-in.visible .proj-tags .proj-tag:nth-child(1) { transition-delay: 0.15s; }
.fade-in.visible .proj-tags .proj-tag:nth-child(2) { transition-delay: 0.22s; }
.fade-in.visible .proj-tags .proj-tag:nth-child(3) { transition-delay: 0.29s; }
.fade-in.visible .proj-tags .proj-tag:nth-child(4) { transition-delay: 0.36s; }
.fade-in.visible .proj-tags .proj-tag:nth-child(5) { transition-delay: 0.43s; }
.fade-in.visible .proj-tags .proj-tag:nth-child(6) { transition-delay: 0.50s; }
.fade-in.visible .proj-tags .proj-tag:nth-child(7) { transition-delay: 0.57s; }

/* === Proj-meta-bar: stagger in === */
.proj-meta-bar .proj-meta-item {
    opacity: 0;
    transform: translateY(10px);
    transition:
        opacity 0.5s var(--ease-out-expo),
        transform 0.5s var(--ease-out-expo);
}
.proj-meta-bar.fade-in.visible .proj-meta-item:nth-child(1) { transition-delay: 0.05s; }
.proj-meta-bar.fade-in.visible .proj-meta-item:nth-child(2) { transition-delay: 0.15s; }
.proj-meta-bar.fade-in.visible .proj-meta-item:nth-child(3) { transition-delay: 0.25s; }
.proj-meta-bar.fade-in.visible .proj-meta-item:nth-child(4) { transition-delay: 0.35s; }
.proj-meta-bar.fade-in.visible .proj-meta-item {
    opacity: 1;
    transform: translateY(0);
}

/* === Proj-feature-item / proj-process-step: stagger === */
.proj-features .proj-feature-item,
.proj-process .proj-process-step {
    opacity: 0;
    transform: translateY(20px);
    transition:
        opacity 0.6s var(--ease-out-expo),
        transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.fade-in.visible .proj-features .proj-feature-item,
.fade-in.visible .proj-process .proj-process-step,
.proj-features.fade-in.visible .proj-feature-item,
.proj-process.fade-in.visible .proj-process-step {
    opacity: 1;
    transform: translateY(0);
}
.fade-in.visible .proj-features .proj-feature-item:nth-child(1),
.proj-features.fade-in.visible .proj-feature-item:nth-child(1) { transition-delay: 0.1s; }
.fade-in.visible .proj-features .proj-feature-item:nth-child(2),
.proj-features.fade-in.visible .proj-feature-item:nth-child(2) { transition-delay: 0.25s; }
.fade-in.visible .proj-features .proj-feature-item:nth-child(3),
.proj-features.fade-in.visible .proj-feature-item:nth-child(3) { transition-delay: 0.4s; }
.fade-in.visible .proj-features .proj-feature-item:nth-child(4),
.proj-features.fade-in.visible .proj-feature-item:nth-child(4) { transition-delay: 0.55s; }

/* === Proj-hero parallax (extend hero scroll cinematic to sub-pages) === */
.proj-hero .proj-hero-body {
    will-change: transform, opacity;
}

/* Reduced-motion overrides pour sous-pages */
@media (prefers-reduced-motion: reduce) {
    .proj-gallery-item img,
    .proj-overview-img img {
        clip-path: none !important;
        transition: none !important;
    }
    .proj-gallery-item:hover img { transform: none !important; filter: none !important; }
    .proj-tags .proj-tag,
    .proj-meta-bar .proj-meta-item,
    .proj-features .proj-feature-item,
    .proj-process .proj-process-step {
        opacity: 1 !important;
        transform: none !important;
        transition: none !important;
    }
}

/* ========================================================================
   REFONTE 2026 — Structure héritée du brutalist (tokens couleur supprimés)
   Les overrides chartreuse ont été retirés (cf. Navy Électrique en L+).
   Reste : typography, section-numbers, anti-IA structures.
   ======================================================================== */

/* === Typography hierarchy boost === */
.hero-title,
.section-title,
.proj-title {
    font-weight: 900;
    letter-spacing: -0.055em;
    line-height: 0.92;
}

.hero-title {
    font-size: var(--pas-affirmation);
}

.section-title {
    font-size: var(--pas-titre);
}

#contact .section-title {
    font-size: var(--pas-titre);
    letter-spacing: -0.07em;
    line-height: 0.88;
}

.proj-title {
    font-size: var(--pas-titre);
    line-height: 0.92;
}

/* Italic moments via Onest italic on lead text */
.hero-sub,
.proj-lead,
.section-sub:not([class*="-coords"]) {
    font-family: var(--font);
    font-weight: 400;
    font-size: var(--pas-lecture);
    line-height: 1.65;
    color: var(--gray-1);
    /* plus de plafond : le texte va au bord de son conteneur, qui borne
       déjà la mesure (.container max-width 1160). Le cap de 540px
       arrêtait la phrase en plein milieu de la colonne. */
}

/* === Section numbers décoratifs (01-08) === */
section[id]:not(#home) {
    position: relative;
}
section[id]:not(#home) > .container {
    position: relative;
}
/* Le petit numéro de coin vivait ici (absolu, en haut à DROITE, z-index 5 :
   il s'écrivait donc PAR-DESSUS le contenu — mesuré sur #stack et #contact,
   où il tombait en travers des cartes bleues et y devenait illisible en
   thème clair, --gray-3 virant à l'encre sombre). Retiré : il doublait le
   filigrane XXL du module 041 (retiré aussi) sans être plus lisible que lui.
   Remplacé par l'ouverture éditoriale du module 128 — même information, en
   flux, en tête de section, au-dessus d'un filet pleine largeur. */

/* === Hero: subtle warm noise overlay === */
.grain {
    opacity: 0.04;
    mix-blend-mode: overlay;
}

/* === Editorial drop cap on about-text first paragraph (structure ; couleurs overridées en Navy) === */
.about-text:first-of-type::first-letter {
    font-family: var(--display);
    /* Hors échelle des 6 pas (tâche 4) : lettrine en em, proportionnelle au
       paragraphe qui la porte (déjà sur un jeton), pas un jeton fixe. */
    font-size: 4em;
    font-weight: 900;
    float: left;
    line-height: 0.85;
    margin: 0.05em 0.12em 0 -0.03em;
}

/* === Mobile tweaks === */
@media (max-width: 768px) {
    .hero-title { font-size: var(--pas-affirmation); }
    /* Le `display:none` mobile du numéro de coin est parti avec lui : le
       même sélecteur sert maintenant l'ouverture éditoriale (module 128),
       qui doit rester VISIBLE sur mobile (le portfolio l'affiche partout).
       Le laisser ici l'aurait masquée sous 768px sans que rien ne le dise. */
    .about-text:first-of-type::first-letter { font-size: 3em; } /* même lettrine, variante mobile (tâche 4) */
}

/* === Fix titres : empêche split-chars de casser les mots === */
.hero-title,
.section-title,
.proj-title {
    word-break: keep-all;
    overflow-wrap: normal;
    hyphens: none;
}
.hero-title {
    font-size: var(--pas-affirmation);
}
.section-title {
    font-size: var(--pas-titre);
}
#contact .section-title {
    font-size: var(--pas-titre);
}
.proj-title {
    font-size: var(--pas-titre);
}

/* Hero column un poil plus large pour respirer (desktop seulement) */
@media (min-width: 901px) {
    .hero-inner {
        grid-template-columns: minmax(0, 1.3fr) minmax(0, 0.85fr);
    }
}

/* ========================================================================
   ANTI-IA — Éléments humains personnels (sticky notes, signature, hors-code)
   ======================================================================== */

/* === Sticky notes — comme des post-it manuscrits === */
.sticky-note {
    position: absolute;
    background: rgba(212, 255, 0, 0.08);
    border: 1px dashed rgba(212, 255, 0, 0.45);
    padding: 0.65rem 0.9rem;
    font-family: var(--mono);
    font-size: var(--pas-etiquette);
    line-height: 1.4;
    color: rgba(250, 246, 236, 0.78);
    border-radius: 2px;
    transform: rotate(-2.5deg);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
    z-index: 4;
    max-width: 180px;
    pointer-events: none;
}
.sticky-note small {
    font-size: var(--pas-etiquette);
    opacity: 0.6;
    font-style: italic;
}

.sticky-note-hero {
    bottom: -3rem;
    right: 18%;
    transform: rotate(3deg);
    background: rgba(255, 87, 34, 0.06);
    border-color: rgba(255, 87, 34, 0.4);
}

.sticky-note-bento {
    top: -1.5rem;
    left: 1rem;
    transform: rotate(-3deg);
    z-index: 10;
    background: rgba(212, 255, 0, 0.10);
}

@media (max-width: 768px) {
    .sticky-note {
    position: absolute;
    background: rgba(5, 8, 17, 0.85);
    border-left: 2px solid var(--accent, #0066ff);
    padding: 0.6rem 0.85rem;
    font-family: var(--mono);
    font-size: var(--pas-etiquette);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    line-height: 1.5;
    color: rgba(226, 232, 240, 0.75);
    z-index: 4;
    max-width: 200px;
    pointer-events: none;
}
}

/* === Section Hors-code === */
@media (max-width: 880px) {
    }

/* === Signature SVG footer (vectorisée) === */
.footer-signature {
    display: flex;
    align-items: center;
    flex-shrink: 0;
}
.footer-signature-svg {
    width: 110px;
    height: auto;
    aspect-ratio: 105 / 68;
    display: block;
    opacity: 0;
    transform: translateY(6px);
    animation: signature-fade-in 1.4s var(--ease-out-expo) 0.4s forwards;
}
@keyframes signature-fade-in {
    to { opacity: 1; transform: translateY(0); }
}
.footer-copy-meta {
    color: var(--gray-3);
    font-style: italic;
    margin-left: 0.25rem;
}

@media (prefers-reduced-motion: reduce) {
    .footer-signature-svg { animation: none; opacity: 1; transform: none; }
}

@media (max-width: 640px) {
    .footer-signature-svg { width: 90px; }
    .footer-copy-meta { display: block; margin-left: 0; margin-top: 0.25rem; }
}

/* ========================================================================
   REFONTE 2026 v2 — "Navy Électrique" Premium SaaS
   Override le brutalist en final cascade
   Inspiré de linear.app · vercel.com · framer.com
   ======================================================================== */

:root {
    /* Navy deep — dark bleu plutôt que warm dark */
    --black: #050811;
    --dark-1: #0a0e1a;
    --dark-2: #0f1525;
    --dark-3: #161e35;
    --dark-4: #1f2a47;

    /* Texte blanc pur, subtil bleu undertone */
    --white: #ffffff;
    --gray-1: rgba(255, 255, 255, 0.95);
    --gray-2: rgba(255, 255, 255, 0.62);
    --gray-3: rgba(255, 255, 255, 0.4);
    --gray-4: rgba(255, 255, 255, 0.14);
    --gray-5: rgba(255, 255, 255, 0.07);

    /* Bleu électrique principal — neutralisé pour --accent/--accent-light par
       html[data-couloir] (132), spécificité supérieure à :root, quel que
       soit l'ordre des fichiers : ces deux lignes ne peignent plus rien,
       gardées pour ne pas casser le :root si 132 disparaissait. --accent-deep
       n'est PAS repris par 132 (tâche 8) : dérivé de l'accent actif plutôt
       que figé, pour suivre le couloir sans choisir une nouvelle teinte
       (mise au point fine du contraste = tâche 6). */
    --accent: #3b82f6;
    --accent-light: #60a5fa;
    --accent-deep: color-mix(in srgb, var(--accent) 82%, black);

    /* Violet pour les moments / accents secondaires */
    --terra: #8b5cf6;
    --terra-light: #a78bfa;

    /* Vert live signals seulement */
    --green: #10b981;
}

body {
    background: var(--black);
    color: var(--gray-1);
}

/* === Typography refinement — moins agressif que brutalist === */
.hero-title,
.section-title,
.proj-title {
    font-weight: 800;
    letter-spacing: -0.045em;
    line-height: 0.96;
}

.hero-title {
    font-size: var(--pas-affirmation);
}
.section-title {
    font-size: var(--pas-titre);
}
#contact .section-title {
    font-size: var(--pas-titre);
    letter-spacing: -0.055em;
}

/* === Accent updates: bleu électrique === */
.location-accent,
.activity-accent,
.contact-title-accent {
    color: var(--accent-light, #60a5fa);
    }
.location-accent .split-char,
.activity-accent .split-char,
.contact-title-accent .split-char {
    color: var(--accent-light, #60a5fa);
    }
.proj-title .accent { color: var(--accent); }

/* Hero typewriter color */
.typewriter-text { color: var(--accent); }
.typewriter-cursor { background: var(--accent); }

/* Eyebrow labels */
/* --accent-texte, pas --accent : 12px en capitales espacées sur un aplat,
   c'est un rôle de TEXTE — voir 132-couloirs-jetons.css, point 3. */
.section-label,
.proj-section-label {
    color: var(--accent-texte);
    font-size: var(--pas-etiquette);
    font-weight: 600;
    letter-spacing: 0.22em;
    text-transform: uppercase;
}
.proj-section-label.design-label { color: var(--terra-light); }

/* === Buttons — bleu solid premium === */
.btn-primary {
    background: var(--accent);
    color: var(--white);
    font-weight: 600;
    border-radius: 10px;
    padding: 1rem 1.6rem;
    /* PAS de box-shadow — même raison que .btn-submit plus bas : la charte
       bannit les halos, et le module 100 nomme la famille. Retiré ici pour
       que le halo n'existe plus, plutôt que d'être éteint plus loin. */
}
.btn-primary svg { stroke: var(--white); }
.btn-primary:hover {
    background: var(--accent-deep);
}

.btn-secondary:hover {
    color: var(--accent);
    border-bottom-color: var(--accent);
}

/* === Side dot — bleu électrique === */
.side-dot.active {
    background: radial-gradient(circle, var(--accent) 5px, transparent 5.5px);
}
.side-dot.active::after {
    box-shadow: 0 0 14px rgba(59, 130, 246, 0.7);
}
.side-dot:hover {
    background: radial-gradient(circle, var(--accent-light) 4px, transparent 4.5px);
}

/* === Hero badges & dual-tags — bleu/violet === */
.hero-badge { color: var(--gray-1); }
.hero-badge-dot { background: var(--green); }
.hero-dual-tag.code {
    color: var(--accent-light);
    border-color: rgba(59, 130, 246, 0.35);
    background: rgba(59, 130, 246, 0.06);
}
.hero-dual-tag.design {
    color: var(--terra-light);
    border-color: rgba(139, 92, 246, 0.35);
    background: rgba(139, 92, 246, 0.06);
}
.hero-dual-tag.code:hover {
    box-shadow: 0 16px 32px rgba(59, 130, 246, 0.25);
    border-color: rgba(59, 130, 246, 0.65);
}
.hero-dual-tag.design:hover {
    box-shadow: 0 16px 32px rgba(139, 92, 246, 0.25);
    border-color: rgba(139, 92, 246, 0.65);
}

/* === Service chips === */
.service-chip.chip-dev {
    color: var(--accent-light);
    background: rgba(59, 130, 246, 0.1);
    border-color: rgba(59, 130, 246, 0.3);
}
.service-chip.chip-design {
    color: var(--terra-light);
    background: rgba(139, 92, 246, 0.1);
    border-color: rgba(139, 92, 246, 0.3);
}
.service-chip.chip-brand {
    color: #f472b6;
    background: rgba(244, 114, 182, 0.1);
    border-color: rgba(244, 114, 182, 0.3);
}

/* === Bento — glassmorphism premium === */
.bento-card {
    background: rgba(15, 21, 37, 0.5);
    border: 1px solid rgba(255, 255, 255, 0.08);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}
.bento-badge.dev {
    color: var(--accent-light);
    border-color: rgba(59, 130, 246, 0.35);
    background: rgba(59, 130, 246, 0.12);
    backdrop-filter: blur(6px);
}
.bento-badge.design {
    color: var(--terra-light);
    border-color: rgba(139, 92, 246, 0.35);
    background: rgba(139, 92, 246, 0.12);
    backdrop-filter: blur(6px);
}
.bento-card::after {
    background: linear-gradient(135deg, var(--accent), var(--terra), var(--accent));
}
.bento-title::after {
    background: linear-gradient(90deg, var(--accent), var(--terra));
}

/* === Form === */
.form-group input,
.form-group textarea {
    background: rgba(15, 21, 37, 0.5);
    border: 1px solid rgba(255, 255, 255, 0.08);
    backdrop-filter: blur(8px);
    border-radius: 10px;
}
.form-group input:focus,
.form-group textarea:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
    outline: none;
}
.btn-submit {
    background: var(--accent);
    color: var(--white);
    border-radius: 10px;
    /* PAS de box-shadow. Le module 100 a banni les halos colorés sous les
       boutons — « le marqueur le plus reconnaissable » du design IA — et les
       a éteints sur .hero-actions .btn-primary, .btn-secondary et
       .showcase-status-cta. Ce bouton-ci avait gardé le sien par simple
       oubli de cette passe : même rôle que .btn-primary, qui n'en a aucun.
       La règle existait, elle s'applique ici aussi.
       Le contour d'un pixel part avec : mesuré au pixel sur les 4
       combinaisons, il rendait 1,16 à 1,22:1 contre l'aplat de #contact —
       invisible. Ce n'était pas une bordure qui détache, c'était le premier
       pixel du halo. Le bouton se détache tout seul par son aplat (1,95 à
       2,47:1 selon la combinaison). */
}
.btn-submit:hover { background: var(--accent-deep); }

/* === Tech icons === */
.tech-icon-item {
    background: rgba(15, 21, 37, 0.45);
    border: 1px solid rgba(255, 255, 255, 0.06);
    backdrop-filter: blur(8px);
    border-radius: 10px;
}
.tech-icon-item:hover {
    border-color: rgba(59, 130, 246, 0.4);
    background: rgba(59, 130, 246, 0.05);
}

/* === Skill levels === */
.level-advanced {
    color: var(--accent-light);
    border-color: rgba(59, 130, 246, 0.4);
    background: rgba(59, 130, 246, 0.1);
}
.level-current {
    color: var(--terra-light);
    border-color: rgba(139, 92, 246, 0.4);
    background: rgba(139, 92, 246, 0.1);
}
.skill-tag.tag-dev:hover { border-color: rgba(59, 130, 246, 0.45); color: var(--accent-light); }
.skill-tag.tag-design:hover { border-color: rgba(139, 92, 246, 0.45); color: var(--terra-light); }

/* === Live signals stay green === */
.contact-avail-dot { background: var(--green); }
.activity-pulse { background: var(--green); box-shadow: 0 0 10px rgba(16, 185, 129, 0.85); }
.location-globe-dot { background: var(--green); box-shadow: 0 0 10px rgba(16, 185, 129, 0.85); }

/* === Hero mesh — navy/blue/violet === */
#hero-mesh {
    --gradient-color-1: #0a0e1a;  /* navy base (dominant) */
    --gradient-color-2: #0066ff;  /* --accent bleu vif (dominant) */
    --gradient-color-3: #0a5f8c;  /* cyan assombri = touche */
    --gradient-color-4: #1c7a50;  /* vert assombri = touche */
    opacity: 1;
}
#hero-mesh.loaded { opacity: 0.85; }

/* Fallback statique (mobile / reduced-motion / no-WebGL) — flux navy→cyan→vert figé */
#hero-mesh.hero-mesh-fallback {
    display: block !important;
    opacity: 0.85;
    background:
        radial-gradient(ellipse 80% 60% at 28% 32%, rgba(0, 102, 255, 0.40), transparent 60%),
        radial-gradient(ellipse 70% 70% at 78% 68%, rgba(0, 212, 170, 0.32), transparent 62%),
        radial-gradient(ellipse 55% 45% at 62% 18%, rgba(74, 222, 128, 0.20), transparent 55%),
        #050811;
}
html[data-theme="light"] #hero-mesh.hero-mesh-fallback {
    opacity: 0.7;
    background:
        radial-gradient(ellipse 80% 60% at 28% 32%, rgba(0, 102, 255, 0.20), transparent 60%),
        radial-gradient(ellipse 70% 70% at 78% 68%, rgba(0, 212, 170, 0.18), transparent 62%),
        #eef3ff;
}

/* === Code window syntax — VS Code Dark+ adapté navy === */
.hc-kw { color: #c084fc; }   /* violet keyword */
.hc-fn { color: #60a5fa; }   /* blue function */
.hc-var { color: #fbbf24; }  /* amber var */
.hc-str { color: #34d399; }  /* mint string */
.hc-op, .hc-p { color: var(--gray-2); }

/* === Scroll progress === */
#progress-bar {
    background: linear-gradient(90deg, var(--accent), var(--terra));
    box-shadow: 0 0 8px rgba(59, 130, 246, 0.5);
}

/* === Terminal === */
.terminal-msg .tk-cmd { color: var(--accent-light); }
.terminal-msg .tk-key { color: var(--terra-light); }
.terminal-msg .tk-num { color: #fbbf24; }
.terminal-cursor-line { background: var(--accent); }

/* === Misc accents === */
.about-quote { border-left-color: var(--accent); }
.proj-badge {
    background: rgba(59, 130, 246, 0.1);
    color: var(--accent-light);
    border-color: rgba(59, 130, 246, 0.32);
}
.proj-badge.design {
    background: rgba(139, 92, 246, 0.1);
    color: var(--terra-light);
    border-color: rgba(139, 92, 246, 0.32);
}

/* === Section orbs — bleu === */
#about::after,
#work::after,
#services::after,
#skills::before,
#contact::before,
#contact::after,
#home::before,
#home::after {
    background: radial-gradient(circle, rgba(59, 130, 246, 0.08) 0%, transparent 65%) !important;
}

/* === Drop cap — bleu→violet === */
.about-text:first-of-type::first-letter {
    color: var(--accent-light, #60a5fa);
    }

/* === Sticky notes — adapté navy === */
.sticky-note {
    background: rgba(59, 130, 246, 0.08);
    border: 1px dashed rgba(59, 130, 246, 0.4);
    color: var(--gray-1);
    transform: rotate(-1.5deg);
    backdrop-filter: blur(6px);
}
.sticky-note-hero {
    background: rgba(139, 92, 246, 0.08);
    border-color: rgba(139, 92, 246, 0.4);
    transform: rotate(2deg);
}
.sticky-note-bento {
    background: rgba(59, 130, 246, 0.10);
    transform: rotate(-2deg);
}

/* === Hors-code cards refined === */
.horscode-card {
    background: rgba(15, 21, 37, 0.55);
    border: 1px solid rgba(255, 255, 255, 0.08);
    backdrop-filter: blur(10px);
    border-radius: 14px;
}
.horscode-card:nth-child(1) { transform: rotate(0); }
.horscode-card:nth-child(2) { transform: rotate(0) translateY(-8px); }
.horscode-card:nth-child(3) { transform: rotate(0); }
.horscode-card:hover {
    border-color: rgba(59, 130, 246, 0.3);
    background: rgba(59, 130, 246, 0.05);
    transform: translateY(-2px);
}
.horscode-meta { color: var(--accent-light); }

/* === Footer signature — bleu électrique === */
.footer-signature { color: var(--accent); }

/* === Border radii doux partout — premium SaaS === */
.bento-card,
.service-card,
.proj-gallery-item,
.proj-overview-img,
.activity-terminal,
.proj-kpi {
    border-radius: 14px;
}

/* Scroll progress bar height plus discret */
#progress-bar { height: 2px; }

/* Header 100% opaque — couvre totalement le contenu qui scroll derrière.
   Anti-pattern : translucent à 0.7 ou 0.96 laisse passer marquees = chaos visuel. */
#header {
    background: #050811 !important;
    border-bottom: 1px solid rgba(255, 255, 255, 0.05);
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
html[data-theme="light"] #header {
    background: #faf6ec !important;
    border-bottom-color: rgba(10, 14, 26, 0.08);
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
}


/* Mobile : le fond garde une vie (le WebGL est coupé pour la batterie). Le
   fallback dérive doucement — transform seul = compositeur, zéro repaint, zéro
   rAF, pas de drain. Coupé en reduced-motion. */
#hero-mesh.hero-mesh-fallback {
    animation: hero-mesh-drift 15s ease-in-out infinite alternate;
    transform-origin: 58% 42%;
}
@keyframes hero-mesh-drift {
    from { transform: translate3d(-9%, -6%, 0) scale(1.35) rotate(-5deg); }
    to   { transform: translate3d(9%, 7%, 0) scale(1.35) rotate(5deg); }
}
@media (prefers-reduced-motion: reduce) {
    #hero-mesh.hero-mesh-fallback { animation: none; transform: none; }
}

/* ========================================================================
   EXPERTISE WOW — Marquee infini + spotlight cursor + currently rotator
   ======================================================================== */

#skills {
    position: relative;
    overflow: hidden;
}

/* Spotlight cursor follow — radial gradient suit la souris */
#skills::before {
    content: "";
    position: absolute;
    inset: 0;
    background: radial-gradient(
        500px circle at var(--mx, 50%) var(--my, 50%),
        rgba(59, 130, 246, 0.12),
        transparent 60%
    );
    pointer-events: none;
    z-index: 1;
    transition: opacity 0.4s;
    opacity: 0;
}
#skills:hover::before { opacity: 1; }
#skills > .container { position: relative; z-index: 2; }

/* === Currently exploring banner === */
.tech-currently {
    display: inline-flex;
    align-items: center;
    gap: 0.85rem;
    margin: 2.5rem 0 2rem;
    padding: 0.55rem 1rem 0.55rem 0.75rem;
    background: rgba(59, 130, 246, 0.06);
    border: 1px solid rgba(59, 130, 246, 0.25);
    border-radius: 6px;
    backdrop-filter: blur(8px);
    font-family: var(--mono);
    font-size: var(--pas-etiquette);
}
.tech-currently::before {
    content: "";
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--accent);
    box-shadow: 0 0 10px rgba(59, 130, 246, 0.85);
    margin-right: 0.15rem;
}
.tech-currently-label {
    color: rgba(255, 255, 255, 0.62); /* a11y : contraste AA sur fond navy */
    text-transform: uppercase;
    letter-spacing: 0.16em;
    font-size: var(--pas-etiquette);
    font-weight: 600;
}
.tech-currently-rotator {
    color: var(--accent);
    font-weight: 600;
    min-width: 8ch;
    display: inline-block;
}
.tech-currently-rotator::after {
    content: "";
    display: inline-block;
    width: 7px;
    height: 1em;
    background: var(--accent);
    margin-left: 4px;
    vertical-align: -0.15em;
    animation: term-blink 1s steps(1, end) infinite;
}

/* === Marquee tech section === */
.tech-marquee-section {
    display: flex;
    flex-direction: column;
    gap: 0.8rem;
    margin-bottom: 3rem;
}
.tech-marquee {
    overflow: hidden;
    padding: 0.5rem 0;
    -webkit-mask-image: linear-gradient(90deg, transparent 0%, black 8%, black 92%, transparent 100%);
    mask-image: linear-gradient(90deg, transparent 0%, black 8%, black 92%, transparent 100%);
}
.tech-marquee-track {
    display: flex;
    gap: 0.85rem;
    width: max-content;
    animation: tech-scroll-fwd 38s linear infinite;
    will-change: transform;
}
.tech-marquee[data-direction="reverse"] .tech-marquee-track {
    animation: tech-scroll-rev 42s linear infinite;
}
@keyframes tech-scroll-fwd {
    from { transform: translateX(0); }
    to   { transform: translateX(-50%); }
}
@keyframes tech-scroll-rev {
    from { transform: translateX(-50%); }
    to   { transform: translateX(0); }
}
.tech-marquee:hover .tech-marquee-track {
    animation-play-state: paused;
}

/* === Tech pill — interactive === */
.tech-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.65rem;
    padding: 0.6rem 1.1rem 0.6rem 0.85rem;
    background: rgba(15, 21, 37, 0.5);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 6px;
    font-family: var(--mono);
    font-size: var(--pas-appui);
    font-weight: 500;
    color: var(--gray-1);
    backdrop-filter: blur(8px);
    flex-shrink: 0;
    transition:
        transform 0.4s var(--ease-out-expo),
        border-color 0.3s,
        background 0.3s,
        box-shadow 0.3s;
    cursor: default;
}
.tech-pill img {
    flex-shrink: 0;
    transition: transform 0.4s ease, filter 0.3s;
    filter: grayscale(0.4) brightness(0.92);
}
.tech-pill:hover {
    transform: scale(1.08) translateY(-2px);
    border-color: rgba(59, 130, 246, 0.55);
    background: rgba(59, 130, 246, 0.1);
    color: var(--accent-light);
    box-shadow: 0 16px 32px rgba(59, 130, 246, 0.2);
}
.tech-pill:hover img {
    transform: rotate(-6deg) scale(1.12);
    filter: grayscale(0) brightness(1.1);
}

@media (prefers-reduced-motion: reduce) {
    .tech-marquee-track { animation: none !important; }
    .tech-currently-rotator::after { animation: none; }
}

@media (max-width: 768px) {
    .tech-pill { font-size: var(--pas-etiquette); padding: 0.5rem 0.9rem 0.5rem 0.7rem; }
    .tech-pill img { width: 18px; height: 18px; }
}

/* === Tooltip skills section: skill-level glow on view enter === */
.skill-levels.fade-in.visible .skill-tag {
    animation: skill-glow-in 0.6s var(--ease-out-expo) forwards;
}
.skill-levels.fade-in.visible .skill-tag:nth-child(1) { animation-delay: 0.05s; }
.skill-levels.fade-in.visible .skill-tag:nth-child(2) { animation-delay: 0.1s; }
.skill-levels.fade-in.visible .skill-tag:nth-child(3) { animation-delay: 0.15s; }
.skill-levels.fade-in.visible .skill-tag:nth-child(4) { animation-delay: 0.2s; }
.skill-levels.fade-in.visible .skill-tag:nth-child(5) { animation-delay: 0.25s; }
.skill-levels.fade-in.visible .skill-tag:nth-child(6) { animation-delay: 0.3s; }
.skill-levels.fade-in.visible .skill-tag:nth-child(7) { animation-delay: 0.35s; }
@keyframes skill-glow-in {
    0%   { transform: translateY(8px) scale(0.96); opacity: 0; }
    50%  { transform: translateY(-2px) scale(1.02); opacity: 1; }
    100% { transform: translateY(0) scale(1); opacity: 1; }
}

/* === Hide emoji column from horscode (already removed from HTML, fallback) === */
.horscode-emoji { display: none; }

/* =====================================================================
   WAOUH FEATURES — Voice intro, Design playground, Case study cinematic
   ===================================================================== */

/* --- B. Voice intro button (hero) --- */
.voice-intro-btn {
    background: transparent;
    border: 1px solid rgba(255, 255, 255, 0.16);
    color: rgba(226, 232, 240, 0.88);
    padding: 0.85rem 1.4rem;
    border-radius: 6px;
    font-size: var(--pas-appui);
    font-family: var(--font-body, 'Onest', system-ui, sans-serif);
    font-weight: 500;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.7rem;
    transition: border-color 0.3s var(--ease-out-expo), color 0.3s, transform 0.3s var(--ease-out-expo);
    margin-left: 0.25rem;
}
.voice-intro-btn:hover {
    border-color: var(--accent);
    color: #fff;
    transform: translateY(-2px);
}
.voice-intro-btn:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
}
.voice-intro-icon {
    display: inline-flex;
    align-items: center;
    gap: 2.5px;
    height: 18px;
}
.voice-bar {
    display: inline-block;
    width: 2.5px;
    height: 6px;
    background: currentColor;
    border-radius: 3px;
    transition: height 0.18s ease;
}
.voice-intro-btn[data-state="speaking"] {
    border-color: var(--accent);
    color: #fff;
    background: rgba(59, 130, 246, 0.08);
}
.voice-intro-btn[data-state="speaking"] .voice-bar {
    animation: voice-wave 0.85s ease-in-out infinite;
}
.voice-intro-btn[data-state="speaking"] .voice-bar:nth-child(1) { animation-delay: 0s; }
.voice-intro-btn[data-state="speaking"] .voice-bar:nth-child(2) { animation-delay: 0.12s; }
.voice-intro-btn[data-state="speaking"] .voice-bar:nth-child(3) { animation-delay: 0.24s; }
.voice-intro-btn[data-state="speaking"] .voice-bar:nth-child(4) { animation-delay: 0.36s; }
.voice-intro-btn[data-state="speaking"] .voice-bar:nth-child(5) { animation-delay: 0.48s; }
@keyframes voice-wave {
    0%, 100% { height: 6px; }
    50%      { height: 16px; }
}
@media (max-width: 640px) {
    .voice-intro-btn { width: 100%; justify-content: center; margin-left: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .voice-intro-btn[data-state="speaking"] .voice-bar { animation: none; }
}

/* --- A. Design playground (floating accent picker) --- */
.design-playground {
    position: fixed;
    bottom: 24px;
    left: 24px;
    z-index: 60;
    font-family: var(--font-body, 'Onest', system-ui, sans-serif);
}
.design-playground-toggle {
    background: rgba(10, 14, 26, 0.88);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 6px;
    padding: 0.55rem 1rem 0.55rem 0.6rem;
    color: #e2e8f0;
    font-size: var(--pas-etiquette);
    font-weight: 500;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    transition: border-color 0.3s, transform 0.3s var(--ease-out-expo), background 0.3s;
}
.design-playground-toggle:hover {
    border-color: rgba(255, 255, 255, 0.25);
    transform: translateY(-2px);
    background: rgba(15, 23, 42, 0.95);
}
.design-playground-toggle:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
}
.design-playground-dot {
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--accent), var(--accent-light));
    box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.06), 0 0 12px var(--accent);
    transition: box-shadow 0.3s;
}
.design-playground[data-state="open"] .design-playground-dot {
    box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.12), 0 0 18px var(--accent);
}
.design-playground-panel {
    position: absolute;
    bottom: calc(100% + 14px);
    left: 0;
    background: rgba(10, 14, 26, 0.96);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 16px;
    padding: 1.1rem 1.15rem 1rem;
    min-width: 250px;
    transform: scale(0.92) translateY(10px);
    opacity: 0;
    pointer-events: none;
    transition: transform 0.32s var(--ease-out-expo), opacity 0.25s;
    transform-origin: bottom left;
    box-shadow: 0 18px 50px -10px rgba(0, 0, 0, 0.6);
}
.design-playground[data-state="open"] .design-playground-panel {
    transform: scale(1) translateY(0);
    opacity: 1;
    pointer-events: auto;
}
.design-playground-title {
    font-size: var(--pas-appui);
    font-weight: 600;
    color: #f8fafc;
    margin: 0;
    letter-spacing: -0.005em;
}
.design-playground-hint {
    font-size: var(--pas-etiquette);
    color: rgba(226, 232, 240, 0.55);
    margin: 0.25rem 0 0.95rem;
    line-height: 1.4;
}
.design-playground-swatches {
    display: flex;
    gap: 0.55rem;
    margin-bottom: 0.85rem;
}
.dp-swatch {
    width: 38px;
    height: 38px;
    border-radius: 50%;
    border: 2px solid rgba(255, 255, 255, 0.08);
    background: var(--c);
    cursor: pointer;
    transition: transform 0.25s var(--ease-out-expo), border-color 0.25s, box-shadow 0.25s;
    padding: 0;
    position: relative;
}
.dp-swatch:hover {
    transform: scale(1.18);
    border-color: rgba(255, 255, 255, 0.2);
}
.dp-swatch[aria-pressed="true"] {
    border-color: white;
    box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.18), 0 0 18px var(--c);
}
.dp-reset {
    background: transparent;
    border: 1px solid rgba(255, 255, 255, 0.14);
    color: rgba(226, 232, 240, 0.78);
    font-size: var(--pas-etiquette);
    padding: 0.45rem 0.85rem;
    border-radius: 8px;
    cursor: pointer;
    width: 100%;
    transition: border-color 0.25s, color 0.25s, background 0.25s;
    font-family: inherit;
}
.dp-reset:hover {
    border-color: rgba(255, 255, 255, 0.28);
    color: #fff;
    background: rgba(255, 255, 255, 0.04);
}
@media (max-width: 640px) {
    .design-playground { bottom: 14px; left: 14px; }
    .design-playground-toggle .design-playground-label { display: none; }
    .design-playground-toggle { padding: 0.5rem 0.55rem; }
    .design-playground-panel { min-width: 220px; }
}

/* --- C. Case study cinematic (scroll-pinned Tangente) --- */
.case-study {
    position: relative;
    height: 350vh;
    background: #050811;
    isolation: isolate;
    contain: layout paint;
}
.case-study::before {
    content: '';
    position: absolute;
    inset: 0;
    background: radial-gradient(ellipse 60% 50% at 50% 50%, rgba(59, 130, 246, 0.06), transparent 70%);
    pointer-events: none;
    z-index: 0;
}
.case-study-pin {
    position: sticky;
    top: 0;
    height: 100vh;
    overflow: hidden;
    display: flex;
    align-items: center;
}
.case-study-stage {
    width: 100%;
    height: 100%;
    overflow: hidden;
}
.case-study-track {
    display: flex;
    height: 100%;
    will-change: transform;
    transform: translate3d(0, 0, 0);
}
.case-slide {
    flex: 0 0 100vw;
    height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: 0 8vw;
    color: #f8fafc;
    position: relative;
}
.case-slide-eyebrow,
.case-slide-num {
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    color: var(--accent);
    letter-spacing: 0.16em;
    text-transform: uppercase;
    font-weight: 500;
}
.case-slide-eyebrow { margin-bottom: 1.5rem; display: inline-block; }
.case-slide-title {
    font-family: 'Syne', system-ui, sans-serif;
    font-size: var(--pas-affirmation);
    font-weight: 700;
    line-height: 1.04;
    letter-spacing: -0.025em;
    max-width: 18ch;
    margin: 0 0 1.6rem;
}
.case-accent {
    color: var(--accent-light, #60a5fa);
    }
.case-slide-h {
    font-family: 'Syne', system-ui, sans-serif;
    font-size: var(--pas-affirmation);
    font-weight: 700;
    margin: 0.9rem 0 1.4rem;
    letter-spacing: -0.025em;
    line-height: 1.02;
}
.case-slide-text {
    font-size: var(--pas-lecture);
    color: rgba(226, 232, 240, 0.8);
    max-width: 52ch;
    line-height: 1.55;
    margin: 0;
}
.case-slide-hint {
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    color: rgba(96, 165, 250, 0.7);
    margin-top: 2.5rem;
    letter-spacing: 0.05em;
    animation: case-hint-pulse 2.4s ease-in-out infinite;
}
@keyframes case-hint-pulse {
    0%, 100% { opacity: 0.6; transform: translateX(0); }
    50%      { opacity: 1;   transform: translateX(6px); }
}
.case-slide-split {
    display: grid;
    grid-template-columns: 1.05fr 0.95fr;
    gap: 4vw;
    align-items: center;
    margin-top: 0.6rem;
}
.case-slide-text-col { display: flex; flex-direction: column; }
.case-slide-visual {
    border-radius: 18px;
    overflow: hidden;
    border: 1px solid rgba(255, 255, 255, 0.08);
    background: rgba(255, 255, 255, 0.02);
    box-shadow: 0 30px 80px -20px rgba(0, 0, 0, 0.55);
    aspect-ratio: 16 / 10;
}
.case-slide-visual img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.case-system-palette {
    display: flex;
    gap: 1rem;
    margin: 1.5rem 0 2rem;
    flex-wrap: wrap;
}
.case-system-color {
    flex: 1 1 200px;
    height: 110px;
    border-radius: 14px;
    color: #f8fafc;
    display: flex;
    align-items: flex-end;
    padding: 1rem 1.1rem;
    border: 1px solid rgba(255, 255, 255, 0.06);
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    letter-spacing: 0.04em;
    transition: transform 0.4s var(--ease-out-expo);
}
.case-system-color em {
    font-style: normal;
    opacity: 0.85;
}
.case-slide--system.is-active .case-system-color {
    transform: translateY(0);
}
.case-slide-cta {
    display: inline-flex;
    align-items: center;
    gap: 0.65rem;
    margin-top: 2rem;
    padding: 1rem 1.85rem;
    background: linear-gradient(115deg, var(--accent), var(--accent-light));
    color: #fff;
    text-decoration: none;
    border-radius: 6px;
    font-weight: 600;
    font-family: inherit;
    width: fit-content;
    box-shadow: 0 18px 40px -12px rgba(59, 130, 246, 0.5);
    transition: transform 0.3s var(--ease-out-expo), box-shadow 0.3s;
}
.case-slide-cta:hover {
    transform: translateY(-2px);
    box-shadow: 0 24px 50px -10px rgba(59, 130, 246, 0.6);
}

.case-study-progress {
    position: absolute;
    bottom: 5vh;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.6rem;
    z-index: 2;
    pointer-events: none;
}
.case-study-progress-bar {
    display: block;
    width: 240px;
    height: 2px;
    background: rgba(255, 255, 255, 0.08);
    border-radius: 3px;
    overflow: hidden;
    position: relative;
}
.case-study-progress-bar::before {
    content: '';
    position: absolute;
    inset: 0;
    width: var(--progress, 0%);
    background: linear-gradient(90deg, var(--accent), var(--accent-light));
    transition: width 0.18s linear;
    border-radius: 3px;
}
.case-study-counter {
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    color: rgba(255, 255, 255, 0.45);
    letter-spacing: 0.16em;
}

@media (max-width: 768px) {
    .case-study {
        height: auto;
        contain: none;
    }
    .case-study-pin {
        position: relative;
        top: auto;
        height: auto;
        display: block;
        overflow: visible;
    }
    .case-study-stage { height: auto; overflow: visible; }
    .case-study-track {
        display: block;
        transform: none !important;
    }
    .case-slide {
        flex: none;
        width: 100%;
        height: auto;
        min-height: auto;
        padding: 5rem 6vw;
    }
    .case-slide-split { grid-template-columns: 1fr; gap: 2rem; }
    .case-slide-visual { max-width: 100%; }
    .case-system-palette { gap: 0.75rem; }
    .case-system-color { flex: 1 1 100%; height: 80px; }
    .case-study-progress { display: none; }
    .case-slide-hint { display: none; }
}

@media (prefers-reduced-motion: reduce) {
    .case-study { height: auto; }
    .case-study-pin { position: relative; height: auto; }
    .case-study-track { display: block; transform: none !important; }
    .case-slide { flex: none; width: 100%; height: auto; padding: 4rem 6vw; }
    .case-study-progress { display: none; }
    .case-slide-hint { animation: none; }
}

/* =====================================================================
   MOBILE OPTIMIZATION PASS — performance, lisibilité, touch targets
   ===================================================================== */

@media (max-width: 768px) {
    /* --- Container padding mobile-first ---
       .hero-inner PARTAGE la gouttière : sans lui il restait sur --gap
       (24px) pendant que toutes les autres sections passaient à 20px →
       le rail gauche décrochait de 4px entre le héros et la suite. */
    .container,
    .hero-inner {
        padding: 0 1.25rem;
    }

    /* --- Section title qui dépasse: clamp ajusté pour mots longs FR --- */
    .section-title,
    .hero-title {
        word-break: normal !important;
        overflow-wrap: normal !important;
        word-wrap: normal !important;
        hyphens: manual !important;
    }
    .section-title {
        font-size: var(--pas-titre);
        line-height: 1.1;
        letter-spacing: -0.03em;
    }
    .hero-title {
        font-size: var(--pas-affirmation);
        line-height: 1.06;
        letter-spacing: -0.025em;
    }
    /* Reveal lines: laisser le wrap naturel se faire (entre mots, jamais mid-word) */
    .reveal-line {
        white-space: normal !important;
        word-break: normal !important;
        overflow-wrap: normal !important;
        display: inline !important;
    }
    .reveal-wrap {
        display: inline !important;
        max-width: 100%;
    }

    /* --- Marquees: ralentit (battery) + smaller height --- */
    .marquee-track,
    .text-marquee-track {
        animation-duration: 60s !important;
    }
    .tech-marquee-track {
        animation-duration: 50s !important;
    }
    .marquee-img {
        max-height: 100px;
    }

    /* --- Hero left accent bar: hide on mobile (only desktop has padding-left) --- */
    .hero-content::before { display: none !important; }

    /* --- Globe mobile: plus compact mais visible --- */
    .location-globe {
        max-width: 280px !important;
        margin: 1rem auto !important;
    }

    /* --- Bento mobile: une colonne verticale claire --- */
    .bento-grid {
        grid-template-columns: 1fr !important;
        grid-template-rows: auto !important;
        gap: 1rem;
    }
    .bento-card,
    .bento-card--featured,
    .bento-card--wide {
        grid-column: auto !important;
        grid-row: auto !important;
        aspect-ratio: 4 / 3;
    }

    /* --- Touch targets: min 44px sur tous les boutons/links primaires --- */
    .btn-primary,
    .btn-secondary,
    .btn-submit,
    .nav-cta,
    .voice-intro-btn,
    .case-slide-cta,
    .design-playground-toggle {
        min-height: 44px;
        padding-top: 0.75rem;
        padding-bottom: 0.75rem;
    }

    /* --- Hero actions: stack vertical full-width --- */
    .hero-actions {
        flex-direction: column;
        align-items: stretch;
        gap: 0.75rem;
        width: 100%;
        max-width: 320px;
    }
    .hero-actions > * {
        width: 100%;
        justify-content: center;
        text-align: center;
    }

    /* --- Side-nav dots: cache complètement sur mobile (pas hover possible) --- */
    .side-nav { display: none; }

    /* --- Sticky notes: souvent en off-screen sur mobile, on cache --- */
    .sticky-note { display: none; }

    /* Le rabotage mobile du numéro de section (font-size !important +
       opacity 0.4) est parti avec le numéro de coin (module 013) : le même
       pseudo-élément porte maintenant l'ouverture éditoriale (module 128),
       déjà dimensionnée sur --pas-etiquette et qui doit rester pleinement
       opaque. Le `!important` d'ici l'aurait emporté sur elle. */

    /* --- Hors-code grid: stack bien --- */
    .horscode-grid {
        grid-template-columns: 1fr;
        gap: 1.25rem;
    }

    /* --- Activity terminal: scroll-x au lieu de wrap --- */
    .activity-terminal {
        font-size: var(--pas-etiquette);
    }

    /* --- Design playground: encore plus petit & coin gauche --- */
    .design-playground-toggle .design-playground-label { display: none; }
    .design-playground-toggle {
        padding: 0.55rem;
    }

    /* --- Reduce all hover-only effects --- */
    .bento-card:hover,
    .service-card:hover {
        transform: none !important;
    }
}

/* --- Très petit écran: ajustements supplémentaires --- */
@media (max-width: 380px) {
    .hero-title { font-size: var(--pas-affirmation); }
    .section-title { font-size: var(--pas-titre); }
    .container,
    .hero-inner { padding: 0 1rem; }
    .hero-badge { font-size: var(--pas-etiquette); padding: 0.4rem 0.7rem; }
}

/* --- Économie batterie: pause animations hors-vue (déjà partiel) --- */
@media (max-width: 768px) {
    .marquee-img {
        will-change: auto;
    }
    /* Désactive les animations de fond coûteuses */
    .grain { opacity: 0.04; }
}

/* =====================================================================
   MOBILE WAOUH — swipe gallery, tilt parallax, haptic-ready interactions
   ===================================================================== */

@media (max-width: 768px) {
    /* === Bento swipe gallery: scroll-snap horizontal === */
    .bento-grid {
        display: flex !important;
        flex-direction: row !important;
        grid-template-columns: none !important;
        grid-template-rows: none !important;
        overflow-x: auto;
        overflow-y: hidden;
        scroll-snap-type: x mandatory;
        scroll-behavior: smooth;
        scrollbar-width: none;
        -ms-overflow-style: none;
        gap: 0.85rem;
        padding: 0.25rem 1.5rem 1.25rem;
        margin: 0 -1.25rem;
        scroll-padding: 0 1.5rem;
    }
    .bento-grid::-webkit-scrollbar { display: none; }
    .bento-card,
    .bento-card--featured,
    .bento-card--wide {
        flex: 0 0 86%;
        scroll-snap-align: center;
        scroll-snap-stop: always;
        aspect-ratio: 4 / 5;
        position: relative;
    }
    .bento-card:active {
        transform: scale(0.98);
        transition: transform 0.15s var(--ease-out-expo);
    }

    .bento-swipe-hint {
        display: flex;
        align-items: center;
        gap: 0.45rem;
        font-family: 'JetBrains Mono', monospace;
        font-size: var(--pas-etiquette);
        color: rgba(96, 165, 250, 0.75);
        letter-spacing: 0.04em;
        margin: 0.5rem 0 0.85rem;
        animation: swipe-hint-pulse 2.6s ease-in-out infinite;
    }
    .bento-swipe-hint svg {
        animation: swipe-arrow 1.6s ease-in-out infinite;
    }
    @keyframes swipe-hint-pulse {
        0%, 100% { opacity: 0.65; }
        50%      { opacity: 1; }
    }
    @keyframes swipe-arrow {
        0%, 100% { transform: translateX(0); }
        50%      { transform: translateX(6px); }
    }

    .bento-dots {
        display: flex;
        justify-content: center;
        gap: 8px;
        margin-top: 0.85rem;
        padding-bottom: 0.5rem;
    }
    .bento-dot {
        width: 6px;
        height: 6px;
        border-radius: 6px;
        background: rgba(255, 255, 255, 0.2);
        transition: width 0.4s var(--ease-out-expo), background 0.3s;
    }
    .bento-dot.is-active {
        width: 22px;
        background: var(--accent);
    }

    /* === Tilt parallax: utilise la propriété individuelle 'translate'
           (séparée de 'transform' donc pas écrasée par .fade-in.visible) === */
    .hero-watermark,
    .hero-badge,
    .hero-dual,
    .hero-title,
    .hero-sub {
        transition: translate 0.18s ease-out;
    }
    .hero-watermark {
        translate: calc(var(--tx, 0) * 5px) calc(var(--ty, 0) * 5px);
    }
    .hero-badge {
        translate: calc(var(--tx, 0) * -3px) calc(var(--ty, 0) * -1.5px);
    }
    .hero-dual {
        translate: calc(var(--tx, 0) * -2px) calc(var(--ty, 0) * -0.7px);
    }
    .hero-title {
        translate: calc(var(--tx, 0) * -1px) calc(var(--ty, 0) * -0.5px);
    }
    .hero-sub {
        translate: calc(var(--tx, 0) * 1.5px) calc(var(--ty, 0) * 0.5px);
    }

    /* Bouton "activer la motion" sur iOS uniquement (apparait si nécessaire) */
    .tilt-permission {
        position: fixed;
        bottom: 80px;
        right: 14px;
        z-index: 50;
        background: rgba(10, 14, 26, 0.92);
        backdrop-filter: blur(12px);
        border: 1px solid rgba(255, 255, 255, 0.14);
        color: #e2e8f0;
        font-size: var(--pas-etiquette);
        padding: 0.55rem 0.85rem;
        border-radius: 6px;
        cursor: pointer;
        font-family: var(--font);
        display: none;
        align-items: center;
        gap: 0.4rem;
        opacity: 0;
        animation: tilt-perm-fadein 0.4s var(--ease-out-expo) 0.6s forwards;
    }
    .tilt-permission.is-visible { display: inline-flex; }
    @keyframes tilt-perm-fadein {
        to { opacity: 1; }
    }

    /* Haptic feedback visual cue: scale-tap sur tous les interactifs primaires */
    .btn-primary:active,
    .btn-secondary:active,
    .voice-intro-btn:active,
    .case-slide-cta:active,
    .design-playground-toggle:active,
    .dp-swatch:active,
    .dp-reset:active,
    .nav-cta:active,
    .nav-link:active {
        transform: scale(0.96);
        transition: transform 0.08s ease-out;
    }
}

/* Reduce motion: disable tilt and swipe animations */
@media (prefers-reduced-motion: reduce) {
    .hero-watermark,
    .hero-badge,
    .hero-dual,
    .hero-title,
    .hero-sub { translate: 0 0 !important; }
    .bento-swipe-hint,
    .bento-swipe-hint svg { animation: none; }
}

/* =====================================================================
   LIGHT THEME — black & white inversion
   Activée par [data-theme="light"] sur <html>
   ===================================================================== */
html[data-theme="light"] {
    --black: #fafaf8;
    --dark-1: #ffffff;
    --dark-2: #f1f3f5;
    --dark-3: #e9ecef;
    --dark-4: #dee2e6;

    --white: #0a0e1a;
    --gray-1: rgba(10, 14, 26, 0.92);
    --gray-2: rgba(10, 14, 26, 0.62);
    --gray-3: rgba(10, 14, 26, 0.5);
    --gray-4: rgba(10, 14, 26, 0.16);
    --gray-5: rgba(10, 14, 26, 0.08);

    /* --accent/--accent-light : neutralisées par html[data-couloir] (132),
       même spécificité (0,1,1) qu'ici mais fichier plus tardif dans la
       cascade → 132 gagne toujours. --accent-deep n'est pas repris par 132
       (tâche 8) : dérivée de l'accent actif au lieu d'une teinte figée. */
    --accent: #2563eb;
    --accent-light: #3b82f6;
    --accent-deep: color-mix(in srgb, var(--accent) 82%, black);

    --terra: #7c3aed;
    --terra-light: #8b5cf6;

    --green: #059669;
}

/* Body + base */
html[data-theme="light"] body {
    background: var(--black);
    color: var(--gray-1);
}

/* Grain overlay : noir subtil au lieu de blanc */
html[data-theme="light"] .grain {
    opacity: 0.025;
    filter: invert(1);
}

/* Header glass-morphism */
html[data-theme="light"] #header {
    background: rgba(255, 255, 255, 0.78);
    border-bottom: 1px solid rgba(10, 14, 26, 0.06);
}

/* Hero mesh canvas : palette pastel pour light theme — visible et doux */
html[data-theme="light"] #hero-mesh {
    --gradient-color-1: #dbeafe;  /* bleu pastel */
    --gradient-color-2: #cffafe;  /* cyan pastel */
    --gradient-color-3: #d1fae5;  /* mint */
    --gradient-color-4: #e0f2fe;  /* ciel */
    opacity: 0.65;
    mix-blend-mode: normal;
}
html[data-theme="light"] #hero-mesh.loaded { opacity: 0.7; }

/* Watermarks et grands textes ghost.
   `[data-section-num]::before` a quitté cette liste avec le numéro de coin
   (module 013) : le même pseudo-élément porte maintenant l'ouverture
   éditoriale (module 128), qui doit rester ENCRÉE en thème clair. L'y
   laisser l'aurait peinte en `color: transparent`, donc invisible. */
html[data-theme="light"] .hero-watermark {
    -webkit-text-stroke: 1px rgba(10, 14, 26, 0.06);
    color: transparent;
}

/* Cards / sections sombres */
html[data-theme="light"] .service-card,
html[data-theme="light"] .bento-card,
html[data-theme="light"] .skill-card,
html[data-theme="light"] .horscode-card,
html[data-theme="light"] .activity-terminal,
html[data-theme="light"] .proj-overview-img,
html[data-theme="light"] .proj-gallery-item,
html[data-theme="light"] .proj-feature-item,
html[data-theme="light"] .case-slide-visual,
html[data-theme="light"] .contact-form input,
html[data-theme="light"] .contact-form textarea {
    background: var(--dark-1);
    border-color: var(--gray-4);
}

/* Section alt backgrounds */
html[data-theme="light"] .proj-section--alt {
    background: var(--dark-2);
}

/* Scroll progress + side nav dots */
html[data-theme="light"] .side-dot {
    background: rgba(10, 14, 26, 0.18);
}
html[data-theme="light"] .side-dot.active {
    background: var(--accent);
}

/* Hamburger lines en light */
html[data-theme="light"] .hamburger-line {
    background: rgba(10, 14, 26, 0.65);
}
html[data-theme="light"] .hamburger.open .hamburger-line {
    background: var(--gray-1);
}

/* Mobile fullscreen nav */
html[data-theme="light"] .nav-links {
    background: linear-gradient(160deg, #fafaf8, #ffffff);
}
@media (max-width: 768px) {
    html[data-theme="light"] body:has(.nav-links.open) header {
        background: linear-gradient(160deg, #fafaf8, #ffffff);
    }
}
html[data-theme="light"] .nav-link {
    color: var(--gray-1);
    border-bottom-color: rgba(10, 14, 26, 0.08);
}

/* Footer */
html[data-theme="light"] footer {
    background: var(--dark-2);
    border-top: 1px solid var(--gray-4);
}
html[data-theme="light"] .footer-signature {
    color: var(--accent);
}

/* Marquees */
html[data-theme="light"] .marquee-section,
html[data-theme="light"] .text-marquee {
    background: var(--dark-2);
    border-block: 1px solid var(--gray-4);
}

/* Tech pills */
html[data-theme="light"] .tech-pill,
html[data-theme="light"] .skill-tag,
html[data-theme="light"] .proj-tag {
    background: var(--dark-1);
    border-color: var(--gray-4);
    color: var(--gray-1);
}

/* Hero windows (code/design preview) */
html[data-theme="light"] .hero-win {
    background: var(--dark-1);
    border-color: var(--gray-4);
    box-shadow: 0 30px 80px -20px rgba(10, 14, 26, 0.18);
}
html[data-theme="light"] .hero-win-bar {
    background: var(--dark-2);
    border-bottom-color: var(--gray-4);
}

/* Buttons */
html[data-theme="light"] .btn-secondary {
    border-color: var(--gray-4);
    color: var(--gray-1);
}
html[data-theme="light"] .btn-secondary:hover {
    background: var(--dark-1);
}

/* Activity terminal — on garde un style "console" mais adouci */
html[data-theme="light"] .activity-terminal {
    background: #0f1525;
    color: rgba(255, 255, 255, 0.92);
}

/* Case study (process) sections */
html[data-theme="light"] .case-study {
    background: var(--dark-2);
}
html[data-theme="light"] .case-slide,
html[data-theme="light"] .case-slide-text,
html[data-theme="light"] .case-slide-h,
html[data-theme="light"] .case-slide-title {
    color: var(--gray-1);
}

/* Sticky notes */
html[data-theme="light"] .sticky-note {
    background: rgba(212, 255, 0, 0.18);
    color: rgba(10, 14, 26, 0.85);
}

/* Design playground */
html[data-theme="light"] .design-playground-toggle {
    background: rgba(255, 255, 255, 0.92);
    color: var(--gray-1);
    border-color: var(--gray-4);
}
html[data-theme="light"] .design-playground-panel {
    background: var(--dark-1);
    border-color: var(--gray-4);
    color: var(--gray-1);
}

/* Voice intro button */
html[data-theme="light"] .voice-intro-btn {
    color: var(--gray-1);
    border-color: var(--gray-4);
}

/* Bento dots */
html[data-theme="light"] .bento-dot {
    background: rgba(10, 14, 26, 0.18);
}

/* === Theme toggle button === */
.theme-toggle {
    background: transparent;
    border: 1px solid var(--gray-4);
    border-radius: 999px;
    width: 36px;
    height: 36px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    color: var(--gray-1);
    margin-right: 0.75rem;
    transition: border-color 0.3s, transform 0.3s var(--ease-out-expo), color 0.3s;
    position: relative;
    z-index: 101;
    flex-shrink: 0;
}
.theme-toggle:hover {
    border-color: var(--accent);
    color: var(--accent);
    transform: rotate(15deg);
}
.theme-toggle svg {
    width: 18px;
    height: 18px;
    stroke: currentColor;
    stroke-width: 1.8;
    fill: none;
    transition: transform 0.5s var(--ease-out-expo);
}
.theme-toggle .icon-sun { display: none; }
html[data-theme="light"] .theme-toggle .icon-sun { display: block; }
html[data-theme="light"] .theme-toggle .icon-moon { display: none; }
html[data-theme="light"] .theme-toggle:hover svg {
    transform: rotate(180deg);
}
@media (max-width: 480px) {
    .theme-toggle { width: 44px; height: 44px; margin-right: 0.25rem; } /* cible tactile 44px */
    .theme-toggle svg { width: 16px; height: 16px; }
}

/* Smooth color transitions globalement quand theme change */
html.theme-switching,
html.theme-switching body,
html.theme-switching * {
    transition: background-color 0.4s var(--ease-out-expo),
                color 0.4s var(--ease-out-expo),
                border-color 0.4s var(--ease-out-expo) !important;
}

/* === Light theme: fix hardcoded white text === */
html[data-theme="light"] .hero-title,
html[data-theme="light"] .section-title,
html[data-theme="light"] .proj-title,
html[data-theme="light"] .case-slide-title,
html[data-theme="light"] .case-slide-h,
html[data-theme="light"] .logo,
html[data-theme="light"] .nav-cta,
html[data-theme="light"] .hero-sub,
html[data-theme="light"] .about-title,
html[data-theme="light"] .work-header h2,
html[data-theme="light"] .horscode-card,
html[data-theme="light"] .horscode-line,
html[data-theme="light"] .footer-copy,
html[data-theme="light"] .footer-social-link,
html[data-theme="light"] .activity-meta,
html[data-theme="light"] .skills-intro p,
html[data-theme="light"] .nav-link,
html[data-theme="light"] .design-playground-title,
html[data-theme="light"] .voice-intro-label,
html[data-theme="light"] .case-slide-text,
html[data-theme="light"] .proj-section-text,
html[data-theme="light"] .proj-section-title,
html[data-theme="light"] .proj-feature-title,
html[data-theme="light"] .proj-cta-title,
html[data-theme="light"] .bento-title,
html[data-theme="light"] .bento-desc {
    color: var(--gray-1);
}

/* Logo cursor underscore */
html[data-theme="light"] .logo-cursor {
    color: var(--accent);
}

/* nav-cta = pilule Contact en gradient — keep white text on gradient bg */
html[data-theme="light"] .nav-cta {
    color: #fff;
}

/* Hero badge "Disponible" */
html[data-theme="light"] .hero-badge {
    background: rgba(16, 185, 129, 0.08);
    color: #047857;
    border-color: rgba(16, 185, 129, 0.3);
}

/* Hero dual tags */
html[data-theme="light"] .hero-dual-tag.code {
    background: rgba(59, 130, 246, 0.08);
    color: var(--accent);
    border-color: rgba(59, 130, 246, 0.3);
}
html[data-theme="light"] .hero-dual-tag.design {
    background: rgba(139, 92, 246, 0.08);
    color: var(--terra);
    border-color: rgba(139, 92, 246, 0.3);
}
html[data-theme="light"] .hero-dual-sep {
    color: var(--gray-3);
}

/* Hero badges flottants (PHP, Figma, Laravel) */
html[data-theme="light"] .hero-badge-float {
    background: var(--dark-1);
    color: var(--gray-1);
    border-color: var(--gray-4);
    box-shadow: 0 8px 20px -8px rgba(10, 14, 26, 0.18);
}

/* Hero code window */
html[data-theme="light"] .hero-code-body {
    color: var(--gray-1);
}
html[data-theme="light"] .hc-kw { color: #c026d3; }
html[data-theme="light"] .hc-fn { color: #2563eb; }
html[data-theme="light"] .hc-str { color: #059669; }
html[data-theme="light"] .hc-var { color: #ea580c; }
html[data-theme="light"] .hc-p { color: var(--gray-3); }
html[data-theme="light"] .hc-op { color: var(--gray-2); }

/* Hero design window typo */
html[data-theme="light"] .hd-typo-big,
html[data-theme="light"] .hd-typo-small {
    color: var(--gray-1);
}

/* Section labels (eyebrows colorés) */
html[data-theme="light"] .section-label,
html[data-theme="light"] .proj-section-label {
    color: var(--accent-texte);
}

/* About text */
html[data-theme="light"] .about-text {
    color: var(--gray-2);
}

/* Sub-page proj-hero */
html[data-theme="light"] .proj-hero {
    background: var(--dark-2);
}
html[data-theme="light"] .proj-watermark {
    -webkit-text-stroke: 1px rgba(10, 14, 26, 0.05);
    color: transparent;
}
html[data-theme="light"] .proj-meta-label {
    color: var(--gray-3);
}
html[data-theme="light"] .proj-meta-val {
    color: var(--gray-1);
}
html[data-theme="light"] .proj-breadcrumb,
html[data-theme="light"] .proj-breadcrumb a {
    color: var(--gray-3);
}
html[data-theme="light"] .proj-breadcrumb a:hover {
    color: var(--accent);
}
html[data-theme="light"] .proj-feature-num,
html[data-theme="light"] .proj-process-num {
    color: var(--accent);
}
html[data-theme="light"] .proj-feature-text,
html[data-theme="light"] .proj-process-text {
    color: var(--gray-2);
}
html[data-theme="light"] .proj-process-step {
    background: var(--dark-1);
    border-color: var(--gray-4);
}
html[data-theme="light"] .proj-cta {
    background: var(--dark-2);
}

/* Form inputs labels */
html[data-theme="light"] .contact-form label,
html[data-theme="light"] .form-group label {
    color: var(--gray-1);
}
html[data-theme="light"] .contact-form input,
html[data-theme="light"] .contact-form textarea {
    color: var(--gray-1);
    border-color: var(--gray-4);
}
html[data-theme="light"] .contact-form input::placeholder,
html[data-theme="light"] .contact-form textarea::placeholder {
    color: var(--gray-3);
}
html[data-theme="light"] .contact-item-label,
html[data-theme="light"] .contact-item-val {
    color: var(--gray-1);
}
html[data-theme="light"] .social-link {
    color: var(--gray-1);
}
html[data-theme="light"] .contact-avail {
    background: rgba(16, 185, 129, 0.08);
    color: #047857;
}

/* Skills */
html[data-theme="light"] .skill-level-label.level-advanced {
    background: rgba(16, 185, 129, 0.1);
    color: #047857;
}
html[data-theme="light"] .skill-level-label.level-current {
    background: rgba(59, 130, 246, 0.1);
    color: var(--accent);
}
html[data-theme="light"] .skill-tag.tag-dev {
    background: rgba(59, 130, 246, 0.06);
    color: var(--accent);
    border-color: rgba(59, 130, 246, 0.2);
}
html[data-theme="light"] .skill-tag.tag-design {
    background: rgba(139, 92, 246, 0.06);
    color: var(--terra);
    border-color: rgba(139, 92, 246, 0.2);
}

/* Bento overlay text — quand pas hover, transparent. Fix juste "bento-num" */
html[data-theme="light"] .bento-num {
    color: rgba(255, 255, 255, 0.85);
}
html[data-theme="light"] .bento-overlay {
    background: linear-gradient(to top, rgba(0, 0, 0, 0.85), transparent 70%);
}

/* Service cards */
html[data-theme="light"] .service-card-title,
html[data-theme="light"] .service-num {
    color: var(--gray-1);
}
html[data-theme="light"] .service-card-desc {
    color: var(--gray-2);
}
html[data-theme="light"] .service-num {
    color: var(--gray-4);
}

/* Tech currently rotator */
html[data-theme="light"] .tech-currently-label {
    color: var(--gray-3);
}
html[data-theme="light"] .tech-currently-rotator {
    color: var(--gray-1);
}

/* Activity terminal stays dark — but timestamps */
html[data-theme="light"] .activity-status-online {
    color: var(--green);
}

/* Hors-code texts */
html[data-theme="light"] .horscode-meta {
    color: var(--gray-3);
}
html[data-theme="light"] .horscode-tag {
    color: var(--gray-3);
}

/* Mobile nav meta in light */
html[data-theme="light"] .nav-links::before,
html[data-theme="light"] .nav-links::after {
    color: var(--gray-3);
}

/* Sub-page proj-nav-strip */
html[data-theme="light"] .proj-nav-link {
    background: var(--dark-1);
    border-color: var(--gray-4);
    color: var(--gray-1);
}
html[data-theme="light"] .proj-nav-dir {
    color: var(--gray-3);
}
html[data-theme="light"] .proj-nav-name {
    color: var(--gray-1);
}

/* Proj-cta-label "Et maintenant ?" — --accent-texte, pas --accent : 11px en
   capitales espacées sur un aplat, c'est un rôle de TEXTE (132, point 3).
   Depuis que data-couloir est posé sur les pages projet et /marketplace,
   l'accent y a bougé sans que ce rôle suive : 4,65 → 3,16:1 en clair/design. */
html[data-theme="light"] .proj-cta-label {
    color: var(--accent-texte);
}
html[data-theme="light"] .proj-cta-sub {
    color: var(--gray-2);
}

/* Switch icons color */
html[data-theme="light"] .theme-toggle {
    color: var(--gray-1);
}

/* === Light theme: fixes finaux audit === */

/* Globe canvas: inverser pour passer dark → light */
html[data-theme="light"] .location-globe canvas {
    filter: invert(0.92) hue-rotate(180deg) saturate(1.4);
    border-radius: 50%;
}
html[data-theme="light"] .location-globe-caption {
    background: var(--dark-1);
    color: var(--gray-1);
    border-color: var(--gray-4);
}
html[data-theme="light"] .location-globe-coords {
    color: var(--gray-1);
}

/* Contact section — bg dark hardcodé, on l'écrase */
html[data-theme="light"] #contact {
    background: var(--black) !important;
}
html[data-theme="light"] #contact::before {
    background: linear-gradient(90deg, transparent, rgba(37, 99, 235, 0.4) 35%, rgba(16, 185, 129, 0.3) 65%, transparent);
}
html[data-theme="light"] #contact::after {
    opacity: 0.05;
}

/* Section bg surcharges qui peuvent rester sombres */
html[data-theme="light"] #services,
html[data-theme="light"] #about,
html[data-theme="light"] #work,
html[data-theme="light"] #skills,
html[data-theme="light"] #location,
html[data-theme="light"] #activity,
html[data-theme="light"] #hors-code,
html[data-theme="light"] #case-study {
    background: var(--black);
}

/* Activity terminal — on garde fond sombre mais on adoucit le contour */
html[data-theme="light"] .activity-terminal {
    border: 1px solid var(--gray-4);
    box-shadow: 0 20px 60px -20px rgba(10, 14, 26, 0.18);
}

/* Skills DEV/DESIGN panels (fond sombre dans skills) */
html[data-theme="light"] .skills-dual,
html[data-theme="light"] .skills-dev,
html[data-theme="light"] .skills-design {
    background: var(--dark-2);
    border-color: var(--gray-4);
    color: var(--gray-1);
}
html[data-theme="light"] .skills-dev h4,
html[data-theme="light"] .skills-design h4,
html[data-theme="light"] .dual-label {
    color: var(--gray-1);
}

/* Marquee text (Code × Design × Fullstack) */
html[data-theme="light"] .text-marquee {
    color: var(--gray-1);
}
html[data-theme="light"] .text-marquee-track span {
    color: var(--gray-1);
}
html[data-theme="light"] .tmq-sep {
    color: var(--accent);
}

/* Tech currently rotator wrapper */
html[data-theme="light"] .tech-currently {
    background: var(--dark-1);
    border-color: var(--gray-4);
}

/* Tech marquee section background */
html[data-theme="light"] .tech-marquee-section {
    background: transparent;
}

/* Hero scroll indicator */
html[data-theme="light"] .scroll-indicator {
    color: var(--gray-3);
}

/* Logo cursor blink */
html[data-theme="light"] .logo-cursor {
    color: var(--accent);
}

/* Hero badge dot vert */
html[data-theme="light"] .hero-badge-dot {
    background: var(--green);
    box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.18);
}

/* Contact avail dot pulse */
html[data-theme="light"] .contact-avail-dot {
    background: var(--green);
}

/* Section labels (eyebrows) */
html[data-theme="light"] .work-sub,
html[data-theme="light"] .section-sub {
    color: var(--gray-2);
}

/* Bento overlay text quand visible (descriptions) */
html[data-theme="light"] .bento-card .bento-title,
html[data-theme="light"] .bento-card .bento-desc {
    color: rgba(255, 255, 255, 0.95);
}

/* Btn-submit form */
html[data-theme="light"] .btn-submit {
    color: #fff;
}

/* Footer signature et infos visibles en light */
html[data-theme="light"] .footer-signature-svg {
    color: var(--accent);
}
html[data-theme="light"] .footer-copy-meta {
    color: var(--gray-3);
}

/* Hide grain animation in light (texture sombre sur fond clair = bruit ugly) */
html[data-theme="light"] .grain {
    display: none;
}

/* Watermarks fade très subtil (même raison qu'au-dessus :
   `[data-section-num]::before` retiré de la liste, il sert désormais
   l'ouverture éditoriale du module 128 et doit rester lisible). */
html[data-theme="light"] #about::before,
html[data-theme="light"] #work::before,
html[data-theme="light"] #skills::before,
html[data-theme="light"] #services::before {
    color: rgba(10, 14, 26, 0.04);
    -webkit-text-stroke: 1px rgba(10, 14, 26, 0.05);
}

/* Side-nav hover label */
html[data-theme="light"] .side-dot::after {
    background: var(--dark-1);
    color: var(--gray-1);
    border: 1px solid var(--gray-4);
}

/* === Light theme: panels DEV/DESIGN expertise === */
html[data-theme="light"] .skills-dev,
html[data-theme="light"] .skills-design {
    background: var(--dark-1);
    border: 1px solid var(--gray-4);
}

/* Tags dev/design */
html[data-theme="light"] .dev-tag,
html[data-theme="light"] .design-tag {
    color: var(--gray-1);
    background: var(--dark-2);
    border-color: var(--gray-4);
}
html[data-theme="light"] .dev-tag:hover {
    color: var(--green);
    border-color: var(--green);
}
html[data-theme="light"] .design-tag:hover {
    color: var(--accent);
    border-color: var(--accent);
}

/* Sub-titres "BACKEND", "FRONTEND", "OUTILS" etc. */
html[data-theme="light"] .dev-skill-group-title,
html[data-theme="light"] .design-skill-group-title {
    color: var(--gray-3);
}

/* Labels DÉVELOPPEMENT et DESIGN avec brackets */
html[data-theme="light"] .skills-dev-label,
html[data-theme="light"] .skills-design-label {
    color: var(--accent);
}
html[data-theme="light"] .skills-dev-label::before,
html[data-theme="light"] .skills-design-label::before,
html[data-theme="light"] .skills-design-label::after {
    color: rgba(10, 14, 26, 0.2);
}

/* Skills section text général */
html[data-theme="light"] .skills-intro,
html[data-theme="light"] .skills-intro p {
    color: var(--gray-2);
}

/* =====================================================================
   PERFORMANCE — content-visibility pour sections off-screen
   Le navigateur skip le layout/paint quand la section est hors viewport.
   Effet : navigation plus fluide, premier paint plus rapide.
   ===================================================================== */
#location,
#case-study,
#hors-code,
#activity {
    content-visibility: auto;
    contain-intrinsic-size: 0 1000px;
}
/* Skills + work garde rendering normal — utilisateurs scrollent souvent à work */

/* =====================================================================
   WAOUH 1 — INTRO LOADER (compteur 00 → 100 puis mask reveal)
   ===================================================================== */
.intro-loader {
    position: fixed;
    inset: 0;
    z-index: 9999;
    background: #050811;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    opacity: 1;
    visibility: visible;
    /* Transition out : bg fade 0.7s, delay 0.1s — révèle le hero plus tôt (revue UX) */
    transition: opacity 0.7s cubic-bezier(0.22, 1, 0.36, 1) 0.1s,
                visibility 0s linear 0.85s;
}
.intro-loader[data-state="done"] {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}

/* Stage : pendant le loader actif, animation lente continue
   Pendant la sortie : scale + translate + fade dramatique en 1.2s,
   commence immédiatement, avant le bg fade */
.loader-stage {
    text-align: center;
    transform: translateZ(0) scale(1);
    opacity: 1;
    transition: transform 0.8s cubic-bezier(0.4, 0, 0.2, 1),
                opacity 0.6s cubic-bezier(0.4, 0, 0.2, 1),
                filter 0.8s cubic-bezier(0.4, 0, 0.2, 1);
    filter: blur(0);
}
.intro-loader[data-state="done"] .loader-stage {
    transform: translateZ(0) scale(1.18) translateY(-8px);
    opacity: 0;
    filter: blur(8px);
}

/* Le sous-titre minimaliste avec flèche */
.loader-sub {
    margin: clamp(1rem, 2vh, 1.75rem) 0 0;
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    letter-spacing: 0.34em;
    text-transform: uppercase;
    color: rgba(200, 162, 92, 0.65);
    display: inline-flex;
    align-items: center;
    gap: 0.85rem;
    opacity: 0;
    animation: loader-sub-in 0.85s ease 1.05s forwards;
}
.loader-sub-arrow {
    color: rgba(200, 162, 92, 0.85);
    animation: loader-arrow-move 1.8s ease-in-out 1.4s infinite;
    transform-origin: center;
}
@keyframes loader-sub-in {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: translateY(0); }
}
@keyframes loader-arrow-move {
    0%, 100% { transform: translateX(0); }
    50%      { transform: translateX(6px); opacity: 1; }
}

/* Iris glow — petit point lumineux qui pulse au centre */
.loader-iris {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--gold, #c8a25c);
    box-shadow: 0 0 24px var(--gold, #c8a25c), 0 0 48px rgba(200, 162, 92, 0.4);
    transform: translate(-50%, -50%) scale(0);
    animation: loader-iris-pulse 2.6s ease-in-out 0.4s infinite;
    pointer-events: none;
}
.intro-loader[data-state="done"] .loader-iris {
    opacity: 0;
    transition: opacity 0.4s ease;
}
@keyframes loader-iris-pulse {
    0%, 100% { transform: translate(-50%, -50%) scale(0); opacity: 0; }
    50%      { transform: translate(-50%, -50%) scale(1); opacity: 0.5; }
}
@keyframes loader-iris-close-pulse {
    0%   { transform: translate(-50%, -50%) scale(1); opacity: 0.5; }
    100% { transform: translate(-50%, -50%) scale(0); opacity: 1; }
}

/* Tag archive top-left */
.loader-archive-tag {
    position: absolute;
    top: 7vh;
    left: 7vw;
    margin: 0;
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.32);
    opacity: 0;
    animation: loader-elt-in 0.9s cubic-bezier(0.22, 1, 0.36, 1) 0.15s forwards;
}

/* Mitia. — letter stagger éditorial */
.intro-loader-name {
    font-family: 'Syne', system-ui, sans-serif;
    font-size: var(--pas-signature);
    font-weight: 700;
    letter-spacing: -0.055em;
    color: #efe9d8;
    line-height: 0.9;
    display: flex;
    align-items: baseline;
    gap: 0;
    margin: 0;
}
.intro-loader-letter {
    display: inline-block;
    opacity: 0;
    transform: translateY(85%);
    animation: loader-letter-in 1.05s cubic-bezier(0.16, 1, 0.3, 1) forwards;
    animation-delay: calc(var(--i, 0) * 90ms + 250ms);
}
.intro-loader-dot {
    color: var(--gold, #c8a25c);
    display: inline-block;
    opacity: 0;
    transform: scale(0);
    animation: loader-dot-pop 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) 0.85s forwards;
}
.intro-loader[data-state="done"] .intro-loader-letter {
    animation: loader-letter-out 0.7s cubic-bezier(0.55, 0.06, 0.68, 0.19) forwards;
    animation-delay: calc(var(--i, 0) * 25ms);
}
@keyframes loader-letter-in {
    to { opacity: 1; transform: translateY(0); }
}
@keyframes loader-letter-out {
    to { opacity: 0; transform: translateY(-30%); }
}
@keyframes loader-dot-pop {
    to { opacity: 1; transform: scale(1); }
}

/* Rule line — width pilotée par JS en sync avec le counter */
.loader-rule {
    display: block;
    width: 0;
    height: 1px;
    margin: 2rem auto 1.25rem;
    background: linear-gradient(90deg, transparent, var(--gold, #c8a25c) 30%, var(--gold, #c8a25c) 70%, transparent);
    box-shadow: 0 0 8px rgba(200, 162, 92, 0.35);
    max-width: clamp(120px, 18vw, 260px);
    transition: width 0.05s linear, opacity 0.3s ease;
}
.intro-loader[data-state="done"] .loader-rule {
    opacity: 0;
}

/* Corner marks — film clapperboard style */
.loader-corner {
    position: absolute;
    width: 24px;
    height: 24px;
    border: 1px solid rgba(200, 162, 92, 0.4);
    opacity: 0;
    animation: loader-corner-in 0.8s cubic-bezier(0.16, 1, 0.3, 1) 0.05s forwards;
}
.loader-corner--tl { top: 3vh; left: 3vw; border-right: none; border-bottom: none; }
.loader-corner--tr { top: 3vh; right: 3vw; border-left: none; border-bottom: none; animation-delay: 0.15s; }
.loader-corner--bl { bottom: 3vh; left: 3vw; border-right: none; border-top: none; animation-delay: 0.25s; }
.loader-corner--br { bottom: 3vh; right: 3vw; border-left: none; border-top: none; animation-delay: 0.35s; }
@keyframes loader-corner-in {
    from { opacity: 0; transform: scale(0.5); }
    to   { opacity: 1; transform: scale(1); }
}
.intro-loader[data-state="done"] .loader-corner {
    animation: loader-corner-out 0.5s ease forwards;
}
@keyframes loader-corner-out {
    to { opacity: 0; transform: scale(0.6); }
}

/* Archive tag rec dot — pulse continu (cinema record indicator) */
.loader-tag-num { color: rgba(200, 162, 92, 0.85); }
.loader-tag-sep { color: rgba(255, 255, 255, 0.2); margin: 0 0.35rem; }
.loader-tag-rec {
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #e23232;
    box-shadow: 0 0 6px #e23232;
    margin-left: 0.65rem;
    vertical-align: middle;
    animation: loader-rec-blink 1.4s ease-in-out infinite;
}
@keyframes loader-rec-blink {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.35; transform: scale(0.85); }
}

/* Letters subtle breathing pendant loading */
.intro-loader:not([data-state="done"]) .intro-loader-letter {
    animation:
        loader-letter-in 1.05s cubic-bezier(0.16, 1, 0.3, 1) forwards,
        loader-letter-breath 3.6s ease-in-out infinite;
    animation-delay: calc(var(--i, 0) * 90ms + 250ms), calc(var(--i, 0) * 200ms + 1.5s);
}
@keyframes loader-letter-breath {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-2px); }
}

/* Scan line — traverse l'écran verticalement */
.loader-scan {
    position: absolute;
    left: 0;
    right: 0;
    height: 1px;
    background: linear-gradient(90deg, transparent, rgba(200, 162, 92, 0.6) 40%, rgba(200, 162, 92, 0.6) 60%, transparent);
    top: -2%;
    opacity: 0;
    animation: loader-scan-sweep 4s cubic-bezier(0.4, 0, 0.6, 1) 1.2s infinite;
    pointer-events: none;
}
@keyframes loader-scan-sweep {
    0%   { top: -2%; opacity: 0; }
    8%   { opacity: 0.55; }
    92%  { opacity: 0.55; }
    100% { top: 102%; opacity: 0; }
}

/* Live clock */
#loader-clock {
    color: rgba(200, 162, 92, 0.7);
    font-variant-numeric: tabular-nums;
    margin-left: 0.15rem;
}

/* Counter — number flip subtle */
.intro-loader-counter #intro-counter {
    color: rgba(255, 255, 255, 0.85);
    font-variant-numeric: tabular-nums;
    font-weight: 500;
}

/* Signature italique Fraunces */
.loader-sig {
    font-family: var(--display);
    font-size: var(--pas-lecture);
    color: rgba(255, 255, 255, 0.62);
    margin: 0;
    letter-spacing: -0.005em;
    opacity: 0;
    animation: loader-elt-in 0.8s ease 1.1s forwards;
}
.loader-sig em {
    font-style: italic;
    color: rgba(200, 162, 92, 0.85);
}

/* Counter mono refined */
.intro-loader-counter {
    position: absolute;
    bottom: 7vh;
    right: 7vw;
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    color: rgba(255, 255, 255, 0.55);
    letter-spacing: 0.04em;
    opacity: 0;
    animation: loader-elt-in 0.6s ease 0.4s forwards;
}
.counter-sep {
    margin: 0 0.4rem;
    color: rgba(255, 255, 255, 0.25);
}
.counter-target {
    color: rgba(200, 162, 92, 0.7);
}

/* Meta coordonnées bottom-left */
.intro-loader-meta {
    position: absolute;
    bottom: 7vh;
    left: 7vw;
    margin: 0;
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    letter-spacing: 0.12em;
    color: rgba(255, 255, 255, 0.32);
    text-transform: uppercase;
    opacity: 0;
    animation: loader-elt-in 0.6s ease 0.4s forwards;
}

/* Film noise overlay subtil */
.loader-noise {
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0.06;
    mix-blend-mode: overlay;
    background-image:
        repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(255, 255, 255, 0.4) 2px, rgba(255, 255, 255, 0.4) 3px);
    background-size: 100% 4px;
    animation: loader-noise-drift 0.18s steps(3) infinite;
}
@keyframes loader-noise-drift {
    0%   { transform: translateY(0); }
    33%  { transform: translateY(-1px); }
    66%  { transform: translateY(1px); }
    100% { transform: translateY(0); }
}

@keyframes loader-elt-in {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* Reduced motion : tout simple */
@media (prefers-reduced-motion: reduce) {
    .intro-loader[data-state="done"] { clip-path: inset(0 0 0 0); opacity: 0; }
    .intro-loader-letter,
    .intro-loader-dot,
    .loader-archive-tag,
    .loader-rule,
    .loader-sig,
    .intro-loader-counter,
    .intro-loader-meta,
    .loader-noise {
        animation: none !important;
        opacity: 1 !important;
        transform: none !important;
        width: 200px;
    }
}
.intro-loader-dot {
    color: var(--accent);
    display: inline-block;
    transform: scale(0);
    animation: intro-dot-pop 0.4s cubic-bezier(.34,1.56,.64,1) 0.6s forwards;
}
@keyframes intro-dot-pop { to { transform: scale(1); } }

.intro-loader-counter {
    position: absolute;
    bottom: 6vh;
    right: 6vw;
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-appui);
    color: var(--accent);
    letter-spacing: 0.06em;
    opacity: 0;
    animation: intro-fade 0.5s ease 0.15s forwards;
}
.counter-pct {
    color: rgba(255, 255, 255, 0.4);
    margin-left: 2px;
    /* Parent .intro-loader-counter déjà fixé en var(--pas-appui) : 0.85em
       d'origine (≈12px) contre 14px ici, écart ~18-21% (tâche 4, arbitré,
       accepté — élément qui sort en prefers-reduced-motion: reduce). */
    font-size: var(--pas-appui);
}

.intro-loader-meta {
    position: absolute;
    bottom: 6vh;
    left: 6vw;
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    color: rgba(255, 255, 255, 0.4);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    opacity: 0;
    animation: intro-fade 0.5s ease 0.45s forwards;
}
@keyframes intro-fade { to { opacity: 1; } }

/* Bloque le scroll pendant le loader */
body.loader-active { overflow: hidden; }

/* Arrival veil — simple fade out smooth, sans stagger du contenu.
   Le veil couvre la page, attend 300ms pour stabilité, puis fade
   opacity 1→0 en 1.4s ease soft. Le contenu sous-jacent reste à
   son rendu naturel — aucune animation sur les éléments. */
.arrival-veil {
    position: fixed;
    inset: 0;
    z-index: 9990;
    background: #050811;  /* Match portfolio bg exact */
    pointer-events: none;
    opacity: 1;
    will-change: opacity;
    transition: opacity 1.4s cubic-bezier(0.22, 1, 0.36, 1);
}
.arrival-veil.is-fading {
    opacity: 0;
}
html[data-theme="light"] .arrival-veil {
    background: #faf6ec;
}
@media (prefers-reduced-motion: reduce) {
    .arrival-veil { display: none !important; }
}

@media (prefers-reduced-motion: reduce) {
    .intro-loader { display: none !important; }
    body.is-arriving { animation: none !important; }
}

/* Light theme : fond clair pour le loader */
html[data-theme="light"] .intro-loader { background: #fafaf8; }
html[data-theme="light"] .intro-loader-name { color: #0a0e1a; }
html[data-theme="light"] .intro-loader-counter { color: var(--accent); }
html[data-theme="light"] .intro-loader-meta,
html[data-theme="light"] .counter-pct { color: rgba(10, 14, 26, 0.4); }


/* =====================================================================
   WAOUH 2 — PAGE TRANSITIONS inter-projets (sweep accent)
   ===================================================================== */
.page-transition {
    position: fixed;
    inset: 0;
    z-index: 9998;
    pointer-events: none;
    overflow: hidden;
}
.page-transition-panel {
    position: absolute;
    inset: 0;
    background: linear-gradient(135deg, var(--accent), var(--accent-light));
    transform: translateX(100%);
    transition: transform 0.65s cubic-bezier(.76,0,.24,1);
}
/* État LEAVING : panel glisse de droite à gauche, couvre */
.page-transition[data-state="leaving"] .page-transition-panel {
    transform: translateX(0);
}
/* État ARRIVAL : panel déjà couvrant, on le fait sortir vers la gauche */
html[data-page-transition="arrival"] .page-transition-panel {
    transform: translateX(0);
    transition: none;
}
html[data-page-transition="arrival-out"] .page-transition-panel {
    transform: translateX(-100%);
    transition: transform 0.55s cubic-bezier(.65,0,.35,1);
}

@media (prefers-reduced-motion: reduce) {
    .page-transition-panel { transition: none !important; }
}


/* =====================================================================
   WAOUH 3 — BENTO CURSOR PREVIEW (image qui suit le curseur au hover)
   ===================================================================== */
.bento-preview {
    position: fixed;
    top: 0; left: 0;
    width: 280px;
    height: 200px;
    pointer-events: none;
    z-index: 60;
    border-radius: 14px;
    overflow: hidden;
    opacity: 0;
    transform: translate3d(0, 0, 0) scale(0.7);
    transform-origin: center center;
    transition: opacity 0.3s ease, transform 0.4s var(--ease-out-expo);
    box-shadow: 0 30px 80px -20px rgba(0, 0, 0, 0.5);
    border: 1px solid rgba(255, 255, 255, 0.08);
    will-change: transform, opacity;
}
.bento-preview.is-active {
    opacity: 1;
}
.bento-preview img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
@media (max-width: 768px), (hover: none) {
    .bento-preview { display: none; }
}

/* Désactive le cursor-view "Voir ↗" sur les bentos quand le preview est dispo */
@media (hover: hover) and (min-width: 769px) {
    .cursor-view.bento-mode { display: none; }
}

/* === FIX: cache la swipe-hint mobile par défaut sur desktop === */
.bento-swipe-hint {
    display: none;
}

/* =====================================================================
   HERO SHOWCASE COMMERCIAL — vitrine business (au lieu de tech stack)
   ===================================================================== */
.hero-showcase {
    position: relative;
    width: 100%;
    height: 100%;
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: auto auto auto;
    grid-template-areas:
        "status status"
        "metrics metrics"
        "testimonial testimonial";
    gap: 1rem;
    align-content: center;
    padding: 2.5rem 0;
}

.showcase-card {
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 16px;
    padding: 1.4rem 1.5rem;
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    position: relative;
    transition: transform 0.5s var(--ease-out-expo), border-color 0.4s, background 0.4s;
}
.showcase-card:hover {
    transform: translateY(-2px);
    border-color: rgba(255, 255, 255, 0.16);
    background: rgba(255, 255, 255, 0.06);
}

/* --- Card 1 : Status live --- */
.showcase-status { grid-area: status; }
.showcase-status-head {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    margin-bottom: 1rem;
}
.showcase-pulse {
    width: 9px; height: 9px;
    background: var(--green);
    border-radius: 50%;
    box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.18);
    animation: pulse-dot 2s ease-in-out infinite;
}
@keyframes pulse-dot {
    0%, 100% { box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.18); }
    50%      { box-shadow: 0 0 0 8px rgba(16, 185, 129, 0.05); }
}
.showcase-status-label {
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    color: var(--green);
    letter-spacing: 0.15em;
    font-weight: 600;
}
.showcase-status-body {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    margin-bottom: 1.1rem;
}
.showcase-avatar {
    position: relative;
    flex-shrink: 0;
}
.showcase-avatar img {
    width: 56px; height: 56px;
    border-radius: 50%;
    object-fit: cover;
    display: block;
    position: relative;
    z-index: 2;
}
.showcase-avatar-ring {
    position: absolute;
    inset: -3px;
    border-radius: 50%;
    background: conic-gradient(from 0deg, var(--accent), var(--accent-light), var(--terra), var(--accent));
    
    z-index: 1;
}
@keyframes ring-rotate { to { transform: rotate(360deg); } }
.showcase-status-name {
    font-family: 'Syne', system-ui, sans-serif;
    font-size: var(--pas-lecture);
    font-weight: 700;
    color: #f8fafc;
    margin: 0 0 0.15rem;
    letter-spacing: -0.01em;
}
.showcase-status-detail {
    font-size: var(--pas-appui);
    color: rgba(255, 255, 255, 0.55);
    margin: 0;
}
/* « sous 2h » : de l'information, sur un aplat — --accent-texte (132). */
.showcase-status-detail strong {
    color: var(--accent-texte);
    font-weight: 600;
}
.showcase-status-cta {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.7rem 1.1rem;
    background: linear-gradient(115deg, var(--accent), var(--accent-light));
    color: #fff;
    text-decoration: none;
    border-radius: 6px;
    font-weight: 600;
    font-size: var(--pas-appui);
    transition: transform 0.3s, box-shadow 0.3s;
    box-shadow: 0 8px 24px -6px rgba(59, 130, 246, 0.4);
}
.showcase-status-cta:hover {
    transform: translateY(-2px);
    box-shadow: 0 12px 32px -8px rgba(59, 130, 246, 0.55);
}

/* --- Card 2 : Metrics --- */
.showcase-metrics { grid-area: metrics; }
.showcase-metrics-eyebrow {
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    color: rgba(255, 255, 255, 0.4);
    letter-spacing: 0.15em;
    font-weight: 600;
    margin: 0 0 1rem;
}
.showcase-metrics-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1.1rem 1.5rem;
}
.metric { display: flex; flex-direction: column; gap: 0.15rem; }
.metric-num {
    color: var(--white, #fff);
    font-family: 'Syne', system-ui, sans-serif;
    /* Hors échelle des 6 pas (tâche 4) : CSS mort — `.metric-num` /
       `.showcase-metrics-grid` n'apparaissent nulle part dans le markup de
       ce dépôt (portfolio-legacy.html, portfolio-en.html), seul un
       querySelectorAll silencieux les cible encore dans
       portfolio-bundle.js. Ne vaut pas la peine de forcer un jeton pour un
       sélecteur qui ne rend jamais rien. Laissé en clamp() littéral propre,
       qui reproduit sa plage d'origine (même choix que le portfolio pour
       ce même sélecteur mort). */
    font-size: clamp(1.5rem, 2.4vw, 2rem);
    font-weight: 700;
    color: #f8fafc;
    letter-spacing: -0.025em;
    line-height: 1;
    }
.metric-label {
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    color: rgba(255, 255, 255, 0.5);
    letter-spacing: 0.04em;
}

/* --- Card 3 : Testimonial --- */
.showcase-testimonial { grid-area: testimonial; }
.showcase-quote {
    color: var(--accent);
    opacity: 0.5;
    margin-bottom: 0.55rem;
}
.showcase-testimonial-quote {
    font-family: 'Syne', system-ui, sans-serif;
    font-size: var(--pas-lecture);
    color: rgba(255, 255, 255, 0.92);
    line-height: 1.4;
    margin: 0 0 0.65rem;
    font-weight: 500;
    letter-spacing: -0.01em;
}
.showcase-testimonial-quote em {
    font-style: italic;
    color: var(--accent);
    background: linear-gradient(120deg, rgba(59, 130, 246, 0.18), rgba(96, 165, 250, 0.08));
    padding: 0 0.25rem;
    border-radius: 3px;
}
.showcase-testimonial-author {
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    color: rgba(255, 255, 255, 0.4);
    margin: 0;
    letter-spacing: 0.04em;
}

/* --- Quick CTAs flottants (remplacent les badges PHP/Figma/Laravel) --- */
.showcase-quickcta {
    position: absolute;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.55rem 0.95rem;
    border-radius: 6px;
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    font-weight: 500;
    text-decoration: none;
    background: rgba(10, 14, 26, 0.85);
    border: 1px solid rgba(255, 255, 255, 0.12);
    color: #f8fafc;
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    box-shadow: 0 8px 24px -8px rgba(0, 0, 0, 0.4);
    transition: transform 0.3s var(--ease-out-expo), border-color 0.3s, background 0.3s;
    animation: showcase-cta-float 5s ease-in-out infinite;
    z-index: 5;
}
.showcase-quickcta svg { stroke: currentColor; }
.showcase-quickcta:hover {
    transform: translateY(-2px) scale(1.04);
    border-color: var(--accent);
    color: var(--accent-light);
}
.cta-call {
    top: -14px;
    right: 8%;
    animation-delay: 0s;
}
.cta-mail {
    top: 38%;
    right: -8px;
    animation-delay: 1s;
}
.cta-whats {
    bottom: 12%;
    left: -10px;
    color: #25d366;
    border-color: rgba(37, 211, 102, 0.3);
    animation-delay: 2s;
}
.cta-whats:hover { color: #25d366; border-color: #25d366; }
@keyframes showcase-cta-float {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-6px); }
}

/* --- Light theme adapations --- */
html[data-theme="light"] .showcase-card {
    background: rgba(255, 255, 255, 0.65);
    border-color: rgba(10, 14, 26, 0.08);
}
html[data-theme="light"] .showcase-status-name,
html[data-theme="light"] .metric-num {
    color: #0f172a;
    color: var(--gray-1);
    }
html[data-theme="light"] .showcase-testimonial-quote {
    color: var(--gray-1);
}
html[data-theme="light"] .showcase-status-detail,
html[data-theme="light"] .showcase-testimonial-author,
html[data-theme="light"] .metric-label,
html[data-theme="light"] .showcase-metrics-eyebrow {
    color: var(--gray-2);
}
html[data-theme="light"] .showcase-quickcta {
    background: rgba(255, 255, 255, 0.92);
    color: var(--gray-1);
    border-color: rgba(10, 14, 26, 0.12);
}

/* --- Responsive: stack en mobile --- */
@media (max-width: 900px) {
    .hero-showcase {
        grid-template-columns: 1fr;
        grid-template-areas:
            "status"
            "metrics"
            "testimonial";
        gap: 0.85rem;
        padding: 1.5rem 0;
    }
    .showcase-quickcta { display: none; } /* trop chargé sur mobile */
}
@media (max-width: 768px) {
    .showcase-card { padding: 1.1rem 1.2rem; }
    .showcase-metrics-grid { gap: 0.85rem 1rem; }
    .metric-num { font-size: var(--pas-sous-titre); }
}


/* =====================================================================
   VOICE INTRO TOAST — feedback erreur visible
   ===================================================================== */
.voice-toast {
    position: fixed;
    bottom: 80px;
    left: 50%;
    transform: translate(-50%, 20px);
    background: rgba(10, 14, 26, 0.95);
    color: #f8fafc;
    padding: 0.85rem 1.4rem;
    border-radius: 12px;
    border: 1px solid rgba(255, 255, 255, 0.12);
    backdrop-filter: blur(16px);
    font-size: var(--pas-appui);
    box-shadow: 0 18px 50px -12px rgba(0, 0, 0, 0.5);
    z-index: 99;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s, transform 0.3s var(--ease-out-expo);
    max-width: 90vw;
}
.voice-toast.is-visible {
    opacity: 1;
    transform: translate(-50%, 0);
}
.voice-toast.is-error {
    border-color: rgba(239, 68, 68, 0.4);
    color: #fca5a5;
}
html[data-theme="light"] .voice-toast {
    background: rgba(255, 255, 255, 0.96);
    color: var(--gray-1);
    border-color: rgba(10, 14, 26, 0.12);
}


/* =====================================================================
   TYPEWRITER GLITCH — effet glitch entre mots
   ===================================================================== */
.typewriter-text {
    color: var(--accent-light, #60a5fa);
    font-style: italic;
    font-weight: 700;
    transition: filter 0.2s;
}
.typewriter-text.is-glitching {
    filter: blur(0.5px);
    animation: typewriter-glitch 0.06s steps(1) infinite;
}
@keyframes typewriter-glitch {
    0%   { transform: translate(0); }
    20%  { transform: translate(-2px, 1px); }
    40%  { transform: translate(2px, -1px); }
    60%  { transform: translate(-1px, -1px); }
    80%  { transform: translate(1px, 2px); }
    100% { transform: translate(0); }
}

/* =====================================================================
   PROCESS STORYTELLING — stat-pills, pull-quotes, icone animées
   ===================================================================== */

/* Stat pill avec icone par étape */
.case-slide-stat {
    display: inline-flex;
    align-items: center;
    gap: 0.85rem;
    padding: 0.75rem 1.1rem;
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 14px;
    background: rgba(255, 255, 255, 0.03);
    margin: 1.2rem 0 1.5rem;
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    transform: translateY(20px);
    opacity: 0;
    transition: transform 0.7s var(--ease-out-expo), opacity 0.6s ease;
    transition-delay: 0.15s;
}
.case-slide.is-active .case-slide-stat {
    transform: translateY(0);
    opacity: 1;
}
.case-stat-icon {
    width: 26px; height: 26px;
    color: var(--accent);
    flex-shrink: 0;
}
.case-stat-content { display: flex; flex-direction: column; gap: 0.05rem; }
.case-stat-num {
    color: var(--white, #fff);
    font-family: 'Syne', system-ui, sans-serif;
    font-size: var(--pas-sous-titre);
    font-weight: 700;
    color: #f8fafc;
    line-height: 1;
    letter-spacing: -0.02em;
    }
.case-stat-lbl {
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    color: rgba(255, 255, 255, 0.55);
    letter-spacing: 0.04em;
}

/* Icone animation par étape */
.stat-icon-phone {
    animation: icon-phone-shake 2.4s ease-in-out infinite;
    transform-origin: 50% 90%;
}
@keyframes icon-phone-shake {
    0%, 90%, 100% { transform: rotate(0); }
    93%, 96% { transform: rotate(-12deg); }
    94%, 97% { transform: rotate(12deg); }
}
.stat-icon-layers {
    
}
@keyframes icon-layers-stack {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-3px); }
}
.stat-icon-code {
    animation: icon-code-pulse 2s ease-in-out infinite;
}
@keyframes icon-code-pulse {
    0%, 100% { transform: scaleX(1); opacity: 1; }
    50%      { transform: scaleX(1.15); opacity: 0.7; }
}
.stat-icon-rocket {
    
    transform-origin: 50% 50%;
}
@keyframes icon-rocket-launch {
    0%, 70%, 100% { transform: translate(0, 0) rotate(0); }
    80%           { transform: translate(2px, -3px) rotate(-3deg); }
    90%           { transform: translate(-2px, 3px) rotate(3deg); }
}

/* Pull quotes percutantes par slide */
.case-slide-pullquote {
    font-family: 'Syne', system-ui, sans-serif;
    font-size: var(--pas-sous-titre);
    font-style: italic;
    color: rgba(255, 255, 255, 0.95);
    line-height: 1.4;
    margin: 1.8rem 0 1.2rem;
    padding-left: 1.2rem;
    border-left: 3px solid var(--accent);
    max-width: 32ch;
    transform: translateX(-12px);
    opacity: 0;
    transition: transform 0.8s var(--ease-out-expo), opacity 0.7s ease;
    transition-delay: 0.4s;
}
.case-slide.is-active .case-slide-pullquote {
    transform: translateX(0);
    opacity: 1;
}
.case-slide-pullquote em {
    color: var(--accent-light, #60a5fa);
    font-style: italic;
    font-weight: 600;
}

/* Light theme adaptations */
html[data-theme="light"] .case-slide-stat {
    background: rgba(10, 14, 26, 0.04);
    border-color: rgba(10, 14, 26, 0.1);
}
html[data-theme="light"] .case-stat-num {
    color: #0f172a;
    }
html[data-theme="light"] .case-stat-lbl,
html[data-theme="light"] .case-slide-pullquote {
    color: var(--gray-1);
}

/* Mobile: stat-pill et pullquote compactés */
@media (max-width: 768px) {
    .case-slide-stat { padding: 0.6rem 0.85rem; gap: 0.65rem; margin: 1rem 0; }
    .case-stat-num { font-size: var(--pas-lecture); }
    .case-stat-icon { width: 22px; height: 22px; }
    .case-slide-pullquote {
        font-size: var(--pas-lecture);
        padding-left: 0.85rem;
        margin: 1.4rem 0;
    }
}

/* =====================================================================
   MANIFESTO — Mes principes (5 cards typo magazine)
   ===================================================================== */
#manifesto {
    padding: clamp(4rem, 8vw, 8rem) 0; /* harmonisé 128px max */
    position: relative;
    background: var(--black);
    overflow: hidden;
}
#manifesto::before {
    content: '';
    position: absolute;
    inset: 0;
    background: radial-gradient(ellipse 60% 40% at 80% 20%, rgba(59, 130, 246, 0.04), transparent 70%),
                radial-gradient(ellipse 50% 30% at 20% 80%, rgba(139, 92, 246, 0.03), transparent 70%);
    pointer-events: none;
}
.manifesto-head {
    max-width: 720px;
    margin-bottom: clamp(3rem, 6vw, 5rem);
}
.manifesto-list {
    list-style: none;
    padding: 0;
    margin: 0;
    counter-reset: manifesto;
    display: flex;
    flex-direction: column;
    gap: 0;
    border-top: 1px solid var(--gray-5);
}
.manifesto-item {
    display: grid;
    grid-template-columns: 3rem 1fr;
    gap: 1.25rem;
    padding: clamp(1.8rem, 4vw, 2.8rem) 0;
    border-bottom: 1px solid var(--gray-5);
    align-items: baseline;
    position: relative;
    transition: padding 0.5s var(--ease-out-expo), background 0.4s ease;
}
.manifesto-item:hover {
    padding-left: clamp(0.5rem, 1.5vw, 1.2rem);
    background: linear-gradient(to right, rgba(59, 130, 246, 0.025), transparent 60%);
}
.manifesto-item::before {
    content: '';
    position: absolute;
    top: 50%;
    left: -2rem;
    width: 0;
    height: 1px;
    background: linear-gradient(to right, var(--accent), transparent);
    transition: width 0.6s var(--ease-out-expo);
}
.manifesto-item:hover::before {
    width: 1.5rem;
}
.manifesto-num {
    color: var(--white, #fff);
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-appui);
    font-weight: 500;
    color: var(--accent);
    letter-spacing: 0.08em;
    line-height: 1;
    transform: translateY(0.4em);
    }
.manifesto-body {
    display: flex;
    flex-direction: column;
    gap: 0.65rem;
    max-width: 56ch;
}
.manifesto-title {
    font-family: 'Syne', system-ui, sans-serif;
    font-size: var(--pas-titre);
    font-weight: 700;
    line-height: 1.15;
    letter-spacing: -0.02em;
    color: #f8fafc;
    margin: 0;
}
.manifesto-text {
    font-size: var(--pas-appui);
    line-height: 1.55;
    color: rgba(255, 255, 255, 0.55);
    margin: 0;
    max-width: 50ch;
}

/* Stagger animations on scroll */
.manifesto-item.fade-in {
    transform: translateY(40px);
    opacity: 0;
}
.manifesto-item.fade-in.visible {
    transform: translateY(0);
    opacity: 1;
    transition: transform 0.9s var(--ease-out-expo), opacity 0.7s ease;
}
.manifesto-item.fade-in.visible[data-principle="01"] { transition-delay: 0s; }
.manifesto-item.fade-in.visible[data-principle="02"] { transition-delay: 0.08s; }
.manifesto-item.fade-in.visible[data-principle="03"] { transition-delay: 0.16s; }
.manifesto-item.fade-in.visible[data-principle="04"] { transition-delay: 0.24s; }
.manifesto-item.fade-in.visible[data-principle="05"] { transition-delay: 0.32s; }

@media (max-width: 768px) {
    .manifesto-item {
        grid-template-columns: 60px 1fr;
        gap: 1rem;
        padding: 1.5rem 0;
    }
    .manifesto-num {
        font-size: var(--pas-etiquette);
        transform: translateY(0.3em);
    }
}

/* Light theme */
html[data-theme="light"] #manifesto { background: var(--black); }
html[data-theme="light"] .manifesto-list,
html[data-theme="light"] .manifesto-item { border-color: rgba(10, 14, 26, 0.08); }
html[data-theme="light"] .manifesto-title { color: var(--gray-1); }
html[data-theme="light"] .manifesto-text { color: var(--gray-2); }


/* =====================================================================
   PERSONALIZED HERO BADGE — fade in/out de transition
   ===================================================================== */
.hero-badge-text {
    display: inline-block;
    transition: opacity 0.5s var(--ease-out-expo), transform 0.5s var(--ease-out-expo);
}
.hero-badge.is-swapping .hero-badge-text {
    opacity: 0;
    transform: translateY(-4px);
}


/* =====================================================================
   UI SOUNDS — toggle button (opt-in)
   ===================================================================== */
.sound-toggle {
    position: fixed;
    bottom: 24px;
    /* Décalé à droite du bouton Palette (design-playground) pour éviter
       l'overlap visuel "Palette/Sons OFF" en bas-gauche. */
    left: 132px;
    z-index: 60;
    background: rgba(10, 14, 26, 0.85);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 6px;
    padding: 0.55rem 0.95rem;
    color: rgba(226, 232, 240, 0.8);
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    transition: all 0.3s var(--ease-out-expo);
    letter-spacing: 0.04em;
}
.sound-toggle:hover {
    border-color: rgba(255, 255, 255, 0.22);
    color: #f8fafc;
    transform: translateY(-2px);
}
.sound-toggle.is-on {
    border-color: var(--accent);
    color: var(--accent-light);
    background: rgba(59, 130, 246, 0.12);
}
.sound-toggle .sound-icon {
    width: 14px; height: 14px;
    stroke: currentColor;
    fill: none;
    stroke-width: 2;
}

@media (max-width: 640px) {
    /* Mobile : icône seule (label caché), positionnée à droite de Palette */
    .sound-toggle { bottom: 16px; left: 88px; padding: 0.45rem 0.55rem; font-size: var(--pas-etiquette); }
    .sound-toggle .sound-label { display: none; }
}

html[data-theme="light"] .sound-toggle {
    background: rgba(255, 255, 255, 0.92);
    color: var(--gray-1);
    border-color: rgba(10, 14, 26, 0.12);
}
html[data-theme="light"] .sound-toggle.is-on {
    background: rgba(59, 130, 246, 0.08);
    border-color: var(--accent);
    color: var(--accent);
}

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

/* =====================================================================
   IMAGINE TON PROJET — generative case study form (revolutionary feature)
   ===================================================================== */
#imagine {
    padding: clamp(4rem, 8vw, 8rem) 0; /* harmonisé 128px max */
    position: relative;
    background: var(--black);
    overflow: hidden;
}
#imagine::before {
    content: '';
    position: absolute;
    inset: 0;
    background: radial-gradient(ellipse 70% 40% at 50% 0%, rgba(59, 130, 246, 0.05), transparent 70%);
    pointer-events: none;
}

.imagine-head {
    max-width: 760px;
    margin-bottom: clamp(3rem, 6vw, 4rem);
}
.imagine-accent {
    color: var(--accent-light, #60a5fa);
    font-style: italic;
}

/* === Form grid === */
.imagine-form {
    display: flex;
    flex-direction: column;
    gap: 2.5rem;
    max-width: 880px;
    transition: opacity 0.5s var(--ease-out-expo), transform 0.5s var(--ease-out-expo), filter 0.4s;
}
.imagine-form[data-state="loading"] {
    opacity: 0.4;
    pointer-events: none;
    filter: blur(2px);
}
.imagine-form[data-state="done"] {
    opacity: 0;
    transform: translateY(-20px);
    pointer-events: none;
    height: 0;
    overflow: hidden;
    margin: 0;
}

.imagine-q {
    border: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}
.imagine-q-label {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    font-family: 'Syne', system-ui, sans-serif;
    font-size: var(--pas-sous-titre);
    font-weight: 600;
    color: #f8fafc;
    margin: 0;
    letter-spacing: -0.015em;
    padding: 0;
}
.imagine-q-num {
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    color: var(--accent);
    background: rgba(59, 130, 246, 0.12);
    padding: 0.25rem 0.5rem;
    border-radius: 4px;
    letter-spacing: 0.05em;
    flex-shrink: 0;
}

.imagine-options {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    gap: 0.65rem;
}
.imagine-option {
    cursor: pointer;
    user-select: none;
}
.imagine-option input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}
.imagine-option-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.55rem;
    padding: 1.1rem 0.85rem;
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 12px;
    color: rgba(255, 255, 255, 0.7);
    font-size: var(--pas-appui);
    text-align: center;
    transition: all 0.3s var(--ease-out-expo);
    min-height: 86px;
}
.imagine-option-card svg {
    width: 22px; height: 22px;
    color: var(--gray-3);
    transition: color 0.3s;
}
.imagine-option:hover .imagine-option-card {
    border-color: rgba(255, 255, 255, 0.18);
    background: rgba(255, 255, 255, 0.05);
    transform: translateY(-2px);
}
.imagine-option input:checked + .imagine-option-card {
    border-color: var(--accent);
    background: rgba(59, 130, 246, 0.08);
    color: #f8fafc;
    box-shadow: 0 0 0 1px var(--accent), 0 12px 30px -12px rgba(59, 130, 246, 0.4);
}
.imagine-option input:checked + .imagine-option-card svg {
    color: var(--accent);
}

/* Vibe option — colored swatch */
.vibe-card {
    flex-direction: row;
    justify-content: flex-start;
    gap: 0.7rem;
    padding: 0.75rem 0.95rem;
    min-height: 56px;
}
.vibe-swatch {
    width: 28px;
    height: 28px;
    border-radius: 6px;
    flex-shrink: 0;
}

/* Delay option — emphasis */
.imagine-option--delay .imagine-option-card {
    flex-direction: column;
    gap: 0.2rem;
    align-items: flex-start;
    text-align: left;
    padding: 0.95rem 1rem;
}
.imagine-option--delay strong {
    font-family: 'Syne', system-ui, sans-serif;
    font-size: var(--pas-lecture);
    color: inherit;
    font-weight: 600;
}
.imagine-option--delay em {
    font-style: normal;
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    color: rgba(255, 255, 255, 0.45);
    letter-spacing: 0.04em;
}
.imagine-option--delay input:checked + .imagine-option-card em {
    color: var(--accent);
}

/* Submit button */
.imagine-submit {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    padding: 1.1rem 1.85rem;
    background: linear-gradient(115deg, var(--accent), var(--accent-light));
    color: #fff;
    font-family: inherit;
    font-weight: 600;
    font-size: var(--pas-lecture);
    border: none;
    border-radius: 6px;
    cursor: pointer;
    align-self: flex-start;
    box-shadow: 0 18px 40px -12px rgba(59, 130, 246, 0.5);
    transition: transform 0.3s var(--ease-out-expo), box-shadow 0.3s;
    margin-top: 1rem;
}
.imagine-submit:hover {
    transform: translateY(-2px);
    box-shadow: 0 24px 50px -10px rgba(59, 130, 246, 0.6);
}
.imagine-submit:active {
    transform: translateY(-1px);
}

/* === Result panel === */
.imagine-result {
    max-width: 880px;
    margin-top: 0;
    border: 1px solid rgba(59, 130, 246, 0.25);
    border-radius: 18px;
    background: linear-gradient(160deg, rgba(59, 130, 246, 0.04), rgba(139, 92, 246, 0.025));
    padding: 0;
    opacity: 0;
    transform: translateY(20px);
    height: 0;
    overflow: hidden;
    transition: opacity 0.5s var(--ease-out-expo), transform 0.6s var(--ease-out-expo), height 0.6s var(--ease-out-expo);
}
.imagine-result.is-visible {
    opacity: 1;
    transform: translateY(0);
    height: auto;
    margin-top: 1.5rem;
    padding: 1.75rem 2rem 2rem;
}

.imagine-result-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 1.25rem;
    flex-wrap: wrap;
}
.imagine-result-eyebrow {
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    color: var(--accent);
    letter-spacing: 0.15em;
    font-weight: 500;
}
.imagine-reset {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    background: transparent;
    border: 1px solid rgba(255, 255, 255, 0.14);
    color: rgba(255, 255, 255, 0.6);
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    padding: 0.4rem 0.75rem;
    border-radius: 6px;
    cursor: pointer;
    transition: all 0.25s;
}
.imagine-reset:hover {
    color: #f8fafc;
    border-color: rgba(255, 255, 255, 0.25);
}

.imagine-result-title {
    font-family: 'Syne', system-ui, sans-serif;
    font-size: var(--pas-titre);
    font-weight: 700;
    line-height: 1.15;
    letter-spacing: -0.025em;
    color: #f8fafc;
    margin: 0 0 1.1rem;
    min-height: 1.5em;
}
.imagine-result-title .accent {
    color: var(--accent-light, #60a5fa);
    }
.imagine-result-text {
    font-size: var(--pas-lecture);
    line-height: 1.6;
    color: rgba(255, 255, 255, 0.78);
    margin: 0 0 1.75rem;
    max-width: 60ch;
    min-height: 4.5em;
}

.imagine-result-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 1rem;
    padding: 1.25rem 0;
    margin-bottom: 1.5rem;
    border-top: 1px solid rgba(255, 255, 255, 0.08);
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.imagine-result-meta {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}
.imagine-result-meta-label {
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    color: rgba(255, 255, 255, 0.4);
    letter-spacing: 0.1em;
    text-transform: uppercase;
}
.imagine-result-meta-val {
    font-family: 'Syne', system-ui, sans-serif;
    font-size: var(--pas-lecture);
    color: #f8fafc;
    font-weight: 600;
    letter-spacing: -0.01em;
}

.imagine-result-actions {
    display: flex;
    gap: 0.75rem;
    flex-wrap: wrap;
    margin-bottom: 1rem;
}
.imagine-cta-primary {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.95rem 1.6rem;
    background: linear-gradient(115deg, var(--accent), var(--accent-light));
    color: #fff;
    text-decoration: none;
    border-radius: 6px;
    font-weight: 600;
    font-size: var(--pas-appui);
    transition: transform 0.3s, box-shadow 0.3s;
    box-shadow: 0 10px 24px -8px rgba(59, 130, 246, 0.4);
}
.imagine-cta-primary:hover { transform: translateY(-2px); }
.imagine-cta-secondary {
    display: inline-flex;
    align-items: center;
    padding: 0.95rem 1.4rem;
    color: rgba(255, 255, 255, 0.85);
    text-decoration: none;
    border: 1px solid rgba(255, 255, 255, 0.15);
    border-radius: 6px;
    font-weight: 500;
    font-size: var(--pas-appui);
    transition: all 0.3s;
}
.imagine-cta-secondary:hover {
    border-color: rgba(255, 255, 255, 0.3);
    color: #fff;
    transform: translateY(-2px);
}

.imagine-result-note {
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    color: rgba(255, 255, 255, 0.4);
    margin: 0;
    line-height: 1.5;
    font-style: italic;
}

/* Cursor blink during typing animation */
.imagine-typing::after {
    content: '▋';
    color: var(--accent);
    animation: imagine-cursor-blink 0.8s steps(2) infinite;
    margin-left: 2px;
    font-weight: 400;
}
@keyframes imagine-cursor-blink {
    50% { opacity: 0; }
}

/* Light theme */
html[data-theme="light"] #imagine { background: var(--black); }
html[data-theme="light"] .imagine-q-label,
html[data-theme="light"] .imagine-result-title,
html[data-theme="light"] .imagine-option input:checked + .imagine-option-card,
html[data-theme="light"] .imagine-result-meta-val { color: var(--gray-1); }
html[data-theme="light"] .imagine-result-text { color: var(--gray-2); }
html[data-theme="light"] .imagine-option-card {
    background: rgba(10, 14, 26, 0.03);
    border-color: rgba(10, 14, 26, 0.1);
    color: var(--gray-1);
}
html[data-theme="light"] .imagine-result {
    background: linear-gradient(160deg, rgba(59, 130, 246, 0.06), rgba(139, 92, 246, 0.03));
    border-color: rgba(59, 130, 246, 0.2);
}
html[data-theme="light"] .imagine-result-meta-label,
html[data-theme="light"] .imagine-result-eyebrow,
html[data-theme="light"] .imagine-result-note {
    color: var(--gray-3);
}
html[data-theme="light"] .imagine-cta-secondary {
    color: var(--gray-1);
    border-color: var(--gray-4);
}

/* Mobile */
@media (max-width: 768px) {
    .imagine-options { grid-template-columns: 1fr 1fr; }
    .imagine-q-label { font-size: var(--pas-lecture); flex-wrap: wrap; }
    .imagine-result.is-visible { padding: 1.25rem; }
    .imagine-result-actions { flex-direction: column; align-items: stretch; }
    .imagine-cta-primary, .imagine-cta-secondary { justify-content: center; }
}

/* Hero showcase: ajuste grid maintenant qu'on a 2 cards (au lieu de 3) */
.hero-showcase {
    grid-template-areas:
        "status status"
        "testimonial testimonial";
    grid-template-rows: auto auto;
}

/* =====================================================================
   LOGO PREMIUM — symbole Monark + logotype « Mitia »
   (chargé partout via styles.css)
   ===================================================================== */
.logo {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    position: relative;   /* ancre l'easter-egg « pêche » (pseudo-éléments) */
    overflow: visible; /* laisse respirer la bascule 3D + la ligne de pêche */
    transform-origin: left center;
    transition: transform 0.45s cubic-bezier(0.22, 1, 0.36, 1);
    animation: brand-logo-fade-in 0.6s cubic-bezier(0.22, 1, 0.36, 1) backwards;
}
.logo:hover, .logo:focus-visible { transform: scale(1.04); }
/* Brand logo — symbole Monark (lockup/symbole), aplat monochrome adaptatif
   via currentColor (Blanc sur sombre, Encre sur clair).
   Animation signature raffinée, sans gadget :
   1) entrée one-shot au chargement (le lockup se pose : fondu + léger glissé,
      le symbole Monark se pose avec un scale doux) ;
   2) AU REPOS : rien ne bouge (pas d'orbite infinie) — net et premium ;
   3) survol/focus : bascule 3D sobre du symbole (aller-retour) + zoom discret.
   Transform/opacity uniquement, sans will-change permanent (charte motion). */
.logo .brand-logo {
    fill: currentColor;
}
/* Survol/focus : bascule 3D sobre du symbole (rotateY aller-retour), pas de
   toupie 360. Le zoom du lockup est porté par le lien .logo. */
.logo:hover .brand-logo,
.logo:focus-visible .brand-logo { animation: brand-logo-tilt 0.7s cubic-bezier(0.34, 0.85, 0.3, 1); }
/* Symbole Monark = 1er <path> : pose d'entrée one-shot, scale doux (pas de
   rebond ressort ni de rotate). transform-box pour pivoter au centre. */
.logo .brand-logo > path {
    transform-box: fill-box;
    transform-origin: 50% 50%;
    animation: brand-mark-pop 0.6s cubic-bezier(0.22, 1, 0.36, 1) 0.08s backwards;
}
@keyframes brand-logo-fade-in {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: none; }
}
@keyframes brand-mark-pop {
    from { transform: scale(0.9); }
    to   { transform: none; }
}
/* Bascule 3D sobre au survol : le symbole s'incline puis revient. */
@keyframes brand-logo-tilt {
    0%   { transform: perspective(560px) rotateY(0deg); }
    45%  { transform: perspective(560px) rotateY(-20deg); }
    100% { transform: perspective(560px) rotateY(0deg); }
}
html[data-theme="light"] .logo .brand-logo { fill: #16181D; }
/* ===== Édition fête nationale malagasy (26 juin) — symbole Monark drapeau =======
   Art officiel « drapeau ondulant » (public/assets/brand), basculé via
   <html data-fete-mg> (script de date dans Layout). Cadre blanc sur fond sombre
   (défaut), cadre encre sur thème clair. Chargé uniquement quand affiché. */
.brand-mark-fete { display: none; }
html[data-fete-mg] .logo .brand-logo .brand-mark-mono { display: none; }
html[data-fete-mg] .logo .brand-logo .brand-mark-fete { display: block; }
.brand-mark-fete-light { display: none; }
html[data-theme="light"] .logo .brand-logo .brand-mark-fete-dark { display: none; }
html[data-theme="light"] .logo .brand-logo .brand-mark-fete-light { display: block; }
.logo .brand-logo .brand-mark-fete {
    transform-box: fill-box;
    transform-origin: 50% 50%;
    animation: brand-mark-pop 0.85s cubic-bezier(0.34, 1.56, 0.64, 1) 0.05s backwards;
}
/* Footer éditorial : symbole Monark discret avant le nom */
.ed-foot-logo { fill: currentColor; opacity: 0.5; margin-right: 0.15em; }
.logo-brace {
    display: inline-block;
    color: var(--accent);
    font-weight: 500;
    opacity: 0;
    transform: translateY(60%);
    animation: logo-brace-in 0.55s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
    transition: color 0.25s ease, transform 0.25s ease;
    will-change: transform;
}
.logo-brace-l { animation-delay: 0.02s; }
.logo-brace-r { animation-delay: 0.50s; }
@keyframes logo-brace-in {
    0%   { opacity: 0; transform: translateY(60%); }
    100% { opacity: 1; transform: translateY(0); }
}
.logo:hover .logo-brace {
    color: var(--green);
}
.logo:hover .logo-brace-l { transform: translateX(-2px); }
.logo:hover .logo-brace-r { transform: translateX(2px); }
.logo-letter {
    display: inline-block;
    opacity: 0;
    transform: translateY(120%) rotate(8deg);
    animation: logo-letter-in 0.7s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
    will-change: transform;
}
.logo-letter:nth-child(1) { animation-delay: 0.10s; }
.logo-letter:nth-child(2) { animation-delay: 0.18s; }
.logo-letter:nth-child(3) { animation-delay: 0.26s; }
.logo-letter:nth-child(4) { animation-delay: 0.34s; }
.logo-letter:nth-child(5) { animation-delay: 0.42s; }

/* ===== Easter-egg « pêche » (clin d'œil DreamWorks) ================
   Au SURVOL du logo seulement, une fine ligne descend de l'extrémité droite
   du logotype (le « a ») avec un bouchon/flotteur qui oscille doucement, puis
   se rétracte au repos. CSS pur (pseudo-éléments), zéro markup, ancré à droite
   du lien .logo (là où se dessine le « a »). Position affinée visuellement. */
.logo::before,               /* la ligne */
.logo::after {               /* le bouchon */
    content: "";
    position: absolute;
    pointer-events: none;
    opacity: 0;
    z-index: 1;
}
.logo::before {
    right: 4px;
    top: 5px;
    width: 1px;
    height: 0;
    background: currentColor;
    transition: height 0.32s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.18s ease;
}
.logo::after {
    right: 2.5px;
    top: 5px;
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: var(--accent);
    transition: opacity 0.18s ease;
}
.logo:hover::before,
.logo:focus-visible::before { opacity: 0.55; height: 19px; }
.logo:hover::after,
.logo:focus-visible::after {
    opacity: 1;
    transform: translateY(19px);   /* pend au bout de la ligne (tenu aussi en reduced-motion) */
    animation: logo-fish-bob 1.8s ease-in-out 0.3s infinite;
}
/* le bouchon flotte au bout de la ligne + léger tangage */
@keyframes logo-fish-bob {
    0%, 100% { transform: translate(0, 19px); }
    50%      { transform: translate(1.6px, 17.5px); }
}

/* Reduced-motion : logo à l'état final, aucune animation (entrée + survol + pêche). */
@media (prefers-reduced-motion: reduce) {
    .logo,
    .logo .brand-logo,
    .logo .brand-logo > path,
    .logo:hover .brand-logo,
    .logo:focus-visible .brand-logo,
    .logo::before,
    .logo::after,
    .logo:hover::after,
    .logo:focus-visible::after { animation: none !important; }
    .logo-brace,
    .logo-letter { animation: none !important; opacity: 1; transform: none; }
}

/* =====================================================================
   SHOWCASE ATELIER CARD — version premium dans le hero showcase
   (chargé partout via styles.css)
   ===================================================================== */
.showcase-atelier {
    grid-area: testimonial;
    text-decoration: none;
    color: inherit;
    display: grid;
    grid-template-columns: 110px 1fr;
    align-items: center;
    gap: 1.25rem;
    position: relative;
    overflow: hidden;
    background: linear-gradient(135deg, rgba(167, 139, 250, 0.1), rgba(59, 130, 246, 0.06) 50%, rgba(16, 185, 129, 0.06));
    border: 1px solid rgba(167, 139, 250, 0.25);
    transition: all 0.5s var(--ease-out-expo);
    padding: 1.4rem 1.5rem;
    border-radius: 16px;
}
.showcase-atelier::before {
    content: '';
    position: absolute;
    inset: 0;
    background: radial-gradient(ellipse 60% 80% at 0% 50%, rgba(167, 139, 250, 0.12), transparent 60%);
    pointer-events: none;
    opacity: 0.7;
    transition: opacity 0.5s ease;
}
.showcase-atelier:hover {
    border-color: rgba(167, 139, 250, 0.5);
    transform: translateY(-2px);
    box-shadow: 0 24px 60px -16px rgba(167, 139, 250, 0.4);
}
.showcase-atelier:hover::before { opacity: 1; }

.atelier-card-orb {
    position: relative;
    width: 100px;
    height: 100px;
    flex-shrink: 0;
    perspective: 600px;
    transform-style: preserve-3d;
}
.orb-core {
    position: absolute;
    inset: 30%;
    border-radius: 50%;
    background: radial-gradient(circle at 35% 30%, #c4b5fd, #7c3aed 60%, #4c1d95);
    box-shadow: 0 0 30px rgba(167, 139, 250, 0.6),
                inset -8px -8px 20px rgba(76, 29, 149, 0.6),
                inset 6px 6px 16px rgba(196, 181, 253, 0.4);
    animation: orb-pulse 3s ease-in-out infinite;
}
@keyframes orb-pulse {
    0%, 100% { transform: scale(1); }
    50%      { transform: scale(1.08); box-shadow: 0 0 50px rgba(167, 139, 250, 0.85), inset -8px -8px 20px rgba(76, 29, 149, 0.7), inset 6px 6px 16px rgba(196, 181, 253, 0.55); }
}

.orb-ring {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    border: 1px solid rgba(255, 255, 255, 0.2);
    pointer-events: none;
}
.orb-ring-1 { transform: rotateX(70deg); border-color: rgba(167, 139, 250, 0.45); animation: ring-spin-1 8s linear infinite; }
.orb-ring-2 { transform: rotateY(70deg) rotateZ(20deg); border-color: rgba(59, 130, 246, 0.45); animation: ring-spin-2 12s linear infinite; inset: 8%; }
.orb-ring-3 { transform: rotateX(60deg) rotateZ(45deg); border-color: rgba(16, 185, 129, 0.4); animation: ring-spin-3 14s linear infinite reverse; inset: 4%; }
@keyframes ring-spin-1 { to { transform: rotateX(70deg) rotateZ(360deg); } }
@keyframes ring-spin-2 { to { transform: rotateY(70deg) rotateZ(380deg); } }
@keyframes ring-spin-3 { to { transform: rotateX(60deg) rotateZ(-315deg); } }

.orb-satellite {
    position: absolute;
    width: 8px; height: 8px;
    border-radius: 50%;
    top: 50%; left: 50%;
    margin: -4px 0 0 -4px;
    box-shadow: 0 0 10px currentColor;
}
.orb-sat-1 { background: #a78bfa; color: #a78bfa;  }
.orb-sat-2 { background: #3b82f6; color: #3b82f6;  }
.orb-sat-3 { background: #10b981; color: #10b981;  }
@keyframes sat-orbit-1 { 0%   { transform: rotate(0deg)   translate(45px) rotate(0deg);   } 100% { transform: rotate(360deg) translate(45px) rotate(-360deg); } }
@keyframes sat-orbit-2 { 0%   { transform: rotate(120deg) translate(38px) rotate(-120deg); } 100% { transform: rotate(480deg) translate(38px) rotate(-480deg); } }
@keyframes sat-orbit-3 { 0%   { transform: rotate(240deg) translate(50px) rotate(-240deg); } 100% { transform: rotate(600deg) translate(50px) rotate(-600deg); } }

.showcase-atelier:hover .orb-core { animation-duration: 1.8s; }
.showcase-atelier:hover .orb-sat-1 { animation-duration: 2.5s; }
.showcase-atelier:hover .orb-sat-2 { animation-duration: 3.5s; }
.showcase-atelier:hover .orb-sat-3 { animation-duration: 4.5s; }

.atelier-card-content {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    min-width: 0;
    position: relative;
    z-index: 1;
}
.atelier-card-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    color: #c4b5fd;
    letter-spacing: 0.16em;
    font-weight: 600;
    text-transform: uppercase;
    width: fit-content;
}
.atelier-card-pulse {
    width: 6px; height: 6px;
    background: #a78bfa;
    border-radius: 50%;
    box-shadow: 0 0 0 3px rgba(167, 139, 250, 0.2);
    animation: card-pulse 2s ease-in-out infinite;
}
@keyframes card-pulse {
    0%, 100% { box-shadow: 0 0 0 3px rgba(167, 139, 250, 0.2); }
    50%      { box-shadow: 0 0 0 6px rgba(167, 139, 250, 0.05); }
}
.atelier-card-title {
    font-family: 'Syne', system-ui, sans-serif;
    font-size: var(--pas-sous-titre);
    font-weight: 700;
    color: #f8fafc;
    margin: 0.15rem 0 0.2rem;
    letter-spacing: -0.02em;
    line-height: 1.05;
}
.atelier-card-italic {
    color: var(--accent-light, #60a5fa);
    font-style: italic;
    }
.atelier-card-sub {
    font-size: var(--pas-etiquette);
    color: rgba(255, 255, 255, 0.6);
    margin: 0 0 0.6rem;
    line-height: 1.5;
}
.atelier-card-cta {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    color: #c4b5fd;
    font-size: var(--pas-etiquette);
    font-weight: 600;
    margin-top: auto;
    transition: gap 0.3s var(--ease-out-expo);
}
.atelier-card-cta svg { transition: transform 0.3s var(--ease-out-expo); }
.showcase-atelier:hover .atelier-card-cta { color: #a78bfa; gap: 0.55rem; }
.showcase-atelier:hover .atelier-card-cta svg { transform: translate(2px, -2px); }

html[data-theme="light"] .showcase-atelier {
    background: linear-gradient(135deg, rgba(167, 139, 250, 0.08), rgba(59, 130, 246, 0.05) 50%, rgba(16, 185, 129, 0.05));
    border-color: rgba(167, 139, 250, 0.25);
}
html[data-theme="light"] .atelier-card-title { color: var(--gray-1); }
html[data-theme="light"] .atelier-card-sub { color: var(--gray-2); }
html[data-theme="light"] .atelier-card-eyebrow { color: #7c3aed; }
html[data-theme="light"] .atelier-card-cta { color: #7c3aed; }

@media (max-width: 640px) {
    .showcase-atelier { grid-template-columns: 80px 1fr; gap: 0.85rem; padding: 1.1rem 1.2rem; }
    .atelier-card-orb { width: 80px; height: 80px; }
    .atelier-card-title { font-size: var(--pas-lecture); }
}

/* En mobile, le hero-showcase gardait une hauteur figée (héritée du montage
   desktop 2 colonnes) : la rangée « testimonial » était trop courte et la carte
   atelier rognait son CTA (« Entrer dans l'atelier » collé au bord bas). On
   laisse la grille ET les cartes se dimensionner à leur contenu. */
@media (max-width: 900px) {
    .hero-showcase { height: auto; align-content: start; }
    .hero-showcase > * { align-self: start; }
    .showcase-atelier { min-height: max-content; }
}

/* =====================================================================
   LOGO MARK — echo rings (replaces _ cursor)
   ===================================================================== */
.logo-mark {
    position: relative;
    display: inline-block;
    width: 14px;
    height: 14px;
    margin-left: 6px;
    vertical-align: 2px;
    flex-shrink: 0;
}
.mark-core,
.mark-ring {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    pointer-events: none;
}
.mark-core {
    inset: 35%;
    background: var(--accent);
    box-shadow: 0 0 8px var(--accent), 0 0 0 1px rgba(59, 130, 246, 0.3);
    animation: mark-core-breathe 2.4s ease-in-out infinite;
}
@keyframes mark-core-breathe {
    0%, 100% { transform: scale(1); box-shadow: 0 0 8px var(--accent), 0 0 0 1px rgba(59, 130, 246, 0.3); }
    50%      { transform: scale(1.18); box-shadow: 0 0 14px var(--accent), 0 0 0 2px rgba(59, 130, 246, 0.5); }
}

.mark-ring {
    border: 1.5px solid var(--accent);
    transform: scale(0.3);
    opacity: 0;
    animation: mark-echo 2.4s cubic-bezier(0.22, 1, 0.36, 1) infinite;
}
.mark-ring:nth-of-type(3) { animation-delay: 1.2s; }
@keyframes mark-echo {
    0%   { transform: scale(0.3); opacity: 0; }
    15%  { opacity: 1; }
    100% { transform: scale(1.7); opacity: 0; }
}

/* Hover : speed up + color shift */
.logo:hover .mark-core { animation-duration: 1s; }
.logo:hover .mark-ring { animation-duration: 1s; border-color: var(--terra-light); }

/* Light theme */
html[data-theme="light"] .mark-core { background: var(--accent); box-shadow: 0 0 8px var(--accent), 0 0 0 1px rgba(37, 99, 235, 0.3); }
html[data-theme="light"] .mark-ring { border-color: var(--accent); }

@media (prefers-reduced-motion: reduce) {
    .mark-core, .mark-ring { animation: none; }
    .mark-ring { opacity: 0.6; transform: scale(1); }
}

/* =====================================================================
   LOGO WORDMARK CLEAN — "Mitia." avec point accent stylisé
   ===================================================================== */
.logo {
    display: inline-flex !important;
    align-items: baseline;
    gap: 0;
    font-family: 'Syne', system-ui, sans-serif;
    font-weight: 700;
    font-size: var(--pas-sous-titre);
    letter-spacing: -0.02em;
    line-height: 1;
    text-decoration: none;
    color: var(--gray-1);
    transition: color 0.3s;
}
.logo-name {
    display: inline-flex;
    align-items: baseline;
}
.logo-letter {
    display: inline-block;
    opacity: 0;
    transform: translateY(100%);
    animation: logo-letter-in 0.7s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
    will-change: transform;
}
.logo-letter:nth-child(1) { animation-delay: 0.10s; }
.logo-letter:nth-child(2) { animation-delay: 0.18s; }
.logo-letter:nth-child(3) { animation-delay: 0.26s; }
.logo-letter:nth-child(4) { animation-delay: 0.34s; }
.logo-letter:nth-child(5) { animation-delay: 0.42s; }

@keyframes logo-letter-in {
    0%   { opacity: 0; transform: translateY(100%); }
    60%  { opacity: 1; }
    100% { opacity: 1; transform: translateY(0); }
}

.logo-dot {
    display: inline-block;
    color: var(--accent);
    margin-left: 1px;
    transform: translateY(0);
    opacity: 0;
    animation: logo-dot-in 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) 0.55s forwards,
               logo-dot-pulse 2.4s ease-in-out 1.2s infinite;
    will-change: transform, color;
    line-height: 1;
}
@keyframes logo-dot-in {
    0%   { opacity: 0; transform: translateY(-8px) scale(0); }
    60%  { opacity: 1; }
    100% { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes logo-dot-pulse {
    0%, 100% { color: var(--accent); transform: scale(1); }
    50%      { color: var(--accent-light); transform: scale(1.25); }
}

.logo:hover .logo-letter {
    animation: logo-letter-wiggle 0.5s ease-in-out;
}
.logo:hover .logo-letter:nth-child(1) { animation-delay: 0s; }
.logo:hover .logo-letter:nth-child(2) { animation-delay: 0.04s; }
.logo:hover .logo-letter:nth-child(3) { animation-delay: 0.08s; }
.logo:hover .logo-letter:nth-child(4) { animation-delay: 0.12s; }
.logo:hover .logo-letter:nth-child(5) { animation-delay: 0.16s; }
@keyframes logo-letter-wiggle {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-2px); color: var(--accent); }
}

.logo:hover .logo-dot {
    animation-duration: 0.8s, 0.6s;
    color: var(--accent-light);
}

/* Hide all old mark / cursor elements (legacy) */
.logo-mark, .logo-cursor, .mark-core, .mark-ring { display: none !important; }

@media (prefers-reduced-motion: reduce) {
    .logo-letter, .logo-dot { animation: none; opacity: 1; transform: none; }
    /* Brand logo : ni orbite 3D, ni flip, ni pop ; feedback de survol en opacité. */
    .logo, .logo .brand-logo, .logo .brand-logo > path, .logo .brand-logo .brand-mark-fete { animation: none !important; transform: none !important; opacity: 1; }
    .logo:hover, .logo:focus-visible { opacity: 0.72; }
}

/* =====================================================================
   v5 — Statut de disponibilité dynamique (Tana)
   ===================================================================== */
.location-status {
    display: inline-flex !important;
    align-items: center;
    gap: 0.5rem;
}
.location-status-dot {
    width: 8px; height: 8px;
    border-radius: 50%;
    background: #10b981; /* green = on */
    box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.18);
    animation: status-breath 2.4s ease-in-out infinite;
}
.location-status[data-mode="soon"] .location-status-dot {
    background: #f59e0b;
    box-shadow: 0 0 0 4px rgba(245, 158, 11, 0.18);
}
.location-status[data-mode="off"] .location-status-dot {
    background: #64748b;
    box-shadow: 0 0 0 4px rgba(100, 116, 139, 0.18);
    animation: none;
}
@keyframes status-breath {
    0%, 100% { transform: scale(1); opacity: 1; }
    50%      { transform: scale(1.18); opacity: 0.85; }
}

/* =====================================================================
   v5 — Footer share button (navigator.share + clipboard fallback)
   ===================================================================== */
.footer-share-btn {
    background: none;
    border: none;
    font: inherit;
    cursor: pointer;
    color: inherit;
    padding: 0;
}
.footer-share-btn:hover {
    color: var(--accent);
}
.footer-share-btn:active {
    transform: scale(0.95);
}

/* =====================================================================
   v5 — Bottom sheet preview projet (tap long mobile)
   ===================================================================== */
.project-sheet {
    position: fixed;
    inset: 0;
    z-index: 9990;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    visibility: hidden;
    pointer-events: none;
}
.project-sheet.is-open {
    visibility: visible;
    pointer-events: auto;
}
.project-sheet-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.6);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    opacity: 0;
    transition: opacity 0.4s var(--ease-out-expo);
}
.project-sheet.is-open .project-sheet-backdrop {
    opacity: 1;
}
.project-sheet-panel {
    position: relative;
    width: 100%;
    max-width: 600px;
    max-height: 85vh;
    background: linear-gradient(170deg, #0f1424, #0a0e1a);
    border-radius: 22px 22px 0 0;
    border-top: 1px solid rgba(255, 255, 255, 0.08);
    box-shadow: 0 -20px 50px rgba(0, 0, 0, 0.5);
    transform: translateY(100%);
    transition: transform 0.45s var(--ease-out-expo);
    overflow: hidden;
    overflow-y: auto;
}
.project-sheet.is-open .project-sheet-panel {
    transform: translateY(0);
}
.project-sheet-grip {
    position: absolute;
    top: 8px; left: 50%;
    transform: translateX(-50%);
    width: 40px; height: 4px;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.18);
}
.project-sheet-close {
    position: absolute;
    top: 12px; right: 14px;
    width: 36px; height: 36px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(255, 255, 255, 0.08);
    color: rgba(255, 255, 255, 0.85);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    z-index: 2;
}
.project-sheet-close:active {
    transform: scale(0.92);
}
.project-sheet-img {
    width: 100%;
    height: 200px;
    object-fit: cover;
    display: block;
    background: var(--dark-3);
}
.project-sheet-body {
    padding: 1.5rem 1.5rem 2rem;
}
.project-sheet-eyebrow {
    font-family: var(--mono);
    font-size: var(--pas-etiquette);
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: var(--accent-light);
    margin: 0 0 0.5rem;
}
.project-sheet-title {
    font-family: var(--font-display);
    font-size: var(--pas-sous-titre); /* 26px→20px : titre de fiche projet, "intertitre" (tâche 4, arbitré) */
    font-weight: 700;
    margin: 0 0 0.85rem;
    color: rgba(255, 255, 255, 0.95);
    letter-spacing: -0.02em;
}
.project-sheet-desc {
    color: rgba(255, 255, 255, 0.65);
    line-height: 1.5;
    margin: 0 0 1.25rem;
    font-size: var(--pas-appui);
}
.project-sheet-cta {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.85rem 1.25rem;
    border-radius: 6px;
    background: linear-gradient(115deg, var(--accent), var(--accent-light));
    color: white;
    text-decoration: none;
    font-weight: 600;
    font-size: var(--pas-appui);
}
html[data-theme="light"] .project-sheet-panel {
    background: linear-gradient(170deg, #ffffff, #fafaf8);
    border-top-color: rgba(10, 14, 26, 0.08);
}
html[data-theme="light"] .project-sheet-close {
    background: rgba(10, 14, 26, 0.06);
    border-color: rgba(10, 14, 26, 0.1);
    color: var(--gray-1);
}
html[data-theme="light"] .project-sheet-grip {
    background: rgba(10, 14, 26, 0.18);
}
html[data-theme="light"] .project-sheet-title { color: var(--gray-1); }
html[data-theme="light"] .project-sheet-desc { color: var(--gray-2); }

/* =====================================================================
   MOBILE TEXT VISIBILITY — Force tout le contenu visible sur mobile
   Désactive les animations fade-in/split-char/reveal qui dépendent du JS
   IntersectionObserver, garantissant que TOUT le contenu s'affiche.
   ===================================================================== */
@media (max-width: 768px) {
    .fade-in,
    .fade-in.stagger-1,
    .fade-in.stagger-2,
    .fade-in.stagger-3,
    .fade-in.stagger-4,
    .fade-in.stagger-5 {
        opacity: 1 !important;
        transform: none !important;
        transition: opacity 0.3s ease, transform 0.3s ease !important;
    }
    .split-char {
        transform: none !important;
        opacity: 1 !important;
    }
    .reveal-line {
        transform: none !important;
    }
    .is-split .split-char,
    .is-split.visible .split-char,
    .reveal-wrap.visible .split-char {
        transform: none !important;
        opacity: 1 !important;
    }
    /* Slider case-slides: empile tous les slides pour voir tout le contenu */
    .case-slide-stat,
    .case-slide-pullquote {
        opacity: 1 !important;
        transform: none !important;
    }
}

/* =====================================================================
   v6 — MAGAZINE SECTION PRESENTATION
   Chapter headers + sticky side nav + section dividers
   ===================================================================== */

/* Le filigrane XXL centré (« 01 / PROJETS », « 02 / STACK »… en contour
   fantôme, `-webkit-text-stroke` sur `color: transparent`) vivait ici.
   Retiré, comme côté portfolio : il DOUBLAIT le petit numéro de coin du
   module 013 (retiré aussi) sans être plus lisible que lui — mesuré à
   1280px, son contour passait derrière le titre de section et le rendait
   sale sans jamais se lire lui-même, et le thème clair le traitait en
   ghost stroke encore plus pâle (rgba(10,14,26,0.06)).
   Un seul repère désormais, réutilisé plutôt que doublé : l'ouverture
   éditoriale du module 128 (numéro + filet, en tête de section). */

/* Place all section content above watermark */
section[id]:not(#home) > .container > * {
    position: relative;
    z-index: 1;
}

/* Section headers enrichis — eyebrow + line accent + meta */
.section-label {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 0.7rem;
}
.section-label::before {
    content: '';
    width: 32px;
    height: 1px;
    background: linear-gradient(90deg, var(--accent), transparent);
    display: inline-block;
    transform-origin: left;
    transform: scaleX(0);
    transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}
section[id].in-view .section-label::before,
.section-label.visible::before {
    transform: scaleX(1);
}

/* Section divider entre les sections — ligne stylisée */
section[id]:not(#home):not(#case-study) {
    position: relative;
}
section[id]:not(#home):not(#case-study)::before {
    content: '';
    position: absolute;
    top: 0; left: 50%;
    width: 1px;
    height: 60px;
    background: linear-gradient(180deg, transparent, var(--accent) 50%, transparent);
    transform: translateX(-50%);
    opacity: 0;
    transition: opacity 1s ease;
}
section[id]:not(#home):not(#case-study).in-view::before {
    opacity: 0.6;
}
/* Pages éditoriales : séparateur en or, pas le bleu --accent du portfolio */
.editorial-page section[id]:not(#home):not(#case-study)::before {
    background: linear-gradient(180deg, transparent, var(--gold, #c8a25c) 50%, transparent);
}

/* =====================================================================
   STICKY SIDE NAV (vertical, à droite)
   ===================================================================== */
.side-nav-vert {
    position: fixed;
    right: clamp(1rem, 2vw, 2.5rem);
    top: 50%;
    transform: translateY(-50%);
    z-index: 80;
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.5s ease;
}
.side-nav-vert.is-visible {
    opacity: 1;
    pointer-events: auto;
}
.snv-item {
    display: inline-flex;
    align-items: center;
    gap: 0.65rem;
    text-decoration: none;
    color: var(--gray-3);
    font-family: var(--mono);
    font-size: var(--pas-etiquette);
    letter-spacing: 0.16em;
    text-transform: uppercase;
    transition: color 0.3s, gap 0.3s;
    position: relative;
}
.snv-item::before {
    content: '';
    width: 18px;
    height: 1px;
    background: currentColor;
    display: inline-block;
    transition: width 0.4s cubic-bezier(0.16, 1, 0.3, 1), background 0.4s;
    flex-shrink: 0;
}
.snv-num {
    font-family: var(--display);
    font-style: italic;
    font-size: var(--pas-appui);
    color: var(--gray-2);
    min-width: 1.4em;
    text-align: right;
}
.snv-text {
    opacity: 0;
    transform: translateX(-6px);
    transition: opacity 0.4s, transform 0.4s;
    pointer-events: none;
    white-space: nowrap;
}
.snv-item:hover {
    color: var(--gray-1);
}
.snv-item:hover::before {
    width: 32px;
    background: var(--accent);
}
.snv-item:hover .snv-text {
    opacity: 1;
    transform: translateX(0);
}
.snv-item.is-active {
    color: var(--gray-1);
}
.snv-item.is-active::before {
    width: 42px;
    background: var(--accent);
}
/* Le numéro est lu, pas décoré : --accent-texte (132, point 3). */
.snv-item.is-active .snv-num {
    color: var(--accent-texte);
}
.snv-item.is-active .snv-text {
    opacity: 1;
    transform: translateX(0);
}

html[data-theme="light"] .snv-item { color: var(--gray-3); }
html[data-theme="light"] .snv-item.is-active { color: var(--gray-1); }

/* =====================================================================
   CHAPTER MARKER — petits chiffres romains à gauche
   ===================================================================== */
section[id]:not(#home):not(#case-study):not(#hors-code)[data-chapter]::after {
    content: attr(data-chapter);
    position: absolute;
    top: 3.5rem;
    left: clamp(1rem, 3vw, 3rem);
    font-family: var(--display);
    font-style: italic;
    font-weight: 300;
    font-size: var(--pas-appui);
    color: var(--accent);
    opacity: 0.6;
    letter-spacing: 0.05em;
    z-index: 4;
    writing-mode: vertical-rl;
}

/* =====================================================================
   IN-VIEW REVEAL via IntersectionObserver
   ===================================================================== */
section[id]:not(#home).in-view .section-label,
section[id]:not(#home).in-view .section-title {
    animation: section-rise 1s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
@keyframes section-rise {
    from { transform: translateY(8px); }
    to   { transform: translateY(0); }
}

/* Hide on mobile to keep clean */
@media (max-width: 1024px) {
    .side-nav-vert { display: none; }
}
@media (max-width: 768px) {
    /* Les deux réglages de `.container::after` (taille sous 1025px, masquage
       sous 768px) réglaient le filigrane XXL du module 041, retiré : sans
       lui ils ne pilotaient plus rien. Le `::before` ci-dessous reste : il
       vise la SECTION (le trait vertical de séparation), pas le conteneur. */
    section[id]:not(#home):not(#case-study)::before {
        height: 36px;
    }
}

/* =====================================================================
   v6.1 — Cache l'ancien .side-nav (dots minimalistes)
   Le nouveau .side-nav-vert magazine premium le remplace
   ===================================================================== */
.side-nav { display: none !important; }

/* =====================================================================
   v6.3 — Lien Tarifs accent (or) dans la nav portfolio
   ===================================================================== */
.nav-link-accent {
    position: relative;
    color: #c8a25c !important;
}
.nav-link-accent::after {
    content: '$';
    margin-left: 6px;
    /* Parent .nav-link fixé à 14px (module 000, non touché) : 0.75em
       d'origine (10,5px) contre 12px ici, écart ~14% (tâche 4, arbitré). */
    font-size: var(--pas-etiquette);
    opacity: 0.7;
}
.nav-link-accent:hover {
    color: #e4c486 !important;
}

/* =====================================================================
   v7.2 — Navbar dark uniquement sur landing/tarifs (bg charbon partout)
   Sur portfolio, le navbar suit le data-theme actif (light ou dark)
   La classe .editorial-page est ajoutée sur <html> par landing/tarifs
   ===================================================================== */
html.editorial-page header#header {
    background: rgba(10, 9, 7, 0.88) !important;
    backdrop-filter: blur(14px) !important;
    -webkit-backdrop-filter: blur(14px) !important;
    border-bottom: 1px solid rgba(237, 229, 214, 0.08) !important;
}
html.editorial-page header#header .nav-link {
    color: rgba(237, 229, 214, 0.78);
}
html.editorial-page header#header .nav-link:hover {
    color: #ede5d6;
}
html.editorial-page header#header .nav-link-accent {
    color: #c8a25c !important;
}
html.editorial-page header#header .nav-link-accent:hover {
    color: #e4c486 !important;
}
html.editorial-page header#header .logo,
html.editorial-page header#header .logo-name,
html.editorial-page header#header .logo-letter {
    color: #ede5d6 !important;
}
/* Le symbole Monark est un SVG dont le fill est forcé par le module 033
   (#16181D en clair) → invisible sur cette navbar. On le fait RESUIVRE
   currentColor (couleur du .logo, correctement thémée par page) : crème sur la
   navbar sombre de /atelier, encre sur une navbar claire éventuelle. */
html.editorial-page header#header .logo .brand-logo,
html.editorial-page header#header .logo .brand-mark-mono {
    fill: currentColor !important;
}
html.editorial-page header#header .logo-brace {
    color: #c8a25c !important;
}
html.editorial-page header#header .theme-toggle {
    background: transparent !important;
    border: 1px solid rgba(237, 229, 214, 0.15) !important;
    color: #ede5d6 !important;
}
html.editorial-page header#header .theme-toggle svg {
    stroke: #ede5d6 !important;
}
html.editorial-page header#header .hamburger-line {
    background: #ede5d6 !important;
}
html.editorial-page header#header .nav-cta {
    background: #c8a25c !important;
    color: #0a0907 !important;
    border: 1px solid #c8a25c !important;
}
html.editorial-page header#header .nav-cta:hover {
    background: #e4c486 !important;
    border-color: #e4c486 !important;
}

/* =====================================================================
   v7.1 — Fix nav-links background blanc résiduel
   Le style .nav-links avait un gradient blanc destiné au mobile fullscreen,
   qui s'appliquait aussi en desktop → carré blanc visible derrière les liens
   ===================================================================== */
header#header .nav-links {
    background: transparent !important;
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
    border: 0 !important;
    box-shadow: none !important;
}
/* Mobile : le menu fullscreen recouvre l'écran en charbon dark cohérent */
@media (max-width: 768px) {
    header#header .nav-links.open {
        background: linear-gradient(160deg, #14110d, #0a0907) !important;
    }
}


/* =====================================================================
   FOOTER LEGAL LINKS (mentions / confidentialité)
   ===================================================================== */
.footer-legal {
    display: inline-flex;
    gap: 0.5rem;
    align-items: center;
    font-family: var(--mono, 'JetBrains Mono', monospace);
    font-size: var(--pas-etiquette);
    letter-spacing: 0.04em;
    margin-top: 0.4rem;
}
.footer-legal-link {
    color: var(--gray-3, rgba(255, 255, 255, 0.4));
    text-decoration: none;
    transition: color 0.2s;
}
.footer-legal-link:hover {
    color: var(--accent-light, #00d4aa);
}
.footer-legal-sep {
    color: var(--gray-4, rgba(255, 255, 255, 0.18));
}

/* Petits liens légaux dans le footer landing/tarifs */
.ed-footer .foot-link-legal {
    font-size: var(--pas-etiquette);
    opacity: 0.72; /* 0.55 passait sous WCAG AA (~4.36:1) sur 5 liens persistants */
    transition: opacity 0.2s, color 0.2s;
}
.ed-footer .foot-link-legal:hover {
    opacity: 1;
    color: var(--gold, #c8a25c);
}


/* =====================================================================
   SECTION CLIENTS · social proof (logos / noms factuels)
   ===================================================================== */
#clients {
    /* Harmonisation : padding standardisé + background --black pour
       continuer l'alternance avec activity (--dark-1). */
    padding: clamp(4rem, 8vw, 8rem) 0; /* harmonisé 128px max */
    background: var(--black);
}
.clients-sub {
    color: var(--gray-2, rgba(255, 255, 255, 0.55));
    margin-bottom: 3rem;
    max-width: 540px;
}
.clients-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1rem;
    list-style: none;
    padding: 0;
    margin: 0;
}
.client-card {
    position: relative;
    background: rgba(255, 255, 255, 0.02);
    border: 1px solid rgba(255, 255, 255, 0.07);
    border-radius: 16px;
    transition: border-color 0.3s, background 0.3s, transform 0.3s;
}
.client-card:hover {
    border-color: rgba(0, 212, 170, 0.3);
    background: rgba(0, 212, 170, 0.04);
    transform: translateY(-2px);
}
.client-link {
    display: block;
    padding: 1.65rem 1.6rem;
    text-decoration: none;
    color: inherit;
    height: 100%;
}
.client-name {
    display: block;
    font-family: var(--display, 'Syne', sans-serif);
    font-weight: 700;
    font-size: var(--pas-lecture);
    color: var(--white, #fff);
    letter-spacing: -0.015em;
    margin-bottom: 0.35rem;
}
.client-meta {
    display: block;
    font-family: var(--mono, 'JetBrains Mono', monospace);
    font-size: var(--pas-etiquette);
    color: rgba(255, 255, 255, 0.62); /* a11y : contraste AA */
    letter-spacing: 0.04em;
}
/* Card stat (le +5 projets) */
.client-card--stat {
    background: linear-gradient(135deg, rgba(0, 212, 170, 0.08), rgba(74, 222, 128, 0.05));
    border-color: rgba(0, 212, 170, 0.25);
    padding: 1.65rem 1.6rem;
    display: flex;
    flex-direction: column;
    justify-content: center;
}
.client-card--stat:hover {
    transform: none;
}
.client-stat-num {
    font-family: var(--display, 'Syne', sans-serif);
    font-weight: 800;
    font-size: var(--pas-titre);
    color: var(--accent-light, #00d4aa);
    line-height: 1;
    margin-bottom: 0.5rem;
    letter-spacing: -0.04em;
}
.client-stat-text {
    font-family: var(--body, 'Onest', sans-serif);
    font-size: var(--pas-appui);
    color: var(--gray-2, rgba(255, 255, 255, 0.7));
    line-height: 1.5;
}

@media (max-width: 900px) {
    .clients-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 560px) {
    .clients-grid { grid-template-columns: 1fr; }
}

/* ── La dernière carte comble sa ligne ─────────────────────────────────
   Le mur comptait six clients : deux lignes pleines à trois colonnes,
   trois lignes pleines à deux. Le retrait d'Omega le fait tomber à cinq,
   et la ligne du bas s'arrête avant le bord — capture à 1280 : la carte
   « +5 projets livrés » laissait une case vide à sa droite, bien visible
   entre deux cartes qui, elles, ont un cadre.

   Pas de JS ici, contrairement à la grille des travaux : ce mur n'est ni
   filtré ni réordonné, sa dernière carte est toujours la dernière du
   DOM. La règle se tait d'elle-même dès que le compte redevient un
   multiple du nombre de colonnes. */
@media (min-width: 901px) {
    .clients-grid > .client-card:last-child:nth-child(3n + 2) { grid-column: span 2; }
    .clients-grid > .client-card:last-child:nth-child(3n + 1) { grid-column: span 3; }
}
@media (min-width: 561px) and (max-width: 900px) {
    .clients-grid > .client-card:last-child:nth-child(odd) { grid-column: span 2; }
}


/* =====================================================================
   TECH PILLS · variante design (marquee créatif)
   ===================================================================== */
.tech-pill--design {
    border-color: rgba(196, 113, 127, 0.20);
    background: linear-gradient(135deg, rgba(196, 113, 127, 0.05), rgba(200, 162, 92, 0.04));
}
.tech-pill--design:hover {
    border-color: rgba(196, 113, 127, 0.45);
    background: linear-gradient(135deg, rgba(196, 113, 127, 0.10), rgba(200, 162, 92, 0.08));
}
.tech-pill--design svg {
    flex-shrink: 0;
}
.tech-marquee--design {
    /* Liseré subtil pour signaler "côté design" */
    position: relative;
}
.tech-marquee--design::before {
    content: "DESIGN ·";
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    font-family: var(--mono, 'JetBrains Mono', monospace);
    font-size: var(--pas-etiquette);
    letter-spacing: 0.18em;
    color: rgba(196, 113, 127, 0.5);
    background: linear-gradient(90deg, var(--dark-2, #0a0e1a) 60%, transparent);
    padding: 0.3rem 0.85rem 0.3rem 1rem;
    z-index: 2;
    pointer-events: none;
}

/* =====================================================================
   PORTFOLIO ANIMS — Direction ÉDITORIAL MICRO-INTERACTIONS (B1-B11)
   Workflow ultracode /portfolio : 9/10 unanime sur 3 jurys (perf+éditorial+tech)
   ===================================================================== */

/* ============== B1 — CASCADE TYPO SECTION (eyebrow→title→sub 80ms) ============== */
@media (prefers-reduced-motion: no-preference) {
    .section-label.fade-in,
    .section-title.fade-in,
    .section-sub.fade-in {
        transition-property: opacity, transform;
        transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
        transition-duration: 700ms;
    }
    .section-label.fade-in.visible { transition-delay: 0ms; }
    .section-title.fade-in.visible { transition-delay: 80ms; }
    .section-sub.fade-in.visible   { transition-delay: 160ms; }
    section .section-label + .section-title.fade-in.visible { transition-delay: 80ms; }
    section .section-title + .section-sub.fade-in.visible,
    section .section-title + p.fade-in.visible { transition-delay: 160ms; }
}

/* ============== B5 — EYEBROW LINE DRAW (trait or sous kicker) ============== */
@media (prefers-reduced-motion: no-preference) {
    section .section-label.fade-in {
        position: relative;
        display: inline-block;
    }
    section .section-label.fade-in::after {
        content: "";
        position: absolute;
        left: 0;
        bottom: -8px;
        width: 40px;
        height: 1px;
        background: linear-gradient(90deg, var(--gold, #c8a25c), rgba(200, 162, 92, 0.2));
        transform: scaleX(0);
        transform-origin: left center;
        transition: transform 400ms cubic-bezier(0.16, 1, 0.3, 1);
        transition-delay: 200ms;
    }
    section .section-label.fade-in.visible::after {
        transform: scaleX(1);
    }
}

/* ============== B9 — SKILLS BAR MASK SEQUENTIAL (clip-path vs width) ============== */
@media (prefers-reduced-motion: no-preference) {
    .skill-bar { position: relative; overflow: hidden; }
    .skill-bar .skill-progress,
    .skill-bar .skill-fill,
    .skill-bar > span:not(.skill-label) {
        clip-path: inset(0 100% 0 0);
        transition: clip-path 900ms cubic-bezier(0.25, 1, 0.5, 1);
    }
    .skill-bar.is-revealed .skill-progress,
    .skill-bar.is-revealed .skill-fill,
    .skill-bar.is-revealed > span:not(.skill-label) {
        clip-path: inset(0 0 0 0);
    }
    .skill-bar:nth-child(1).is-revealed { transition-delay: 0ms; }
    .skill-bar:nth-child(2).is-revealed { transition-delay: 120ms; }
    .skill-bar:nth-child(3).is-revealed { transition-delay: 240ms; }
    .skill-bar:nth-child(4).is-revealed { transition-delay: 360ms; }
    .skill-bar:nth-child(5).is-revealed { transition-delay: 480ms; }
}

/* ============== B8 — WATERMARK MASK REVEAL (faisceau qui balaie) ============== */
@media (prefers-reduced-motion: no-preference) {
    .hero-watermark {
        clip-path: inset(0 100% 0 0);
        transition: clip-path 1200ms linear;
    }
    .hero-watermark.is-watermark-revealed { clip-path: inset(0 0 0 0); }
    #about::before,
    #work::before,
    #skills::after {
        clip-path: inset(0 100% 0 0);
        transition: clip-path 1200ms linear;
    }
    #about.is-watermark-revealed::before,
    #work.is-watermark-revealed::before,
    #skills.is-watermark-revealed::after { clip-path: inset(0 0 0 0); }
}

/* ============== B2 — MASK REVEAL PULL-QUOTE (bande de papier tirée) ============== */
@media (prefers-reduced-motion: no-preference) {
    #about .about-text:first-of-type {
        clip-path: inset(0 0 100% 0);
        transition: clip-path 700ms cubic-bezier(0.65, 0, 0.35, 1);
    }
    #about .about-text:first-of-type.is-quote-revealed {
        clip-path: inset(0 0 0 0);
    }
}

/* ============== B3 — DROP CAP CLIP DRAW (lettrine livre) ============== */
@media (prefers-reduced-motion: no-preference) {
    #about .about-text:first-of-type::first-letter,
    #manifesto .manifesto-item[data-principle="01"] .manifesto-text::first-letter {
    color: var(--accent-light, #60a5fa);
        font-family: var(--display);
        font-weight: 600;
        /* Hors échelle des 6 pas (tâche 4), même famille que 013 : lettrine
           en em, proportionnelle au paragraphe qui la porte, pas un jeton
           fixe. */
        font-size: 4.4em;
        line-height: 0.86;
        float: left;
        margin: 0.08em 0.12em 0 -0.04em;
        color: var(--gold, #c8a25c);
        clip-path: inset(100% 0 0 0);
        transition: clip-path 800ms cubic-bezier(0.65, 0, 0.35, 1);
        transition-delay: 200ms;
    }
    #about .about-text:first-of-type.is-quote-revealed::first-letter,
    #manifesto .manifesto-item[data-principle="01"].visible .manifesto-text::first-letter {
        clip-path: inset(0 0 0 0);
    }
}

/* ============== B7 — COLOR HOVER LINK (sobre presse) ============== */
@media (hover: hover) {
    .manifesto-title,
    .bento-card-title,
    footer a:not(.btn),
    .nav-link:not(.btn) {
        position: relative;
        transition: color 250ms ease;
    }
    .manifesto-title::after,
    .bento-card-title::after,
    footer a:not(.btn)::after,
    .nav-link:not(.btn)::after {
        content: "";
        position: absolute;
        left: 0;
        right: 0;
        bottom: -2px;
        height: 1px;
        background: currentColor;
        transform: scaleX(0);
        transform-origin: left center;
        transition: transform 300ms cubic-bezier(0.16, 1, 0.3, 1);
        opacity: 0.7;
    }
    .manifesto-title:hover::after,
    .bento-card-title:hover::after,
    footer a:not(.btn):hover::after,
    .nav-link:not(.btn):hover::after {
        transform: scaleX(1);
    }
    .manifesto-title:hover,
    .bento-card-title:hover,
    footer a:not(.btn):hover {
        color: var(--gold, #c8a25c);
    }
}

/* ============== B10 — EDITORIAL QUOTE (citation centrale, ton sobre) ============== */
.editorial-quote-section {
    padding: clamp(48px, 7vh, 96px) 6vw;
    display: flex;
    justify-content: center;
    align-items: center;
    position: relative;
    overflow: hidden;
}
.editorial-quote {
    font-family: var(--display);
    font-style: italic;
    font-weight: 400;
    font-size: var(--pas-titre);
    line-height: 1.25;
    letter-spacing: -0.015em;
    text-align: center;
    margin: 0;
    max-width: 780px;
    color: var(--ink, #efe9d8);
    position: relative;
    transform: translateZ(0);
    opacity: 0.35;
    transition: opacity 600ms cubic-bezier(0.16, 1, 0.3, 1);
}
.editorial-quote.is-quote-active {
    opacity: 1;
}
.editorial-quote-mark {
    display: inline-block;
    color: var(--gold, #c8a25c);
    font-family: serif; /* N'avait pas Syne en repli, rendait serif générique — var(--display) changerait le rendu */
    font-weight: 500;
    /* Volontairement laissé en em, jamais migré (tâche 4) : enfant de
       .editorial-quote (var(--pas-titre), jusqu'à 41,6px). Fixé en jeton
       fixe (ex. var(--pas-appui) = 14px), le guillemet perdrait ~65% de sa
       taille au desktop — le piège em-traité-comme-rem, déjà rencontré sur
       le portfolio pour ce même sélecteur. */
    font-size: 0.9em;
    opacity: 0;
    transform: translateY(4px);
    transition: opacity 400ms ease, transform 400ms ease;
    margin: 0 0.12em;
    vertical-align: baseline;
}
.editorial-quote.is-quote-active .editorial-quote-mark--open {
    opacity: 1;
    transform: translateY(0);
    transition-delay: 100ms;
}
.editorial-quote.is-quote-active .editorial-quote-mark--close {
    opacity: 1;
    transform: translateY(0);
    transition-delay: 200ms;
}

/* ============== B4 — MANIFESTO REVEAL CASCADE (sticky retiré, flux naturel) ============== */
@media (hover: hover) and (min-width: 900px) and (prefers-reduced-motion: no-preference) {
    #manifesto .manifesto-item {
        opacity: 0.4;
        transform: translateY(16px);
        transition:
            opacity 500ms cubic-bezier(0.16, 1, 0.3, 1),
            transform 500ms cubic-bezier(0.16, 1, 0.3, 1);
    }
    #manifesto .manifesto-item.is-active,
    #manifesto .manifesto-item.visible {
        opacity: 1;
        transform: translateY(0);
    }
}

/* ============== B6 — HERO NAME CHAR MORPH (typo vivante one-shot) ============== */
@media (prefers-reduced-motion: no-preference) {
    .hero-title .char {
        display: inline-block;
        opacity: 0;
        transform: translateY(20px);
        font-variation-settings: "wght" 400;
        animation: heroCharMorph 700ms cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
        animation-delay: calc(var(--i, 0) * 30ms);
    }
    .hero-title .char.char-space { width: 0.3em; }
    @keyframes heroCharMorph {
        0%   { opacity: 0; transform: translateY(20px); font-variation-settings: "wght" 400; }
        60%  { opacity: 1; }
        100% { opacity: 1; transform: translateY(0); font-variation-settings: "wght" 700; }
    }
    .hero-title .char.char-done { will-change: auto; }
}

/* ============== B11 — REDUCED MOTION GUARD (global safety net) ============== */
@media (prefers-reduced-motion: reduce) {
    .section-label.fade-in,
    .section-title.fade-in,
    .section-sub.fade-in {
        transition: none !important;
        transition-delay: 0 !important;
    }
    section .section-label.fade-in::after {
        transform: scaleX(1) !important;
        transition: none !important;
    }
    .skill-bar .skill-progress,
    .skill-bar .skill-fill,
    .skill-bar > span:not(.skill-label) { clip-path: inset(0 0 0 0) !important; transition: none !important; }
    .hero-watermark,
    #about::before, #work::before, #skills::after { clip-path: inset(0 0 0 0) !important; transition: none !important; }
    #about .about-text:first-of-type { clip-path: inset(0 0 0 0) !important; transition: none !important; }
    .editorial-quote { opacity: 1 !important; transform: none !important; transition: none !important; }
    .editorial-quote-mark { transform: scale(1) !important; transition: none !important; }
    #manifesto .manifesto-item { opacity: 1 !important; transform: none !important; position: static !important; height: auto !important; }
    .hero-title .char { opacity: 1 !important; transform: none !important; animation: none !important; }
}

/* =====================================================================
   COMMAND PALETTE ⌘K
   ===================================================================== */
body.cmdk-open { overflow: hidden; }
.cmdk-overlay {
    position: fixed;
    inset: 0;
    z-index: 10000;
    display: none;
    align-items: flex-start;
    justify-content: center;
    padding: clamp(4rem, 14vh, 11rem) 1rem 2rem;
    background: rgba(5, 8, 17, 0.55);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    opacity: 0;
    transition: opacity 0.2s ease;
}
.cmdk-overlay.is-open { display: flex; opacity: 1; }
.cmdk-panel {
    width: 100%;
    max-width: 560px;
    background: #0c1018;
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 16px;
    box-shadow: 0 28px 80px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.03) inset;
    overflow: hidden;
    transform: translateY(8px) scale(0.99);
    transition: transform 0.24s cubic-bezier(0.16, 1, 0.3, 1);
}
.cmdk-overlay.is-open .cmdk-panel { transform: translateY(0) scale(1); }
.cmdk-input-wrap {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 1rem 1.15rem;
    border-bottom: 1px solid rgba(255, 255, 255, 0.07);
}
.cmdk-search-ico {
    font-family: 'JetBrains Mono', monospace;
    color: var(--gold, #c8a25c);
    font-size: var(--pas-lecture);
    opacity: 0.8;
}
.cmdk-input {
    flex: 1;
    background: transparent;
    border: none;
    outline: none;
    color: #efe9d8;
    font-family: 'Syne', system-ui, sans-serif;
    font-size: var(--pas-lecture);
    letter-spacing: -0.01em;
}
.cmdk-input::placeholder { color: rgba(237, 229, 214, 0.4); }
.cmdk-esc {
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: rgba(237, 229, 214, 0.45);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 5px;
    padding: 0.2rem 0.4rem;
}
.cmdk-list {
    list-style: none;
    margin: 0;
    padding: 0.5rem;
    max-height: min(50vh, 420px);
    overflow-y: auto;
}
.cmdk-group {
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: rgba(237, 229, 214, 0.4);
    padding: 0.85rem 0.85rem 0.4rem;
}
.cmdk-item {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 0.7rem 0.85rem;
    border-radius: 10px;
    cursor: pointer;
    color: rgba(237, 229, 214, 0.9);
}
.cmdk-item.is-active { background: rgba(200, 162, 92, 0.13); }
.cmdk-ico {
    width: 22px;
    text-align: center;
    color: var(--gold, #c8a25c);
    font-size: var(--pas-appui);
    flex-shrink: 0;
}
.cmdk-label {
    font-family: 'Syne', system-ui, sans-serif;
    font-weight: 600;
    font-size: var(--pas-appui);
}
.cmdk-hint {
    margin-left: auto;
    font-size: var(--pas-etiquette);
    color: rgba(237, 229, 214, 0.45);
    text-align: right;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 45%;
}
.cmdk-empty {
    padding: 2rem;
    text-align: center;
    color: rgba(237, 229, 214, 0.45);
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-appui);
}
.cmdk-foot {
    display: flex;
    gap: 1.25rem;
    padding: 0.7rem 1.15rem;
    border-top: 1px solid rgba(255, 255, 255, 0.07);
    font-size: var(--pas-etiquette);
    color: rgba(237, 229, 214, 0.4);
}
.cmdk-foot kbd {
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 4px;
    padding: 0.05rem 0.3rem;
    margin-right: 0.15rem;
}
/* Hint cliquable ⌘K (header) */
.cmdk-trigger {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 8px;
    padding: 0.35rem 0.6rem;
    font-family: 'JetBrains Mono', monospace;
    font-size: var(--pas-etiquette);
    color: rgba(237, 229, 214, 0.55);
    cursor: pointer;
    transition: border-color 0.2s, color 0.2s;
}
.cmdk-trigger:hover { border-color: var(--gold, #c8a25c); color: var(--gold, #c8a25c); }
html[data-theme="light"] .cmdk-overlay { background: rgba(250, 246, 236, 0.5); }
html[data-theme="light"] .cmdk-panel { background: #fff; border-color: rgba(10,14,26,0.1); }
html[data-theme="light"] .cmdk-input { color: #0a0e1a; }
html[data-theme="light"] .cmdk-item { color: rgba(10,14,26,0.85); }
@media (prefers-reduced-motion: reduce) {
    .cmdk-overlay, .cmdk-panel { transition: none; }
}

/* =====================================================================
   PERF HUD — moniteur Core Web Vitals live (bas-droite, desktop)
   ===================================================================== */
.perf-hud {
    position: fixed;
    right: 1.25rem;
    bottom: 1.25rem;
    z-index: 95;
    font-family: 'JetBrains Mono', monospace;
}
.perf-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    appearance: none;
    cursor: pointer;
    background: rgba(12, 16, 24, 0.82);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    border: 1px solid rgba(237, 229, 214, 0.14);
    border-radius: 6px;
    padding: 0.45rem 0.8rem;
    color: rgba(237, 229, 214, 0.85);
    font-size: var(--pas-etiquette);
    letter-spacing: 0.06em;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
    transition: border-color 0.2s, transform 0.2s;
}
.perf-badge:hover { border-color: var(--gold, #c8a25c); transform: translateY(-1px); }
.perf-bolt { color: var(--gold, #c8a25c); }
.perf-dot {
    width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;
    background: #6b7280;
    transition: background 0.3s;
}
.perf-dot[data-r="good"] { background: #4ade80; box-shadow: 0 0 7px rgba(74, 222, 128, 0.7); }
.perf-dot[data-r="ni"]   { background: #fbbf24; box-shadow: 0 0 7px rgba(251, 191, 36, 0.6); }
.perf-dot[data-r="poor"] { background: #f87171; box-shadow: 0 0 7px rgba(248, 113, 113, 0.6); }
.perf-dot[data-r="wait"] { background: rgba(237, 229, 214, 0.25); box-shadow: none; }

.perf-panel {
    position: absolute;
    right: 0;
    bottom: calc(100% + 0.6rem);
    width: 256px;
    background: rgba(10, 14, 22, 0.94);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    border: 1px solid rgba(237, 229, 214, 0.12);
    border-radius: 14px;
    padding: 0.9rem 1rem 0.85rem;
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
    opacity: 0;
    transform: translateY(8px) scale(0.98);
    transform-origin: bottom right;
    pointer-events: none;
    transition: opacity 0.22s ease, transform 0.22s cubic-bezier(0.16, 1, 0.3, 1);
}
.perf-hud.is-open .perf-panel { opacity: 1; transform: none; pointer-events: auto; }
.perf-head {
    display: flex; align-items: center; justify-content: space-between;
    font-size: var(--pas-etiquette); letter-spacing: 0.14em; color: rgba(237, 229, 214, 0.45);
    margin-bottom: 0.7rem;
}
.perf-close {
    appearance: none; background: none; border: none; cursor: pointer;
    color: rgba(237, 229, 214, 0.62); font-size: var(--pas-appui); line-height: 1; padding: 0;
}
.perf-close:hover { color: var(--gold, #c8a25c); }
.perf-rows { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.perf-row { display: grid; grid-template-columns: 2.4rem 1fr auto; align-items: center; gap: 0.5rem; }
.perf-row-fps { grid-template-columns: 2.4rem auto 1fr auto; }
.perf-k { font-size: var(--pas-etiquette); color: rgba(237, 229, 214, 0.45); letter-spacing: 0.06em; }
.perf-v { font-size: var(--pas-appui); color: var(--ivory, #ede5d6); font-variant-numeric: tabular-nums; }
.perf-spark { display: flex; align-items: flex-end; gap: 1px; height: 16px; }
.perf-spark i { width: 3px; background: var(--gold, #c8a25c); opacity: 0.65; border-radius: 1px; min-height: 1px; }
.perf-foot {
    margin-top: 0.8rem; padding-top: 0.65rem;
    border-top: 1px solid rgba(237, 229, 214, 0.08);
    font-size: var(--pas-etiquette); color: rgba(237, 229, 214, 0.55); text-align: center;
}
.perf-note { margin-top: 0.4rem; font-size: var(--pas-etiquette); color: rgba(237, 229, 214, 0.32); text-align: center; }

/* ===================================================================
   PASSE UI/UX — contrastes (audit juin 2026)
   Surtout thème clair (opt-in) où des couleurs en dur passaient sous AA.
   Overrides depuis styles.css (global) via préfixe `html` pour gagner la
   cascade sans toucher tarifs.css / landing.css / carnet.css.
   =================================================================== */
/* Rotator accueil : variantes foncées lisibles en thème clair (AA ≥ 5:1). */
html[data-theme="light"] .rot-word[data-disc="dev"]    { color: #8a6516; }
html[data-theme="light"] .rot-word[data-disc="design"] { color: #a8324a; }
html[data-theme="light"] .rot-word[data-disc="mobile"] { color: #3c6b34; }
/* Cartes tarifs : surface opaque sombre en thème clair (sinon délavées et
   l'accent or/rose/vert tombe à ~1.5:1). Carte sombre sur page claire = ok. */
html[data-theme="light"] .pkg-card {
    background: linear-gradient(170deg, #14110d, #0a0907);
    border-color: rgba(237, 229, 214, 0.14);
}
/* Labels config tarifs : dark AA (0.5→0.66) ; clair → encre lisible. */
html .cfg-result-label { color: rgba(237, 229, 214, 0.66); }
html[data-theme="light"] .cfg-result-label,
html[data-theme="light"] .cfg-type-box small { color: rgba(10, 14, 26, 0.62); }
/* Sous-titre bouton booking (toujours sur ivoire) : 0.55→0.72 (AA). */
html .bcta-sub { color: rgba(10, 9, 7, 0.72); }
/* Tags carnet : 0.5→0.64 (sortait juste sous AA). */
html .carnet-tags li { color: rgba(237, 229, 214, 0.64); }

/* Skip-link « Aller au contenu » : caché hors focus, visible au clavier. */
.skip-link {
    position: fixed; top: 0; left: 8px; z-index: 2000;
    transform: translateY(-130%);
    background: var(--accent, #3b82f6); color: #fff;
    padding: 0.6rem 1rem; border-radius: 0 0 8px 8px;
    font-size: var(--pas-appui); font-weight: 600; text-decoration: none;
    transition: transform 0.2s var(--ease-soft, ease);
}
.skip-link:focus { transform: translateY(0); outline: 2px solid #fff; outline-offset: 2px; }
/* Charte : pas de backdrop-filter sur le footer éditorial (élément animé). */
html .ed-footer { -webkit-backdrop-filter: none !important; backdrop-filter: none !important; }

/* Sélecteur de langue (FR · EN) — global : page portfolio + pages EN. */
.lang-switch { display: inline-flex; align-items: center; gap: 0.4rem; font-family: var(--mono, 'JetBrains Mono', monospace); font-size: var(--pas-etiquette); letter-spacing: 0.06em; }
.lang-link { color: rgba(255, 255, 255, 0.55); text-decoration: none; padding: 0.25rem 0.5rem; border-radius: 6px; transition: color 0.2s ease, background 0.2s ease; }
.lang-link:hover, .lang-link:focus-visible { color: rgba(255, 255, 255, 0.92); }
.lang-link.is-active { color: var(--color-gold, #c8a25c); background: rgba(200, 162, 92, 0.12); }
.lang-sep { color: rgba(255, 255, 255, 0.3); }
html[data-theme="light"] .lang-link { color: rgba(10, 10, 10, 0.55); }
html[data-theme="light"] .lang-link:hover, html[data-theme="light"] .lang-link:focus-visible { color: rgba(10, 10, 10, 0.9); }
html[data-theme="light"] .lang-link.is-active { color: #9a6b00; background: rgba(154, 107, 0, 0.1); }

/* ====================================================================
   SURVOL DES CARTES — retrait des filets/voiles dégradés bleu→vert au
   survol (look « trop IA »), remplacés par un survol éditorial sobre :
   léger soulèvement + ombre douce. Les filets décoratifs de section
   passent en unicolore discret.
   ==================================================================== */
/* Cartes services : plus de filet dégradé en haut, ni de voile coloré. */
.service-card::before { background: transparent !important; }
.service-card::after  { background: transparent !important; }
.service-card {
    transition: background 0.3s ease,
                transform 0.4s var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1)),
                box-shadow 0.4s ease;
}
.service-card:hover { transform: translateY(-2px); box-shadow: 0 18px 38px -22px rgba(0, 0, 0, 0.62); }
html[data-theme="light"] .service-card:hover { box-shadow: 0 16px 34px -20px rgba(0, 0, 0, 0.16); }
/* Cartes projets (bento) : plus de filet dégradé animé sous le titre. */
.bento-title::after { background: transparent !important; }
/* Cartes repères (stats) : retire le filet doré décoratif en haut à gauche. */
.stat-card::before { display: none !important; }
/* Pages projet + témoignages : retire toutes les barres/filets de couleur en
   haut (et l'accent latéral) des cartes — chiffres, KPI, features, témoignages.
   Même motif « IA ». !important pour passer devant project.css/mobile-premium. */
.proj-stat::before,
.proj-stat-item::before,
.proj-kpi::before,
.proj-feature-item::before,
.testimonial-card::before { display: none !important; }
/* Filets décoratifs de section : unicolore sobre au lieu du dégradé bleu→vert. */
.work-header::after { background: var(--accent, #3b82f6) !important; opacity: 0.65; }
@media (prefers-reduced-motion: reduce) {
    .service-card:hover { transform: none; }
}

html[data-theme="light"] .perf-badge { background: rgba(255, 255, 255, 0.85); color: rgba(10, 14, 26, 0.8); }
html[data-theme="light"] .perf-panel { background: rgba(255, 255, 255, 0.96); border-color: rgba(10, 14, 26, 0.1); }
html[data-theme="light"] .perf-v { color: #0a0e1a; }

@media (max-width: 768px) { .perf-hud { display: none; } }

/* ====================================================================
   THÈME CLAIR — lisibilité du texte (mobile + desktop). Beaucoup de textes
   secondaires utilisaient des couleurs EN DUR du thème sombre
   (rgba(255,255,255,X)) qui zappent les variables → restaient blancs sur
   fond clair = illisibles. On les repasse en encre. Les widgets volontairement
   sombres en thème clair (terminal) gardent un texte clair. Vérifié au
   contraste WCAG sur toutes les sections du portfolio.
   ==================================================================== */
html[data-theme="light"] .hero-sub,
html[data-theme="light"] .section-sub,
html[data-theme="light"] .atelier-card-sub,
html[data-theme="light"] .manifesto-text,
html[data-theme="light"] .no-text,
html[data-theme="light"] .imagine-reset,
html[data-theme="light"] .dev-tag,
html[data-theme="light"] .design-tag,
html[data-theme="light"] .client-meta,
html[data-theme="light"] .social-link,
html[data-theme="light"] .design-playground-hint,
html[data-theme="light"] .dp-reset,
html[data-theme="light"] .about-tag,
html[data-theme="light"] .m-item,
html[data-theme="light"] .imagine-option-card em { color: rgba(10, 14, 26, 0.7) !important; }
html[data-theme="light"] .no-title,
html[data-theme="light"] .contact-item-val { color: rgba(10, 14, 26, 0.92) !important; }
html[data-theme="light"] .editorial-quote-text { color: rgba(10, 14, 26, 0.88) !important; }
html[data-theme="light"] .tech-currently-rotator { color: #2e7d32 !important; }
/* Widgets sombres en thème clair : le texte reste clair. */
html[data-theme="light"] .terminal-name,
html[data-theme="light"] .terminal-time,
html[data-theme="light"] .tk-mute { color: rgba(255, 255, 255, 0.5) !important; }
/* Compteur de filtre : suit la couleur du bouton (clair sur actif, encre sur inactif). */
html[data-theme="light"] .work-filter-count { color: inherit !important; opacity: 0.6; }

/* ── Tarifs & pages projet ── */
/* Cartes CTA volontairement sombres en thème clair : le texte reste clair
   (le réglage précédent les assombrissait à tort → sombre sur sombre). */
html[data-theme="light"] .cta-sub,
html[data-theme="light"] .bcta-sub { color: rgba(255, 255, 255, 0.72) !important; }
/* Valeurs crème (échelle du configurateur) sur fond clair → encre. */
html[data-theme="light"] .cfg-range-scale,
html[data-theme="light"] .cfg-range-scale span { color: rgba(10, 14, 26, 0.6) !important; }
/* Libellés dorés (eyebrows, prix d'option, meta) : doré assombri pour la
   lisibilité sur fond clair (était ~2.7:1). */
html[data-theme="light"] .section-eyebrow,
html[data-theme="light"] .cta-final-eyebrow,
html[data-theme="light"] .cfg-opt-price,
html[data-theme="light"] .tarifs-silence-meta { color: #8a6a22 !important; }
html[data-theme="light"] .muted { color: rgba(10, 14, 26, 0.58) !important; }
@media (prefers-reduced-motion: reduce) { .perf-panel, .perf-badge { transition: none; } }

/* =====================================================================
   DÉ-IA — TYPOGRAPHIE : justification + fin des cascades de texte
   ---------------------------------------------------------------------
   Chargé en dernier (concat par ordre de nom) pour gagner la cascade CSS.
   Deux règles voulues par le client :
   1. AUCUN texte en cascade : les reveals lettre-par-lettre (.split-char),
      ligne-par-ligne (.pq-line) et l'effet typewriter (curseur) sont figés
      en état final — le texte apparaît en bloc.
   2. Corps de texte NON justifié (tâche 5) : le justify CSS sans césure
      fiable creusait des rivières blanches, un marqueur de texte posé par
      une machine — précisément ce que ce module est censé combattre. La
      césure (hyphens:auto) et text-align-last:left restent : elles ne
      dépendent pas de justify et aident un texte aligné à gauche aussi.
   ===================================================================== */

/* ── 1) Fin des cascades ─────────────────────────────────────── */
.split-char {
    transform: none !important;
    transition: none !important;
    opacity: 1 !important;
    animation: none !important;
}
.pq-line,
.pq-line-inner {
    transform: none !important;
    transition: none !important;
    opacity: 1 !important;
    clip-path: none !important;
}
/* Curseur typewriter masqué (le mot est désormais statique) */
.typewriter-cursor { display: none !important; }
.typewriter-text.is-static { color: var(--accent-light, #60a5fa); }

/* ── 2) Césure du corps de texte (plus de justify, tâche 5) ───── */
.hero-sub,
.section-sub,
.about-text,
.manifesto-text,
.proj-section-text,
.tarifs-sub,
.section-sub-lead,
.pkg-tagline,
.ds-sub,
.ds-section-sub,
.carnet-sub,
.carnet-item-excerpt,
.carnet-article-excerpt,
.carnet-prose p,
.legal-body p,
.faq-a,
.cfg-lead,
.imagine-sub,
.booking-sub {
    text-align-last: left;
    -webkit-hyphens: auto;
    hyphens: auto;
}

/* Sur écrans étroits, la justification crée des rivières : on relâche. */
@media (max-width: 620px) {
    .hero-sub,
    .section-sub,
    .about-text,
    .manifesto-text,
    .proj-section-text,
    .tarifs-sub,
    .pkg-tagline,
    .ds-section-sub,
    .carnet-sub,
    .carnet-item-excerpt,
    .carnet-prose p,
    .legal-body p,
    .faq-a {
        text-align: left;
        hyphens: manual;
    }
}

/* =====================================================================
   100 — DÉ-IA : HERO ÉDITORIAL
   ---------------------------------------------------------------------
   Ce module est concaténé en dernier, il gagne donc la cascade.

   Constat : le hero portait les marqueurs typiques du design généré,
   dégradé plein écran bleu-vert, pastilles à accolades, orbe violet
   pulsant, lévitations infinies, halo lumineux sur le titre, et une
   nuée de boutons flottants. La page /atelier, elle, tient sur de la
   typographie et des filets. Ce module aligne le hero sur cette
   direction, sans toucher à la couleur de marque.
   ===================================================================== */

/* ── 1. Le fond animé, recoloré ───────────────────────────────────── */
/* Le mesh était en bleu électrique et vert (#0066ff, #1c7a50), la palette
   SaaS que produisent les générateurs. Il n'était pas le problème en soi,
   ses couleurs l'étaient. Repeint dans le registre encre et or de la page
   atelier : le mouvement reste, l'effet « template » disparaît. */
#hero-mesh {
    --gradient-color-1: #0d0b07;   /* encre chaude, la base */
    --gradient-color-2: #6a5121;   /* bronze */
    --gradient-color-3: #c8a25c;   /* l'or de la marque, le point lumineux */
    --gradient-color-4: #251a0e;   /* ombre chaude */
}
#hero-mesh.loaded { opacity: 0.95; }
@media (max-width: 600px) { #hero-mesh.loaded { opacity: 0.8; } }

/* Thème clair : mêmes tons, côté sable et ivoire */
html[data-theme="light"] #hero-mesh {
    /* les tons sable frôlaient le fond crème : on descend d'un cran */
    --gradient-color-1: #f6efdf;   /* crème chaud, la base */
    --gradient-color-2: #e3caa0;   /* sable */
    --gradient-color-3: #cfae70;   /* or clair, le point marqué */
    --gradient-color-4: #efe3cc;   /* ivoire */
}
html[data-theme="light"] #hero-mesh.loaded { opacity: 0.95; }

/* Fallback mobile aux tons chauds (le module 015 le posait en bleu/cyan, ce qui
   jurait avec le mesh or/bronze du bureau). Ici on l'harmonise. */
#hero-mesh.hero-mesh-fallback {
    opacity: 0.9;
    background:
        radial-gradient(ellipse 80% 60% at 28% 32%, rgba(200, 162, 92, 0.34), transparent 60%),
        radial-gradient(ellipse 70% 70% at 78% 68%, rgba(106, 81, 33, 0.34), transparent 62%),
        radial-gradient(ellipse 55% 45% at 62% 18%, rgba(200, 162, 92, 0.16), transparent 55%),
        #0d0b07;
}
html[data-theme="light"] #hero-mesh.hero-mesh-fallback {
    opacity: 0.85;
    background:
        radial-gradient(ellipse 80% 60% at 28% 32%, rgba(207, 174, 112, 0.30), transparent 60%),
        radial-gradient(ellipse 70% 70% at 78% 68%, rgba(227, 202, 160, 0.30), transparent 62%),
        #f6efdf;
}

#home {
    /* --black vaut #0a0a0a en sombre et #fafaf8 en clair : le jeton
       s'inverse tout seul, aucun override de thème n'est nécessaire ici. */
    background: var(--black);
}

/* Filet de marge, à la manière d'une planche d'atelier */
#home .hero-inner {
    position: relative;
}
@media (min-width: 900px) {
    #home .hero-content {
        padding-left: clamp(1.25rem, 2.5vw, 2.25rem);
        border-left: 1px solid var(--gray-4);
    }
}

/* ── 2. Les halos lumineux ────────────────────────────────────────── */
.hero-title,
.hero-title * { text-shadow: none !important; }

/* Anneaux et lueurs colorés, le marqueur le plus reconnaissable.

   PORTÉE ÉLARGIE : la règle ne visait que le hero (`.hero-actions .btn-*`),
   parce qu'elle avait été écrite en regardant le hero. Or ces boutons
   existent ailleurs — `.btn-primary` sur 17 pages, `.nav-cta` sur les 21 —
   et hors du hero ils gardaient leur halo. Mesuré : le CTA « Voir le site
   live » d'une page projet luisait à 1280 comme à 390, et le « Me
   contacter » du menu burger luisait sous 768px alors que le MÊME bouton
   n'a aucune ombre au-dessus. Deux boutons de même rôle, l'un luit,
   l'autre non : c'est le demi-geste que la charte ne veut pas.
   `.btn-submit` figure dans la liste bien que son ombre ait déjà été
   retirée à la source (module 015) : la famille est nommée ici en un seul
   endroit, et la règle empêche qu'on la réintroduise ailleurs.
   Le `!important` est conservé : il est ce qui permet de battre les
   déclarations de mobile-premium.css, chargé après styles.css. */
.hero-badge,
.hero-badge-dot,
.btn-primary,
.btn-secondary,
.btn-submit,
.nav-cta,
.showcase-status-cta { box-shadow: none !important; }

/* Le bouton principal se tient par son aplat, pas par sa lueur */
.hero-actions .btn-primary { border: 1px solid transparent; }

/* Orbe violet pulsant de la carte atelier, remplacé par du texte */
.atelier-card-orb { display: none !important; }
.showcase-atelier {
    display: block !important;
}
.atelier-card-content { width: 100%; }

/* ── 3. Les lévitations infinies ──────────────────────────────────── */
.hero-dual-tag,
.showcase-quickcta,
.showcase-card,
.hero-badge {
    animation: none !important;
}

/* ── 4. Les pastilles à accolades → titraille éditoriale ──────────── */
/* Pas de chips, mais pas du texte nu non plus : les deux disciplines
   passent dans la police d'affichage, à une échelle qui leur donne du
   poids, et le × de la marque devient l'articulation colorée. */
.hero-dual {
    align-items: baseline;
    gap: 0.85rem;
    margin-bottom: 0.35rem;
}
.hero-dual-tag {
    background: none !important;
    border: 0 !important;
    border-radius: 0 !important;
    padding: 0 !important;
    font-family: var(--display, 'Syne', sans-serif) !important;
    font-size: var(--pas-lecture) !important;
    font-weight: 600 !important;
    letter-spacing: -0.015em;
    text-transform: none;
    color: var(--gray-1) !important;
    transform: none !important;
    transition: color 0.25s ease;
}
.hero-dual-tag.code:hover,
.hero-dual-tag.design:hover {
    background: none !important;
    color: var(--white) !important;
    transform: none !important;
}
.hero-dual-sep {
    font-size: var(--pas-lecture) !important;
    font-weight: 400;
    /* Ce « × » n'est pas aria-hidden : il se lit. --accent-texte (132). */
    color: var(--accent-texte) !important;
}

/* ── 5. La nuée de boutons flottants ──────────────────────────────── */
/* Appeler, Écrire, WhatsApp doublaient déjà la section contact. */
.showcase-quickcta { display: none !important; }

/* ── 6. Badge → dateline, à la manière d'une manchette ────────────── */
.hero-badge {
    display: flex !important;
    align-items: center;
    gap: 0.6rem;
    background: none !important;
    border: 0 !important;
    border-radius: 0 !important;
    padding: 0 !important;
    font-family: var(--mono);
    font-size: var(--pas-etiquette);
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--gray-2) !important;
    margin-bottom: 1.35rem;
}
/* le filet part du texte et file vers la droite */
.hero-badge::after {
    content: "";
    flex: 1 1 auto;
    max-width: 190px;
    height: 1px;
    background: var(--gray-4);
}
.hero-badge-dot {
    width: 6px; height: 6px; border-radius: 50%;
    background: var(--green); flex: none;
}

/* ── Thème clair ──────────────────────────────────────────────────── */
html[data-theme="light"] .hero-dual-tag { color: rgba(10,10,10,0.62) !important; }
html[data-theme="light"] .hero-badge { color: rgba(10,10,10,0.68) !important; }
html[data-theme="light"] #home .hero-content { border-left-color: rgba(10,10,10,0.12); }

/* ── Reduced motion : rien à rétablir, tout est déjà statique ─────── */

/* ── 7. Le mobilier flottant ──────────────────────────────────────── */
/* Quatre widgets se disputaient le bas de l'écran. Ils encombraient sans
   servir la démonstration. Le bouton d'ambiance reste dans le hero, celui
   qui flottait ici n'en était qu'un doublon. */
.sound-toggle,                 /* « Sons OFF » */
.design-playground,            /* sélecteur de palette */
.perf-hud,                     /* badge de performance */
#ambient-music-toggle {        /* doublon du bouton d'ambiance du hero */
    display: none !important;
}

/* ── 8. Le titre s'anime — en bloc, jamais lettre par lettre ──────── */
/* Les reveals lettre-par-lettre restent proscrits (module 099, demande
   explicite). Le titre entre donc d'un seul tenant, une fois le chargeur
   parti : c'est une entrée, pas une cascade. */
@keyframes hero-entree {
    from { opacity: 0; transform: translateY(18px); }
    to   { opacity: 1; transform: none; }
}
body.intro-done .hero-title {
    animation: hero-entree 0.95s cubic-bezier(0.22, 1, 0.36, 1) both;
}
body.intro-done .hero-sub {
    animation: hero-entree 0.95s cubic-bezier(0.22, 1, 0.36, 1) 0.12s both;
}
@media (prefers-reduced-motion: reduce) {
    body.intro-done .hero-title,
    body.intro-done .hero-sub { animation: none; }
}

/* ── 9. Thème clair : corrections de lisibilité ───────────────────── */
/* Plusieurs éléments gardaient une couleur pensée pour le fond sombre. */

/* Séparateur FR · EN, invisible sur fond crème */
html[data-theme="light"] .lang-sep { color: rgba(10, 14, 26, 0.38) !important; }

/* Texte blanc posé sur les visuels de projet : certaines images sont claires,
   le texte s'y effaçait. Ombre portée de lisibilité (décalée, pas un halo). */
html[data-theme="light"] .bento-num,
html[data-theme="light"] .bento-title,
html[data-theme="light"] .bento-desc,
html[data-theme="light"] .bento-badge {
    text-shadow: 0 1px 3px rgba(10, 14, 26, 0.72);
}

/* Repères de navigation latérale, trop pâles */
html[data-theme="light"] .side-nav-vert a { color: rgba(10, 14, 26, 0.72); }

/* Lien du bandeau cookies : l'or clair ne tient pas sur le crème */
html[data-theme="light"] .cookie-banner-link { color: #8a6a28 !important; }

/* ── 10. Les micro-étiquettes ─────────────────────────────────────── */
/* « Disponible · Freelance & CDI » était une pastille : fond teinté,
   bordure colorée, coins arrondis. Même traitement que la dateline du
   hero, mono espacé et point de statut, sans le cartouche. */
.contact-avail {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    background: none !important;
    border: 0 !important;
    border-radius: 0 !important;
    padding: 0 !important;
    font-family: var(--mono);
    font-size: var(--pas-etiquette);
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--gray-3) !important;
}
.contact-avail-dot {
    width: 6px; height: 6px; flex: none;
    background: var(--green);
    box-shadow: none !important;
    animation: none !important;
}
html[data-theme="light"] .contact-avail { color: rgba(10, 10, 10, 0.5) !important; }

/* ── 11. La citation : fond éditorial et entrée en bloc ───────────── */
/* Elle s'animait déjà, mais d'une opacité 0.35 vers 1 : imperceptible.
   Et la section n'avait aucun fond. Panneau d'encre chaude tenu par deux
   filets d'or, guillemet fantôme en fond, et l'entrée devient franche. */
.editorial-quote-section {
    position: relative;
    isolation: isolate;
    background: #0d0b07;
    border-top: 1px solid rgba(200, 162, 92, 0.18);
    border-bottom: 1px solid rgba(200, 162, 92, 0.18);
}
/* guillemet de fond, à l'échelle du chapiteau */
.editorial-quote-section::before {
    content: "\201C";
    position: absolute;
    left: 50%; top: 50%;
    transform: translate(-50%, -46%);
    font-family: Georgia, serif; /* N'avait pas Syne en repli, rendait Georgia — var(--display) changerait le rendu */
    /* Hors échelle des 6 pas ET de --pas-signature (200-420px, qui plafonne
       à 144px à 1280) : guillemet fantôme géant, pas du texte (tâche 4,
       laissé littéral — même choix que le portfolio). */
    font-size: clamp(200px, 32vw, 420px);
    line-height: 1;
    color: rgba(200, 162, 92, 0.06);
    pointer-events: none;
    z-index: -1;
}

/* Entrée : la citation monte d'un bloc, jamais lettre par lettre */
.editorial-quote {
    opacity: 0;
    transform: translateY(26px);
    transition: opacity .9s cubic-bezier(.22, 1, .36, 1),
                transform .9s cubic-bezier(.22, 1, .36, 1);
}
.editorial-quote.is-quote-active {
    opacity: 1;
    transform: none;
}
/* les guillemets d'or suivent, légèrement décalés */
.editorial-quote .editorial-quote-mark {
    opacity: 0;
    transform: translateY(10px);
    transition: opacity .7s ease, transform .7s cubic-bezier(.22, 1, .36, 1);
}
.editorial-quote.is-quote-active .editorial-quote-mark { opacity: 1; transform: none; }
.editorial-quote.is-quote-active .editorial-quote-mark--open  { transition-delay: .18s; }
.editorial-quote.is-quote-active .editorial-quote-mark--close { transition-delay: .3s; }

html[data-theme="light"] .editorial-quote-section {
    background: #faf6ec;
    border-top-color: rgba(10, 10, 10, 0.12);
    border-bottom-color: rgba(10, 10, 10, 0.12);
}
html[data-theme="light"] .editorial-quote-section::before { color: rgba(200, 162, 92, 0.16); }

@media (prefers-reduced-motion: reduce) {
    .editorial-quote,
    .editorial-quote .editorial-quote-mark {
        opacity: 1 !important; transform: none !important; transition: none !important;
    }
}

/* ==============================================================
   104 — Contact « façon sitetrip » · dernier de la cascade
   Le #contact est déjà une section sombre dans les deux thèmes.
   On en fait un aplat profond dérivé de l'accent (tâche 8 — l'ancienne
   justification « bookend avec #manifesto » était périmée, #manifesto
   n'existe pas ici), on neutralise le vert décoratif, et on ajoute un
   titre cinétique géant en clôture (les lettres se posent au scroll).
   Charte : reduced-motion, pas de backdrop-filter, alignements
   vérifiés mobile + web.
   ============================================================== */

/* ── Aplat profond, dérivé de l'accent ──────────────────────────
   Était #0a3ba8 fixe, justifié « bookend avec #manifesto » — #manifesto
   n'existe pas dans ce dépôt (hérité du portfolio complet, périmé).
   Migré tâche 8, passe de correction 2 : c'est la plus grande surface
   du site à contredire la bascule (couloir design, barre en terre cuite,
   section entière restée bleue). Dérivé de --accent par color-mix, même
   technique que --accent-deep mais assombri davantage (45% contre 82%) :
   le texte le plus translucide de la section (.contact-item-label,
   blanc à 60% d'opacité) est le point serré, pas le texte plein blanc.
   Ratios mesurés (alpha-compositing inclus), tâche 8 — dev et design en
   thème clair seulement, --accent n'était pas encore différencié par
   thème :
     dev    : blanc plein 12,84:1 · label 60% 5,52:1
     design : blanc plein 10,83:1 · label 60% 5,03:1
   Les deux au-dessus du seuil AA (4,5:1), avec marge.
   Revérifié aux 4 COMBINAISONS (tâche 6, passe de correction 3, après
   que --accent design sombre ait été assombri en tâche 6 pour
   .btn-primary — cette règle n'a pas de garde de thème, elle lit
   `var(--accent)` tel quel, donc sa valeur RÉELLE bouge avec lui) :
     dev/sombre    : blanc plein 12,84:1 · label 60% 5,52:1 (inchangé)
     dev/clair     : blanc plein 12,84:1 · label 60% 5,52:1 (inchangé)
     design/sombre : blanc plein 12,91:1 · label 60% 5,67:1 (remonté)
     design/clair  : blanc plein 10,83:1 · label 60% 5,03:1 (inchangé, mesure d'origine)
   Toutes au-dessus du seuil AA, dans les 4 combinaisons — aucune
   régression, une amélioration côté design/sombre.
   !important pour battre le html[data-theme="light"] #contact du
   module 020 (qui reforce var(--black)). */
#contact,
html[data-theme="light"] #contact {
    background: color-mix(in srgb, var(--accent) 45%, black) !important;
    background-image: radial-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px) !important;
    background-size: 30px 30px !important;
}
/* Halo décoratif : bleu mono (on retire le vert « IA » du dégradé 020). */
#contact::before,
html[data-theme="light"] #contact::before {
    background: radial-gradient(circle, rgba(255, 255, 255, 0.10) 0, transparent 70%) !important;
}

/* Email/tel plus présents (hierarchy) + halo lisible sur bleu. */
#contact .contact-item-val {
    color: #ffffff;
}
#contact .contact-item-label {
    color: rgba(255, 255, 255, 0.6);
}
#contact .contact-item-val:hover {
    color: #ffd27a; /* même or que les lettrines du manifesto → cohérence */
}
#contact .social-link {
    color: rgba(255, 255, 255, 0.75);
}
#contact .social-link:hover {
    color: #fff;
    border-color: rgba(255, 255, 255, 0.5);
}

/* ── TITRE CINÉTIQUE GÉANT ─────────────────────────────────────
   Signature de clôture. Deux lignes massives, composition en
   diagonale (ligne 1 au fer à gauche, ligne 2 au fer à droite).
   UNE seule idée cinétique : chaque ligne monte derrière un masque
   (overflow hidden), puis le « ? » or se pose en dernier.
   One-shot via .is-in, transform-only, aucune boucle infinie. */
.contact-kinetic {
    display: flex;
    flex-direction: column;
    margin-top: clamp(3.5rem, 9vw, 8rem);
    font-family: var(--display, "Syne", sans-serif);
    font-weight: 800;
    font-size: var(--pas-signature);
    line-height: 0.9;
    letter-spacing: -0.035em;
    text-transform: uppercase;
    color: #ffffff;
}
/* Chaque ligne est son propre masque de reveal.
   On masque UNIQUEMENT vers le bas (là d'où la ligne monte pendant le reveal)
   via clip-path — pas overflow:hidden, qui rognait aussi À DROITE et coupait
   le « ? » sur grand écran (font au max, le glyphe déborde sa boîte).
   inset(top right bottom left) : top -60% et gauche/droite -100% = aucun clip
   sur ces bords ; bottom 0 = masque ce qui descend sous la ligne (le reveal).
   Le padding-bottom garde le point du « ? » à l'intérieur du bord bas. */
.contact-kinetic .ck-line {
    display: block;
    clip-path: inset(-60% -100% 0 -100%);
    /* laisse respirer l'avance du dernier glyphe (letter-spacing négatif) */
    padding-inline-end: 0.06em;
    /* line-height 0.9 rognait le bas du « ? » (son point) au bord bas du clip.
       On étend la boîte vers le bas et on compense par une marge négative égale
       → le glyphe est entier, la mise en page ne bouge pas. */
    padding-bottom: 0.18em;
    margin-bottom: -0.18em;
}
.contact-kinetic .ck-line--2 {
    text-align: right; /* diagonale composée : « PARLE ? » ferme au fer à droite */
}
/* ≥1025px : la side-nav fixe vit sur le bord droit — on rentre la
   ligne 2 pour que le « ? » ne passe pas dessous. */
@media (min-width: 1025px) {
    .contact-kinetic .ck-line--2 {
        padding-inline-end: calc(0.06em + clamp(2rem, 7vw, 7.5rem));
    }
}
.contact-kinetic .ck-line-in {
    display: block;
    white-space: nowrap; /* « PARLE ? » = un seul bloc, le ? ne descend jamais */
    transform: translateY(118%);
    transition: transform 1s var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1));
}
.contact-kinetic .ck-line--2 .ck-line-in {
    transition-delay: 0.14s;
}
.contact-kinetic.is-in .ck-line-in {
    transform: translateY(0);
}
/* Le « ? » : seul accent — or des lettrines manifesto, arrive en dernier. */
.contact-kinetic .ck-q {
    display: inline-block;
    margin-left: 0.1em;
    color: #ffd27a;
    transform: translateY(140%);
    transition: transform 0.9s var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1)) 0.34s;
    transform-origin: 50% 0;   /* le « ? » pend PAR LE HAUT (cf. balancement plus bas) */
}
.contact-kinetic.is-in .ck-q {
    transform: translateY(0);
}

/* ── VIE CONTINUE : le « ? » balance une fois posé ──────────────────
   Le titre ne doit pas retomber inerte après son entrée. UNE seule idée
   (charte) : le « ? » — l'unique accent or — se balance comme un
   pendule suspendu par le haut. Même période (2.4 s) que la ligne du
   pêcheur en pied de page → les deux signatures du site riment.
   On anime la propriété INDÉPENDANTE `rotate`, PAS `transform` : le
   reveal garde `transform: translateY()` pour lui et les deux se
   composent, au lieu de se battre pour la même propriété (sinon
   l'animation écraserait la transition d'entrée dès le départ).
   Gaté par .is-onscreen → à l'arrêt hors écran (perf, charte).
   Rotation autour du HAUT : le bas du glyphe remonte très légèrement
   (d·cosθ < d), il ne peut donc pas sortir par le bas du masque de
   reveal ; latéralement le clip est ouvert (inset … -100%). */
.contact-kinetic.is-in.is-onscreen .ck-q {
    animation: ck-q-swing 2.4s ease-in-out infinite;
}
@keyframes ck-q-swing {
    0%   { rotate: -4deg; }
    50%  { rotate:  4deg; }
    100% { rotate: -4deg; }
}

@media (prefers-reduced-motion: reduce) {
    .contact-kinetic .ck-line-in,
    .contact-kinetic .ck-q {
        transform: none;
        transition: none;
        animation: none;   /* coupe aussi le balancement continu du « ? » */
        rotate: none;
    }
}

/* Mobile : le géant reste dans la largeur, interlignage un peu plus aéré. */
@media (max-width: 768px) {
    .contact-kinetic {
        font-size: var(--pas-affirmation);
        margin-top: 2.75rem;
        line-height: 0.94;
    }
}

/* ── LISIBILITÉ THÈME CLAIR ────────────────────────────────────
   #contact est un APLAT BLEU dans les DEUX thèmes. Or l'inversion light
   (modules 020 / 057, en !important) rend liens et textes FONCÉS → illisibles
   sur le bleu : sous-titre, email/tél/localisation, puce « Disponible »,
   libellés du formulaire. On rétablit du texte CLAIR comme en sombre.
   Le sélecteur inclut #contact → gagne en spécificité sur les règles globales. */
html[data-theme="light"] #contact .section-sub { color: rgba(255, 255, 255, 0.72) !important; }
html[data-theme="light"] #contact .contact-avail,
html[data-theme="light"] #contact .contact-avail span { color: rgba(255, 255, 255, 0.85) !important; }
html[data-theme="light"] #contact .contact-item-val { color: #ffffff !important; }
html[data-theme="light"] #contact .contact-item-val:hover { color: #ffd27a !important; }
html[data-theme="light"] #contact .contact-item-label { color: rgba(255, 255, 255, 0.6) !important; }
html[data-theme="light"] #contact .social-link { color: rgba(255, 255, 255, 0.8) !important; }
html[data-theme="light"] #contact .social-link:hover { color: #ffffff !important; }

/* Libellés du formulaire (« Votre nom », « Votre email », « Votre message »)
   ET le statut d'envoi (#form-status) — passe de correction 3, tâche 8.
   Contrairement au reste de ce bloc, le défaut --gray-3 NE suffisait PAS
   en thème sombre non plus : 40 % de blanc, jamais retouché ici (le
   raisonnement d'origine — « le sombre est déjà clair, seul le clair
   inversé pose problème » — ne tenait pas pour ces deux éléments).
   Mesuré sur l'aplat dérivé (color-mix 45 %, voir plus haut) :
     dev 3,25:1 · design 3,12:1 — sous le seuil AA (4,5:1).
   Unifié en une seule règle, sans condition de thème. Reprend la valeur
   déjà validée en thème clair (78 % de blanc) plutôt que d'en inventer
   une nouvelle : dev 8,32:1 · design 7,31:1 — largement au-dessus du
   seuil, tout en restant sous le blanc plein (100 %) du texte saisi
   dans les champs : la hiérarchie (libellé < contenu) est préservée,
   pas écrasée. #contact porte un id : la spécificité gagne sur 020
   (html[data-theme="light"] .form-group label) sans dépendre de l'ordre
   des fichiers ; !important gardé pour rester cohérent avec le reste du
   bloc.
   ⚠ Affirmation corrigée (tâche 6, passe de correction 3) : « le fond ne
   dépend pas du thème, seulement du couloir » n'est plus vraie depuis
   que --accent design est différencié par thème (tâche 6, pour
   .btn-primary) — cette règle lit `var(--accent)` sans garde de thème,
   donc SA valeur réelle suit. Revérifié aux 4 combinaisons :
     dev/sombre    : 8,33:1 (inchangé)
     dev/clair     : 8,33:1 (inchangé)
     design/sombre : 8,47:1 (remonté, --accent design sombre assombri)
     design/clair  : 7,29:1 (inchangé, mesure d'origine ci-dessus)
   Toutes largement au-dessus du seuil AA, dans les 4 combinaisons. */
#contact .contact-form label,
#contact .form-group label,
#contact .form-status {
    color: rgba(255, 255, 255, 0.78) !important;
}

/* L'étiquette « Contact » (relecture d'ensemble). Même défaut que les
   libellés ci-dessus, et pour une raison plus gênante encore : elle lisait
   var(--accent-texte) — donc l'accent — sur un aplat DÉRIVÉ de ce même
   accent. Un accent sur son propre aplat ne peut pas contraster, quelle
   que soit la teinte : mesuré 3,46:1 (design/sombre) et 3,39:1
   (dev/sombre), sous le seuil AA. Défaut ANTÉRIEUR au chantier, et alors
   pire — sur l'ancien aplat #0a3ba8 codé en dur, #3b82f6 donnait 2,59:1 ;
   la migration de l'aplat au jeton l'a déjà amélioré, elle ne l'a pas
   causé. Seul le thème clair avait reçu un correctif dédié (70 % de
   blanc) ; la règle est unifiée pour les deux thèmes à 78 %, la valeur
   déjà validée juste au-dessus pour les libellés du formulaire — pas une
   valeur de plus. Mesuré : 8,47:1 (design/sombre) et 8,40:1 (dev/sombre). */
#contact .section-label {
    color: rgba(255, 255, 255, 0.78) !important;
}

/* ==============================================================
   106 — « Happy clients » façon sitetrip · dernier de la cascade
   #testimonials devient un carousel horizontal scroll-snap
   (swipe/scroll) + pagination par points. Pur CSS pour le défilement,
   petit JS (B9) pour l'état des points.
   Charte : reduced-motion (scroll instantané), alignements mobile+web.
   ============================================================== */

.testi-carousel {
    position: relative;
    margin-top: 2.5rem;
}

/* Piste horizontale scroll-snap. La grille de 3 devient une rangée qui défile.
   #testimonials + !important : mobile-premium.css force un display:grid (dont un
   grid-template-columns:1fr !important en mobile) — on reprend la main ici. */
#testimonials .testimonials-grid {
    display: flex !important;
    grid-template-columns: none !important;
    flex-wrap: nowrap !important;
    gap: 1.5rem;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    -webkit-overflow-scrolling: touch;
    padding: 0.5rem 0.25rem 1.5rem;
    margin: 0;
    scrollbar-width: none;              /* Firefox */
}
#testimonials .testimonials-grid::-webkit-scrollbar { display: none; } /* WebKit */

#testimonials .testimonials-grid .testimonial-card {
    flex: 0 0 auto;
    scroll-snap-align: start;
    /* Desktop : ~2,3 cartes visibles (on voit qu'il y en a d'autres). */
    width: clamp(300px, 40%, 460px);
}

/* Points de pagination. */
.testi-dots {
    display: flex;
    justify-content: center;
    gap: 0.6rem;
    margin-top: 0.5rem;
}
.testi-dots button {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: currentColor;
    opacity: 0.25;
    transition: opacity 0.25s, transform 0.25s, width 0.25s;
    padding: 0;
}
.testi-dots button.is-active {
    opacity: 1;
    width: 26px;
    border-radius: 5px;
    background: var(--accent);
}

@media (prefers-reduced-motion: reduce) {
    #testimonials .testimonials-grid { scroll-behavior: auto; }
}

/* Mobile : une carte par écran, on laisse « peeker » la suivante. */
@media (max-width: 768px) {
    #testimonials .testimonials-grid .testimonial-card {
        width: 84% !important;
    }
}

/* ==============================================================
   103 — #stack « what we do » façon sitetrip (site Réalisations)
   Le stack (Dev / Design) devient : titre collant à gauche + deux
   cartes accent pleines à droite (comme « THIS IS WHAT YOUR JOURNEY
   LOOKS LIKE »). Les marquees techniques restent en pleine largeur
   dessous. Charte : alignements mobile + web, reduced-motion hérité.
   ============================================================== */

/* ── Layout : titre collant (gauche) + cartes (droite) ──
   Seuil relevé de 1000px à 1200px en août 2026, quand #stack a reçu sa
   réserve pour le rail vertical (module 129) : entre 1025 et 1199px, la
   colonne de droite ne mesurait plus que 412px, les deux cartes tombaient
   à 195px et l'en-tête « //DÉVELOPPEMENT » (201px intrinsèques,
   `white-space: nowrap` quelques lignes plus bas) était tranché net.
   Mesuré : il faut 201px de largeur utile dans la carte, on ne les a qu'à
   partir de ~1150px et confortablement (+11px) à 1200px. Sous ce seuil,
   #stack reprend donc, entre 1025 et 1199px, la disposition déjà éprouvée
   en dessous de 1000px — titre pleine largeur au-dessus, les deux cartes
   empilées et pleine largeur dessous (733px utiles à 1025px) — plutôt
   qu'une variante serrée inventée pour l'occasion.
   Le seuil vaut pour TOUT le bloc : la grille du conteneur, le titre
   collant et le passage pleine largeur des défilés vont ensemble. */
@media (min-width: 1200px) {
    #stack .container {
        display: grid;
        grid-template-columns: minmax(240px, 320px) 1fr;
        gap: clamp(2.5rem, 5vw, 5rem);
        align-items: start;
    }
    #stack .skills-intro {
        position: sticky;
        top: calc(var(--nav-h, 64px) + 2.5rem);
        margin: 0;
    }
    /* Les marquees passent sous la grille, pleine largeur. */
    #stack .tech-marquee-section {
        grid-column: 1 / -1;
        margin-top: 2.5rem;
    }
}

/* ── Les deux régions → cartes accent pleines ── */
#stack .skills-dual {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 1.25rem;
    margin: 0;
}
#stack .skills-dev,
#stack .skills-design {
    background: #0a3ba8 !important;
    border: none !important;
    border-radius: 18px;
    padding: clamp(1.6rem, 2.2vw, 2.25rem);
    color: #fff;
}

/* Titre collant : borné pour que « construis » tienne dans la colonne. */
@media (min-width: 1000px) {
    #stack .skills-intro .section-title {
        font-size: var(--pas-titre);
        line-height: 1;
    }
}

/* En-tête de carte (le label Développement / Design), taille contenue. */
#stack .skills-dev-label,
#stack .skills-design-label {
    display: block;
    font-family: var(--display, "Syne", sans-serif);
    font-weight: 800;
    font-size: var(--pas-appui); /* « DÉVELOPPEMENT » (13 car. + préfixe //) doit tenir */
    letter-spacing: 0.02em;
    white-space: nowrap;
    color: #fff !important;
    margin-bottom: 1.5rem;
    padding-bottom: 0.85rem;
    border-bottom: 1px solid rgba(255, 255, 255, 0.16);
}

/* Titres de sous-groupes + tags, lisibles sur le bleu.
   !important : le module 057 force ces couleurs en encre sombre en thème clair. */
#stack .dev-skill-group-title,
#stack .design-skill-group-title,
html[data-theme="light"] #stack .dev-skill-group-title,
html[data-theme="light"] #stack .design-skill-group-title {
    color: rgba(255, 255, 255, 0.6) !important;
}
#stack .dev-skill-group,
#stack .design-skill-group {
    margin-bottom: 1.1rem;
}
#stack .dev-tag,
#stack .design-tag,
html[data-theme="light"] #stack .dev-tag,
html[data-theme="light"] #stack .design-tag {
    background: rgba(255, 255, 255, 0.14) !important;
    color: #fff !important;
    border: 1px solid rgba(255, 255, 255, 0.12) !important;
}

/* ── Sous le seuil ci-dessus : titre au-dessus, cartes empilées ──
   (borne relevée de 999px à 1199px en même temps que la grille : les deux
   media queries sont complémentaires, elles doivent bouger ensemble sous
   peine de laisser un intervalle sans règle.) */
@media (max-width: 1199px) {
    #stack .skills-dual {
        grid-template-columns: 1fr;
        margin-top: 2rem;
    }
}

/* ==============================================================
   104 — Parité avec le portfolio principal (site Réalisations)
   Deux réglages pour aligner Réalisations sur le principal :
   typo hero plus affirmée + filet décoratif en accent mono (on
   retire le vert « IA »). Le vert reste réservé aux statuts.
   ============================================================== */

/* ── Palette : filet bleu→vert décoratif → accent mono ── */
.hero-dual::after,
.service-card::before {
    background: linear-gradient(90deg, var(--accent), var(--accent-light)) !important;
}

/* ── Typo hero plus affirmée (desktop only, mobile déjà tuné) ── */
.hero-title {
    line-height: 0.95;
    letter-spacing: -0.02em;
}
@media (min-width: 900px) {
    .hero-title {
        font-size: var(--pas-affirmation);
        margin-block: 1.5rem;
    }
    /* Les mots (spans char-split) ne cassent jamais en plein milieu. */
    .hero-title > span[data-split],
    .hero-title > span[data-reveal-on-load] {
        display: inline-block;
        white-space: nowrap;
    }
}

/* =====================================================================
   110 — CORRECTIFS lisibilité + carousel (site réalisations, juillet 2026)
   ---------------------------------------------------------------------
   Concaténé en dernier → gagne la cascade sur les modules sitetrip
   (101-106). Trois défauts signalés en production :
     1. Titre du contact « Travaillons » resté en encre sur l'aplat bleu.
     2. Label de carte stack « // DÉVELOPPEMENT » serré/coupé au bord mobile.
     3. Carousel témoignages : pas de glissement souris (voir script.js).
   ===================================================================== */

/* 1. Contact : le mot « Travaillons » gardait la couleur d'encre de
      .section-title (rgba(10,14,26,.92)), quasi invisible sur le bleu.
      Tout le titre passe en blanc. */
#contact .section-title,
#contact .section-title .reveal-wrap,
#contact .section-title .reveal-line,
#contact .section-title .split-word,
#contact .section-title .split-char {
    color: #ffffff !important;
    -webkit-text-fill-color: #ffffff !important;
}

/* 2. Label de carte stack : « // DÉVELOPPEMENT » (13 car. + préfixe) tenait
      exactement dans la carte, à ~5px du bord en desktop et collé en mobile.
      Au moindre écart de rendu de police le « T » final saute → « coupé ».
      On retire l'interlettrage et on réduit un cran la taille pour garantir
      une vraie marge à droite, aux deux bouts (mobile ET desktop 2 colonnes). */
@media (max-width: 480px) {
    #stack .skills-dev-label,
    #stack .skills-design-label {
        font-size: var(--pas-etiquette);
        letter-spacing: 0;
    }
}
@media (min-width: 1000px) {
    #stack .skills-dev-label,
    #stack .skills-design-label {
        font-size: var(--pas-etiquette);
        letter-spacing: 0;
    }
}

/* 3. Carousel témoignages : curseur « saisir », scroll direct (pas de smooth
      qui lutte contre le drag) et surtout user-select coupé pendant le drag —
      sinon le navigateur sélectionne le texte et son auto-scroll de sélection
      lutte contre le scrollLeft manuel = grosse latence. */
[data-testi-carousel] .testimonials-grid { cursor: grab; }
[data-testi-carousel] .testimonials-grid.is-dragging {
    cursor: grabbing;
    scroll-behavior: auto;
    scroll-snap-type: none;   /* le snap se réactive au relâchement */
    user-select: none;
    -webkit-user-select: none;
}
[data-testi-carousel] .testimonials-grid.is-dragging .testimonial-card {
    pointer-events: none;     /* un drag ne déclenche pas un lien de la carte */
}

/* 4. Points de pagination des témoignages : leur fond est `currentColor`, mais
      un <button> n'hérite pas la couleur du parent → currentColor retombait sur
      le noir par défaut du navigateur. Point inactif noir sur fond sombre =
      invisible (on ne voyait plus qu'il y avait d'autres témoignages à suivre).
      On force l'héritage : la couleur suit le thème (sombre en clair, clair en
      sombre). Le point actif garde var(--accent). */
.testi-dots button { color: inherit; }

/* 5. Stack : entrée en CASCADE. Le reveal montait chaque bloc d'un seul tenant
      (mouvement à peine perceptible). Désormais les ENFANTS — 3 rangées de
      marquees, 2 cartes Dev/Design — montent en décalé quand la section entre
      à l'écran. CSS pur, branché sur le .visible déjà posé par l'IO de
      script.js. Marche pour #stack (Réalisations) et #skills (portfolio) via
      les classes. Transform + opacité seulement, one-shot, reduced-motion OK. */
@keyframes stack-rise {
    from { opacity: 0; transform: translateY(22px); }
    to   { opacity: 1; transform: none; }
}
/* le conteneur ne bouge plus lui-même : le mouvement passe aux enfants */
.tech-marquee-section.fade-in,
.skills-dual.fade-in {
    opacity: 1;
    transform: none;
    will-change: auto;
}
/* enfants masqués avant le reveal (aucun flash) */
.tech-marquee-section .tech-marquee,
.skills-dual > * { opacity: 0; }
/* cascade à l'entrée */
.tech-marquee-section.visible .tech-marquee,
.skills-dual.visible > * {
    animation: stack-rise 0.7s var(--ease-out-expo) both;
}
.tech-marquee-section.visible .tech-marquee:nth-child(1) { animation-delay: 0.05s; }
.tech-marquee-section.visible .tech-marquee:nth-child(2) { animation-delay: 0.15s; }
.tech-marquee-section.visible .tech-marquee:nth-child(3) { animation-delay: 0.25s; }
.skills-dual.visible > :nth-child(1) { animation-delay: 0.10s; }
.skills-dual.visible > :nth-child(2) { animation-delay: 0.22s; }
@media (prefers-reduced-motion: reduce) {
    .tech-marquee-section .tech-marquee,
    .skills-dual > * { opacity: 1; animation: none; }
}

/* 6. Titre hero : révélation en BLOC (pas de cascade — direction éditoriale),
      mais courbe alignée sur l'entrée du logo pour un rendu premium cohérent.
      On remplace juste la courbe/durée du fade-in, sans toucher au mécanisme. */
.hero-title.fade-in {
    transition:
        opacity 0.8s cubic-bezier(0.22, 1, 0.36, 1),
        transform 0.8s cubic-bezier(0.22, 1, 0.36, 1);
}

/* ==============================================================
   111 — Stack plus visible pour les clients · fin de cascade
   Demande : que les clients voient BIEN le stack, au lieu de
   petites pastilles animées. Trois leviers :
   1. logos en couleurs réelles d'emblée (plus de désaturation) ;
   2. pastilles plus grandes (icône + texte) ;
   3. défilement ralenti + pause au survol → lisible.
   Charte : reduced-motion déjà géré (016), pas de repaint lourd.
   ============================================================== */

/* 1 · Logos tech en vraies couleurs dès le départ (recognition = crédibilité).
   Avant : grisés/assombris, colorés seulement au survol. */
.tech-pill img {
    filter: none !important;
}

/* 2 · Pastilles plus grandes et lisibles. */
.tech-pill {
    gap: 0.7rem;
    padding: 0.7rem 1.3rem 0.7rem 1rem;
    font-size: var(--pas-appui);          /* ~15px, contre ~13px */
    letter-spacing: 0.01em;
    border-radius: 9px;
}
.tech-pill img {
    width: 28px !important;      /* contre 22px en dur dans le HTML */
    height: 28px !important;
}
.tech-pill--design svg,
.tech-pill svg {
    width: 26px;
    height: 26px;
}

/* Un peu plus d'air entre les rangées, pastilles qui respirent. */
.tech-marquee-section { gap: 1rem; }
.tech-marquee-track { gap: 1rem; }

/* 3 · Défilement plus lent → le client a le temps de lire chaque techno.
   Spécificité montée (.tech-marquee .tech-marquee-track) pour battre le
   « animation-duration: 24s !important » de waouh.css (même sélecteur simple). */
.tech-marquee .tech-marquee-track { animation-duration: 60s !important; }
.tech-marquee[data-direction="reverse"] .tech-marquee-track { animation-duration: 66s !important; }

/* Mobile : un dixième de moins que le pas d'appui, pour que les pastilles
   restent compactes plutôt que de reprendre la taille desktop. */
@media (max-width: 768px) {
    .tech-pill {
        font-size: calc(var(--pas-appui) * 0.9);
        padding: 0.55rem 1rem 0.55rem 0.8rem;
    }
    .tech-pill img { width: 24px !important; height: 24px !important; }
}

/* ==============================================================
   112 — Mur de grandes marques dans #clients · fin de cascade
   Met en avant que PrestAvenue (client direct de Mitia) accompagne
   de grandes marques de renommée (Audi, BNP, HSBC, TotalEnergies…).
   Bandeau blanc arrondi sur la section sombre + logos en couleurs
   réelles, affichés TOUS d'un coup en grille statique (plus de défilé).
   Cadrage honnête : ce sont les marques de PrestAvenue, pas des
   clients directs. Charte : zéro animation infinie, alignements mobile+web.
   ============================================================== */

/* ── Logo du client sur sa carte (POEM, Asantsika) ─────────────
   Tuile claire pour que TOUT logo reste lisible sur les deux thèmes
   (le logo Asantsika a du texte navy, invisible sur carte sombre). */
.client-card--logo .client-logo {
    display: inline-flex;
    align-items: center;
    justify-content: flex-start;
    background: #ffffff;
    border-radius: 10px;
    padding: 0.6rem 0.85rem;
    margin-bottom: 0.85rem;
    box-shadow: 0 6px 18px -10px rgba(0, 0, 0, 0.5);
}
.client-card--logo .client-logo img {
    height: 44px;
    width: auto;
    max-width: 160px;
    object-fit: contain;
    display: block;
}
/* POEM : logo carré avec marge interne → on l'agrandit un peu pour équilibrer
   sa présence avec le logo Asantsika (format large). */
.client-card--logo .client-logo img[src*="poem"] { height: 56px; }
/* PrestAvenue : wordmark très large (ratio ~6:1) → un peu plus de largeur. */
.client-card--logo .client-logo img[src*="prestavenue"] { max-width: 190px; }

@media (max-width: 560px) {
    .client-card--logo .client-logo img { height: 38px; }
    .client-card--logo .client-logo img[src*="poem"] { height: 48px; }
}

.clients-brands {
    margin-top: clamp(2.5rem, 5vw, 4rem);
}

/* Label : PrestAvenue mis en avant. Couleurs par TOKENS (s'inversent selon
   le thème) — sinon le texte blanc devient illisible sur le crème clair. */
.clients-brands-label {
    font-family: var(--body, "Onest", sans-serif);
    font-size: var(--pas-sous-titre);
    color: var(--gray-2, rgba(255, 255, 255, 0.6));
    margin-bottom: 1.5rem;
    max-width: 52ch;
    line-height: 1.5;
}
.clients-brands-label em {
    font-style: normal;
    color: var(--gray-1, #fff);   /* ink fort en clair, blanc en sombre */
    font-weight: 600;
}
.clients-brands-label .cb-presta {
    color: var(--accent-light, #00d4aa);
    font-weight: 700;
    text-decoration: none;
    border-bottom: 1.5px solid transparent;
    transition: border-color 0.2s;
}
.clients-brands-label .cb-presta:hover {
    border-bottom-color: currentColor;
}

/* ═══ MUR STATIQUE MONOCHROME ═════════════════════════════════════
   Les 11 marques visibles d'un coup (plus de défilé). Plus de bandeau
   blanc (il cassait la présentation) : les logos posent directement sur
   la section, en NOIR & BLANC (silhouette). Traitement adaptatif au
   thème : silhouette claire sur section sombre, encre sur section claire.
   Survol → pleine intensité. */
.clients-brands-band {
    background: transparent;
    border-radius: 0;
    overflow: visible;
    box-shadow: none;
}
.clients-brands-track {
    list-style: none; margin: 0; padding: 0;
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: clamp(1.5rem, 3vw, 2.75rem) clamp(1.25rem, 2.5vw, 2.25rem);
    background: transparent;
}
.cb-logo {
    background: transparent;
    display: flex; align-items: center; justify-content: center;
    min-height: clamp(84px, 8vw, 112px);
    padding: 0.4rem 0.9rem;
    border-radius: 12px;
    transition: background 0.25s ease, box-shadow 0.25s ease;
}
/* Survol : tuile blanche → les VRAIES couleurs du logo ressortent, y compris
   les logos foncés (Audi, BNP, SG…) qui seraient invisibles sur le fond sombre
   sans support clair. */
.cb-logo:hover {
    background: #ffffff;
    box-shadow: 0 12px 28px -14px rgba(0, 0, 0, 0.55);
}
.cb-logo img {
    max-height: 50px;
    max-width: 86%;
    width: auto;
    object-fit: contain;
    /* Section sombre : négatif en niveaux de gris → tout devient clair MAIS
       le détail interne est conservé (contrairement à la silhouette pleine :
       BNP, Société Générale, TotalEnergies restaient des pavés illisibles). */
    filter: grayscale(1) invert(1) brightness(1.15);
    opacity: 0.85;
    transition: opacity 0.25s ease, transform 0.28s cubic-bezier(.22, 1, .36, 1), filter 0.28s ease;
}
/* Survol : vraies couleurs (on retire le grayscale/invert) sur la tuile blanche. */
.cb-logo:hover img { filter: none; opacity: 1; transform: scale(1.05); }
/* Thème clair → section crème → niveaux de gris (encre, détail conservé) */
html[data-theme="light"] .cb-logo img {
    filter: grayscale(1) contrast(1.05);
    opacity: 0.78;
}
html[data-theme="light"] .cb-logo:hover img { filter: none; opacity: 1; }
/* Calibrage optique : logos très larges/étroits ramenés au même poids visuel.
   Les pavés (BNP, SG, Total, Makita) agrandis — signalés peu lisibles. */
.cb-logo img[alt="BNP Paribas"] { max-height: 50px; max-width: 94%; }
.cb-logo img[alt="HSBC"] { max-height: 46px; }
.cb-logo img[alt="Société Générale"] { max-height: 52px; }
.cb-logo img[alt="HP"] { max-height: 44px; max-width: 66%; }
.cb-logo img[alt="Crédit Agricole"] { max-height: 54px; }
.cb-logo img[alt="IFP School"] { max-height: 52px; }
.cb-logo img[alt="Anine Bing"] { max-height: 46px; }
.cb-logo img[alt="TotalEnergies"] { max-height: 54px; }
.cb-logo img[alt="Makita"] { max-height: 52px; }
.cb-logo img[alt="Touche pas à mon poste"] { max-height: 54px; }

/* 12e cellule = tuile compteur (couleurs par tokens → s'adaptent au thème). */
.cb-logo--count {
    flex-direction: column;
    gap: 0.15rem;
    line-height: 1;
    text-align: center;
}
.cb-logo--count .cb-count-num {
    font-family: var(--display, "Syne", sans-serif);
    font-weight: 800;
    font-size: var(--pas-titre);
    letter-spacing: -0.03em;
    color: var(--gray-1, #fff);
}
.cb-logo--count .cb-count-lbl {
    font-size: var(--pas-etiquette);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--gray-2, rgba(255, 255, 255, 0.5));
    max-width: 12ch;
}

@media (max-width: 900px) {
    .clients-brands-track { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 560px) {
    .clients-brands-track { grid-template-columns: repeat(2, 1fr); }
    .cb-logo { min-height: 72px; padding: 0.5rem; }
}

/* ==============================================================
   112 — Signature de fin « le pêcheur a ferré le a de Mitia »
   FUSIONNÉE AU FOOTER (un seul « Mitia » en pied de page).
   Scène : un pêcheur à casquette assis dans une barque-croissant de
   lune, canne ployée sous la charge, ligne qui plonge sur le « a ».
   L'hameçon (œillet + hampe + courbure en J + ardillon) est PLANTÉ
   dans le sommet de la lettre : la hampe entre à droite, le sillon
   knockout tunnelle dans le bol, la pointe RESSORT à l'épaule gauche.
   Le « a » est un <text> SVG, ENFANT du groupe hameçon (.bc-tackle) :
   il est donc rigidement accroché à l'hameçon — il pend et balance
   AVEC lui, par construction (aucun décalage possible). Le pêcheur
   mouline comme une vraie personne : la CANNE reste stable, la MAIN
   pompe au coude, le CORPS se cale à l'effort ; la ligne + l'hameçon
   (donc le « a ») balancent en pendule.
   Charte : transform-only, reduced-motion géré, mobile + web.
   ============================================================== */

/* ── Placement dans le footer : signature centrée, de l'air au-dessus
      pour que le pêcheur (perché sur le croissant) respire. ── */
.footer-catch {
    text-align: center;
    padding-top: clamp(3.6rem, 8vw, 5.5rem);   /* headroom pour le pêcheur perché */
    margin-bottom: clamp(1.4rem, 3vw, 2.2rem);
}
.footer-catch .brand-catch-word {
    display: inline-block;
    margin: 0;
    font-family: var(--display, "Syne", sans-serif);
    font-weight: 800;
    font-size: var(--pas-affirmation);
    line-height: 1;
    letter-spacing: -0.03em;
    color: var(--gray-1, #fff);
    white-space: nowrap;
}

/* Le conteneur du « a » : réserve sa case dans le mot ; le glyphe réel
   est le <text> SVG, suspendu à l'hameçon au-dessus de cette case. */
.bc-a {
    position: relative;
    display: inline-block;
    width: 0.6em;                   /* la case du « a » dans « Miti·a » */
}
/* Le « a » = texte SVG, à la même fonte que le mot, sans contour. */
.bc-a-glyph {
    font-family: var(--display, "Syne", sans-serif);
    font-weight: 800;
    font-size: 90px;                /* en unités du viewBox → cale sur « Miti » */
    letter-spacing: -0.03em;
    fill: currentColor;
    stroke: none;
}

/* Le pêcheur (avec sa prise) posé au-dessus de la case du « a ».
   La ligne tombe pile sur la case ; l'hameçon tient le « a ». */
.bc-fisher {
    position: absolute;
    left: 50%;
    bottom: -0.17em;                /* le « a » ferré pend juste au-dessus de la ligne de base */
    width: 1.5em;
    height: auto;
    transform: translateX(-52%);    /* centre le « a » (x=73/140) sur la case */
    color: var(--gray-1, #fff);     /* s'adapte au thème */
    pointer-events: none;
    overflow: visible;
}

/* ── Entrée : LE TRAIT QUI SE DESSINE — voir la section dédiée plus
      bas (« ENTRÉE — LE TRAIT QUI SE DESSINE ») : le personnage naît
      comme un croquis à l'encre, trait après trait. L'ancien fondu
      (bc-fisher-in, opacité + 8px) est remplacé. ── */

/* ══ MOULINAGE VIVANT (continu tant que la section est à l'écran) ══
   Rig anatomique, pivots ABSOLUS en coordonnées du viewBox (0 0 140 184)
   — la ligne + le « a » sont DANS le bras, donc les bbox sont hautes ; un
   pivot en % de bbox décrocherait la main de l'épaule. On ancre chaque
   articulation à son point réel. Gaté par .is-onscreen (pause hors écran). */
.bc-guy    { transform-box: view-box; transform-origin: 28px 41px; }      /* assise (hanche dans la barque) */
.bc-uarm   { transform-box: view-box; transform-origin: 35.5px 22px; }    /* épaule */
.bc-farm   { transform-box: view-box; transform-origin: 42.5px 26.5px; }  /* coude  */
.bc-tackle { transform-box: view-box; transform-origin: 73px 24px; }      /* bout de la canne (scion) */

/* Liseré couleur-du-fond autour de l'hameçon : il MORD dans le « a » (même
   couleur → sinon invisible, blanc sur blanc). Le halo creuse un fin sillon
   sombre dans la lettre pour qu'on voie le trait blanc de l'hameçon planté. */
.bc-hook-halo { stroke: var(--black); }
html[data-theme="light"] .bc-hook-halo { stroke: var(--dark-2); }

/* Un SEUL geste coordonné : la CANNE reste stable (petite épaule), la MAIN
   mouline (pompage court au coude → la canne ne swingue pas, le « a »
   reste ferré), le CORPS se cale à l'effort (cue le plus vivant), et la
   ligne + l'hameçon (avec le « a ») balancent en pendule. Périodes
   HARMONIQUES : 2.4s (corps/épaule/ligne) et 1.2s (manivelle = 2 pompes
   par respiration) → un seul mouvement calme, jamais mécanique. */
.brand-catch.is-onscreen .bc-guy    { animation: bc-body-brace 2.4s ease-in-out infinite; }
.brand-catch.is-onscreen .bc-uarm   { animation: bc-uarm-lift 2.4s ease-in-out infinite; }
.brand-catch.is-onscreen .bc-farm   { animation: bc-farm-crank 1.2s ease-in-out infinite; }
.brand-catch.is-onscreen .bc-tackle { animation: bc-tackle-sway 2.4s ease-in-out infinite; }

@keyframes bc-body-brace {        /* le corps se cale doucement à l'effort */
    0%   { transform: rotate(0deg); }
    34%  { transform: rotate(-1.4deg); }
    64%  { transform: rotate(0.5deg); }
    100% { transform: rotate(0deg); }
}
@keyframes bc-uarm-lift {         /* l'épaule accompagne à peine → canne stable */
    0%   { transform: rotate(0deg); }
    36%  { transform: rotate(-1.1deg); }
    70%  { transform: rotate(0.4deg); }
    100% { transform: rotate(0deg); }
}
@keyframes bc-farm-crank {        /* la MAIN mouline : pompage court et régulier */
    0%   { transform: rotate(-1.4deg); }
    50%  { transform: rotate(3.6deg); }
    100% { transform: rotate(-1.4deg); }
}
@keyframes bc-tackle-sway {       /* la ligne + l'hameçon + le « a » ferré balancent (pendule) */
    0%   { transform: rotate(0.7deg); }
    32%  { transform: rotate(-1.8deg); }
    66%  { transform: rotate(2deg); }
    100% { transform: rotate(0.7deg); }
}

/* ══ « Miti » VIVANT : le reflet pêché ride la surface de l'eau ══════
   Même philosophie que le rig du pêcheur : un mouvement COMPOSÉ (pas un
   simple bob) + une logique narrative. Le « a » est ferré au bout de la
   ligne ; « Miti » est son reflet à la surface. La PRISE ride l'eau : une
   vague ÉMANE du « a » (à droite) et remonte le mot vers la gauche, en
   s'atténuant — comme une ride réelle dont l'énergie se dissipe.
   Chaque lettre est un FLOTTEUR qui tangue sur la ligne d'eau :
   - trajectoire ELLIPTIQUE (translate x+y, pas juste vertical) → ça
     flotte au lieu de rebondir ;
   - ROULIS pivotant sur la BASE de la lettre (transform-origin = waterline)
     → tangage, pas rotation sur place ;
   - amplitude + retard PARAMÉTRÉS par lettre (variables CSS → un seul
     keyframe, esprit « splitter unique ») : la lettre proche du « a »
     tangue en 1er et le + fort ; le M capital, le + loin, reste l'ANCRE
     (retard max, amplitude min).
   Période VERROUILLÉE sur le pendule de la ligne (bc-tackle-sway, 2.4s) →
   « Mitia » respire comme un seul organisme, la prise étant la cause du
   mouvement. transform-only, gaté par .is-onscreen (pause hors écran). */
.bc-static { display: inline-block; }
.bc-static .bc-ch {
    display: inline-block;
    transform-origin: 50% 100%;   /* pivot = la ligne d'eau : la lettre penche sur sa base */
    --sway: 1.5px;                /* dérive horizontale, l'axe DOMINANT du « a » (défaut) */
    --lift: 0.6px;                /* composante verticale, bien plus faible (défaut) */
    --tilt: 0.4deg;               /* inclinaison, suit le sens de la dérive (défaut) */
}
.brand-catch.is-onscreen:not([data-active="fil"]):not([data-active="trace"]):not([data-active="casse"]) .bc-static .bc-ch {
    animation-name: bc-letter-wave;
    animation-duration: 2.4s;     /* = la période du rig (bc-tackle-sway) */
    /* linear : la courbe ci-dessous est ÉCHANTILLONNÉE sur le mouvement réel
       du « a » (12 points), elle porte donc déjà son propre easing. Un
       ease-in-out ré-adoucirait chaque segment et décalerait la phase. */
    animation-timing-function: linear;
    animation-iteration-count: infinite;
}
/* AUCUN retard : toutes les lettres sont EN PHASE avec la tige. Ce qui
   décroît du « a » vers le M, c'est l'AMPLITUDE — comportement d'un système
   couplé (le mot est amarré, le « a » pend librement au bout de la ligne).
   Un déphasage, même de 0.05s, est exactement ce qui faisait « pas synchro ». */
.bc-static .bc-ch { animation-delay: 0s; }
/* Couplage ~60 % de la course du « a » (qui balance de ±7.5 px) pour la lettre
   voisine, puis dégressif jusqu'au M. À 30 % le mouvement était juste, mais on
   ne le VOYAIT pas ; à 60 % il se lit, tout en gardant le mot amarré (le « a »
   pend librement, il reste donc toujours le plus ample). Phase inchangée. */
.bc-static .bc-ch:nth-child(4) { --sway: 4.6px; --lift: 1.9px; --tilt: 1.3deg;  }
.bc-static .bc-ch:nth-child(3) { --sway: 3.8px; --lift: 1.6px; --tilt: 1.1deg;  }
.bc-static .bc-ch:nth-child(2) { --sway: 3px;   --lift: 1.3px; --tilt: 0.85deg; }
.bc-static .bc-ch:nth-child(1) { --sway: 2.4px; --lift: 1px;   --tilt: 0.7deg;  }
/* ── Courbe RELEVÉE sur la tige, pas devinée ──────────────────────────
   Mesure (animations figées, pas de 200 ms sur le cycle de 2.4 s) du
   déplacement réel du « a » suspendu à la tige :
     · amplitude horizontale 14.95 px  ← axe DOMINANT (le « a » pendule)
     · amplitude verticale    2.94 px
     · extrême DROITE à 42 %, extrême GAUCHE à 75 % du cycle
   Les 12 coefficients ci-dessous SONT ce relevé, normalisé. « Miti » suit
   donc la tige au pixel de phase près : même sens, même instant, et le
   vertical reste secondaire comme sur le vrai mouvement. */
@keyframes bc-letter-wave {
    0%   { transform: translate(calc(var(--sway) *  0.43), calc(var(--lift) * -0.38)) rotate(calc(var(--tilt) *  0.43)); }
    8%   { transform: translate(calc(var(--sway) *  0.32), calc(var(--lift) * -0.16)) rotate(calc(var(--tilt) *  0.32)); }
    17%  { transform: translate(calc(var(--sway) *  0.14), calc(var(--lift) *  0.27)) rotate(calc(var(--tilt) *  0.14)); }
    25%  { transform: translate(calc(var(--sway) *  0.27), calc(var(--lift) *  0.24)) rotate(calc(var(--tilt) *  0.27)); }
    33%  { transform: translate(calc(var(--sway) *  0.64), calc(var(--lift) * -0.28)) rotate(calc(var(--tilt) *  0.64)); }
    42%  { transform: translate(var(--sway),               calc(var(--lift) * -1))    rotate(var(--tilt)); }
    50%  { transform: translate(calc(var(--sway) *  0.69), calc(var(--lift) * -0.76)) rotate(calc(var(--tilt) *  0.69)); }
    58%  { transform: translate(calc(var(--sway) * -0.07), calc(var(--lift) *  0.09)) rotate(calc(var(--tilt) * -0.07)); }
    67%  { transform: translate(calc(var(--sway) * -0.81), calc(var(--lift) *  0.80)) rotate(calc(var(--tilt) * -0.81)); }
    75%  { transform: translate(calc(var(--sway) * -1),    var(--lift))               rotate(calc(var(--tilt) * -1)); }
    83%  { transform: translate(calc(var(--sway) * -0.59), calc(var(--lift) *  0.73)) rotate(calc(var(--tilt) * -0.59)); }
    92%  { transform: translate(calc(var(--sway) *  0.13), 0)                         rotate(calc(var(--tilt) *  0.13)); }
    100% { transform: translate(calc(var(--sway) *  0.43), calc(var(--lift) * -0.38)) rotate(calc(var(--tilt) *  0.43)); }
}

/* ── Fallback total : reduced-motion → scène statique, « Mitia » lisible ── */
@media (prefers-reduced-motion: reduce) {
    .brand-catch .bc-fisher,
    .brand-catch.is-armed .bc-fisher { opacity: 1 !important; transform: translateX(-52%) !important; animation: none !important; }
    .brand-catch .bc-guy,
    .brand-catch .bc-uarm,
    .brand-catch .bc-farm,
    .brand-catch .bc-tackle,
    .brand-catch .bc-rod,
    .brand-catch .bc-fline,
    .brand-catch .bc-hook,
    .brand-catch .bc-ch,
    .brand-catch .bc-a-glyph { animation: none !important; transform: none !important; }
}

/* ── Mobile : un peu plus compact ── */
@media (max-width: 620px) {
    .footer-catch { padding-top: 3rem; }
    .footer-catch .brand-catch-word { font-size: var(--pas-affirmation); }
}


/* ══════════════════════════════════════════════════════════════
   LA TROUPE — trois scènes pour une seule signature
   Le « a » de « Mitia » n'est jamais posé en dur : quelqu'un le
   tient, le porte ou le peint. Une scène au hasard à chaque visite,
   un clic passe à la suivante.
     · peche  — le pêcheur ferre le « a » depuis sa barque
     · fil    — le funambule descend un câble amarré au « a »
     · trace  — le peintre fait naître le « a » sous son rouleau
   Contrat commun (à respecter pour toute nouvelle scène) :
     viewBox 0 0 140 184 · le « a » est un <text x=73 y=168> en
     .bc-a-glyph → il tombe PILE sur la ligne de base du mot.
     Zone franche : le mot « Miti » occupe y 103→168, donc tout
     personnage vit AU-DESSUS de y≈100 (ou hors de sa largeur).
   Charte : transform-only, pause hors écran (.is-onscreen),
   reduced-motion → scène figée, « Mitia » toujours lisible.
   ══════════════════════════════════════════════════════════════ */

.bc-scene {
    position: absolute;
    left: 50%;
    bottom: -0.17em;
    width: 1.5em;
    height: auto;
    transform: translateX(-52%);
    color: var(--gray-1, #fff);
    pointer-events: none;
    overflow: visible;           /* les scènes débordent du viewBox (câble, perche) */
}
/* Une seule scène visible. Sans JS aucun data-active n'est posé →
   le pêcheur s'affiche : la signature reste entière, script éteint. */
.brand-catch .bc-scene { display: none; }
.brand-catch:not([data-active]) .bc-scene--peche,
.brand-catch[data-active="peche"] .bc-scene--peche,
.brand-catch[data-active="fil"] .bc-scene--fil,
.brand-catch[data-active="trace"] .bc-scene--trace { display: block; }

/* Le clic fait tourner la troupe (easter egg) — pointeur fin seulement. */
@media (hover: hover) {
    .brand-catch.is-armed { cursor: pointer; }
}

/* ══ ENTRÉE — LE TRAIT QUI SE DESSINE ══════════════════════════
   Le personnage naît comme un croquis à l'encre, trait après trait,
   comme sous une plume invisible. Mécanique : stroke-dasharray =
   stroke-dashoffset = longueur RÉELLE du chemin (--len par élément,
   mesurée UNE fois par le driver via getTotalLength() et posée
   AVANT .is-armed) ; .is-caught anime l'offset longueur → 0 → le
   trait se dessine de son origine à sa fin (l'ordre du « d »).
   Les SURFACES PLEINES (tête, calotte, moulinet, nœud) ne se
   tracent pas : elles GONFLENT comme une goutte d'encre (scale
   depuis leur ancre, léger dépassement) au passage de la plume ;
   le lavis du croissant (bc-moon-fill) monte en opacité pendant
   que son contour s'encre. ORDRE DU RÉCIT, par scène : le SUPPORT
   (barque / câble / nacelle), puis le CORPS de bas en haut (jambes
   → torse → tête → casquette), puis l'OUTIL (canne / perche /
   rouleau), puis le LIEN vers le « a » (ligne + hameçon / plongée
   du câble / passe de peinture décalée). Le « a » ne se retrace
   JAMAIS : en peche/fil il est visible dès la première frame
   (c'est la signature) ; en trace il naît sous le rouleau
   (mécanisme existant, simplement décalé après le croquis).
   ENCHAÎNEMENT SANS SAUT : les traits vivent DANS les groupes des
   boucles de vie (mêmes transform-origin) — le dernier état du
   dessin EST l'état de repos des boucles, qui composent par-dessus
   dès la première frame (le croquis respire pendant qu'il se
   dessine). Sans JS : pas de .is-armed → traits pleins, scène
   entière. Reduced-motion : le driver ne s'arme jamais + resets
   dans le bloc dédié plus bas. */

/* Mesure : les 3 scènes affichées le temps des getTotalLength(),
   SYNCHRONE (aucun paint entre l'ajout et le retrait de la classe). */
.brand-catch.bc-measuring .bc-scene { display: block; }

/* État armé : l'encre est retirée (dash = longueur du chemin, offset
   idem → trait invisible) et les pleins sont à l'échelle zéro.
   Fallback 400px : couvre le plus long trait (câble ≈ 184) si la
   mesure manquait — le phasé serait approximatif mais l'état final
   resterait un trait plein. */
.brand-catch.is-armed :is(.bc-moon, .bc-legs, .bc-torso, .bc-cap-halo, .bc-cap,
    .bc-arm1, .bc-arm2, .bc-rod, .bc-fline, .bc-hook, .bc-hook-halo,
    .bc-wire, .bc-knot-tail, .bc-leg-b path, .bc-leg-f path, .bc-arms, .bc-perche,
    .bc-fan-line,
    .bc-cable, .bc-deck, .bc-rail, .bc-bucket path,
    .bc-forearms, .bc-shaft, .bc-crank, .bc-roller, .bc-roller-halo) {
    stroke-dasharray: var(--len, 400px);
    stroke-dashoffset: var(--len, 400px);
}
.brand-catch.is-armed :is(.bc-head, .bc-cap-dome, .bc-reel, .bc-hook-eye,
    .bc-knot, .bc-knot-halo, .bc-fan-head) { transform: scale(0); }
.brand-catch.is-armed .bc-moon-fill { opacity: 0; }

/* Ancres des pleins : la tête gonfle sur place, la calotte pousse
   depuis le crâne (base de son bbox). fill-box = ancres locales,
   aucune collision avec les origins view-box des groupes de vie
   (les pleins n'ont aucun autre transform en propre). */
.bc-head, .bc-reel, .bc-hook-eye, .bc-knot, .bc-knot-halo, .bc-fan-head {
    transform-box: fill-box; transform-origin: center;
}
.bc-cap-dome { transform-box: fill-box; transform-origin: 50% 100%; }

@keyframes bc-ink      { to { stroke-dashoffset: 0; } }
@keyframes bc-ink-pop  { from { transform: scale(0); } 62% { transform: scale(1.14); } to { transform: scale(1); } }
@keyframes bc-ink-wash { from { opacity: 0; } to { opacity: 1; } }

/* ── peche · la plume : barque (+ lavis), jambes, torse, tête,
      casquette, bras, canne (+ moulinet), la ligne PLONGE, l'hameçon
      se referme sur le « a » (déjà là — il ne se retrace pas). ── */
.brand-catch.is-armed.is-caught .bc-scene--peche .bc-moon      { animation: bc-ink 0.36s cubic-bezier(0.55, 0.08, 0.45, 0.96) 0s both; }
.brand-catch.is-armed.is-caught .bc-scene--peche .bc-moon-fill { animation: bc-ink-wash 0.34s ease-out 0.16s both; }
.brand-catch.is-armed.is-caught .bc-scene--peche .bc-legs      { animation: bc-ink 0.26s cubic-bezier(0.55, 0.08, 0.45, 0.96) 0.28s both; }
.brand-catch.is-armed.is-caught .bc-scene--peche .bc-torso     { animation: bc-ink 0.16s ease-out 0.48s both; }
.brand-catch.is-armed.is-caught .bc-scene--peche .bc-head      { animation: bc-ink-pop 0.22s cubic-bezier(0.34, 1.4, 0.5, 1) 0.6s both; }
.brand-catch.is-armed.is-caught .bc-scene--peche .bc-cap-dome  { animation: bc-ink-pop 0.18s cubic-bezier(0.34, 1.4, 0.5, 1) 0.72s both; }
.brand-catch.is-armed.is-caught .bc-scene--peche .bc-cap-halo  { animation: bc-ink 0.13s ease-out 0.72s both; }
.brand-catch.is-armed.is-caught .bc-scene--peche .bc-cap       { animation: bc-ink 0.11s ease-out 0.82s both; }
.brand-catch.is-armed.is-caught .bc-scene--peche .bc-arm1      { animation: bc-ink 0.12s ease-out 0.7s both; }
.brand-catch.is-armed.is-caught .bc-scene--peche .bc-arm2      { animation: bc-ink 0.12s ease-out 0.8s both; }
.brand-catch.is-armed.is-caught .bc-scene--peche .bc-rod       { animation: bc-ink 0.2s cubic-bezier(0.55, 0.08, 0.45, 0.96) 0.9s both; }
.brand-catch.is-armed.is-caught .bc-scene--peche .bc-reel      { animation: bc-ink-pop 0.16s cubic-bezier(0.34, 1.4, 0.5, 1) 0.98s both; }
.brand-catch.is-armed.is-caught .bc-scene--peche .bc-fline     { animation: bc-ink 0.26s cubic-bezier(0.6, 0.05, 0.55, 1) 1.08s both; }
.brand-catch.is-armed.is-caught .bc-scene--peche .bc-hook-eye  { animation: bc-ink-pop 0.14s cubic-bezier(0.34, 1.4, 0.5, 1) 1.3s both; }
.brand-catch.is-armed.is-caught .bc-scene--peche .bc-hook-halo,
.brand-catch.is-armed.is-caught .bc-scene--peche .bc-hook      { animation: bc-ink 0.2s cubic-bezier(0.55, 0.08, 0.45, 0.96) 1.34s both; }

/* ── fil · la plume part du « a » : nœud, le câble FILE vers le
      haut-gauche, puis le marcheur là où il est sur son câble :
      jambes, torse, tête, casquette, bras, la perche d'un seul
      grand trait. La plongée du câble (bc-wire-land) attend la fin
      du croquis : le poids se pose quand le funambule EXISTE. ── */
.brand-catch.is-armed.is-caught .bc-scene--fil .bc-knot-halo,
.brand-catch.is-armed.is-caught .bc-scene--fil .bc-knot       { animation: bc-ink-pop 0.18s cubic-bezier(0.34, 1.4, 0.5, 1) 0s both; }
.brand-catch.is-armed.is-caught .bc-scene--fil .bc-wire       { animation: bc-ink 0.5s cubic-bezier(0.55, 0.08, 0.45, 0.96) 0.06s both; }
.brand-catch.is-armed.is-caught .bc-scene--fil .bc-leg-b path,
.brand-catch.is-armed.is-caught .bc-scene--fil .bc-leg-f path { animation: bc-ink 0.2s cubic-bezier(0.55, 0.08, 0.45, 0.96) 0.58s both; }
.brand-catch.is-armed.is-caught .bc-scene--fil .bc-torso      { animation: bc-ink 0.15s ease-out 0.76s both; }
.brand-catch.is-armed.is-caught .bc-scene--fil .bc-head       { animation: bc-ink-pop 0.2s cubic-bezier(0.34, 1.4, 0.5, 1) 0.88s both; }
.brand-catch.is-armed.is-caught .bc-scene--fil .bc-cap-dome   { animation: bc-ink-pop 0.16s cubic-bezier(0.34, 1.4, 0.5, 1) 0.98s both; }
.brand-catch.is-armed.is-caught .bc-scene--fil .bc-cap-halo   { animation: bc-ink 0.12s ease-out 0.98s both; }
.brand-catch.is-armed.is-caught .bc-scene--fil .bc-cap        { animation: bc-ink 0.1s ease-out 1.07s both; }
.brand-catch.is-armed.is-caught .bc-scene--fil .bc-arms       { animation: bc-ink 0.16s ease-out 1.02s both; }
.brand-catch.is-armed.is-caught .bc-scene--fil .bc-perche     { animation: bc-ink 0.3s cubic-bezier(0.55, 0.08, 0.45, 0.96) 1.14s both; }
/* le public arrive en dernier : la plume esquisse les badauds une fois
   le funambule entier (perche finie à 1.44s), leurs têtes gonflent */
.brand-catch.is-armed.is-caught .bc-scene--fil .bc-fan-line   { animation: bc-ink 0.24s cubic-bezier(0.55, 0.08, 0.45, 0.96) 1.5s both; }
.brand-catch.is-armed.is-caught .bc-scene--fil .bc-fan-head   { animation: bc-ink-pop 0.18s cubic-bezier(0.34, 1.4, 0.5, 1) 1.66s both; }
/* Le bout libre du nœud : tracé bref PUIS frisson continu — les deux
   noms dans UNE déclaration quand .is-onscreen, sinon la règle du
   tracé (plus spécifique) écraserait la boucle bc-tail-flick. */
.brand-catch.is-armed.is-caught .bc-scene--fil .bc-knot-tail  { animation: bc-ink 0.12s ease-out 0.14s both; }
.brand-catch.is-onscreen.is-armed.is-caught .bc-scene--fil .bc-knot-tail {
    animation: bc-ink 0.12s ease-out 0.14s both,
               bc-tail-flick 1.7s ease-in-out infinite;
}

/* ── trace · les câbles TOMBENT, la nacelle se construit (pont,
      rambarde, seau), le peintre se dessine EN POSITION (bras armé
      à gauche — les transforms .is-armed tiennent la pose), le
      manche, le rouleau se CHARGE d'encre — puis la passe existante
      fait naître le « a » (décalée de 1.8s, le temps du croquis). ── */
.brand-catch.is-armed.is-caught .bc-scene--trace .bc-cable       { animation: bc-ink 0.34s cubic-bezier(0.55, 0.08, 0.45, 0.96) 0s both; }
.brand-catch.is-armed.is-caught .bc-scene--trace .bc-deck        { animation: bc-ink 0.2s cubic-bezier(0.55, 0.08, 0.45, 0.96) 0.28s both; }
.brand-catch.is-armed.is-caught .bc-scene--trace .bc-rail        { animation: bc-ink 0.15s ease-out 0.44s both; }
.brand-catch.is-armed.is-caught .bc-scene--trace .bc-bucket path { animation: bc-ink 0.18s ease-out 0.54s both; }
.brand-catch.is-armed.is-caught .bc-scene--trace .bc-legs        { animation: bc-ink 0.18s cubic-bezier(0.55, 0.08, 0.45, 0.96) 0.62s both; }
.brand-catch.is-armed.is-caught .bc-scene--trace .bc-torso       { animation: bc-ink 0.13s ease-out 0.76s both; }
.brand-catch.is-armed.is-caught .bc-scene--trace .bc-head        { animation: bc-ink-pop 0.2s cubic-bezier(0.34, 1.4, 0.5, 1) 0.86s both; }
.brand-catch.is-armed.is-caught .bc-scene--trace .bc-cap-dome    { animation: bc-ink-pop 0.16s cubic-bezier(0.34, 1.4, 0.5, 1) 0.96s both; }
.brand-catch.is-armed.is-caught .bc-scene--trace .bc-cap-halo    { animation: bc-ink 0.12s ease-out 0.96s both; }
.brand-catch.is-armed.is-caught .bc-scene--trace .bc-cap         { animation: bc-ink 0.1s ease-out 1.05s both; }
.brand-catch.is-armed.is-caught .bc-scene--trace .bc-arms        { animation: bc-ink 0.14s ease-out 1s both; }
.brand-catch.is-armed.is-caught .bc-scene--trace .bc-forearms    { animation: bc-ink 0.12s ease-out 1.1s both; }
.brand-catch.is-armed.is-caught .bc-scene--trace .bc-shaft       { animation: bc-ink 0.24s cubic-bezier(0.55, 0.08, 0.45, 0.96) 1.18s both; }
.brand-catch.is-armed.is-caught .bc-scene--trace .bc-crank       { animation: bc-ink 0.09s ease-out 1.4s both; }
.brand-catch.is-armed.is-caught .bc-scene--trace .bc-roller-halo,
.brand-catch.is-armed.is-caught .bc-scene--trace .bc-roller      { animation: bc-ink 0.18s ease-out 1.46s both; }

/* ── Détail-récompense (peche) : la barque TANGUE sur une ride.
      Pivot au ras de l'eau, ±0.9° → le point d'assise du pêcheur
      bouge < 0.15px : aucun décrochage du corps. Période 3.6s,
      harmonique du rig (2.4s) → cycle commun 7.2s. ── */
.bc-boat { transform-box: view-box; transform-origin: 35px 41px; }
.brand-catch.is-onscreen .bc-scene--peche .bc-boat { animation: bc-moon-bob 3.6s ease-in-out infinite; }
@keyframes bc-moon-bob {
    0%   { transform: rotate(0deg); }
    32%  { transform: rotate(-0.9deg); }
    70%  { transform: rotate(0.7deg); }
    100% { transform: rotate(0deg); }
}

/* ── fil · le funambule ─────────────────────────────────────────
   Le câble est amarré au sommet du « a » (nœud plein sur halo + bout
   libre) et grimpe vers le haut-gauche avec une flèche sous le poids.
   CHAÎNE CINÉMATIQUE — la correction remonte le corps et chaque
   maillon est EN RETARD sur le précédent (c'est le retard qui fait le
   poids), le tout sur UNE période de 2.8 s ; l'irrégularité (dérive
   lente → rattrape sèche → re-dérive → sur-correction) est DANS les
   keyframes et leurs courbes, pas dans un déphasage mécanique :
     câble (pivot = nœud 73,121) : oscillation forcée par le marcheur
       → même période, il plonge quand le corps dérive ;
     corps entier (.bc-walker, pivot = point d'appui, origine locale 0,0) :
       dérive + rattrapes, avec transfert de poids (translate y :
       affaissement entre deux appuis, remontée sur l'appui) ;
     buste (.bc-torso-rig, hanche locale 0.5,−21.3) : contre-penche en retard
       (~8 % de cycle) → il reste proche de la VERTICALE GRAVITAIRE
       pendant que le câble et les jambes bougent dessous ;
     bras + perche (.bc-pole, épaule locale 3.5,−37.3) : UN SEUL SOLIDE — les
       mains ne peuvent pas lâcher la perche, par construction
       (dist. main↔axe de la perche invariante ≈ 0.5 px, sous le
       rayon du trait). Contrepoids : amplitude max, retard max
       (~14 %), avec dépassement d'inertie avant le retour.
   Entrée : le câble PLONGE à l'arrivée du marcheur (.bc-wire-land,
   one-shot) puis rebondit — le poids se pose.
   Détail-récompense : le bout libre du nœud frissonne sous la
   tension (1.7 s, premier avec 2.8 s → la combinaison ne se répète
   pas avant ~48 s). */
.bc-wire-rig  { transform-box: view-box; transform-origin: 73px 121px; }
.bc-wire-land { transform-box: view-box; transform-origin: 73px 121px; }
.bc-walker    { transform-box: view-box; transform-origin: 0px 0px; }        /* appui (origine locale) */
.bc-torso-rig { transform-box: view-box; transform-origin: 0.5px -21.3px; }  /* hanche (locale) */
.bc-pole      { transform-box: view-box; transform-origin: 3.5px -37.3px; }  /* épaule (locale) */
.bc-knot-tail { transform-box: view-box; transform-origin: 75px 120px; }
.bc-scene--fil .bc-a-glyph { transform-box: view-box; transform-origin: 73px 168px; }

/* L'arrivée : le câble encaisse le poids (one-shot, compose avec la
   vibration continue qui vit sur .bc-wire-rig, un autre élément).
   Délai 1.5s = la fin du croquis (perche tracée à 1.44s) : le poids
   ne se pose que quand le funambule existe. */
.brand-catch[data-active="fil"].is-armed.is-caught .bc-wire-land {
    animation: bc-wire-land 1.15s cubic-bezier(0.3, 0, 0.2, 1) 1.5s both;
}
@keyframes bc-wire-land {
    0%   { transform: rotate(0deg); }
    32%  { transform: rotate(-1.7deg); }
    58%  { transform: rotate(0.75deg); }
    80%  { transform: rotate(-0.3deg); }
    100% { transform: rotate(0deg); }
}

.brand-catch.is-onscreen .bc-wire-rig  { animation: bc-wire-vibe 2.8s ease-in-out infinite; }
.brand-catch.is-onscreen .bc-walker    { animation: bc-walk-sway 2.8s linear infinite; }
.brand-catch.is-onscreen .bc-torso-rig { animation: bc-torso-lag 2.8s ease-in-out infinite; }
.brand-catch.is-onscreen .bc-pole      { animation: bc-pole-counter 2.8s ease-in-out infinite; }
.brand-catch.is-onscreen .bc-knot-tail { animation: bc-tail-flick 1.7s ease-in-out infinite; }
.brand-catch.is-onscreen .bc-scene--fil .bc-a-glyph { animation: bc-a-tremble 2.8s ease-in-out infinite; }

@keyframes bc-wire-vibe {            /* forcé par le marcheur : plonge quand il dérive */
    0%   { transform: rotate(0deg); }
    30%  { transform: rotate(-0.85deg); }
    68%  { transform: rotate(0.35deg); }
    100% { transform: rotate(0deg); }
}
/* Le corps : dérive lente (ease-in, ça part tout seul), rattrape SÈCHE
   (sortie rapide), re-dérive, ANTICIPATION (60 % : il charge encore un
   peu avant la grande correction), sur-correction, retour. Les courbes
   par segment portent le poids — linear global + easing par keyframe. */
@keyframes bc-walk-sway {
    0%   { transform: translate(0, 0) rotate(0deg); animation-timing-function: cubic-bezier(0.55, 0, 0.85, 0.4); }
    26%  { transform: translate(0.5px, 0.7px) rotate(2.2deg); animation-timing-function: cubic-bezier(0.15, 0.85, 0.25, 1); }
    38%  { transform: translate(0.2px, 0.1px) rotate(1.4deg); animation-timing-function: cubic-bezier(0.5, 0, 0.8, 0.45); }
    55%  { transform: translate(0.4px, 0.6px) rotate(1.9deg); animation-timing-function: ease-in-out; }
    60%  { transform: translate(0.45px, 0.7px) rotate(2.15deg); animation-timing-function: cubic-bezier(0.2, 0.8, 0.3, 1); }
    74%  { transform: translate(-0.3px, 0.3px) rotate(-1.3deg); animation-timing-function: ease-in-out; }
    88%  { transform: translate(-0.1px, 0) rotate(-0.4deg); animation-timing-function: ease-in-out; }
    100% { transform: translate(0, 0) rotate(0deg); }
}
@keyframes bc-torso-lag {            /* le buste contre en retard : quasi vertical */
    0%   { transform: rotate(0deg); }
    34%  { transform: rotate(-1.05deg); }
    48%  { transform: rotate(-0.7deg); }
    63%  { transform: rotate(-1.2deg); }
    71%  { transform: rotate(-1.35deg); }
    84%  { transform: rotate(0.8deg); }
    100% { transform: rotate(0deg); }
}
@keyframes bc-pole-counter {         /* le contrepoids, dernier maillon : le plus
                                        ample, le plus en retard, et il DÉPASSE
                                        (inertie) avant de revenir */
    0%   { transform: rotate(0deg); }
    22%  { transform: rotate(-1.8deg); }
    42%  { transform: rotate(-3.9deg); }
    54%  { transform: rotate(-2.2deg); }
    64%  { transform: rotate(-3.2deg); }
    80%  { transform: rotate(2.9deg); }
    92%  { transform: rotate(0.8deg); }
    100% { transform: rotate(0deg); }
}
@keyframes bc-tail-flick {           /* le bout libre frissonne sous la tension */
    0%   { transform: rotate(0deg); }
    38%  { transform: rotate(5deg); }
    52%  { transform: rotate(-2.5deg); }
    70%  { transform: rotate(1deg); }
    100% { transform: rotate(0deg); }
}
@keyframes bc-a-tremble {            /* la lettre encaisse, juste après le câble */
    0%   { transform: rotate(0.6deg); }
    36%  { transform: rotate(-1deg); }
    72%  { transform: rotate(0.45deg); }
    100% { transform: rotate(0.6deg); }
}

/* ── trace · le peintre ─────────────────────────────────────────
   Le « a » n'existe pas encore : un masque le découvre EXACTEMENT au
   rythme du manchon (mêmes durée + courbe + keyframe à 76 %).
   CHAÎNE DU GESTE — trois articulations réelles :
     épaule (.bc-brush, pivot 62,36)  : la passe, 51° → 0 ;
     coude  (.bc-fore,  pivot 67,43)  : plié au départ (+8°), il
       s'ÉTEND au fil de la passe (−3° au bord droit) → la trajectoire
       du manchon n'est plus un arc de cercle parfait ;
     poignet (.bc-roller-head, coude outil 120,112) : contre-pivot,
       le manchon reste d'aplomb, appuyé dans le trait (+6° net au
       départ, −2° au relâcher) ; à la fin il ROULE l'outil en biais
       (−30°) — sa position de repos.
   SYNCHRO (modèle 3 maillons, vérifiée numériquement point à point) :
     bord du masque = 125 + tx ; manchon = épaule ∘ coude ∘ poignet.
     Départ 29 (bord gauche de l'encre), 76 % → 116.5 (bord droit),
     dérive ≤ 1.3 px sur toute la passe (< demi-largeur du manchon).
     Encre réelle du glyphe (canvas, Syne 800 90px) :
     x 28→116.5 · y 122.5→169.
   3ᵉ LOI DE NEWTON : la nacelle RECULE pendant qu'il pousse le
   rouleau (.bc-stage-react, one-shot) — faible pendant l'appui (le
   contact avec la surface le stabilise), franche au relâcher, puis
   ça s'amortit. Ensuite le pendule résiduel (.bc-stage) bat à SA
   période propre (3.1 s, première avec 4.7 s → pas de répétition
   visible).
   L'APRÈS (le peintre ne reste pas figé, mais RIEN de faux) : il LÈVE
   le rouleau du mur et le TIENT loin à droite de la lettre (jamais
   dessus) ; UNE goutte d'excès se détache au décollage puis tombe
   (one-shot). Ensuite l'OUTIL RESTE au repos : un manchon ne « tourne »
   pas dans le vide. La seule vie perpétuelle est HUMAINE — la tête
   REDESCEND admirer la lettre et boucle doucement (bc-admire) — plus la
   nacelle suspendue qui dérive à peine. La lettre, une fois peinte,
   n'est JAMAIS re-masquée : le wipe vit sur .is-armed.is-caught, pas
   sur .is-onscreen. */
.bc-stage       { transform-box: view-box; transform-origin: 71px 0px; }
.bc-stage-react { transform-box: view-box; transform-origin: 71px 0px; }
.bc-brush       { transform-box: view-box; transform-origin: 62px 36px; }
.bc-brush-idle  { transform-box: view-box; transform-origin: 62px 36px; }
.bc-fore        { transform-box: view-box; transform-origin: 67px 43px; }
.bc-noggin      { transform-box: view-box; transform-origin: 62px 34px; }
.bc-roller-head { transform-box: view-box; transform-origin: 120px 112px; transform: rotate(-30deg); }
.bc-drip        { transform-box: fill-box; transform-origin: center; }
.bc-wipe        { transform-box: view-box; }

/* armé mais pas encore joué : lettre cachée, bras remonté à gauche
   (épaule +51°, coude plié +8°), manchon d'aplomb appuyé (+6 net =
   51 + 8 − 53), tête baissée vers le départ du trait (+7°).
   rotate(+) tourne HORAIRE : sous le pivot, ça part vers la GAUCHE. */
.brand-catch.is-armed .bc-wipe        { transform: translateX(-96px); }
.brand-catch.is-armed .bc-brush       { transform: rotate(51deg); }
.brand-catch.is-armed .bc-fore        { transform: rotate(8deg); }
.brand-catch.is-armed .bc-roller-head { transform: rotate(-53deg); }
.brand-catch.is-armed .bc-noggin      { transform: rotate(7deg); }

/* Toute la passe attend la fin du croquis : délai 1.8s (rouleau
   chargé à 1.64s + une respiration). Pendant le délai, les
   transforms .is-armed tiennent la pose de départ (= keyframe from,
   fill both : zéro saut). */
.brand-catch.is-armed.is-caught .bc-wipe        { animation: bc-paint-wipe 1.8s cubic-bezier(0.4, 0, 0.2, 1) 1.8s both; }
.brand-catch.is-armed.is-caught .bc-brush       { animation: bc-paint-sweep 1.8s cubic-bezier(0.4, 0, 0.2, 1) 1.8s both; }
.brand-catch.is-armed.is-caught .bc-fore        { animation: bc-fore-reach 1.8s cubic-bezier(0.4, 0, 0.2, 1) 1.8s both; }
.brand-catch.is-armed.is-caught .bc-roller-head { animation: bc-roller-level 1.8s cubic-bezier(0.4, 0, 0.2, 1) 1.8s both; }
.brand-catch.is-armed.is-caught .bc-stage-react { animation: bc-stage-recoil 2.6s ease-in-out 1.8s both; }
/* la tête : one-shot (suit le trait) PUIS boucle (admire) — les deux
   noms dans UNE déclaration, sinon la règle armée écrase la boucle.
   La boucle attend .is-painted (posé par le driver à la fin de la
   passe, +0.8 s = 4.4 s au premier passage, comme avant) : l'admire de
   la tête, la levée d'outil (one-shot) et la goutte partagent le même
   gate + délai → elles redémarrent EN PHASE quand on revient à
   l'écran (avant, tête et outil se désynchronisaient). */
.brand-catch.is-armed.is-caught .bc-noggin      { animation: bc-noggin-follow 1.8s cubic-bezier(0.4, 0, 0.2, 1) 1.8s both; }
.brand-catch.is-onscreen.is-armed.is-caught.is-painted .bc-noggin {
    animation: bc-noggin-follow 1.8s cubic-bezier(0.4, 0, 0.2, 1) 1.8s both,
               bc-admire 4.7s ease-in-out 0.8s infinite;
}
/* Après la passe, le rouleau NE « tourne » PLUS dans le vide. Un
   manchon tourne sur son propre axe (invisible de profil) ; le faire
   pivoter autour du poignet était un faux geste — retiré. La tête garde
   donc son seul mouvement vrai : bc-roller-level la pose au repos
   (−30°, en biais) et elle y RESTE. La vie de l'après vient du PEINTRE
   (il admire, bc-admire), pas d'une pirouette de l'outil. */

@keyframes bc-paint-wipe {
    from { transform: translateX(-96px); }
    76%  { transform: translateX(-8.5px); } /* bord du masque = bord droit de l'encre */
    to   { transform: translateX(0); }
}
@keyframes bc-paint-sweep {                 /* épaule : la passe */
    from { transform: rotate(51deg); }
    76%  { transform: rotate(8.8deg); }
    to   { transform: rotate(0deg); }
}
@keyframes bc-fore-reach {                  /* coude : plié → étendu au bout du trait */
    from { transform: rotate(8deg); }
    76%  { transform: rotate(-3deg); }
    to   { transform: rotate(0deg); }
}
@keyframes bc-roller-level {                /* poignet : manchon d'aplomb, appuyé */
    from { transform: rotate(-53deg); }     /* net +6° : il charge dans le trait  */
    76%  { transform: rotate(-7.8deg); }    /* net −2° : il relâche en finissant  */
    to   { transform: rotate(-30deg); }     /* il roule l'outil en biais : repos  */
}
@keyframes bc-noggin-follow {               /* le regard suit le trait */
    from { transform: rotate(7deg); }
    50%  { transform: rotate(4.2deg); }
    76%  { transform: rotate(2.5deg); }
    to   { transform: rotate(0deg); }
}
@keyframes bc-stage-recoil {                /* réaction : faible pendant l'appui
                                               (le mur le stabilise), franche au
                                               relâcher, puis amortie */
    0%   { transform: rotate(0deg); }
    20%  { transform: rotate(0.4deg); }
    45%  { transform: rotate(0.5deg); }
    58%  { transform: rotate(-0.2deg); }
    70%  { transform: rotate(0.9deg); }
    82%  { transform: rotate(-0.45deg); }
    92%  { transform: rotate(0.15deg); }
    100% { transform: rotate(0deg); }
}

/* Sillons couleur-du-fond : manchon dans l'encre, nœud assis sur la
   lettre, calotte détachée du crâne (cf. .bc-hook-halo). */
.bc-roller-halo, .bc-knot-halo, .bc-cap-halo { stroke: var(--black); }
html[data-theme="light"] .bc-roller-halo,
html[data-theme="light"] .bc-knot-halo,
html[data-theme="light"] .bc-cap-halo { stroke: var(--dark-2); }

/* L'après (rien d'inventé — que du vrai) : il LÈVE le rouleau du mur
   et le TIENT pour admirer (bc-tool-lift, one-shot : il ne re-pompe
   pas le mur en boucle). UNE goutte d'excès se détache au décollage
   (une seule fois : la peinture ne coule pas sans fin). Ensuite l'outil
   RESTE au repos ; la seule vie perpétuelle est le PEINTRE qui étudie
   sa lettre (bc-admire, tête) et la nacelle suspendue qui dérive à
   peine. Gate .is-painted + délai 0.8 s ; tout repart en phase au
   retour à l'écran. */
.brand-catch.is-onscreen .bc-stage      { animation: bc-stage-pend 3.1s ease-in-out 4.4s infinite; }
.brand-catch.is-onscreen.is-painted .bc-brush-idle { animation: bc-tool-lift 1.5s cubic-bezier(0.33, 0, 0.2, 1) 0.8s both; }
.brand-catch.is-onscreen.is-painted .bc-drip       { animation: bc-drip-fall 2.4s cubic-bezier(0.3, 0.1, 0.5, 1) 1.5s both; }

@keyframes bc-stage-pend {
    0%   { transform: rotate(0deg); }
    36%  { transform: rotate(-0.5deg); }
    72%  { transform: rotate(0.28deg); }
    100% { transform: rotate(0deg); }
}
@keyframes bc-tool-lift {                   /* il DÉCOLLE le rouleau du mur d'un
                                               mouvement franc, le poids retombe
                                               un peu, puis il le TIENT (fill both :
                                               reste levé, appui du regard) */
    0%   { transform: rotate(0deg); }
    55%  { transform: rotate(-7deg); }      /* levée : décollage du mur */
    78%  { transform: rotate(-5.4deg); }    /* le poids de l'outil retombe */
    100% { transform: rotate(-6deg); }      /* tenu en l'air, il admire */
}
@keyframes bc-drip-fall {                   /* excès de peinture au décollage : UNE
                                               goutte gonfle au bout du manchon
                                               (base −10,+9 → le rend au bout de la
                                               tête levée), se détache et ACCÉLÈRE
                                               en tombant. One-shot, puis plus rien. */
    0%   { opacity: 0; transform: translate(-10px, 8px) scale(0.4); }
    18%  { opacity: 0.85; transform: translate(-10px, 10px) scale(0.85); }  /* elle gonfle */
    30%  { opacity: 0.9; transform: translate(-10px, 11.5px) scale(1); }    /* col qui s'étire */
    42%  { opacity: 0.9; transform: translate(-10.5px, 17px) scale(0.95); }
    58%  { opacity: 0.82; transform: translate(-11.3px, 26px) scale(0.9); }
    74%  { opacity: 0; transform: translate(-12px, 37px) scale(0.85); }
    100% { opacity: 0; transform: translate(-10px, 8px) scale(0.4); }
}
@keyframes bc-admire {                      /* suit l'outil levé, puis redescend
                                               ADMIRER la lettre peinte */
    0%   { transform: rotate(0deg); }
    15%  { transform: rotate(-3deg); }
    34%  { transform: rotate(-2deg); }
    52%  { transform: rotate(1deg); }
    64%  { transform: rotate(6.5deg); }
    80%  { transform: rotate(5.5deg); }
    92%  { transform: rotate(2deg); }
    100% { transform: rotate(0deg); }
}

/* ══ COUPLAGE VISITEUR — curseur + progression de scroll ═══════
   Le driver [data-catch] (script.js) écrit sur .brand-catch, via UNE
   seule boucle rAF démarrée/arrêtée par l'IO (.is-onscreen) :
     --bc-px, --bc-py : pointeur normalisé [-1,1] autour du « a »
                        (desktop seulement, hover:hover, LERPÉ)
     --bc-near        : proximité 0..1 (portée ~300 px)
     --bc-at          : attention 0..1 — pique quand le pointeur BOUGE
                        près de la scène, retombe seule (~2.5 s) →
                        « il te regarde, puis revient à son travail »
     --bc-sp          : progression de scroll 0..1, SANS window.scrollY
                        (piège overflow-x) : (vh − rect.top)/(bas de
                        page − rect.top), lue dans la boucle rAF.
   Tout s'applique sur des groupes ENVELOPPES dédiés (.bc-look,
   .bc-shy, .bc-ride, .bc-stage-drift) qui COMPOSENT avec les
   keyframes — aucun transform animé n'est écrasé (leçon .bc-brush-idle).
   Sans JS : variables absentes → fallbacks neutres = scène statique
   identique à aujourd'hui. Reduced-motion : le driver ne démarre
   jamais (early return) + resets ci-dessous. */

/* Le funambule DESCEND réellement le câble avec le scroll. Chemin =
   la quadratique du câble, en sens descente ; offset-rotate 0 = il
   garde la verticale gravitaire, il ne bascule pas avec la pente.
   Bornes mesurées en longueur d'arc (jamais hors câble, jamais sur
   « Miti ») : 18.44 % (t=.18, appui (−52.5,42.1)) → 62.69 % (t=.62,
   appui (14.9,86.1), y max 92 < zone franche 103). Fallback 0.626 =
   pile la station statique historique (appui (−10.5,70.3), 46.16 %). */
.bc-ride {
    offset-path: path("M-80 23Q-3.5 77 73 121");
    offset-rotate: 0deg;
    offset-distance: calc(18.44% + var(--bc-sp, 0.626) * 44.25%);
}
@supports not (offset-path: path("M0 0")) {
    /* repli : pas de motion-path → station statique historique */
    .bc-ride { transform: translate(-10.5px, 70.3px); }
}

/* Il te REMARQUE : la tête suit le pointeur (toujours), le corps
   s'écarte un peu quand tu approches (proximité en facteur). */
.bc-look { transform-box: view-box; }
.bc-scene--fil .bc-look {
    transform-origin: 3.5px -36.5px;          /* la nuque (locale) */
    transform: rotate(calc(var(--bc-px, 0) * 9deg + var(--bc-py, 0) * 3.5deg));
}
.bc-scene--fil .bc-shy {
    transform: translate(calc(var(--bc-px, 0) * var(--bc-near, 0) * -4.5px),
                         calc(var(--bc-py, 0) * var(--bc-near, 0) * -2.5px));
}
/* Le peintre LÈVE la tête (base −13° = il s'interrompt, visière vers
   toi), te suit un peu, puis l'attention retombe → il revient à son
   travail. Base forte + direction faible : jugé en capture, −9° se
   noyait dans le terme directionnel. */
.bc-scene--trace .bc-look {
    transform-origin: 62px 34px;              /* la nuque */
    transform: rotate(calc((var(--bc-px, 0) * 5deg + var(--bc-py, 0) * 4deg - 13deg) * var(--bc-at, 0)));
}
/* Le pêcheur suit du regard, discret — pose et mécanique intactes. */
.bc-scene--peche .bc-look {
    transform-origin: 38px 20.5px;            /* la nuque */
    transform: rotate(calc((var(--bc-px, 0) * 6deg + var(--bc-py, 0) * 4deg) * var(--bc-at, 0)));
}

/* La nacelle dérive doucement le long du mot et finit de descendre
   avec toi. Amplitude x bridée à ±2.6 px : pendant la passe, le
   manchon reste dans le budget de synchro (1.3 modèle + 1.3 recul
   + 2.6 dérive = 5.2 < 5.5 = demi-largeur du halo). La lettre, elle,
   ne bouge JAMAIS. Fallback 0.626 → dérive nulle sans JS. */
.bc-scene--trace .bc-stage-drift {
    --amp: calc(var(--bc-sp, 0.626) - 0.626);
    /* Amplitude bridée TANT QUE la lettre se peint (le manchon doit rester
       dans le budget de synchro). Une fois la passe finie, --bc-pd monte à 1
       en douceur (lerpé par le driver) et la nacelle dérive franchement :
       plus rien à désynchroniser, le masque est grand ouvert. */
    transform: translate(calc(var(--amp) * -4px + var(--amp) * var(--bc-pd, 0) * -7px),
                         calc(var(--amp) * 8px + var(--amp) * var(--bc-pd, 0) * 14px));
}


/* ── Le pas · jambes ARTICULÉES (cuisse + tibia), pied COLLÉ au câble ──
   `offset-distance` déplace le marcheur mais ne peut pas déclencher un
   cycle de jambes : l'anim est en PAUSE et « scrubbée » par un
   animation-delay négatif piloté par --bc-sp (position dans le cycle =
   fonction du scroll). 7 pas sur la descente, comme avant — mais le rig
   passe de 1 à 2 segments par jambe (genou), et le cycle est GÉNÉRÉ par
   cinématique inverse (scripts hors dépôt, relevé ci-dessous) :
     · pendant son APPUI, le pied porteur est SUR la ligne du câble
       (pente locale ~0.63 passant par l'ancre (0,0) du motion-path) et
       recule dans le repère du corps à la vitesse EXACTE d'avance du
       corps (SPAN = course/14 = 5.75 u par demi-cycle) → pied planté
       dans le repère monde, zéro moonwalk, zéro flottement ;
     · l'autre jambe ENJAMBE avec une petite levée (2.4 u avant,
       1.9 u arrière) ; aux extrêmes du ciseau les DEUX pieds touchent ;
     · le pied déroule talon → à plat → pousse d'orteil : l'orteil est
       dessiné pré-tourné pour être à plat au MILIEU de l'appui ;
     · prédiction numérique (201 sp, pente réelle s(sp), interpolation
       linéaire des stops comme le navigateur) : pire écart pied
       porteur ↔ câble = 0.11 u viewBox ≈ 0.09 px écran.
   Pivots : hanche (0.5,−21.3) pour les cuisses, genoux DESSINÉS pour
   les tibias (le groupe tibia vit DANS la cuisse : chaîne articulée,
   même principe que coude/épaule du pêcheur). */
.bc-leg-f, .bc-leg-b { transform-box: view-box; transform-origin: 0.5px -21.3px; }
.bc-shin-f { transform-box: view-box; transform-origin: 6.39px -10.16px; }
.bc-shin-b { transform-box: view-box; transform-origin: 6.55px -10.25px; }
.brand-catch .bc-leg-f,
.brand-catch .bc-leg-b,
.brand-catch .bc-shin-f,
.brand-catch .bc-shin-b {
    animation-duration: 1s;
    animation-timing-function: linear;
    animation-iteration-count: infinite;
    animation-play-state: paused;                       /* scrubbée, jamais jouée */
    animation-delay: calc(var(--bc-sp, 0.626) * -7s);   /* 7 pas sur la course */
}
.brand-catch .bc-leg-f  { animation-name: bc-thigh-f; }
.brand-catch .bc-shin-f { animation-name: bc-shin-f; }
.brand-catch .bc-leg-b  { animation-name: bc-thigh-b; }
.brand-catch .bc-shin-b { animation-name: bc-shin-b; }
/* Cycles relevés (IK) — φ 0→50 % : appui de la jambe AVANT (pied sur le
   câble), volée de l'ARRIÈRE ; φ 50→100 % : l'inverse. Les stops portent
   tout l'easing : timing linear obligatoire. */
@keyframes bc-thigh-f {
    0%     { transform: rotate(0deg); }
    6.25%  { transform: rotate(-2.2deg); }
    12.5%  { transform: rotate(-3.8deg); }
    18.75% { transform: rotate(-5deg); }
    25%    { transform: rotate(-5.9deg); }
    31.25% { transform: rotate(-6.5deg); }
    37.5%  { transform: rotate(-6.8deg); }
    43.75% { transform: rotate(-7deg); }
    50%    { transform: rotate(-7deg); }
    56.25% { transform: rotate(-11.4deg); }
    62.5%  { transform: rotate(-15.1deg); }
    68.75% { transform: rotate(-17.5deg); }
    75%    { transform: rotate(-17.9deg); }
    81.25% { transform: rotate(-16.1deg); }
    87.5%  { transform: rotate(-12.2deg); }
    93.75% { transform: rotate(-6.6deg); }
    100%   { transform: rotate(0deg); }
}
@keyframes bc-shin-f {
    0%     { transform: rotate(0deg); }
    6.25%  { transform: rotate(6.6deg); }
    12.5%  { transform: rotate(12.2deg); }
    18.75% { transform: rotate(17deg); }
    25%    { transform: rotate(21.4deg); }
    31.25% { transform: rotate(25.4deg); }
    37.5%  { transform: rotate(29deg); }
    43.75% { transform: rotate(32.3deg); }
    50%    { transform: rotate(35.3deg); }
    56.25% { transform: rotate(40.1deg); }
    62.5%  { transform: rotate(42deg); }
    68.75% { transform: rotate(40.9deg); }
    75%    { transform: rotate(36.8deg); }
    81.25% { transform: rotate(30.1deg); }
    87.5%  { transform: rotate(21.2deg); }
    93.75% { transform: rotate(10.8deg); }
    100%   { transform: rotate(0deg); }
}
@keyframes bc-thigh-b {
    0%     { transform: rotate(0deg); }
    6.25%  { transform: rotate(-3.5deg); }
    12.5%  { transform: rotate(-7.3deg); }
    18.75% { transform: rotate(-10.6deg); }
    25%    { transform: rotate(-12.7deg); }
    31.25% { transform: rotate(-13.2deg); }
    37.5%  { transform: rotate(-12.1deg); }
    43.75% { transform: rotate(-9.6deg); }
    50%    { transform: rotate(-6.2deg); }
    56.25% { transform: rotate(-6deg); }
    62.5%  { transform: rotate(-5.6deg); }
    68.75% { transform: rotate(-5deg); }
    75%    { transform: rotate(-4.3deg); }
    81.25% { transform: rotate(-3.5deg); }
    87.5%  { transform: rotate(-2.5deg); }
    93.75% { transform: rotate(-1.3deg); }
    100%   { transform: rotate(0deg); }
}
@keyframes bc-shin-b {
    0%     { transform: rotate(0deg); }
    6.25%  { transform: rotate(3.9deg); }
    12.5%  { transform: rotate(6.1deg); }
    18.75% { transform: rotate(6.3deg); }
    25%    { transform: rotate(4.3deg); }
    31.25% { transform: rotate(0.3deg); }
    37.5%  { transform: rotate(-5.1deg); }
    43.75% { transform: rotate(-11.1deg); }
    50%    { transform: rotate(-16.7deg); }
    56.25% { transform: rotate(-13.9deg); }
    62.5%  { transform: rotate(-11.3deg); }
    68.75% { transform: rotate(-9deg); }
    75%    { transform: rotate(-6.8deg); }
    81.25% { transform: rotate(-4.8deg); }
    87.5%  { transform: rotate(-3deg); }
    93.75% { transform: rotate(-1.4deg); }
    100%   { transform: rotate(0deg); }
}

/* ── La perche PLOIE sous son poids ───────────────────────────────
   Redessinée avec une vraie flèche (les bouts tombent de ~3 u, comme
   une perche de funambule réelle) SANS toucher aux mains : la courbe
   passe à 0.51 / 0.39 u des deux mains (contrat ≈ 0.5 conservé —
   bras + perche restent UN solide, le flex scale depuis la prise et
   les mains sont sur son axe → elles ne bougent pas). Le flex bat à
   la période du contrepoids (2.8 s) : la perche respire quand il
   corrige fort. Reduced-motion : figée (bloc dédié). */
.bc-perche { transform-box: view-box; transform-origin: 2.5px -26.3px; }
.brand-catch.is-onscreen .bc-perche { animation: bc-perche-flex 2.8s ease-in-out infinite; }
/* une fois armée, la règle d'encre (5 classes) écraserait le flex : les
   deux noms dans UNE déclaration (leçon .bc-knot-tail) — bc-ink garde
   son identité (même nom/durée), il ne se rejoue pas. */
.brand-catch.is-onscreen.is-armed.is-caught .bc-scene--fil .bc-perche {
    animation: bc-ink 0.3s cubic-bezier(0.55, 0.08, 0.45, 0.96) 1.14s both,
               bc-perche-flex 2.8s ease-in-out infinite;
}
@keyframes bc-perche-flex {
    0%   { transform: scaleY(1); }
    42%  { transform: scaleY(1.3); }     /* flèche max quand le contrepoids tire */
    64%  { transform: scaleY(1.08); }
    80%  { transform: scaleY(0.94); }    /* léger retour élastique */
    100% { transform: scaleY(1); }
}

/* ── fil · le petit PUBLIC sous le câble ──────────────────────────
   Trois spectateurs posés sur une ligne d'horizon implicite (pieds à
   y=92 écran, au-dessus de la bande du mot y 103→168 — jamais dessus).
   AGRANDIS ×1.45 depuis la ligne de sol : un transform CSS sur
   .bc-crowd REMPLACE l'attribut translate(0 -7) (presentation
   attribute), donc le translate est repris ici ; l'origine est posée
   aux PIEDS → ils grandissent vers le HAUT, dans le vide sous le
   câble, les pieds ne bougent pas. Traits ramenés à 1.5 (×1.45 ≈ 2.2
   rendu) : lisibles, mais toujours plus fins que le funambule (2.5)
   → la profondeur reste. Ils VIVENT — que des gestes RÉELS :
     · les têtes SUIVENT le funambule le long de sa descente (rotation
       calc sur --bc-sp, comme .bc-look — aucune boucle JS de plus) ;
     · la 3ᵉ le POINTE du doigt : le groupe balaie la descente
       (--bc-sp) et le path enfant JABBE — l'emphase de qui montre
       quelque chose (« regarde ! ») sans toucher au couplage scroll ;
     · 1 et 3 SAUTENT d'excitation : poussée franche, flottement au
       sommet, retombée — un saut ne fait que MONTER, les pieds ne
       passent jamais sous la ligne de sol ;
     · la 2ᵉ APPLAUDIT : les deux avant-bras se RAPPROCHENT vraiment
       (±26° → mains presque jointes devant le visage), deux claps
       par cycle — le rythme d'un vrai applaudissement.
   Périodes 1.15 / 1.05 / 1.35 / 1.65 s, premières entre les boucles
   existantes (2.8, 1.7). Tout est gaté .is-onscreen (pause hors
   écran). Sans JS : posés, statiques, visibles (fallback --bc-sp). */
.bc-crowd {
    transform-box: view-box;
    transform-origin: -55.4px 99px;              /* la ligne de sol */
    transform: translate(0, -7px) scale(1.45);   /* reprend l'attribut + grandit */
    stroke-width: 1.5;
}
.bc-fan { transform-box: view-box; }
.bc-crowd .bc-gaze { transform-box: view-box; }
.bc-fan--1 .bc-gaze { transform-origin: -63.8px 85.5px; }   /* nuques */
.bc-fan--2 .bc-gaze { transform-origin: -54.9px 86px; }
.bc-fan--3 .bc-gaze { transform-origin: -45.7px 86.2px; }
.bc-clap-l, .bc-clap-r { transform-box: view-box; transform-origin: -54.9px 87.2px; }
.bc-point { transform-box: view-box; transform-origin: -45.7px 87.4px; }
.bc-point .bc-fan-line { transform-box: view-box; transform-origin: -45.7px 87.4px; }  /* l'épaule : le jab */
.bc-scene--fil .bc-fan--1 .bc-gaze { transform: rotate(calc((var(--bc-sp, 0.626) - 0.45) * 24deg)); }
.bc-scene--fil .bc-fan--2 .bc-gaze { transform: rotate(calc((var(--bc-sp, 0.626) - 0.45) * 28deg)); }
.bc-scene--fil .bc-fan--3 .bc-gaze { transform: rotate(calc((var(--bc-sp, 0.626) - 0.5) * 20deg)); }
.bc-scene--fil .bc-point           { transform: rotate(calc((var(--bc-sp, 0.626) - 0.5) * 55deg)); }
.brand-catch.is-onscreen .bc-fan--1 { animation: bc-fan-hop 1.15s ease-in-out infinite; }
.brand-catch.is-onscreen .bc-fan--3 { animation: bc-fan-hop 1.65s ease-in-out 0.45s infinite; }
.brand-catch.is-onscreen .bc-clap-l { animation: bc-clap-l 1.05s ease-in-out infinite; }
.brand-catch.is-onscreen .bc-clap-r { animation: bc-clap-r 1.05s ease-in-out infinite; }
.brand-catch.is-onscreen .bc-point .bc-fan-line { animation: bc-point-jab 1.35s ease-in-out infinite; }
@keyframes bc-fan-hop {              /* un vrai saut : il ne fait que monter —
                                        décollage, flottement, retombée (×1.45
                                        par le scale du groupe ≈ 3.5 u écran) */
    0%, 52%, 100% { transform: translateY(0); }
    14% { transform: translateY(-2.2px); }
    28% { transform: translateY(-2.4px); }     /* le sommet, une apesanteur d'instant */
}
@keyframes bc-clap-l {               /* les mains se rejoignent à ~1.9 u (contact
                                        visuel), 2 claps par cycle de 1.05 s */
    0%, 100% { transform: rotate(3deg); }
    22% { transform: rotate(26deg); }
    48% { transform: rotate(6deg); }
    72% { transform: rotate(26deg); }
}
@keyframes bc-clap-r {
    0%, 100% { transform: rotate(-3deg); }
    22% { transform: rotate(-26deg); }
    48% { transform: rotate(-6deg); }
    72% { transform: rotate(-26deg); }
}
@keyframes bc-point-jab {            /* le doigt insiste : le bras jabbe autour
                                        de sa visée (portée par le groupe parent
                                        via --bc-sp — couplage scroll intact) */
    0%, 100% { transform: rotate(0deg); }
    30% { transform: rotate(-9deg); }
    52% { transform: rotate(3.5deg); }
}

/* ── peche · le POISSON saute (détail-récompense rare) ────────────
   VRAIE BALISTIQUE. Avant : la courbe n'avait que 7 points et le
   `linear` de base régnait sur les intervalles sans easing déclaré →
   des PALIERS à vitesse constante (anti-gravité) coupés de ruptures
   nettes : le poisson montait, CALAIT, glissait, calait encore. Et le
   saut durait 1,77 s pour 21 px — du ralenti, lu comme de la latence.
   Désormais le vol est un projectile échantillonné dense (17 points,
   ~54 ms) : horizontale à vitesse CONSTANTE, verticale à accélération
   CONSTANTE (l'écart de vitesse entre segments vaut exactement +0,60,
   c'est la gravité), et le corps suit la TANGENTE de sa trajectoire —
   nez en l'air à la sortie, nez en bas à la rentrée. Saut ramené à
   0,86 s (vif, comme un vrai poisson) et cycle 7.9 s pour ne pas
   rallonger le temps mort. Aucun easing par keyframe : la physique est
   DANS les points, un easing la déformerait.
   Transitoire : caché hors du saut (opacity 0 en attribut) — sans JS et
   en reduced-motion il reste sous l'eau, la scène est complète sans lui. */
.bc-fish   { transform-box: view-box; transform-origin: 92px 41px; }
.bc-splash { transform-box: view-box; transform-origin: 113.5px 40px; }
.brand-catch.is-onscreen .bc-scene--peche .bc-fish   { animation: bc-fish-leap 7.9s linear 2.2s infinite; }
.brand-catch.is-onscreen .bc-scene--peche .bc-splash { animation: bc-fish-splash 7.9s linear 2.2s infinite; }
@keyframes bc-fish-leap {
    0%, 2.00% { opacity: 0; transform: translate(0px, 3px) rotate(-55deg); }
    2.68% { opacity: 1; transform: translate(1.34px, -1.42px) rotate(-53.3deg); }   /* il perce la surface */
    3.36% { opacity: 1; transform: translate(2.69px, -5.23px) rotate(-51.0deg); }
    4.04% { opacity: 1; transform: translate(4.03px, -8.44px) rotate(-48.0deg); }
    4.72% { opacity: 1; transform: translate(5.38px, -11.06px) rotate(-43.8deg); }
    5.40% { opacity: 1; transform: translate(6.72px, -13.07px) rotate(-37.5deg); }
    6.08% { opacity: 1; transform: translate(8.06px, -14.48px) rotate(-27.8deg); }
    6.76% { opacity: 1; transform: translate(9.41px, -15.29px) rotate(-13.0deg); }
    7.44% { opacity: 1; transform: translate(10.75px, -15.49px) rotate(6.4deg); }   /* apex : le corps passe à l'horizontale */
    8.12% { opacity: 1; transform: translate(12.09px, -15.10px) rotate(24.8deg); }
    8.80% { opacity: 1; transform: translate(13.44px, -14.10px) rotate(37.9deg); }
    9.48% { opacity: 1; transform: translate(14.78px, -12.51px) rotate(46.3deg); }
    10.16% { opacity: 1; transform: translate(16.12px, -10.31px) rotate(51.8deg); }
    10.84% { opacity: 1; transform: translate(17.47px, -7.51px) rotate(55.6deg); }
    11.53% { opacity: 1; transform: translate(18.81px, -4.11px) rotate(58.4deg); }
    12.21% { opacity: 1; transform: translate(20.16px, -0.10px) rotate(60.4deg); }
    12.89% { opacity: 0; transform: translate(21.50px, 4.50px) rotate(62.0deg); }   /* rentrée, nez en bas */
    100% { opacity: 0; transform: translate(0px, 3px) rotate(-55deg); }
}
@keyframes bc-fish-splash {   /* la gerbe naît À L'IMPACT, brève */
    0%, 12.29% { opacity: 0; transform: scale(0.3); }
    13.19% { opacity: 0.9; transform: scale(0.8); }
    14.09% { opacity: 0.8; transform: scale(1.05); }
    17.19% { opacity: 0; transform: scale(1.2); }
    100% { opacity: 0; transform: scale(0.3); }
}

/* ── trace · le BADAUD vient admirer la lettre finie ──────────────
   Debout sur la ligne de base, à droite du « a » — AGRANDI ×1.42
   depuis ses chevilles (131,168) : il grandit vers le haut et reste
   entièrement à droite de x≈127.9 (la lettre s'arrête à 116.5 : il
   ne la touche jamais, même penché — pire point mesuré 125.4). Armé :
   absent ; il APPARAÎT quand la lettre est finie (.is-painted, posé
   par le driver à la fin de la passe) — fondu + petite montée. Puis
   DEUX vies, toutes deux RÉELLES :
     · il se PENCHE depuis les chevilles vers la lettre — le geste de
       qui s'approche d'un tableau — tient son regard, se redresse
       d'un petit recul d'appréciation (7.4 s) ;
     · la tête ALTERNE vraiment entre l'œuvre et l'artiste : elle se
       LÈVE vers le peintre (−7.5°), redescend sur la lettre (+6°)
       qu'elle approuve de petits hochements (5.3 s, premières entre
       elles et avec 4.7 / 3.9 / 3.1).
   Le scale vit AUSSI dans les keyframes du penché : une animation
   écrase la propriété transform — sans lui, il rétrécirait en
   s'animant. Sans JS et en reduced-motion : visible, statique,
   GRAND (la lettre est déjà peinte). */
.bc-badaud {
    transform-box: view-box;
    transform-origin: 131px 168px;               /* les chevilles */
    transform: scale(1.42);
    stroke-width: 1.55;                          /* ×1.42 ≈ 2.2 rendu : dessous le peintre (2.5) */
    transition: opacity 0.7s ease, transform 0.7s ease;
}
.brand-catch.is-armed .bc-badaud { opacity: 0; transform: translateY(2.5px) scale(1.42); }
.brand-catch.is-painted .bc-badaud { opacity: 1; transform: scale(1.42); }
.brand-catch.is-onscreen.is-painted .bc-badaud { animation: bc-badaud-lean 7.4s ease-in-out 1.2s infinite; }
.bc-nod { transform-box: view-box; transform-origin: 131.9px 153.8px; }
.brand-catch.is-onscreen.is-painted .bc-nod { animation: bc-badaud-nod 5.3s ease-in-out infinite; }
@keyframes bc-badaud-lean {          /* se pencher pour VOIR : dérive lente vers
                                        la lettre, le regard tient, puis il se
                                        redresse (délai 1.2 s : l'entrée en fondu
                                        + montée finit d'abord — keyframe 0 % =
                                        état de repos, zéro saut) */
    0%, 100% { transform: scale(1.42) rotate(0deg); }
    18% { transform: scale(1.42) rotate(-5.5deg); }
    40% { transform: scale(1.42) rotate(-4.3deg); }
    54% { transform: scale(1.42) rotate(-5.2deg); }
    76% { transform: scale(1.42) rotate(1deg); }
}
@keyframes bc-badaud-nod {
    0%, 100% { transform: rotate(0deg); }
    14% { transform: rotate(-7.5deg); }          /* il lève les yeux vers le peintre */
    32% { transform: rotate(-5.5deg); }
    50% { transform: rotate(6deg); }             /* retour sur la lettre… */
    64% { transform: rotate(3.5deg); }           /* …qu'il approuve */
    78% { transform: rotate(6.2deg); }
}

/* ── Fallback total : reduced-motion → scènes figées, « Mitia » lisible.
      Le croquis n'est jamais tracé : le dessin est posé d'un coup,
      complet (dash neutralisé, pleins à l'échelle 1, lavis opaque).
      Le driver ne s'arme de toute façon jamais (early return). ── */
@media (prefers-reduced-motion: reduce) {
    .brand-catch .bc-scene,
    .brand-catch.is-armed .bc-scene { opacity: 1 !important; transform: translateX(-52%) !important; animation: none !important; }
    .brand-catch .bc-scene path,
    .brand-catch .bc-scene circle { stroke-dasharray: none !important; stroke-dashoffset: 0 !important; }
    .brand-catch .bc-moon-fill { opacity: 1 !important; }
    .brand-catch .bc-boat,
    .brand-catch .bc-head, .brand-catch .bc-cap-dome, .brand-catch .bc-reel,
    .brand-catch .bc-hook-eye, .brand-catch .bc-knot, .brand-catch .bc-knot-halo,
    .brand-catch .bc-fan-head {
        animation: none !important; transform: none !important;
    }
    .brand-catch .bc-cs-sort, .brand-catch .bc-cs-armU, .brand-catch .bc-cs-armF,
    .brand-catch .bc-cs-armL, .brand-catch .bc-cs-armLF,
    .brand-catch .bc-cs-p1g, .brand-catch .bc-cs-p2g,
    .brand-catch .bc-cs-lect, .brand-catch .bc-cs-typo,
    .brand-catch .bc-wire-rig, .brand-catch .bc-wire-land, .brand-catch .bc-walker,
    .brand-catch .bc-torso-rig, .brand-catch .bc-pole, .brand-catch .bc-knot-tail,
    .brand-catch .bc-stage, .brand-catch .bc-stage-react, .brand-catch .bc-brush,
    .brand-catch .bc-brush-idle, .brand-catch .bc-fore, .brand-catch .bc-noggin,
    .brand-catch .bc-drip,
    .brand-catch .bc-look, .brand-catch .bc-shy, .brand-catch .bc-stage-drift,
    .brand-catch .bc-leg-f, .brand-catch .bc-leg-b,
    .brand-catch .bc-shin-f, .brand-catch .bc-shin-b, .brand-catch .bc-perche,
    .brand-catch .bc-fish, .brand-catch .bc-splash,
    .brand-catch .bc-fan--1, .brand-catch .bc-fan--2, .brand-catch .bc-fan--3,
    .brand-catch .bc-clap-l, .brand-catch .bc-clap-r,
    .brand-catch .bc-gaze, .brand-catch .bc-point,
    .brand-catch .bc-point .bc-fan-line,
    .brand-catch .bc-nod, .brand-catch .bc-badaud,
    .brand-catch .bc-wipe { animation: none !important; transform: none !important; }

    /* L'échelle du typographe n'est PAS du mouvement : la remise à zéro
       ci-dessus lui rendrait sa demi-taille pendant que sa casse, elle,
       resterait grande. On la lui redonne — figée, sans penché. */
    .brand-catch .bc-cs-typo { transform: scale(1.55) !important; }

    /* Le mot et le caractère : toujours LISIBLES. Les gates de la
       composition posent opacity 0 hors animation ; si l'OS bascule en
       reduced-motion APRÈS l'armement, seul cet !important les rallume. */
    .brand-catch .bc-static .bc-ch,
    .brand-catch .bc-a-glyph { opacity: 1 !important; }
    /* le badaud et le public : SPECTATEURS STATIQUES, visibles et
       GRANDS (la lettre est peinte d'office en reduced-motion — le
       scale de lisibilité n'est pas un mouvement, il reste) ; le
       poisson reste sous l'eau (transitoire, opacity 0 d'attribut).
       Le public garde aussi sa taille : .bc-crowd n'est pas animé,
       son transform statique (translate + scale) n'est pas resetté. */
    .brand-catch .bc-badaud { opacity: 1 !important; transform: scale(1.42) !important; }
    /* le manchon garde son biais de repos, sinon il redevient un trait vertical
       collé au « a » (lecture « apostrophe ») */
    .brand-catch .bc-roller-head { animation: none !important; transform: rotate(-30deg) !important; }
}

/* ── casse · le typographe ──────────────────────────────────────
   Le « a » est un CARACTÈRE. Le typographe l'a pris dans la casse
   (posée à ses pieds, inclinée pour piquer — le grand cassetin le
   plus proche de lui est VIDE : c'est de là qu'il vient) et le POSE
   au bout de la ligne, à la pince. Le composteur n'est pas dessiné :
   la ligne de base du mot EST le composteur — sa présence passe par
   le geste (on compose en bout de ligne, et la paume qui a PORTÉ le
   caractère reste bloquer la ligne). Le couvre-chef est le
   bonnet de papier plié des typographes.
   CHAÎNE DU GESTE — pince et lettre SOUDÉES par le calcul :
     épaule (.bc-cs-armU, pivot 127.4,128.6) ∘ coude (.bc-cs-armF,
     pivot local 130.2,136.94) : angles résolus par cinématique
     inverse à CHAQUE stop de la descente du caractère — dérive
     pointes↔prise mesurée ≤ 0.08 unité viewBox sur toute la pose.
     Le tilt du caractère pivote sur la PRISE (115.5,124) : le point
     tenu est le pivot, comme dans une vraie main.
     Bras gauche PAREIL : épaule (.bc-cs-armL, pivot 128.4,129.6) ∘
     coude (.bc-cs-armLF, pivot 124.4,139.8) — la paume PORTE le
     flanc du caractère et le SUIT, dérive ≤ 0.28 u mesurée ; à
     100 % le bras retombe sur son dessin (0°/0°), paume sur la
     hampe. On ne porte pas d'une pince un caractère grand comme
     soi : la pince guide, la paume porte.
   SÉQUENCE : croquis (casse → corps → bonnet → bras → pince), le
   caractère apparaît dans la pince (1.42 s) ; LA COMPOSITION —
   « M », « i », « t », « i » se posent UN À UN (1.7 → 2.9 s), dans
   le langage du « a » : ils arrivent de haut-droite, touchent la
   ligne de pied, se serrent contre leur voisin. Le nom se construit
   sous les yeux : c'est la lecture que la scène doit donner, on ne
   peut pas avoir l'air d'écrire un mot déjà écrit. Le compositeur
   marque chaque pose d'un coup d'œil (bc-cs-count) : son attention
   est la cause visible de la ligne qui grandit. Puis la POSE
   (3.05 s) : il PORTE À DEUX MAINS — paume gauche sur le flanc
   (sillon bc-cs-alh), pince au coin — descente, l'aplomb revient,
   TOUCHER (72 %), glissé contre le « i » ; la paume qui a porté
   finit sur la hampe et y RESTE — c'est son métier ; la DÉPRISE
   reste AU TRAVAIL : les
   becs s'ouvrent et le bras REDESCEND au flanc. Les trois autres
   scènes gardent un lien physique permanent avec le « a » (hameçon
   planté, câble noué, rouleau à l'encre) ; ici c'est la PAUME GAUCHE
   qui tient ce lien — elle a porté le caractère et reste sur la
   hampe. La main droite n'a donc plus à flotter pour que l'image
   fixe garde le geste, et un bras figé en l'air ne tenait pas.
   La VIE ensuite, humaine seulement : il RELIT sa ligne (plonge sur
   la lettre posée, remonte vers le M — un typographe vérifie à
   rebours), se penche à peine sur la paume qui tient la ligne ;
   l'attention du visiteur lui fait lever le nez (--bc-at). AUCUNE
   boucle d'outil. Une fois posé, « Miti » ne bouge PLUS JAMAIS :
   le plomb serré est rigide — l'immobilité du mot APRÈS sa
   composition est la signature du métier (gate bc-letter-wave).
   Charte : transform-only, pause hors écran, reduced-motion figé. ── */

/* Affichage — ou fusionner cette ligne dans la règle des scènes :
   .brand-catch[data-active="casse"] .bc-scene--casse { display: block; } */
.brand-catch[data-active="casse"] .bc-scene--casse { display: block; }

/* Pivots (coordonnées viewBox, comme le reste de la troupe) */
.bc-cs-sort { transform-box: view-box; transform-origin: 107.3px 100.02px; } /* la prise de la pince, à l'échelle */
.bc-cs-armU { transform-box: view-box; transform-origin: 127.4px 128.6px; } /* épaule droite */
.bc-cs-armF { transform-box: view-box; transform-origin: 130.2px 136.94px; }/* coude */
.bc-cs-armL { transform-box: view-box; transform-origin: 128.4px 129.6px; } /* épaule gauche */
.bc-cs-armLF { transform-box: view-box; transform-origin: 124.4px 139.8px; } /* coude gauche */
.bc-cs-p1g, .bc-cs-p2g { transform-box: view-box; transform-origin: 123.42px 135.67px; } /* les becs pivotent dans la main */
.bc-cs-lect { transform-box: view-box; transform-origin: 127.4px 127.8px; } /* la nuque */
.bc-cs-typo { transform-box: view-box; transform-origin: 130.4px 167.6px; } /* les chevilles (le penché) */

/* Sillon couleur-du-fond : le bec avant et la PAUME gauche mordent
   dans l'encre (cf. bc-hook-halo) */
.bc-cs-p2h, .bc-cs-alh { stroke: var(--black); }
html[data-theme="light"] .bc-cs-p2h,
html[data-theme="light"] .bc-cs-alh { stroke: var(--dark-2); }

/* État armé : traits retirés (l'encre viendra) et POSE DE DÉPART
   tenue par transforms : caractère levé À DEUX MAINS — becs fermés
   sur le coin de la hampe, paume gauche sur le flanc. (Les classes
   partagées — bc-legs, bc-torso, bc-head, bc-cap-dome, bc-cap-halo,
   bc-arms — sont déjà armées par les règles communes du module.) */
.brand-catch.is-armed :is(.bc-cs-tray, .bc-cs-face, .bc-cs-grid, .bc-cs-sorts,
    .bc-cs-p1, .bc-cs-p2, .bc-cs-p2h, .bc-cs-alh) {
    stroke-dasharray: var(--len, 400px);
    stroke-dashoffset: var(--len, 400px);
}
.brand-catch.is-armed .bc-cs-sort { transform: translate(4.5px, -16px) rotate(6deg); }
.brand-catch.is-armed .bc-cs-armU { transform: rotate(131.07deg); }
.brand-catch.is-armed .bc-cs-armF { transform: rotate(-57.04deg); }
.brand-catch.is-armed .bc-cs-armL  { transform: rotate(22.87deg); }
.brand-catch.is-armed .bc-cs-armLF { transform: rotate(55.25deg); }
.brand-catch.is-armed .bc-cs-p1g  { transform: rotate(-23.19deg); }
.brand-catch.is-armed .bc-cs-p2g  { transform: rotate(-9.69deg); }

/* ── casse · LA COMPOSITION : le nom n'est pas encore écrit ──────
   Tant que la casse est armée, « Miti » n'existe pas (opacity, pas
   de layout shift) et le caractère n'est pas encore dans la pince.
   Au tracé : la pince se referme sur le « a » pris dans la casse
   (1.42 s, juste quand elle finit de se dessiner), puis les quatre
   lettres se COMPOSENT une à une — même langage que le « a » : elles
   arrivent de haut-droite (0.05em ≈ 4.5 u, 0.16em ≈ 16 u : la course
   du « a » convertie à l'échelle du mot, 1 u = 0.0107 em), touchent
   la ligne de pied, se serrent contre leur voisine. Tilt 3° (moitié
   du « a » : le pivot est à la BASE — un plomb se dresse sur son
   pied, il ne pend pas à une pince). Cadence 0.32 s : la routine du
   métier ; le « a » final, lui, a droit au geste détaillé. */
.brand-catch.is-armed[data-active="casse"] .bc-static .bc-ch { opacity: 0; }
.brand-catch.is-armed[data-active="casse"] .bc-cs-sort .bc-a-glyph { opacity: 0; }
.brand-catch.is-armed.is-caught[data-active="casse"] .bc-cs-sort .bc-a-glyph {
    animation: bc-cs-glyph-in 0.16s ease-out 1.42s both;
}
.brand-catch.is-armed.is-caught[data-active="casse"] .bc-static .bc-ch {
    animation: bc-cs-compose 0.26s cubic-bezier(0.4, 0, 0.2, 1) both;
}
.brand-catch.is-armed.is-caught[data-active="casse"] .bc-static .bc-ch:nth-child(1) { animation-delay: 1.7s; }
.brand-catch.is-armed.is-caught[data-active="casse"] .bc-static .bc-ch:nth-child(2) { animation-delay: 2.02s; }
.brand-catch.is-armed.is-caught[data-active="casse"] .bc-static .bc-ch:nth-child(3) { animation-delay: 2.34s; }
.brand-catch.is-armed.is-caught[data-active="casse"] .bc-static .bc-ch:nth-child(4) { animation-delay: 2.66s; }
@keyframes bc-cs-glyph-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes bc-cs-compose {
    0%   { opacity: 0; transform: translate(0.05em, -0.16em) rotate(3deg); }
    10%  { opacity: 1; transform: translate(0.045em, -0.13em) rotate(2.7deg); }
    46%  { transform: translate(0.025em, -0.035em) rotate(1.2deg); }
    64%  { transform: translate(0.018em, 0) rotate(0deg); }  /* il touche la ligne de pied */
    100% { opacity: 1; transform: translate(0, 0) rotate(0deg); } /* serré : composé (opacity
                                          explicite : la valeur de base est le 0 du gate) */
}

/* ── casse · la plume : la casse (tiroir, chant avant, cassetins
      inégaux, caractères en vrac), puis le corps de bas en haut, le
      bonnet de papier, les deux bras, la pince. ── */
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-cs-tray  { animation: bc-ink 0.34s cubic-bezier(0.55, 0.08, 0.45, 0.96) 0s both; }
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-cs-face  { animation: bc-ink 0.16s ease-out 0.3s both; }
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-cs-grid  { animation: bc-ink 0.3s cubic-bezier(0.55, 0.08, 0.45, 0.96) 0.24s both; }
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-cs-sorts { animation: bc-ink 0.14s ease-out 0.52s both; }
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-legs     { animation: bc-ink 0.2s cubic-bezier(0.55, 0.08, 0.45, 0.96) 0.6s both; }
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-torso    { animation: bc-ink 0.13s ease-out 0.78s both; }
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-head     { animation: bc-ink-pop 0.2s cubic-bezier(0.34, 1.4, 0.5, 1) 0.9s both; }
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-cap-dome { animation: bc-ink-pop 0.16s cubic-bezier(0.34, 1.4, 0.5, 1) 1.02s both; }
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-cap-halo { animation: bc-ink 0.1s ease-out 1.06s both; }
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-cap      { animation: bc-ink 0.1s ease-out 1.15s both; }
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-cs-al    { animation: bc-ink 0.16s ease-out 1.08s both; }
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-cs-alf,
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-cs-alh   { animation: bc-ink 0.12s ease-out 1.16s both; }
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-cs-au    { animation: bc-ink 0.12s ease-out 1.14s both; }
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-cs-af    { animation: bc-ink 0.1s ease-out 1.24s both; }
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-cs-p1,
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-cs-p2h,
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-cs-p2    { animation: bc-ink 0.12s ease-out 1.32s both; }


/* ── LA POSE (3.05 s → 4.0 s, après la composition) : le caractère
      descend, retrouve son aplomb (le tilt pivote sur la prise),
      TOUCHE (72 %), puis GLISSE contre la ligne (84→100 %). Épaule
      et coude suivent — mêmes stops, même durée, angles IK. ── */
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-cs-sort { animation: bc-cs-sort-pose 0.95s cubic-bezier(0.4, 0, 0.2, 1) 3.05s both; }
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-cs-armU { animation: bc-cs-armU-pose 0.95s cubic-bezier(0.4, 0, 0.2, 1) 3.05s both,
                                                              bc-cs-armU-retour 0.6s cubic-bezier(0.33, 0, 0.2, 1) 4.45s forwards; }
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-cs-armF { animation: bc-cs-armF-pose 0.95s cubic-bezier(0.4, 0, 0.2, 1) 3.05s both,
                                                              bc-cs-armF-retour 0.6s cubic-bezier(0.33, 0, 0.2, 1) 4.45s forwards; }
/* le PORT : la paume gauche porte le flanc du caractère et le SUIT —
   IK épaule ∘ coude aux 7 stops de sort-pose (mêmes durée, courbe,
   délai : un seul geste), glissement paume↔lettre ≤ 0.43 px mesuré
   sur toute la descente ; à 100 % le bras retombe exactement sur
   son dessin (0°/0°) : la paume est sur la hampe et y RESTE (fill
   both) — le blocage n'est plus un geste séparé, c'est la fin du
   port */
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-cs-armL  { animation: bc-cs-armL-carry 0.95s cubic-bezier(0.4, 0, 0.2, 1) 3.05s both; }
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-cs-armLF { animation: bc-cs-armLF-carry 0.95s cubic-bezier(0.4, 0, 0.2, 1) 3.05s both; }
/* la DÉPRISE reste au travail : les becs s'ouvrent, la main se
   relève à peine et RESTE au-dessus de la lettre posée */
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-cs-p1g  { animation: bc-cs-p1-pose 0.95s cubic-bezier(0.4, 0, 0.2, 1) 3.05s both,
                                                              bc-cs-p1-open 0.25s ease-out 4.23s forwards; }
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-cs-p2g  { animation: bc-cs-p2-pose 0.95s cubic-bezier(0.4, 0, 0.2, 1) 3.05s both,
                                                              bc-cs-p2-open 0.25s ease-out 4.23s forwards; }

@keyframes bc-cs-sort-pose {
    0%   { transform: translate(4.5px, -16px) rotate(6deg); }   /* tenue en l'air, de travers */
    26%  { transform: translate(4.5px, -11.2px) rotate(4.5deg); }
    48%  { transform: translate(4.5px, -5.8px) rotate(2.8deg); }
    62%  { transform: translate(4.5px, -2.2px) rotate(1.3deg); }
    72%  { transform: translate(4.5px, 0) rotate(0deg); }       /* elle touche la ligne de base */
    84%  { transform: translate(2.6px, 0) rotate(0deg); }
    100% { transform: translate(0, 0) rotate(0deg); }           /* serrée contre le « i » : composée */
}
@keyframes bc-cs-armU-pose {                 /* épaule : angles IK, dérive ≤ 0.08 u */
    0%   { transform: rotate(131.07deg); }
    26%  { transform: rotate(110.04deg); }
    48%  { transform: rotate(87.1deg); }
    62%  { transform: rotate(70.96deg); }
    72%  { transform: rotate(60.69deg); }
    84%  { transform: rotate(63.61deg); }
    100% { transform: rotate(68.37deg); }
}
@keyframes bc-cs-armF-pose {                 /* coude : angles IK */
    0%   { transform: rotate(-57.04deg); }
    26%  { transform: rotate(-32.72deg); }
    48%  { transform: rotate(-12.77deg); }
    62%  { transform: rotate(-2.69deg); }
    72%  { transform: rotate(2.15deg); }
    84%  { transform: rotate(-5.46deg); }
    100% { transform: rotate(-16.63deg); }
}
@keyframes bc-cs-armU-retour {               /* le bras REDESCEND. Il restait en
                                                suspens pour que l'image fixe garde
                                                le lien avec la lettre — mais depuis
                                                que la paume gauche porte puis tient
                                                la hampe, c'est ELLE qui tient ce
                                                lien. Un bras humain figé en l'air
                                                indéfiniment ne tenait plus debout.
                                                Trajectoire déjà vérifiée : les
                                                pointes sortent par la DROITE, sans
                                                racler l'encre. */
    0%   { transform: rotate(68.37deg); }
    20%  { transform: rotate(51deg); }
    55%  { transform: rotate(25deg); }
    100% { transform: rotate(0deg); }
}
@keyframes bc-cs-armF-retour {               /* le coude accompagne : sans lui,
                                                l'épaule tomberait à 0° en laissant
                                                l'avant-bras à −16.63°, bras cassé */
    0%   { transform: rotate(-16.63deg); }
    20%  { transform: rotate(2.3deg); }
    55%  { transform: rotate(3deg); }
    100% { transform: rotate(0deg); }
}
@keyframes bc-cs-armL-carry {                /* épaule gauche : angles IK */
    0%   { transform: rotate(22.87deg); }
    26%  { transform: rotate(6.05deg); }
    48%  { transform: rotate(-7.86deg); }
    62%  { transform: rotate(-13.57deg); }
    72%  { transform: rotate(-15.93deg); }   /* la lettre touche */
    84%  { transform: rotate(-9.37deg); }
    100% { transform: rotate(0deg); }        /* = le dessin : paume sur la hampe */
}
@keyframes bc-cs-armLF-carry {               /* coude gauche : angles IK */
    0%   { transform: rotate(55.25deg); }
    26%  { transform: rotate(52.59deg); }
    48%  { transform: rotate(41.05deg); }
    62%  { transform: rotate(29.58deg); }
    72%  { transform: rotate(21.48deg); }
    84%  { transform: rotate(13.44deg); }
    100% { transform: rotate(0deg); }
}
/* Le poignet pendant la pose : l'angle rendu aux becs est exactement celui
   qui manque entre l'orientation de l'avant-bras et celle de la lettre —
   la pince et le caractère tournent donc ENSEMBLE, sans glissement. */
@keyframes bc-cs-p1-pose {
    0%   { transform: rotate(-23.19deg); }
    26%  { transform: rotate(-27.98deg); }
    48%  { transform: rotate(-26.69deg); }
    62%  { transform: rotate(-22.13deg); }
    72%  { transform: rotate(-18deg); }
    84%  { transform: rotate(-13.31deg); }
    100% { transform: rotate(-6.9deg); }
}
@keyframes bc-cs-p2-pose {
    0%   { transform: rotate(-9.69deg); }
    26%  { transform: rotate(-14.48deg); }
    48%  { transform: rotate(-13.19deg); }
    62%  { transform: rotate(-8.63deg); }
    72%  { transform: rotate(-4.5deg); }
    84%  { transform: rotate(0.19deg); }
    100% { transform: rotate(6.6deg); }
}
@keyframes bc-cs-p1-open { from { transform: rotate(-6.9deg); } to { transform: rotate(0deg); } }
@keyframes bc-cs-p2-open { from { transform: rotate(6.6deg); }  to { transform: rotate(0deg); } }

/* ── casse · LE POINTAGE : son attention compose ─────────────────
   Le compositeur marque chaque lettre qui se pose d'un coup d'œil
   (dips à 7.5/21.5/35/49 % = les touchers à 1.87/2.19/2.51/2.83 s
   dans la fenêtre 1.7 → 4.0 s), revient à son propre caractère,
   suit sa main qui descend (88 % = le toucher du « a » à 3.73 s),
   se redresse : ligne complète. Gaté .is-caught SANS .is-onscreen
   pour rester en phase avec l'encre et les poses si le visiteur
   quitte l'écran en cours ; la règle .is-onscreen ci-dessous (plus
   spécifique) y ADOSSE la relecture en boucle — les deux listes
   partagent bc-cs-count à l'identique : pas de restart au toggle. */
.brand-catch.is-armed.is-caught .bc-scene--casse .bc-cs-lect { animation: bc-cs-count 2.3s ease-in-out 1.7s both; }
@keyframes bc-cs-count {
    0%    { transform: rotate(0deg); }
    4%    { transform: rotate(-0.6deg); }
    7.5%  { transform: rotate(-2.8deg); }   /* le M touche */
    14%   { transform: rotate(-0.9deg); }
    21.5% { transform: rotate(-2.8deg); }   /* i */
    28%   { transform: rotate(-0.9deg); }
    35%   { transform: rotate(-2.8deg); }   /* t */
    42%   { transform: rotate(-0.9deg); }
    49%   { transform: rotate(-2.8deg); }   /* i */
    58%   { transform: rotate(-0.5deg); }   /* retour à son propre caractère */
    72%   { transform: rotate(-2deg); }     /* il suit sa main qui descend */
    88%   { transform: rotate(-4.6deg); }   /* le toucher du « a » */
    100%  { transform: rotate(0deg); }      /* il se redresse : ligne complète */
}

/* ── La vie ensuite — humaine seulement, gatée hors écran ──────
   Il RELIT sa ligne : plonge sur la lettre posée (proche = regard
   piqué), remonte le mot vers le M (loin = regard tendu), deux
   petits hochements d'approbation, se redresse. Le corps se penche
   à peine sur la paume qui tient la ligne, même période : un seul
   geste. Délai 5.3 s : jamais pendant la pose (la prise IK ne doit
   pas être déplacée par le penché). */
.brand-catch.is-armed.is-caught.is-onscreen .bc-scene--casse .bc-cs-lect {
    animation: bc-cs-count 2.3s ease-in-out 1.7s both,
               bc-cs-read 5.6s ease-in-out 5.3s infinite;
}
.brand-catch.is-onscreen .bc-scene--casse .bc-cs-typo { animation: bc-cs-lean 5.6s ease-in-out 5.3s infinite; }
@keyframes bc-cs-read {
    0%   { transform: rotate(0deg); }
    16%  { transform: rotate(-6.2deg); }     /* il pique sur la lettre posée */
    34%  { transform: rotate(-5deg); }       /* le regard tient */
    46%  { transform: rotate(-2deg); }       /* il remonte la ligne vers le M */
    58%  { transform: rotate(-3.2deg); }     /* hochement… */
    66%  { transform: rotate(-1.8deg); }     /* …d'approbation */
    80%  { transform: rotate(0.8deg); }      /* il se redresse, petit recul */
    100% { transform: rotate(0deg); }
}
@keyframes bc-cs-lean {                      /* penché depuis les chevilles, sur la
                                                paume qui tient la ligne */
    0%   { transform: scale(1.55) rotate(0deg); }
    16%  { transform: scale(1.55) rotate(-1.1deg); }
    40%  { transform: scale(1.55) rotate(-0.8deg); }
    72%  { transform: scale(1.55) rotate(0.3deg); }
    100% { transform: scale(1.55) rotate(0deg); }
}

/* Il te REMARQUE : il lève le nez de sa ligne (base +11° : la face
   remonte vers le visiteur — il regarde à GAUCHE, le sens des signes
   s'inverse par rapport au peintre), te suit un peu, puis
   l'attention retombe et il retourne à sa lettre. */
.bc-scene--casse .bc-look {
    transform-origin: 127.4px 127.8px;       /* la nuque */
    transform: rotate(calc((var(--bc-px, 0) * 5deg - var(--bc-py, 0) * 3.5deg + 11deg) * var(--bc-at, 0)));
}

/* ── casse · ÉCHELLE · le typographe était dessiné deux fois trop
   petit : 58 unités de haut quand le pêcheur, le funambule et le
   peintre occupent la hauteur de la lettre. Mesuré : 46 px d'encre
   contre 79 (funambule), 101 (pêcheur), 131 (peintre).
   Il grandit DEPUIS SES CHEVILLES (130.4,167.6) — le pied reste posé
   sur la ligne de pied, la casse grandit avec lui : sa tête arrive
   à y≈78, soit exactement le haut du « a ».
   Deux conséquences réglées ici, sinon le geste devient faux :
   • la main descend 1.55× plus loin pour les mêmes angles d'épaule
     et de coude → la course du caractère est multipliée d'autant
     (bc-cs-sort-pose ci-dessus), et sa PRISE suit la main.
     Les angles IK ne changent pas : une rotation ne s'échelle pas.
   • une échelle épaissit le trait. Les largeurs sont divisées par
     1.55 pour que l'encre reste à 2.5 — le poids de la troupe. ── */
.bc-cs-atelier { transform-box: view-box; transform-origin: 130.4px 167.6px; transform: scale(1.55); }
.bc-cs-typo    { transform: scale(1.55); }

.bc-scene--casse .bc-cs-typo    { stroke-width: 1.613; }   /* → 2.5 rendu */
.bc-scene--casse .bc-cs-atelier { stroke-width: 1.032; }   /* → 1.6 */
.bc-scene--casse .bc-cs-grid,
.bc-scene--casse .bc-cs-sorts   { stroke-width: 0.839; }   /* → 1.3 */
.bc-scene--casse .bc-cs-p1,
.bc-scene--casse .bc-cs-p2      { stroke-width: 1.097; }   /* → 1.7 */
.bc-scene--casse .bc-cs-p2h,
.bc-scene--casse .bc-cs-alh     { stroke-width: 2.903; }   /* → 4.5 */
.bc-scene--casse .bc-cap-halo   { stroke-width: 1.161; }   /* → 1.8 */
.bc-scene--casse .bc-cap        { stroke-width: 1.677; }   /* → 2.6 */

/* =====================================================================
   114 — Fix menu burger (thème clair)
   Le menu mobile plein écran est TOUJOURS charbon sombre (module 048,
   « charbon cohérent » !important). Or le thème clair (module 020) colore
   le texte du menu en encre sombre → texte sombre sur fond sombre = illisible.
   Ici on force les contenus du menu OUVERT en clair dans les deux thèmes,
   fidèle à l'intention du 048 (un seul panneau sombre).
   ===================================================================== */
@media (max-width: 768px) {
    /* En-tête solidaire du panneau : sombre quand le menu est ouvert
       (sinon bande crème au-dessus de l'overlay charbon = rupture) */
    html[data-theme="light"] body:has(.nav-links.open) header#header {
        background: linear-gradient(160deg, #14110d, #0a0907) !important;
        border-bottom-color: transparent !important;
    }
    /* Logo + marque monochrome en clair sur l'en-tête sombre */
    html[data-theme="light"] body:has(.nav-links.open) .logo,
    html[data-theme="light"] body:has(.nav-links.open) .logo .brand-logo,
    html[data-theme="light"] body:has(.nav-links.open) .brand-mark-mono {
        color: rgba(255, 255, 255, 0.92) !important;
        fill: rgba(255, 255, 255, 0.92) !important;
    }
    /* Bouton thème (lune/soleil) en clair */
    html[data-theme="light"] body:has(.nav-links.open) .theme-toggle {
        color: rgba(255, 255, 255, 0.92) !important;
    }
    /* Croix de fermeture (hamburger.open) en clair */
    html[data-theme="light"] body:has(.nav-links.open) .hamburger.open .hamburger-line {
        background: rgba(255, 255, 255, 0.92) !important;
    }
    /* Liens du menu en clair */
    html[data-theme="light"] .nav-links.open .nav-link {
        color: rgba(255, 255, 255, 0.92) !important;
        border-bottom-color: rgba(255, 255, 255, 0.08) !important;
    }
    /* Sélecteur de langue FR/EN — on restitue l'aspect du thème sombre */
    html[data-theme="light"] .nav-links.open .lang-link {
        color: rgba(255, 255, 255, 0.55) !important;
    }
    html[data-theme="light"] .nav-links.open .lang-link.is-active {
        color: var(--color-gold, #c8a25c) !important;
        background: rgba(200, 162, 92, 0.12) !important;
    }
    html[data-theme="light"] .nav-links.open .lang-sep {
        color: rgba(255, 255, 255, 0.3) !important;
    }
    /* Bascule DESIGN · DEV — même piège, même remède que le sélecteur de
       langue ci-dessus (module 133 ne connaît que le thème clair « posé »,
       pas l'état panneau-toujours-sombre du menu ouvert). */
    html[data-theme="light"] .nav-links.open .couloir-link {
        color: rgba(255, 255, 255, 0.55) !important;
    }
    /* var(--accent) BRUT ne convient pas ici : sur le panneau charbon,
       mesuré au pixel (fond réel rgb(17,15,11)), le bleu du couloir Dev
       tombe à 3,96:1 — sous le seuil AA. C'est le même piège que le
       module 133 avait résolu pour la barre en thème sombre, et le même
       remède : les teintes déjà mesurées là-bas, déclinées par couloir.
       Mesuré sur le même fond : design 5,45:1 · dev 4,93:1. */
    html[data-theme="light"] .nav-links.open .couloir-link.is-active {
        background: rgba(255, 255, 255, 0.08) !important;
        background: color-mix(in srgb, var(--accent) 12%, transparent) !important;
    }
    html[data-theme="light"][data-couloir="design"] .nav-links.open .couloir-link.is-active {
        color: #c8724f !important;
    }
    html[data-theme="light"][data-couloir="dev"] .nav-links.open .couloir-link.is-active {
        color: #257cff !important;
    }
    /* La variante sobre des barres réduites (133) reste sobre dans le
       menu : sans ce retrait, l'aplat revenait par le !important
       ci-dessus en thème CLAIR seulement — le même contrôle aurait porté
       un aplat en clair et pas en sombre, dans le même panneau. */
    html[data-theme="light"] .nav-links.open .couloir-switch--sobre .couloir-link.is-active {
        background: none !important;
    }
    html[data-theme="light"] .nav-links.open .couloir-sep {
        color: rgba(255, 255, 255, 0.35) !important;
    }
    /* Méta NAVIGATION / Tongasoa */
    html[data-theme="light"] .nav-links.open::before,
    html[data-theme="light"] .nav-links.open::after {
        color: rgba(255, 255, 255, 0.4) !important;
    }
}

/* ==============================================================
   115 — Navbar : le logo « Mitia » répond au survol · fin de cascade

   PARTI PRIS (assumé) : AUCUNE animation perpétuelle ici.
   La navbar est de l'UI permanente — on la lit des dizaines de fois
   par visite. Ce qui bouge en boucle devant les yeux fatigue, et
   surtout BANALISE la scène de pêche du pied de page, qui est LE
   moment signature du site. Le footer garde donc l'exclusivité du
   mouvement continu ; ici, le logo est net et stable, et ne répond
   que quand on le touche.

   LE GESTE (0.55 s, une seule fois par survol) : un SCEAU qu'on
   presse. La marque ⛩ s'enfonce et pivote légèrement, pendant que
   les 5 lettres se compriment vers elle — puis tout se repose avec
   un léger dépassement élastique. Un seul mouvement coordonné, pas
   cinq lettres qui gigotent chacune dans leur coin.

   ⚠ Deux pièges SVG, tous deux vérifiés dans le navigateur :
   1. Chaque lettre porte déjà un attribut transform="translate(X,0)"
      qui la POSITIONNE. Animer `transform` en CSS l'écraserait et
      les lettres s'empileraient à l'origine. On anime donc les
      propriétés INDÉPENDANTES `translate` / `rotate` / `scale`, qui
      se composent avec l'attribut au lieu de le remplacer.
   2. Le groupe parent est retourné (scale(1,-1)) : un `translate`
      +Y fait donc MONTER la lettre à l'écran. Les valeurs sont en
      unités du viewBox (≈ 0.025 px à l'écran) → 40 ≈ 1 px.
   ============================================================== */

/* Les 5 lettres = les seuls <path> imbriqués à deux niveaux de <g>
   (la marque « torii » est un path direct du <svg>, la variante fête
   un <g> d'<image>). Sélecteur structurel volontaire : le SVG du logo
   est un asset figé, aucune classe à ajouter dans 4 fichiers HTML. */
.logo .brand-logo g g path {
    transform-box: fill-box;      /* sans quoi l'origine serait (0,0) du viewBox */
    transform-origin: 50% 50%;
}

/* ── La compression : chaque lettre serre vers le CENTRE du mot ────
   Le « t » est le pivot (il ne bouge pas), les extrêmes rentrent le
   plus. C'est ce dégradé signé qui donne l'effet de ressort, plutôt
   qu'un décalage uniforme. */
.logo .brand-logo g g path:nth-of-type(1) { --squeeze:  52px; }   /* M, le plus à gauche */
.logo .brand-logo g g path:nth-of-type(2) { --squeeze:  26px; }
.logo .brand-logo g g path:nth-of-type(3) { --squeeze:   0px; }   /* t, le pivot */
.logo .brand-logo g g path:nth-of-type(4) { --squeeze: -26px; }
.logo .brand-logo g g path:nth-of-type(5) { --squeeze: -52px; }   /* a, le plus à droite */

.logo:hover .brand-logo g g path,
.logo:focus-visible .brand-logo g g path {
    animation: nav-word-press 0.55s cubic-bezier(0.22, 1, 0.36, 1);
}
@keyframes nav-word-press {
    0%   { translate: 0 0; }
    34%  { translate: var(--squeeze) 34px; }        /* serré + légèrement soulevé */
    62%  { translate: calc(var(--squeeze) * -0.16) -8px; }  /* dépassement : ça se relâche */
    100% { translate: 0 0; }                        /* et ça se repose, net */
}

/* ── La marque : elle s'enfonce comme un sceau qu'on presse ───────
   Elle n'a PAS d'attribut transform, mais on reste sur les propriétés
   indépendantes pour rester homogène avec les lettres. */
.logo .brand-mark-mono {
    transform-box: fill-box;
    transform-origin: 50% 50%;
}
.logo:hover .brand-mark-mono,
.logo:focus-visible .brand-mark-mono {
    animation: nav-mark-press 0.55s cubic-bezier(0.22, 1, 0.36, 1);
}
@keyframes nav-mark-press {
    0%   { scale: 1;    rotate: 0deg; }
    34%  { scale: 0.9;  rotate: -7deg; }   /* le sceau s'enfonce */
    62%  { scale: 1.04; rotate: 2deg; }    /* il rebondit */
    100% { scale: 1;    rotate: 0deg; }
}

@media (prefers-reduced-motion: reduce) {
    .logo .brand-logo g g path,
    .logo .brand-mark-mono {
        animation: none;
        translate: none;
        rotate: none;
        scale: none;
    }
}

/* ==============================================================
   116 — La signature de fin sur TOUTES les pages · fin de cascade

   Le pêcheur qui a ferré le « a » de Mitia ne vivait que sur le
   portfolio. Il clôt désormais chaque page (tarifs, design-system,
   playground, carnet, mentions, confidentialité, 404, EN…) via
   Layout.astro, et les pages projet via le partial du footer.

   ⚠ POURQUOI EN FIN DE FLUX DE PAGE, ET NON DANS <Footer /> :
   le footer de ces pages (.ed-footer) est une barre
   `position: fixed; bottom: 0` — un bandeau permanent, pas un vrai
   pied de page. La scène y aurait recouvert le bas de chaque page.

   ⚠ NE PAS TOUCHER AUX COULEURS ICI. Le module 112 colore déjà la
   scène avec var(--gray-1) / var(--black) / var(--dark-2), et ces
   tokens sont DÉJÀ inversés par le thème clair. Piège vérifié :
   en thème clair --ink et --black valent #fafaf8 (blanc), c'est
   --gray-1 qui porte l'encre sombre. Une « correction » de couleur
   ici repeint le mot en blanc sur fond blanc.
   ============================================================== */

.page-catch-wrap {
    /* dégage le bandeau .ed-footer fixe pour que la scène ne passe
       jamais dessous, + de l'air avant elle */
    padding-bottom: clamp(5rem, 9vw, 7.5rem);
    padding-inline: 1.25rem;
}

.page-catch-wrap .footer-catch {
    margin-bottom: 0;
}

/* ==============================================================
   117 — Contrastes WCAG AA · fin de cascade

   Ton manifeste affirme « l'accessibilité fait partie du design ».
   L'audit a montré que la structure tient (aucune image sans alt,
   aucun contrôle sans nom accessible, un seul h1, aucun saut de
   niveau de titre, aucun id dupliqué) — mais que QUATRE textes
   passaient sous le seuil AA de 4.5:1.

   Les couleurs corrigées ici gardent la teinte d'origine : elles
   sont seulement assombries/éclaircies du strict nécessaire.
   Les ratios en commentaire sont mesurés, pas estimés.

   ⚠ Les dégradés se mesurent AUX DEUX EXTRÉMITÉS : les CTA
   passaient à leur extrémité foncée et échouaient à la claire —
   un contrôle sur la seule couleur « moyenne » ne l'aurait pas vu.
   ============================================================== */

/* 1. Lien « Tarifs » de la nav — or sur crème : 2.22:1, le pire du lot.
      #c8a25c → #8a6a1f = 4.68:1. L'or de marque reste inchangé partout
      ailleurs ; seul ce lien sur fond CLAIR est corrigé (sur fond sombre
      il est déjà à 8.37:1).
      ⚠ SPÉCIFICITÉ : « html.editorial-page header#header .nav-link-accent »
      existe déjà avec !important. Un simple html[data-theme] perdait contre
      lui (un id l'emporte sur un attribut) — d'où le header#header ici. */
html[data-theme="light"] header#header .nav-link-accent {
    color: #8a6a1f !important;
}
html[data-theme="light"] header#header .nav-link-accent:hover {
    color: #6f5416 !important;   /* survol : plus foncé encore, jamais plus clair */
}

/* 2. Libellé « MANIFESTE » — bleu sur bleu profond : 2.59:1.
      #3b82f6 → #93c5fd = 5.28:1 (même famille, simplement éclairci). */
#manifesto .section-label,
html[data-theme="light"] #manifesto .section-label {
    color: #93c5fd !important;
}

/* 3. CTA bleus — texte blanc. Migré tâche 8, passe de correction 2 : ce
      dégradé était figé en bleu quel que soit le couloir (exclusion de
      brief appliquée à tort — voir rapport). Dérivé de --accent, même
      technique que --accent-deep (color-mix), pas une teinte inventée.
      L'extrémité claire (var(--accent) brut) échoue en couloir design
      (3,51:1, mesuré) — d'où un second ton lui aussi assombri (84%),
      proche de --accent-deep (82%) pour rester un dégradé, pas un aplat.
      Ratios white-on-fond mesurés aux 4 COMBINAISONS (tâche 6, passe de
      correction 3 — --accent est désormais différencié par thème côté
      Design, pas seulement par couloir ; les chiffres d'une seule mesure
      « design » ne valaient donc plus que pour le thème clair) :
        dev/sombre    : accent-deep 6,61:1 · second ton 6,38:1
        dev/clair     : accent-deep 6,61:1 · second ton 6,38:1 (inchangé, --accent dev ne varie pas par thème)
        design/sombre : accent-deep 6,80:1 · second ton 6,57:1 (remonté — --accent design sombre assombri en tâche 6 pour .btn-primary)
        design/clair  : accent-deep 4,96:1 · second ton 4,76:1 (inchangé, c'est la mesure d'origine)
      Toutes au-dessus du seuil AA (4,5:1), dans les 4 combinaisons. */
.showcase-status-cta,
.imagine-cta-primary {
    background: linear-gradient(115deg, var(--accent-deep), color-mix(in srgb, var(--accent) 84%, black)) !important;
}

/* 4. CTA « Réserver un appel » — le libellé CHANGE DE COULEUR selon le
      thème, donc la correction doit être thématisée :
      · thème clair  → texte BLANC : le dégradé d'origine tombait à 3.77:1
        côté vert. Assombri, il donne 5.17:1 et 5.48:1.
      · thème sombre → texte FONCÉ : là c'est l'inverse, assombrir le fond
        DÉGRADE le contraste (mesuré 3.65:1 quand je l'avais appliqué
        partout). Le dégradé clair d'origine y reste le bon choix.
      Corriger « globalement » cassait donc un thème pour réparer l'autre.
      Migré tâche 8, passe de correction 2 : l'extrémité bleue suit
      maintenant --accent (même second ton dérivé que le point 3 ci-dessus,
      color-mix 84% — un var(--accent) brut échoue en design, 3,51:1).
      Le vert reste #047857 : ce n'est pas un accent, --green reste vert
      partout ailleurs sur le site (015, commentaire « live signals stay
      green »). Cette règle n'existe qu'en thème CLAIR (`.nav-cta` en
      thème sombre suit un autre chemin, pas --accent — hors du périmètre
      de ce sondage) : un seul couple de ratios par couloir, pas quatre.
      Ratios mesurés (tâche 6, passe de correction 3 — recoupés, inchangés
      depuis la mesure d'origine puisque --accent CLAIR ne bouge pas pour
      Design) : bleu dev 6,38:1 · bleu design 4,76:1 · vert (inchangé,
      les deux couloirs) 5,48:1 — tous au-dessus du seuil AA. */
html[data-theme="light"] .nav-cta {
    background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 84%, black), #047857) !important;
}

/* Le focus clavier doit rester visible partout : certains survols
   masquent le contour par défaut. On garantit un anneau net, hors du
   tracé, qui fonctionne sur fond clair comme sur fond sombre. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible,
[tabindex]:focus-visible {
    outline: 2px solid currentColor;
    outline-offset: 3px;
    border-radius: 4px;
}

/* ==============================================================
   118 — Le CONTENU des sections arrive en cascade · fin de cascade
   Audit août 2026 : dans #clients, #contact et à l'intérieur des
   cartes de témoignage, seuls le label et le titre s'animaient. Les
   6 cartes clients, les 12 logos de marques, le formulaire et les
   citations APPARAISSAIENT d'un coup, posés. Le conteneur portait
   bien .fade-in, mais rien ne se passait à l'intérieur.
   Ici : une seule intention par section — un balayage qui suit
   l'ordre de lecture. Rien d'inventé, pas de lévitation : les
   éléments montent de 12px et se posent, une fois.

   ⚠ #stack n'est PAS traité : ses logos défilent déjà (marquee
   tech-scroll-fwd). Y ajouter une cascade se battrait avec le défilé.

   ⚠ On anime `translate` (propriété indépendante) et NON `transform` :
   les cartes clients et les logos ont déjà des transforms au survol.
   Passer par `transform` les aurait écrasés — ce module est le
   dernier de la cascade, il aurait gagné.

   ⚠ On n'anime QUE le déplacement, JAMAIS l'opacité des enfants : le
   conteneur porte déjà .fade-in, il fond donc en UN seul calque. Ajouter
   35 fondus par-dessus coûtait cher — surtout sur les 12 logos de
   marques, qui portent un filter grayscale/invert permanent (fondre une
   image filtrée la re-rastérise à chaque image). Un fondu au lieu de 36.
   Effet de bord heureux : ce module ne peut PAS laisser un élément
   invisible, puisqu'il ne touche jamais l'opacité.

   Charte zéro latence : translate seul (composité), aucune ombre/flou/
   filtre animé, aucun will-change permanent, aucune boucle infinie.
   Le geste joue UNE fois, quand l'observateur pose .visible.
   ============================================================== */

@media (prefers-reduced-motion: no-preference) {

    /* ── état de départ : posé 12px plus bas, transparent ────────── */
    #clients .clients-grid .client-card,
    #clients .clients-brands .cb-logo,
    #contact .contact-info > div,
    #contact .contact-form .form-group,
    #contact .contact-form .btn-submit,
    #testimonials .testimonial-card .testimonial-mark,
    #testimonials .testimonial-card .testimonial-quote,
    #testimonials .testimonial-card .testimonial-author {
        translate: 0 10px;
        transition: translate 0.5s var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1));
    }

    /* ── arrivée : le conteneur devient .visible ─────────────────── */
    #clients .clients-grid.visible .client-card,
    #clients .clients-brands.visible .cb-logo,
    #contact .contact-layout.visible .contact-info > div,
    #contact .contact-layout.visible .form-group,
    #contact .contact-layout.visible .btn-submit,
    #testimonials .testimonial-card.visible .testimonial-mark,
    #testimonials .testimonial-card.visible .testimonial-quote,
    #testimonials .testimonial-card.visible .testimonial-author {
        translate: 0 0;
    }

    /* ── clients · les 6 cartes, puis le mur de marques ──────────── */
    #clients .clients-grid.visible .client-card:nth-child(1) { transition-delay: 0.05s; }
    #clients .clients-grid.visible .client-card:nth-child(2) { transition-delay: 0.11s; }
    #clients .clients-grid.visible .client-card:nth-child(3) { transition-delay: 0.17s; }
    #clients .clients-grid.visible .client-card:nth-child(4) { transition-delay: 0.23s; }
    #clients .clients-grid.visible .client-card:nth-child(5) { transition-delay: 0.29s; }
    #clients .clients-grid.visible .client-card:nth-child(6) { transition-delay: 0.35s; }

    /* le mur : pas de fondu logo par logo trop long — 35ms de pas,
       les 12 marques sont posées en moins de 0,5s. */
    #clients .clients-brands.visible .cb-logo:nth-child(1)  { transition-delay: 0.04s; }
    #clients .clients-brands.visible .cb-logo:nth-child(2)  { transition-delay: 0.075s; }
    #clients .clients-brands.visible .cb-logo:nth-child(3)  { transition-delay: 0.11s; }
    #clients .clients-brands.visible .cb-logo:nth-child(4)  { transition-delay: 0.145s; }
    #clients .clients-brands.visible .cb-logo:nth-child(5)  { transition-delay: 0.18s; }
    #clients .clients-brands.visible .cb-logo:nth-child(6)  { transition-delay: 0.215s; }
    #clients .clients-brands.visible .cb-logo:nth-child(7)  { transition-delay: 0.25s; }
    #clients .clients-brands.visible .cb-logo:nth-child(8)  { transition-delay: 0.285s; }
    #clients .clients-brands.visible .cb-logo:nth-child(9)  { transition-delay: 0.32s; }
    #clients .clients-brands.visible .cb-logo:nth-child(10) { transition-delay: 0.355s; }
    #clients .clients-brands.visible .cb-logo:nth-child(11) { transition-delay: 0.39s; }
    #clients .clients-brands.visible .cb-logo:nth-child(12) { transition-delay: 0.425s; }

    /* ── contact · les coordonnées, puis le formulaire ───────────── */
    #contact .contact-layout.visible .contact-info > div:nth-child(1) { transition-delay: 0.05s; }
    #contact .contact-layout.visible .contact-info > div:nth-child(2) { transition-delay: 0.12s; }
    #contact .contact-layout.visible .contact-info > div:nth-child(3) { transition-delay: 0.19s; }
    #contact .contact-layout.visible .contact-info > div:nth-child(4) { transition-delay: 0.26s; }
    #contact .contact-layout.visible .form-group:nth-of-type(1) { transition-delay: 0.16s; }
    #contact .contact-layout.visible .form-group:nth-of-type(2) { transition-delay: 0.23s; }
    #contact .contact-layout.visible .form-group:nth-of-type(3) { transition-delay: 0.30s; }
    #contact .contact-layout.visible .btn-submit { transition-delay: 0.38s; }

    /* ── témoignages · l'ordre de lecture dans la carte ──────────── */
    #testimonials .testimonial-card.visible .testimonial-mark   { transition-delay: 0.05s; }
    #testimonials .testimonial-card.visible .testimonial-quote  { transition-delay: 0.13s; }
    #testimonials .testimonial-card.visible .testimonial-author { transition-delay: 0.24s; }
}

/* ==============================================================
   119 — Les textes vont au bord, les filets disparaissent
   ·  fin de cascade

   1. TIRETS SUPPRIMÉS. Chaque .section-label était flanqué de trois
      barres décoratives en dégradé (32px avant, 28px et 40px après),
      posées par les modules 005, 041 et 052 : à l'écran ça donnait
      « ——— RÉALISATIONS ——— ». Demande client : les retirer. Elles
      tombaient de toute façon sous la règle « pas de design IA »
      (barres en dégradé purement décoratives).

   2. TEXTES PLEINE LARGEUR. Plusieurs paragraphes étaient bridés très
      en deçà de leur conteneur — le texte s'arrêtait en plein milieu
      et la colonne paraissait mal calée :
        .section-sub          540px dans 1064  → 524px perdus
        .clients-brands-label 664px dans 1064  → 400px
        .proj-cta-sub         480px dans 1064  → 584px
        .proj-section-text    680px dans 1064  → 384px
        .proj-lead            560px dans  784  → 224px
      Ils occupent désormais toute la largeur disponible. Le conteneur
      reste borné (.container max-width 1160), donc la ligne ne part
      jamais à l'infini : c'est lui qui tient la mesure, pas un
      plafond arbitraire posé sur chaque paragraphe.
   ============================================================== */

/* ── 1. plus aucun filet autour des labels ────────────────────── */
.section-label::before,
.section-label::after,
section .section-label.fade-in::after {
    content: none;
    display: none;
}

/* ── 2. le texte atteint le bord de son conteneur ─────────────── */
.section-sub,
.clients-brands-label,
.proj-lead,
.proj-section-text,
.proj-cta-sub {
    max-width: none;
}

/* ── 3. thème clair : contours dessinés pour le sombre ────────────
   Les cartes de #clients portaient `border: 1px solid rgba(255,255,255,0.07)`
   et un fond blanc à 2 % : sur crème, le contour disparaissait — on ne
   voyait plus que le logo et le texte flotter sans cadre. */
html[data-theme="light"] #clients .client-card {
    background: rgba(10, 14, 26, 0.02);
    border-color: rgba(10, 14, 26, 0.12);
}

/* ==============================================================
   Retour en haut de page · fin de cascade
   Un repère d’atelier, pas une pastille flottante : filet fin, angle
   droit, libellé en monospace capitales — le vocabulaire des labels du
   site. Il n’apparaît qu’au dernier écran (classe posée par
   retour-haut.js), et ne bouge au survol que de 2px (charte).
   ============================================================== */
.retour-haut {
    position: fixed;
    right: clamp(1rem, 3vw, 2.5rem);
    bottom: clamp(1rem, 3vw, 2.5rem);
    z-index: 60;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0;
    padding: 0.6rem 0.85rem;
    font-family: var(--mono, "JetBrains Mono", monospace);
    font-size: var(--pas-etiquette);
    font-weight: 500;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--gray-1);
    background: var(--black);
    border: 1px solid var(--gray-4);
    border-radius: 0;
    cursor: pointer;
    opacity: 0;
    translate: 0 8px;
    pointer-events: none;
    transition: opacity 0.35s ease, translate 0.35s ease, border-color 0.25s ease;
}
.retour-haut.is-visible {
    opacity: 1;
    translate: 0 0;
    pointer-events: auto;
}
.retour-haut svg {
    width: 11px;
    height: 11px;
    flex: none;
    transition: translate 0.25s ease;
}
.retour-haut:hover { border-color: var(--gray-2); }
.retour-haut:hover svg { translate: 0 -2px; }
.retour-haut:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* L’atelier a sa propre palette (encre chaude, or) : le bouton s’y accorde. */
.av4 .retour-haut,
body:has(.av4) .retour-haut {
    color: var(--ivo, #f2ede3);
    background: var(--enc, #0a0907);
    border-color: var(--line, rgba(242, 237, 227, 0.16));
}

@media (prefers-reduced-motion: reduce) {
    .retour-haut { transition: none; translate: none; }
    .retour-haut:hover svg { translate: none; }
}

/* ==============================================================
   121 — La PLANCHE du stack · fin de cascade

   Avant : trois bandeaux qui défilaient en boucle infinie
   (tech-scroll-fwd / -rev). Deux défauts, pas un :
     · un défilé perpétuel se lit comme un CHARGEMENT — l'œil croit que
       la page attend (même mécanique que le reflet « shimmer » retiré
       plus tôt), d'où la latence ressentie ;
     · les pastilles étaient tranchées par le bord du cadre, en plein
       mot : « PHP » amputé, « Pos… », un chevauchement en 3ᵉ ligne.
       Ça donnait une section inachevée.

   Maintenant : une planche de spécimens. Les 27 outils sont RANGÉS en
   grille, chacun dans sa case, alignés en colonnes. À l'arrivée ils se
   posent l'un après l'autre — comme on dispose des fiches sur une
   table — puis PLUS RIEN NE BOUGE. Le geste joue une fois.

   ⚠ On anime `translate` et non `transform` : les pastilles ont déjà
   un transform au survol (scale + montée), l'écraser l'aurait cassé.
   Charte : translate + opacity seulement, aucune boucle infinie.
   ============================================================== */

.tech-board {
    /* le .container de #stack est une grille 320px / 672px : sans cette
       ligne la planche atterrit dans la colonne étroite et se replie en
       une seule colonne de 27 lignes. L'ancien bandeau enjambait déjà
       les deux (module 103). */
    grid-column: 1 / -1;
    margin-top: 2.5rem;
    margin-bottom: 3rem;
}
.tech-board-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 0.75rem;
}
/* la pastille occupe toute sa case : c'est ce qui fait la grille */
.tech-board-grid .tech-pill {
    width: 100%;
    justify-content: flex-start;
}
/* « Direction artistique » ne tient pas sur une ligne dans une case de
   203px : il passe à deux lignes et sa rangée devenait plus haute que
   les autres, cassant le rythme de la planche. Interligne resserré :
   deux lignes retrouvent la hauteur d'une pastille normale. */
.tech-board-grid .tech-pill span {
    line-height: 1.15;
}

@media (prefers-reduced-motion: no-preference) {
    /* posées 10px plus haut, transparentes : elles n'ont pas encore
       été déposées sur la planche */
    .tech-board-grid .tech-pill {
        opacity: 0;
        translate: 0 -10px;
        transition:
            opacity 0.42s ease,
            translate 0.42s cubic-bezier(0.22, 0.9, 0.3, 1);
    }
    .tech-board.visible .tech-board-grid .tech-pill {
        opacity: 1;
        translate: 0 0;
    }
    /* l'ordre de pose : de gauche à droite, ligne après ligne */
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(1) { transition-delay: 0.040s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(2) { transition-delay: 0.066s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(3) { transition-delay: 0.092s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(4) { transition-delay: 0.118s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(5) { transition-delay: 0.144s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(6) { transition-delay: 0.170s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(7) { transition-delay: 0.196s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(8) { transition-delay: 0.222s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(9) { transition-delay: 0.248s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(10) { transition-delay: 0.274s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(11) { transition-delay: 0.300s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(12) { transition-delay: 0.326s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(13) { transition-delay: 0.352s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(14) { transition-delay: 0.378s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(15) { transition-delay: 0.404s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(16) { transition-delay: 0.430s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(17) { transition-delay: 0.456s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(18) { transition-delay: 0.482s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(19) { transition-delay: 0.508s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(20) { transition-delay: 0.534s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(21) { transition-delay: 0.560s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(22) { transition-delay: 0.586s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(23) { transition-delay: 0.612s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(24) { transition-delay: 0.638s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(25) { transition-delay: 0.664s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(26) { transition-delay: 0.690s; }
    .tech-board.visible .tech-board-grid .tech-pill:nth-child(27) { transition-delay: 0.716s; }
}

@media (max-width: 560px) {
    .tech-board-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 0.6rem; }
}

/* ==== SIGNATURE CLIQUABLE — dire que la troupe se change ==============
   Le clic sur la signature existait depuis le début, mais RIEN ne le
   disait : pas de curseur, pas d'invite, et surtout aucun accès clavier
   — l'écouteur ne répondait qu'à la souris. Le bouton est injecté par
   public/assets/brand-catch.js (le pilote sort avant en reduced-motion
   ou sans IntersectionObserver : jamais d'invite sur une signature qui
   ne bouge pas). Ici, seulement sa mise en forme.
   Ton éditorial : pas de pilule, pas de dégradé, pas de lévitation. Un
   mot en petites capitales sous le nom, et un signe de rotation. ==== */

.brand-catch[data-catch-on] .brand-catch-word { cursor: pointer; }

/* Sous le mot, jamais à côté : le paragraphe est inline-block, un bouton
   inline se posait donc SUR la scène — mesuré à 70 px de recouvrement avec
   le typographe, 41 avec le peintre. En flex (donc de niveau bloc) il prend
   sa propre ligne, et `width: fit-content` garde la cible à la taille du
   texte au lieu de toute la largeur du pied de page. */
.bc-invite {
    display: flex;
    width: fit-content;
    align-items: baseline;
    gap: 0.45em;
    margin-top: 0.6rem;
    padding: 0.25em 0;
    border: 0;
    background: none;
    font: inherit;
    font-size: var(--pas-etiquette);
    font-weight: 500;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: currentColor;
    opacity: 0.38;
    cursor: pointer;
    transition: opacity 0.22s ease;
}
.bc-invite:hover,
.bc-invite:focus-visible { opacity: 0.85; }

/* Le focus doit se voir : c'est le seul chemin clavier vers la troupe. */
.bc-invite:focus-visible {
    outline: 2px solid currentColor;
    outline-offset: 3px;
    border-radius: 2px;
}

/* Le signe de rotation tourne d'un quart de tour au survol : le geste
   annonce ce que fait le clic. Une seule transition, transform-only. */
.bc-invite-tour {
    display: inline-block;
    /* Volontairement laissé en em, jamais migré (tâche 4) : mesuré avec son
       vrai parent .bc-invite (clamp(0.62rem,1.5vw,0.72rem) avant migration,
       aujourd'hui figé à var(--pas-etiquette) = 12px) — le fixer en jeton
       (ex. var(--pas-lecture) = 17px) romprait la proportion avec le parent
       de +28 à +49% selon la largeur. En em, 1.15 × 12px = 13,8px, proche
       des 11,4-13,2px d'origine (même piège et même arbitrage que sur le
       portfolio, où le migrateur automatique l'avait initialement traité
       comme un rem avant correction). */
    font-size: 1.15em;
    line-height: 1;
    transition: transform 0.32s cubic-bezier(0.22, 0.9, 0.3, 1);
}
.bc-invite:hover .bc-invite-tour,
.bc-invite:focus-visible .bc-invite-tour { transform: rotate(90deg); }

@media (prefers-reduced-motion: reduce) {
    .bc-invite,
    .bc-invite-tour { transition: none; }
}

@media print { .bc-invite { display: none; } }

/* ==== MISE EN TEXTE — trois rôles, pas un de plus =====================
   Mesuré avant travaux : 61 blocs de texte long (13 sur la référence), une
   mesure allant de 14 à 119 caractères par ligne quand la lisibilité tient
   entre 45 et 75, et 24 largeurs de bloc distinctes. Ce n'est pas de la
   composition, c'est du bruit — et c'est ça qui donne à la page son air
   d'avoir été posée par une machine, bien plus que le style.
   Trois rôles ici, et les sections n'ont le droit qu'à ceux-là. ==== */

.texte-affirmation {
    font-family: var(--display);
    font-weight: 800;
    font-size: var(--pas-affirmation);
    line-height: 0.95;
    letter-spacing: -0.02em;
    max-width: 18ch;          /* trois lignes maximum, par construction */
    text-wrap: balance;
}

.texte-lecture {
    font-family: var(--font);
    font-weight: 400;
    font-size: var(--pas-lecture);
    line-height: 1.6;
    max-width: var(--mesure);  /* ~65 caractères */
    text-align: left;          /* JAMAIS justify : sans césure, ça creuse
                                  des rivières blanches — un des marqueurs
                                  les plus francs du texte posé par une
                                  machine */
    text-wrap: pretty;
}
.texte-lecture--large { max-width: var(--mesure-large); }

.texte-etiquette {
    font-family: var(--mono);
    font-weight: 500;
    font-size: var(--pas-etiquette);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    line-height: 1;
}

/* Garde-fou : aucun paragraphe de la page ne dépasse la mesure de lecture,
   quelle que soit la classe qu'on lui donne.
   #main n'existait que sur les pages Astro plus récentes (404, mentions
   légales, confidentialité) — pas sur /portfolio, dont le <main> vient tel
   quel de portfolio-legacy.html. Sans id, ce garde-fou ne capturait RIEN
   sur la page justement visée par cette tâche. Corrigé côté HTML (id="main"
   ajouté au <main> de portfolio-legacy.html, au même endroit que sur les
   autres pages) plutôt que côté sélecteur, pour ne pas élargir la portée
   au-delà de <main> — même correction que sur le portfolio.
   --mesure-large (52rem) est documentée pour LISTES et TABLEAUX (tâche 2,
   000-base-tokens-reset.css), pas pour la prose : un p resté à 52rem passe
   toujours au-dessus de 75 caractères/ligne dès que son conteneur le
   permet.
   --mesure (34rem = 544px, la mesure de .texte-lecture) est la bonne
   valeur par défaut pour un <p>. */
#main p:not([class*="texte-"]) { max-width: var(--mesure); }

/* =====================================================================
   124 · Le blanc des cartes de travaux, rendu lisible
   ---------------------------------------------------------------------
   Mirroir du correctif posé sur le portfolio (module 129 là-bas), après
   l'avoir mesuré ICI et pas seulement là-bas : contraste WCAG du titre
   blanc contre la médiane du fond réellement peint sous lui, relevé sur
   la prod à 1280px.

     POEM Group 3.7:1 · e-passAvenue 3.4:1 · Omega 4.4:1 · Campagne 4.4:1

   Quatre titres sous le seuil AA de 4,5:1. La cause est la même que sur
   le portfolio : le dégradé du module 001 (noir 0.97 en bas, 0.55 à
   mi-hauteur) suffisait tant que les captures de projet étaient sombres,
   et plusieurs sont des interfaces à FOND CLAIR.

   Un dégradé de plus, pas un backdrop-filter ni un blur (proscrits) :
   image de fond statique, aucun coût de composition.

   ⚠ L'AMPLITUDE, elle, n'est PAS mirroirée : ce dépôt n'a pas reçu la
   refonte des tailles de cartes. Seule la lisibilité l'est.
   ===================================================================== */
.bento-overlay {
    background: linear-gradient(
        to top,
        rgba(0, 0, 0, 0.97) 0,
        rgba(0, 0, 0, 0.88) 38%,
        rgba(0, 0, 0, 0.62) 66%,
        rgba(0, 0, 0, 0.22) 86%,
        transparent 100%
    );
}
html[data-theme="light"] .bento-overlay {
    background: linear-gradient(
        to top,
        rgba(0, 0, 0, 0.92) 0,
        rgba(0, 0, 0, 0.8) 42%,
        rgba(0, 0, 0, 0.5) 70%,
        transparent 100%
    );
}

/* L'ordinal (« 01 », « 02 »…) porte une information, pas une décoration.
   À rgba(255,255,255,0.4) il ne tenait nulle part une fois ramené à son
   alpha réel. En retrait du titre, mais lisible. */
.bento-num {
    color: rgba(255, 255, 255, 0.72);
}

/* =====================================================================
   125 · Le pense-bête « tongasoa » recouvrait « Scroll »
   ---------------------------------------------------------------------
   Mesuré sur la prod, boîte du MOT (pas du cadre de l'indicateur, large
   de 580px) contre celle du pense-bête :

     1025 → 47px de recouvrement   1280 → 29px
     1440 → 32px                   1920 → 33px

   On lisait « SCROL » puis le papier jaune. Le pense-bête est posé en
   `right: 18%` du hero, l'indicateur est centré : les deux se croisent
   à toutes les largeurs bureau. Sous 900px l'indicateur passe ailleurs,
   il n'y a pas de collision.

   Le pense-bête glisse vers la droite. C'est lui qui bouge, pas
   l'indicateur : « tongasoa » est un mot d'accueil, sa place exacte est
   libre, alors que l'indicateur de descente doit rester centré sous le
   hero pour se lire comme une invitation à défiler.
   ===================================================================== */
@media (min-width: 900px) {
    .sticky-note-hero {
        right: 4%;
    }
}

/* ── Et le même indicateur était à 1,6:1 ───────────────────────────────
   Mesuré sur la prod : 1,6:1 en thème sombre, 3,6:1 en clair. Il est posé
   sur la dune du hero, un ton MOYEN — ni le blanc ni l'encre n'y tiennent
   seuls. Même remède que sur le portfolio : un halo statique couleur
   fond. Pas d'animation, pas de filtre : rien que la charte proscrive. */
.scroll-indicator {
    color: rgba(255, 255, 255, 0.92);
    text-shadow: 0 1px 2px rgba(5, 8, 17, 0.85), 0 0 6px rgba(5, 8, 17, 0.75);
}
.scroll-indicator svg {
    filter: drop-shadow(0 1px 2px rgba(5, 8, 17, 0.85));
}

/* =====================================================================
   126 · Les textes secondaires, remontés au seuil
   ---------------------------------------------------------------------
   Miroir du module 131 du portfolio, mais mesuré ICI avant d'être posé.
   Contraste WCAG, couleur calculée contre le fond réellement peint :

     a.nav-link            « Réalisations », « Atelier »   3,7:1 (sombre)
     .header-…-time        l'heure locale                  3,6:1 (clair)
     .footer-copy-meta     « · Antananarivo »              3,5:1 (clair)
     .footer-legal-link    mentions, confidentialité       3,5:1 (clair)
     .loader-sub-text      « portfolio »                   1,7:1 (clair)

   Des informations, pas des ornements : les liens de navigation et les
   mentions légales doivent se lire. Remontés juste au-dessus de 4,5:1,
   sans les hisser au niveau du corps — la hiérarchie tient.
   ===================================================================== */
.nav-link,
.header-availability-time,
.footer-copy-meta,
.footer-legal-link,
.loader-sub-text {
    color: rgba(255, 255, 255, 0.75);
}
html[data-theme="light"] .nav-link,
html[data-theme="light"] .header-availability-time,
html[data-theme="light"] .footer-copy-meta,
html[data-theme="light"] .footer-legal-link,
html[data-theme="light"] .loader-sub-text {
    color: rgba(10, 14, 26, 0.75);
}
.nav-link:hover,
.footer-legal-link:hover {
    color: rgba(255, 255, 255, 0.96);
}
html[data-theme="light"] .nav-link:hover,
html[data-theme="light"] .footer-legal-link:hover {
    color: rgba(10, 14, 26, 0.96);
}

/* Les deux accents du copyright viennent d'un style EN LIGNE dans le HTML
   (`<span style="color:var(--terra-light)">[ Design ]</span>`), qui bat
   tout sélecteur — d'où le !important. Retirer ce style changerait la
   ligne FR et orphelinerait sa paire de traduction. */
html[data-theme="light"] .footer-copy span[style*="terra"] {
    color: #6d28d9 !important;
}
/* « { Dev } » / « [ Design ] » : signature à deux métiers, pas un accent de
   site — la couleur NOMME le métier (tâche 8, corrigé après une première
   passe fausse : j'avais fait suivre { Dev } à var(--accent), donc au
   couloir actif ; en couloir design, « Dev » prenait alors la couleur du
   design pendant que « Design » gardait sa propre teinte — contresens
   mesuré aux 4 combinaisons. Rendu à sa couleur fixe d'origine : le style
   en ligne du HTML est maintenant un hex figé (#0066ff, la teinte du site
   avant les couloirs) et non plus var(--accent) — classe .footer-copy-dev
   ajoutée pour ce correctif clair, l'ancien sélecteur par sous-chaîne
   `[style*="accent"]` ne matchant plus un style en ligne sans jeton. */
html[data-theme="light"] .footer-copy-dev {
    color: #1d4ed8 !important;
}

/* Le bouton principal (tâche 8) : ces quatre règles figeaient un bleu quel
   que soit le couloir — écrasant silencieusement var(--accent) de 015
   (même spécificité, ce module chargé après). Repassées au jeton : sombre
   suit --accent/--accent-deep (deep dérivé, voir 015/020) ; clair suit
   --accent-light/--accent, plus clairs pour rester lisibles avec l'encre
   sombre du thème clair (var(--white) s'inverse avec le thème — 020).
   Le contraste WCAG documenté ici visait un seul bleu fixe ; le nouveau
   mesurage par couloir est tâche 6, pas ici. */
.btn-primary {
    background: var(--accent);
}
.btn-primary:hover {
    background: var(--accent-deep);
}
html[data-theme="light"] .btn-primary {
    background: var(--accent-light);
}
/* Tâche 6, passe de correction 1 (coordinateur) — au survol en thème
   clair, `var(--accent)` brut sous un texte quasi-noir donnait 3,98:1
   côté Dev (0066ff, sous le seuil AA 4,5:1 — préexistant à cette tâche,
   pas une régression de 132). --accent lui-même ne doit PAS bouger ici :
   .btn-submit (voir juste plus bas) lit le même jeton comme fond sous
   un texte BLANC et exige la direction opposée. Les deux usages
   exigent des directions opposées du MÊME jeton — pas séparables sans
   jeton dédié (cf. 132-couloirs-jetons.css, la même tension pour
   --accent-light).
   Corrigé en restreignant la portée à CE sélecteur précis : un mélange
   vers le blanc, calculé (pas deviné) — 90 % conserve le sens du survol
   (plus sombre que le repos en --accent-light : lum 0,36→0,20 côté Dev,
   0,41→0,29 côté Design) tout en remontant Dev à 4,63:1 (Design monte
   en bonus à 6,30:1, aucune casse). --accent et --accent-deep restent
   inchangés ; .btn-submit a son propre correctif séparé, ci-dessous
   (passe de correction 3 : le côté Design de .btn-submit, jamais
   vérifié, était réellement sous le seuil — voir ce correctif). */
html[data-theme="light"] .btn-primary:hover {
    background: color-mix(in srgb, var(--accent) 90%, white);
}

/* Tâche 6, passe de correction 3 (coordinateur) — .btn-submit (015 :
   `background: var(--accent); color: var(--white)`, texte forcé blanc
   en clair par 020) n'avait JAMAIS été mesuré côté Design en thème
   clair : fond brut c8724f sous texte blanc 17px/700 (seuil AA normal,
   4,5:1 — pas « grand texte », 17px < 18,66px) → 3,51:1, sous le seuil.
   Le rapport de la passe 1 ne citait que la marge côté Dev (4,83:1,
   exacte) pour justifier de ne pas toucher --accent globalement — vrai,
   mais incomplet : ça laissait Design non vérifié, et il échouait.
   --accent ne doit toujours pas bouger ici (mêmes raisons que pour
   `.btn-primary:hover` ci-dessus, direction opposée). Corrigé en
   restreignant la portée à CE sélecteur précis, au repos seulement (le
   survol, en accent-deep 82 %, passait déjà à 4,96:1 côté Design) :
   mélange vers le NOIR à 86 % — calculé, entre le 100 % du repos
   d'origine et le 82 % du survol (garde une distinction visuelle
   repos/survol). Mesuré : Design 3,51:1 → 4,58:1 ; Dev, non cassé par
   la même règle (pas de garde couloir nécessaire), 4,83:1 → 6,15:1
   (amélioré, aucune régression). */
html[data-theme="light"] .btn-submit {
    background: color-mix(in srgb, var(--accent) 86%, black);
}

/* =====================================================================
   127 · L'AMPLITUDE DES TRAVAUX
   ---------------------------------------------------------------------
   Ce site n'a qu'un objet : montrer les réalisations. Il les affichait en
   vignettes. Mesuré sur dist à 1280px, avant : l'image la plus large de
   la page faisait 703px, et le bento à trois colonnes
   (`320px 320px 250px`, puis `grid-auto-rows: 220px`) imposait ce format
   quelle que soit la définition du fichier — 2 880px pour PrestAvenue,
   2 560 pour Salohy, découpés en 343px.

   DEUX TAILLES, et l'écart se voit :
   - MAJEUR     : pleine largeur du conteneur, un par ligne. POEM Group,
                  qui garde son modificateur historique `--featured` (et
                  donc son survol du module 002/005, son curseur « VIEW »
                  du module 003 et son titre au pas sous-titre du module
                  001 : rien à réécrire).
   - SECONDAIRE : demi-largeur, deux par ligne, empilés sous 900px.
   Les dix secondaires faisaient un compte pair : aucune ligne orpheline.
   Depuis le retrait d'Omega il en reste neuf : la dernière carte visible,
   seule sur sa ligne, passe en pleine largeur (is-orpheline, module 132).
   `--wide` disparaît du HTML — c'était une troisième taille.

   Mesuré après : 1064px pour la majeure, 522px pour une secondaire, à
   1280 comme à 1440 (le conteneur plafonne à `--max-w`, 1160px). Onze
   images passent la barre des 500px au lieu de deux.

   POURQUOI UNE SEULE MAJEURE. Trois majeures donnaient un meilleur
   rythme sur le papier, mais la grille passait de 1 376px de haut à
   3 090 : +1 714px sur une page qui en fait 8 194 à 1280, pour un seuil
   `verify-rendu` de 9 500 qu'il est interdit de déplacer. Une majeure et
   cinq lignes de secondaires tiennent dans le budget (+810px mesurés).
   Le nombre de majeures est d'ailleurs contraint par la parité : onze
   cartes, il en faut un nombre IMPAIR pour que les secondaires se
   répartissent sans orpheline.

   RATIO 16/9 pour les deux, posé sur la CARTE (pas sur l'image) : les
   `<img>` sont déjà en `width/height: 100%` + `object-fit: cover`
   (module 000), donc la carte gouverne et l'image remplit. Les rangées
   en pixels fixes sont neutralisées (`grid-template-rows: none`,
   `grid-auto-rows: auto`), sinon elles gagnent contre `aspect-ratio`.
   16/9 et pas 3/2 : à 1280×720, une majeure en 3/2 ferait 709px de haut
   et remplirait l'écran entière ; en 16/9 elle en fait 598 et laisse
   voir qu'il y a une suite.

   LE RAIL — la réserve est GARDÉE, parce qu'il n'y en avait pas.
   Contrairement au portfolio, aucun module ici ne réserve de place à
   droite de `#work` : la grille remplit déjà tout le conteneur, son bord
   droit tombe à 1172px à 1280, et l'échine du rail (module 042) démarre
   à 1110 — elle recouvre DÉJÀ les cartes de la colonne de droite, de
   62px, avant toute modification. Le bord droit de la grille ne bouge
   donc pas d'un pixel : la majeure s'étend vers la GAUCHE, pas vers la
   droite. Aucune échappée à ouvrir, aucun seuil à poser, aucune
   régression possible sur le rail. Écarts mesurés entre le bord droit de
   la grille et le bord gauche de `.side-nav-vert` (l'échine, pas le
   libellé) : 1025 → −66px, 1280 → −62, 1440 → +32, 1920 → +582 ;
   identiques avant et après.

   Sous 769px, rien du tout — le carrousel à balayage du module 019
   (`display: flex !important`, scroll-snap, `.bento-swipe-hint`,
   `#bento-dots` construit par script.js sous 768) reste seul maître,
   intact.

   Rien n'anime dans ce module : pas de repli reduced-motion à écrire.
   ===================================================================== */

/* ── 769px et plus : une colonne, la carte gouverne sa hauteur ─────── */
@media (min-width: 769px) {
    #work .bento-grid {
        grid-template-columns: 1fr;
        grid-template-rows: none;
        grid-auto-rows: auto;
        gap: 1.25rem;
    }
    #work .bento-card,
    #work .bento-card--featured {
        grid-column: auto;
        grid-row: auto;
        height: auto;
        aspect-ratio: 16 / 9;
    }
}

/* ── 900px et plus : les secondaires se mettent deux par ligne ─────── */
@media (min-width: 900px) {
    #work .bento-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
    #work .bento-card--featured {
        grid-column: 1 / -1;
    }
}

/* =====================================================================
   LA RÉSERVE DU RAIL — elle n'existait pas, il a fallu la créer
   ---------------------------------------------------------------------
   Mesuré au navigateur, bord gauche de l'ÉCHINE du rail (le filet
   `.snv-item::before`, toujours peint) au pire de la course de scroll —
   la boîte de `.side-nav-vert` s'élargit de 24px quand l'élément actif
   passe son filet de 18 à 42px, donc on relève le MINIMUM sur toute la
   descente, pas la valeur d'un instant :

     largeur | échine à | bord droit grille | écart
       1025  |   859,7  |       984         | −124
       1100  |   933,2  |      1056         | −123
       1160  |   992,0  |      1114         | −122
       1200  |  1031,2  |      1132         | −101
       1280  |  1109,6  |      1172         |  −62
       1366  |  1193,9  |      1215         |  −21
       1440  |  1266,4  |      1252         |  +14
       1920  |  1736,8  |      1492         | +245

   Le rail traversait donc DÉJÀ les cartes de 1025 à ~1425px, avant toute
   modification : la grille remplissait le conteneur, et le conteneur
   passe sous le rail. Sur des vignettes de 343px on ne le voyait pas ;
   sur une photographie de 1064px, les filets et les numéros tranchent
   l'image et se lisent comme un défaut de rendu, pas comme un parti pris.
   La consigne est explicite : 900px sans rail valent mieux que 1064 avec.

   La réserve est posée sur `#work > .container` — donc sur l'en-tête, le
   filtre ET la grille — et pas sur la grille seule : insérer la grille
   sans insérer le filet du filtre au-dessus d'elle produirait un
   désalignement bien plus visible que le rail.

   Largeur du rail : 144,8px, constante (elle ne dépend pas du libellé
   actif, tous les éléments s'étirent à la largeur du plus large). Le
   bord gauche vaut donc `100vw − right(clamp(1rem,2vw,2.5rem)) − 144,8`,
   formule vérifiée exacte aux huit largeurs ci-dessus. On ajoute 16px de
   marge : `100vw` compte la barre de défilement classique là où la mise
   en page l'exclut (≤ 17px d'écart, dans le sens qui rapproche le rail),
   la marge l'absorbe.

   Résultat mesuré : 802px à 1025, 985 à 1280, 1027 à 1366, 1062 à 1440,
   1064 au-delà de 1425 où le rail est de toute façon dégagé. La cible de
   900px à 1280 est tenue (985), contre 703 avant, et plus rien ne passe
   sur une photo.

   Sous 1025px le rail n'existe pas (mesuré : absent à 1000 et 1024) :
   aucune réserve, la grille garde tout le conteneur. */
:root {
    --reserve-rail: calc(
        (min(100vw, var(--max-w)) - 100vw) / 2 - var(--gap) + clamp(1rem, 2vw, 2.5rem) + 161px
    );
}
@media (min-width: 1025px) {
    #work > .container {
        padding-right: calc(var(--gap) + max(0px, var(--reserve-rail)));
    }
    /* Le numéro de section est en position absolue : son `right: 0` se
       résout contre la boîte de PADDING du conteneur, il ne suivrait donc
       pas la réserve et resterait seul sous le rail. Le sélecteur reprend
       la forme exacte de celui du module 013 (`section[id]:not(#home) >
       .container::before`, spécificité 1-2-2) plus l'id : à spécificité
       égale, c'est l'ordre de cascade qui tranche, et 127 vient après. */
    section[id]#work > .container::before {
        right: max(0px, var(--reserve-rail));
    }
}

/* ── L'en-tête de section, à la mesure de la réserve ───────────────────
   `.work-header` est un `flex` en `space-between` à trois enfants :
   l'étiquette, le titre, le sous-titre. Mesuré à 1280 avant la réserve,
   ils occupaient 986px pour 1064 disponibles — 78px de mou. La réserve
   en reprend 79 : il n'en restait plus, et les DEUX derniers enfants se
   sont mis à passer à la ligne. Le titre sur deux lignes se tient (il
   est déjà composé en gros, la coupure se lit comme un parti pris) ;
   « Code · Design · Branding » coupé après « Design », avec « Branding »
   seul en dessous, non. Le sous-titre est court et ne se lit que d'un
   bloc : il ne se coupe pas. */
@media (min-width: 769px) {
    .work-sub {
        white-space: nowrap;
    }
}

/* ── Le voile, recalibré pour des cartes deux fois plus grandes ────────
   Le dégradé du module 124 avait été mesuré sur des vignettes de 343px.
   Il tenait le TITRE — mesuré ici après agrandissement, contraste réel
   (alpha composée avec l'opacité 0,9 du voile, fond relevé en MÉDIANE
   sous la boîte du texte, texte rendu transparent pour ne mesurer que ce
   qui est peint dessous) : de 7,16 à 16,5:1 selon les cartes, dans les
   deux thèmes. Aucun titre en défaut.

   L'ORDINAL, lui, est plus haut : mesuré, il tombe à 63-72% de la
   hauteur du voile, contre 37-52% pour le titre — là où le dégradé
   s'éteignait. Six cartes sur onze sous le seuil AA de 4,5:1, dans les
   DEUX thèmes :

     e-passAvenue 2,94 · Omega 2,88 · POEM 3,30 · Campagne 3,53
     Asantsika 3,63 · Salohy 3,81                        (sombre)
     e-passAvenue 2,96 · Omega 3,10 · POEM 3,37 · Campagne 3,51
     Asantsika 3,67 · Salohy 3,85                        (clair)

   Ce sont exactement les six captures à FOND CLAIR : sous l'ordinal de
   e-passAvenue, l'image brute est à 218/255 en thème sombre, 235 en
   clair. Monter l'ordinal au blanc plein n'y suffit pas (3,98 calculé,
   toujours sous le seuil) : c'est le voile qu'il faut porter plus haut.

   La bande forte est donc prolongée jusqu'à 72%, au-delà des deux
   positions. Un dégradé de plus, pas un backdrop-filter ni un blur
   (proscrits par la charte) : image de fond statique, aucun coût de
   composition, rien qui anime. */
.bento-overlay {
    background: linear-gradient(
        to top,
        rgba(0, 0, 0, 0.97) 0,
        rgba(0, 0, 0, 0.9) 45%,
        rgba(0, 0, 0, 0.78) 72%,
        rgba(0, 0, 0, 0.4) 88%,
        transparent 100%
    );
}
html[data-theme="light"] .bento-overlay {
    background: linear-gradient(
        to top,
        rgba(0, 0, 0, 0.94) 0,
        rgba(0, 0, 0, 0.88) 45%,
        rgba(0, 0, 0, 0.76) 72%,
        rgba(0, 0, 0, 0.4) 88%,
        transparent 100%
    );
}

/* ── Le pense-bête, devenu invisible sur une capture blanche ───────────
   `.sticky-note-bento` est du blanc à 0,85 sur un fond bleu à 0,10
   (module 015) : il avait été posé sur une carte sombre. Il se retrouve
   sur la capture d'écran BLANCHE de POEM, désormais en pleine largeur —
   blanc sur blanc, ni le texte ni la flèche ne se détachent en thème
   sombre (le thème clair, lui, tenait déjà : encre sombre sur jaune).
   Vérifié en capture avant/après.

   Un vrai fond opaque, qui ne dépend plus de ce qu'il y a dessous — et
   qui rend du même coup son `backdrop-filter` (module 015) inutile :
   la charte le proscrit, on le retire ici plutôt que de le garder pour
   rien. */
.sticky-note-bento {
    background: rgba(11, 15, 26, 0.94);
    border: 1px dashed rgba(255, 255, 255, 0.28);
    color: rgba(255, 255, 255, 0.92);
    backdrop-filter: none;
}

/* ========================================================================
   128 — RYTHME DES SECTIONS (alignement sur le portfolio, août 2026)
   ------------------------------------------------------------------------
   Porté de `125-rythme-des-sections.css` du portfolio, avec les deux
   adaptations que ce dépôt impose (voir plus bas).

   Ce qui existait ici et qui part avec ce module : DEUX filigranes
   numérotés par section, jamais lisibles ni l'un ni l'autre —
   - le petit numéro de coin (013), en absolu `top:1.5rem; right:0;
     z-index:5`, donc écrit PAR-DESSUS le contenu : mesuré à 1280px il
     tombait en travers de la carte bleue « Design » de #stack et du
     formulaire de #contact, et virait à l'encre sombre illisible en
     thème clair sur ces aplats bleus fixes ;
   - le numéro géant fantôme centré (041), contour `-webkit-text-stroke`
     sur `color: transparent`, qui passait derrière le titre de section
     en le salissant sans jamais se lire.

   Choix : un seul repère, réutilisé plutôt que doublé. Le numéro en mono
   capitales, suivi d'un filet pleine largeur, posé EN FLUX en tête de
   section — registre éditorial (sommaire de revue), pas décoratif. Le
   texte vient toujours de `data-section-num`, déjà présent en HTML sur
   les 5 conteneurs de la page d'accueil : aucun changement de structure,
   donc aucun risque i18n (build-en.mjs apparie des lignes entières).

   Coût en hauteur : ~40px par section (line-height 1.1 sur un texte de
   0.75rem + 0.35rem de padding + 1.25rem de marge), valeurs FIXES et non
   des clamp() — même coût, mesurable, à toutes les largeurs.

   Contraste : --gray-2 (et non --gray-3, la couleur qu'avait le numéro de
   coin) tient dans les deux thèmes sur les 4 sections dont le fond suit le
   thème. #contact fait exception, voir le bloc dédié ci-dessous.

   Aucune animation ici — numéro et filet sont statiques : pas de repli
   `prefers-reduced-motion` à prévoir.
   ======================================================================== */
section[id]:not(#home) > .container::before {
    content: attr(data-section-num);
    display: block;
    font-family: var(--mono);
    font-size: var(--pas-etiquette);
    font-weight: 500;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    line-height: 1.1;
    color: var(--gray-2);
    padding-bottom: 0.35rem;
    margin-bottom: 1.25rem;
    border-bottom: 1px solid var(--gray-4);
    pointer-events: none;
}

/* ------------------------------------------------------------------------
   ADAPTATION 1 — #contact a un aplat BLEU FIXE dans les deux thèmes
   (`#0a3ba8 !important`, module 101). --gray-2 et --gray-4 y virent à
   l'encre sombre en thème clair (rgba(10,14,26,·)) et deviennent
   illisibles sur ce bleu : le numéro et le filet y sont recoloriés en
   clair fixe, HORS thème. `!important` parce que la règle générique
   ci-dessus a le même sélecteur de base et vient du même module —
   sans lui, `html[data-theme="light"]` (spécificité supérieure, module
   020) reprendrait la main sur les jetons.
   #stack n'en a PAS besoin : ce sont ses CARTES qui sont bleues, pas la
   section — son fond suit le thème (mesuré : transparent, donc le
   --black de la page), et l'ouverture se pose au-dessus des cartes.  */
#contact > .container::before {
    color: rgba(255, 255, 255, 0.72) !important;
    border-bottom-color: rgba(255, 255, 255, 0.28) !important;
}

/* ------------------------------------------------------------------------
   ADAPTATION 2 — `.container` est LUI-MÊME une grille CSS sur #stack
   (module 103, `minmax(240px,320px) 1fr` ; seuil 1200px depuis que la
   réserve du rail a rétréci sa colonne de droite — il valait 1000px
   avant, cf. le commentaire de 103). En `display:block`
   (flux normal, ce que pose ce module), le pseudo-élément devient un item
   de grille auto-placé dans la PREMIÈRE cellule dès que le seuil est
   franchi, et décale d'un cran tous les items réels : le titre collant
   « Ce avec quoi je construis » descendrait sous l'ouverture et les deux
   cartes Dev/Design se retrouveraient dans la colonne étroite de 320px.
   `grid-column: 1 / -1` lui fait occuper une rangée entière, hors gabarit
   des colonnes réelles. Préféré à `position:absolute` : le coût en
   hauteur de l'ouverture reste identique PARTOUT, au lieu d'être à
   recompenser section par section.

   Recherche des autres grilles menée sur les 127 modules
   (`grep -n "\.container" *.css` puis lecture de chaque résultat) :
   `#stack .container` (103) est la SEULE `.container` en `display:grid`
   de ce dépôt. `.container` de base (000) est un bloc ; les rares autres
   mentions ne touchent que le padding mobile (018) ou le z-index (016).
   Hors de cette grille la propriété est sans objet (pas de parent
   display:grid → pas d'item de grille → `grid-column` ignoré), la règle
   est donc sans risque de fuite.
   ------------------------------------------------------------------------ */
#stack > .container::before {
    grid-column: 1 / -1;
}

/* ------------------------------------------------------------------------
   Coût en hauteur, mesuré page entière (dist servi, 2 thèmes) :
   +199px à 390, 768 et 1920px = 5 sections × ~40px, exactement le coût
   annoncé. Une seule exception, ASSUMÉE : sur #stack au-dessus de 1200px,
   l'ouverture occupe une RANGÉE de la grille du module 103, elle est donc
   suivie du `row-gap` de cette grille (clamp(2.5rem,5vw,5rem)) — 64px de
   plus à 1280px, 80px au-delà de 1600px. Neutraliser ce gap voudrait dire
   toucher au `gap` de la grille, qui espace aussi le défilé technique
   (`.tech-marquee-section`, déjà réglé sur ce gap + sa propre marge) : le
   remède coûterait plus cher que le mal, et à l'écran cette rangée d'air
   sous le filet se lit comme une respiration de tête de chapitre.
   ------------------------------------------------------------------------ */

/* =====================================================================
   129 — RÉSERVE POUR LE RAIL VERTICAL (module 042)
   ---------------------------------------------------------------------
   Porté de `126-rail-emprise-sections-restantes.css` du portfolio. Ici
   AUCUNE section ne réservait sa bande (le portfolio en avait déjà deux
   qui le faisaient) : `.container` va au bord droit sur les cinq, et le
   rail `.side-nav-vert` (`position:fixed`, bord droit, visible dès que le
   héros sort du champ) s'écrit donc par-dessus du contenu réel.

   Constaté à 1280px, capture à l'appui, sur les CINQ sections :
   - #work         : la 3ᵉ carte de `.bento-grid` (bord droit à 1171px) ;
   - #stack        : la carte bleue « Design » (`.skills-design`), les
                     numéros 00/06/09 du rail tombant en travers de ses
                     pastilles ;
   - #testimonials : la 3ᵉ carte du carousel `.testimonials-grid`, texte
                     de la citation compris ;
   - #clients      : la 3ᵉ carte de `.clients-grid` (logo PrestAvenue) ;
   - #contact      : les champs de `.contact-form` (bord droit à 1171px)
                     — le libellé « CONTACT » du rail, peint parce que la
                     section est active, s'écrivait dans le champ « nom ».
   Le bord GAUCHE du rail est à 1127px à cette largeur : 44px de contenu
   passaient dessous. Mesure faite sur `.side-nav-vert`, pas sur son
   libellé : le libellé n'est peint que sur l'élément actif, l'échine
   (numéros + filets) l'est toujours.

   --rail-emprise MESURÉE au navigateur (Playwright), pas devinée :
   `clientWidth - .side-nav-vert.getBoundingClientRect().left`, aux 12
   largeurs 1025→2200px.

   ⚠ PIÈGE, payé une fois ici : mesurer le rail À L'ARRÊT le sous-estime
   de 24px. `.side-nav-vert` est un flex column, sa largeur vaut celle de
   son item le plus large ; or le filet d'un `.snv-item` passe de 18px à
   42px quand la section devient active (module 042). Au chargement
   (page en haut, aucune section de contenu active) le rail mesure
   120,8px ; en défilement, dès que #work ou #contact s'allume, il
   mesure 144,8px. Une première version de ce module, réglée sur la
   mesure à l'arrêt (175px), ne laissait plus que 1,9px de dégagement
   vers 1414px — la largeur exacte où la réserve retombe sur var(--gap).
   Il faut donc balayer TOUTE la page à chaque largeur et retenir le
   maximum, pas lire une seule valeur au chargement.

   Largeur maximale retenue : 144,83px, constante à toutes les largeurs.
   S'y ajoute le décalage `right: clamp(1rem, 2vw, 2.5rem)`, qui grandit
   avec la fenêtre : emprise réelle 165,3px à 1025px → 184,8px à 2200px
   (clamp saturé). 200px = plafond mesuré + marge.
   ⚠ Valeur PROPRE À CE DÉPÔT : le portfolio réserve 220px parce que son
   rail affiche 8 entrées dont « Savoir-faire ». Ici le rail n'en montre
   que trois (00 Intro, 06 Projets, 09 Contact — SECTION_MAP de
   script.js n'a pas d'entrée pour stack/testimonials/clients) et son
   plus long libellé est « Contact ». Recopier 220px aurait raboté 20px
   de contenu pour rien.

   Formule, reprise telle quelle du portfolio (module 029) :
   - PAS de media query à double borne. Un correctif de ce genre
     (1025–1400px, padding fixe) recrée un chevauchement NET au-delà de
     la borne haute, alors que la marge naturelle du conteneur n'a pas
     encore grandi assez pour prendre le relais. La réserve doit décroître
     en CONTINU. `max()` est continue par construction : aucun saut, à
     aucune largeur.
   - `100%` et NON `100vw` : `100vw` compte la barre de défilement, alors
     que le rail (`position:fixed`, `right:…`) s'ancre sur `clientWidth`,
     qui l'exclut. `100%` dans un `calc()` de padding se résout sur la
     largeur du bloc englobant (la section, pleine `clientWidth`) — même
     base des deux côtés.
   - `padding-right` REMPLACE le padding-right hérité de `.container`
     (`var(--gap)`, longhand > shorthand, 129 chargé après 000) au lieu
     de s'y ajouter : la formule couvre donc ce gap perdu + le recul
     voulu, d'où le plancher `var(--gap)`.
   - `padding-right` plutôt que `max-width` : sur un conteneur
     `margin: 0 auto`, réduire la largeur max recentre la boîte et ne
     gagne que la MOITIÉ de la réduction du côté droit.

   Dégagement résiduel MESURÉ (bord gauche du rail moins bord droit de
   la boîte de contenu, page entière balayée, rail dans son état le plus
   large) : 34,7px à 1025px, 29,7px à 1280px, 26px au minimum vers
   1464px — le point où la réserve retombe sur `var(--gap)` — puis
   croissant (91px à 1600px, 245px à 1920px). Jamais négatif.

   Seuil 1025px : celui du rail lui-même (module 044 le cache sous 1025px,
   et script.js le sait aussi). Sélecteurs scopés par ID : `.container`
   seul est partagé par /marketplace et les pages projet, jamais
   `#work .container` & co, propres à la page d'accueil.
   ===================================================================== */
@media (min-width: 1025px) {
    #work .container,
    #stack .container,
    #testimonials .container,
    #clients .container,
    #contact .container {
        --rail-emprise: 200px;
        padding-right: max(
            var(--gap),
            var(--rail-emprise) - max(0px, (100% - var(--max-w)) / 2)
        );
    }
}

/* =====================================================================
   130 · Une animation signature par section
   ---------------------------------------------------------------------
   Relevé avant : #work, #testimonials et #clients n'avaient que l'entrée
   générique `section-rise`, commune à toutes les sections. Aucun geste
   propre, alors que la charte en demande UN par section.

   Registre : du mouvement VRAI — des objets qui arrivent et se posent.
   Rien de ce que la charte proscrit : pas de gradient-text, pas de glow,
   pas de boucle infinie décorative, pas de lévitation au survol, pas de
   blur sur un élément animé.

   ── Pourquoi le mouvement ne touche JAMAIS l'opacité ──────────────────
   Premier essai : une `animation` en `fill-mode: backwards` qui montait
   l'opacité de 0 à 1. Mesuré sur 5 chargements × 5 largeurs, **un
   chargement sur cinq laissait des cartes à opacity 0** — l'état de départ
   du fill restait figé quand l'animation ne démarrait pas.

   Et la cause première est ANTÉRIEURE à ce module : l'observateur
   d'intersection rate `.bento-grid` sur 2 chargements sur 4 à 1920×1080
   (vérifié sur la prod, sans ce fichier) — un défilement rapide traverse
   l'élément avant qu'il soit évalué, et `.fade-in` vaut `opacity: 0`.

   Donc : ce module ne déplace que le TRANSFORM. L'opacité reste l'affaire
   du `.fade-in` d'origine. Si `.visible` n'arrive jamais, la carte est
   simplement 22px plus bas — visible, lisible, cliquable. Un décalage ne
   peut pas faire disparaître un projet.
   ===================================================================== */

/* ── #work · les projets se posent en cascade ──────────────────────────
   Onze cartes qui arrivent d'un bloc, c'est un mur ; échelonnées, on les
   lit une par une, dans l'ordre de lecture. 55 ms d'écart : assez pour
   percevoir la succession, trop peu pour attendre. */
.bento-grid .bento-card {
    transform: translateY(22px);
    transition: transform 0.62s cubic-bezier(0.16, 1, 0.3, 1);
}
.bento-grid.visible .bento-card {
    transform: none;
}
.bento-grid.visible .bento-card:nth-child(2)  { transition-delay: 0.055s; }
.bento-grid.visible .bento-card:nth-child(3)  { transition-delay: 0.11s; }
.bento-grid.visible .bento-card:nth-child(4)  { transition-delay: 0.165s; }
.bento-grid.visible .bento-card:nth-child(5)  { transition-delay: 0.22s; }
.bento-grid.visible .bento-card:nth-child(6)  { transition-delay: 0.275s; }
.bento-grid.visible .bento-card:nth-child(7)  { transition-delay: 0.33s; }
.bento-grid.visible .bento-card:nth-child(8)  { transition-delay: 0.385s; }
.bento-grid.visible .bento-card:nth-child(9)  { transition-delay: 0.44s; }
.bento-grid.visible .bento-card:nth-child(10) { transition-delay: 0.495s; }
.bento-grid.visible .bento-card:nth-child(11) { transition-delay: 0.55s; }

/* ── #testimonials · les paroles se suivent, le guillemet s'ouvre ──────
   Les trois cartes portaient déjà leur propre `.fade-in` : elles
   arrivaient ensemble. Un décalage les met en file. Le guillemet, lui, se
   pose APRÈS sa carte — l'encre avant le texte, comme une citation qu'on
   ouvre. Il grandit depuis 0.88 : un objet qui se pose, pas un pulse. */
.testimonials-grid .testimonial-card:nth-child(2) { transition-delay: 0.11s; }
.testimonials-grid .testimonial-card:nth-child(3) { transition-delay: 0.22s; }

.testimonial-card .testimonial-mark {
    transform: scale(0.88) translateY(5px);
    transition: transform 0.5s cubic-bezier(0.34, 1.32, 0.5, 1) 0.2s;
}
.testimonial-card.visible .testimonial-mark {
    transform: none;
}
.testimonials-grid .testimonial-card:nth-child(2).visible .testimonial-mark { transition-delay: 0.31s; }
.testimonials-grid .testimonial-card:nth-child(3).visible .testimonial-mark { transition-delay: 0.42s; }

/* ── #clients · RIEN ICI, et c'est le correctif ────────────────────────
   J'avais posé une cascade en `transform`. Elle ne s'est jamais jouée :
   la section anime la propriété `translate` (module d'origine, dans un
   bloc `prefers-reduced-motion: no-preference`), donc `transition-property`
   vaut `translate` — mon `transform` changeait d'un coup, sans transition,
   et écrasait au passage le décalage déjà en place.

   Or le balayage des six cartes EXISTE DÉJÀ, échelonné de 0,05 à 0,35 s
   (`#clients .clients-grid.visible .client-card:nth-child(n)`). Il n'y
   avait rien à ajouter : il fallait ne rien casser. Bloc retiré.

   Leçon générale pour ce dépôt : avant d'animer, lire quelle PROPRIÉTÉ la
   section anime déjà. `translate` et `transform` ne se composent pas, la
   seconde gagne et annule la première. */

/* ── Le repli, non négociable ──────────────────────────────────────────
   Aucune de ces entrées ne porte d'information : sans mouvement, tout
   est à sa place immédiatement. */
@media (prefers-reduced-motion: reduce) {
    .bento-grid .bento-card,
    .testimonial-card .testimonial-mark {
        transform: none !important;
        transition: none !important;
    }
    .testimonials-grid .testimonial-card {
        transition-delay: 0s !important;
    }
}

/* =====================================================================
   131 · Les titres écrits en DEUX blocs se lisent sur deux lignes
   ---------------------------------------------------------------------
   Trois titres de section sont balisés en deux `.reveal-wrap` successifs
   — donc en deux LIGNES voulues : #stack, #clients, #contact. Les blocs
   étant en ligne, la coupure dépendait du hasard de la place disponible.

   Même règle que sur le portfolio (module 132 là-bas), pour que les deux
   sites se coupent au même endroit. `:has()` ne vise que les titres qui
   portent DEUX blocs : « Projets sélectionnés », qui n'en a qu'un,
   continue de se couper selon la place. */
.section-title:has(.reveal-wrap + .reveal-wrap) .reveal-wrap {
    display: block;
}

/* ============================================================
   132 · DEUX COULOIRS — Design / Dev

   Un seul système, deux jeux de jetons. Le couloir est un attribut
   sur <html> ; tout le reste du site lit les mêmes variables.

   Pas de police ajoutée : verify-typo porte fraunces: 0, et tirer
   deux voix d'une seule famille (Syne 800 serrée contre Syne 400
   espacée) est un geste plus fort que d'opposer un serif à un sans.
   Ce sont l'accent, le rayon, la mesure et la gouttière qui portent
   le changement de monde.
   ============================================================ */

/* --- Couloir Design : la création --------------------------- */
html[data-couloir="design"] {
    --accent: #ab5836;          /* terre cuite, assombrie (voir plus bas) */
    --accent-light: #e09b7d;    /* terre cuite — inchangée (voir plus bas) */
    --mesure: 30rem;            /* colonne courte, éditoriale */
    /* Plafond ramené de 3,9rem à 3rem — le MÊME que le couloir Dev.
       Mesuré, et ce n'est pas ce que je croyais : --gap est la gouttière
       horizontale de .hero-inner (padding: 0 var(--gap), module 000), pas un
       rythme vertical. Elle rétrécit la colonne du hero d'environ 1,2 px par
       pixel de gouttière, et la rangée des trois CTA a besoin de 573 px pour
       tenir sur une ligne. À 1280 px : 579 px disponibles en Dev (elle tient),
       572 px en Design avec 3,9rem — il manquait UN pixel, « Écouter une
       ambiance » passait à la ligne et finissait à 635 px, 26 px sous la ligne
       de flottaison d'un écran de 609 px. C'est le viewport réel du
       propriétaire, dans le couloir par défaut, sur le premier écran.
       Un plafond intermédiaire (3,4rem) ne rendait que 3 px : trop court pour
       un texte dont la largeur dépend des métriques de la police.
       La respiration reste perceptible là où elle se voit — 2rem contre
       1,5rem au plancher (+33 % sur mobile) et 5vw contre 4vw entre les deux
       (+25 % de 640 à 960 px) ; c'est seulement au-delà de ~960 px, là où la
       rangée de CTA touche le bord, que les deux couloirs partagent le
       plafond. */
    --gap: clamp(2rem, 5vw, 3rem);
    --radius: 2px;              /* arêtes franches */
    --pas-titre: clamp(2.1rem, 3.8vw, 3rem);
}

/* Tâche 6 — le contraste mesuré sur les 4 combinaisons (thème × couloir),
   pas seulement sur le couloir/thème par défaut.

   1. `.btn-primary` (fond RAW --accent, texte blanc, thème sombre) :
      c8724f donnait 3,51:1, sous le seuil AA — le bouton principal du
      hero, jamais mesuré avant cette tâche. C'est un fond en COULEUR
      UNIE (background-color résolu), aucune ambiguïté de mesure.
      Assombri à ab5836 (même teinte, même saturation, L 54,7 % → 44,1 %) :
      5,02:1 mesuré.
      ⚠ ab5836 est PLUS SOMBRE que l'original, à l'inverse d'une « version
      éclaircie pour fond sombre » : ici l'accent sert de FOND (pas de
      texte) sous du blanc — la direction qui marche est l'inverse de
      celle qui vaut pour un accent utilisé comme texte (point 3). Vu et
      confirmé au pixel avant d'écrire cette valeur.
      Effet de bord repéré et corrigé séparément : `.couloir-link.is-active`
      (133-couloirs-bascule.css) lit aussi var(--accent) et a perdu du
      contraste par ricochet — restauré à sa propre couleur dédiée dans
      133, pas ici (piste de correction 1, coordinateur).
   2. Thème clair : `.btn-primary` y lit --accent-light (fond) au repos et
      --accent (fond) au survol, avec un texte QUASI-NOIR (var(--white)
      s'inverse en clair, 020) — cette lecture reste correcte avec
      --accent inchangé (5,48:1 mesuré) : aucune raison de le toucher en
      thème clair, donc une redéclaration ci-dessous restaure la valeur
      d'origine pour ce thème seulement. */
html[data-theme="light"][data-couloir="design"] {
    --accent: #c8724f;
}

/* 3. `.typewriter-text.is-static` (mot du hero, 70 px/700 — grand texte
      WCAG, seuil AA 3:1 et non 4,5:1) lit --accent-light, sur le mesh
      WebGL du hero — un fond NON UNI, en dégradé.

      Passe de correction 1 (coordinateur) : la première mesure de cette
      tâche échantillonnait un point arbitraire sur le texte rendu
      transparent — un artefact (pixels d'anticrénelage, points chauds du
      dégradé), pas la vraie relation texte/fond. Le coordinateur a
      capturé le mot, l'a regardé (lisible), puis mesuré correctement en
      séparant encre et fond par couleur (verify-rendu.mjs,
      sondeTexteSurFondNonUni). Reprenant sa méthode, cette tâche a donc
      RÉEXAMINÉ ses deux corrections initiales, pas seulement la valeur
      qui restait en échec :

      Thème SOMBRE : mesuré à nouveau, e09b7d (la valeur D'ORIGINE,
      jamais touchée par la première passe) donne 4,23 à 6,06:1 selon le
      viewport — largement au-dessus de 3:1. La première passe l'avait
      éclairci vers edc6b6 pour satisfaire la mesure par point, qui
      trouvait des échecs qui n'existaient pas. « Ne touche pas aux
      teintes pour satisfaire un instrument faussé » (coordinateur) :
      REVERT à e09b7d, la valeur d'origine — inutile de la changer.
      Thème CLAIR : mesuré à nouveau avec la bonne méthode, e09b7d
      d'origine donne 1,65 à 2,00:1 selon le viewport — un VRAI échec,
      pas un artefact. Conserve donc la correction de la première passe :
      conflit réel, pas une marge courte. `.btn-primary` clair lit la
      MÊME variable comme FOND sous un texte quasi-noir (point 2, mais
      pour --accent-light), qui exige l'inverse : un fond CLAIR. Vérifié
      avec les pixels réels les plus défavorables : la fenêtre qui
      satisferait les deux exigences à la fois n'existe pas. Assombri à
      c65d2f (même teinte/saturation, L 68,4 % → 48,0 %) comme meilleur
      compromis : `.btn-primary` clair reste ≥ 4,5:1 à tous les viewports
      (marge réelle, mesurée), et le mot passe de 1,65-2,00:1 à
      2,78-2,86:1 selon le viewport (mesuré avec la bonne méthode) —
      amélioration nette et réelle, mais NE PASSE PAS 3:1 partout. Un
      seul jeton porte ici deux rôles opposés (texte sur fond clair ↔
      fond clair sous texte sombre) : documenté, pas résolu — ce n'est
      pas réglable par une teinte (voir le rapport de tâche 6, § réserve
      « --accent-light scindé en deux jetons », 59 points d'appel
      recensés par le coordinateur). */
html[data-theme="light"][data-couloir="design"] {
    --accent-light: #c65d2f;
}

html[data-couloir="design"] h1,
html[data-couloir="design"] h2,
html[data-couloir="design"] .bento-card h3 {
    font-weight: 800;
    letter-spacing: -0.04em;
}

/* Les étiquettes quittent le mono pour Syne très espacée : c'est la
   seconde voix de la famille, celle qui fait respirer la page. */
html[data-couloir="design"] .section-label,
html[data-couloir="design"] .bento-badge {
    font-family: var(--display);
    font-weight: 400;
    letter-spacing: 0.3em;
}

/* --- Couloir Dev : le système ------------------------------- */
html[data-couloir="dev"] {
    --accent: #0066ff;
    --accent-light: #60a5fa;
    /* 34rem, pas 38 : la spec annonçait « Dev (actuel) : 38rem » alors que la
       valeur en place valait 34rem (000-base-tokens-reset.css). Le couloir Dev
       n'est pas censé changer la mesure — il est l'état d'origine. Mesuré à
       768 px : 84 caractères par ligne avec 38rem, contre un seuil de 75. */
    --mesure: 34rem;
    --radius: 10px;
}

/* Tâche 6 — même mesure que le couloir Design ci-dessus, sur les 4
   combinaisons, avec la méthode corrigée en passe de correction 1
   (séparation encre/fond par couleur, pas un point d'échantillon —
   voir le commentaire du bloc Design ci-dessus). Deux différences avec
   Design :
   - --accent (fond de `.btn-primary`/`.btn-submit` en thème sombre, texte
     blanc) donnait DÉJÀ 4,83:1 avec 0066ff — au-dessus du seuil AA normal
     (4,5:1), donc INCHANGÉ. Le brief anticipait des échecs seulement côté
     Design ; mesuré, ce n'est pas le cas ici.
   - --accent-light (`.typewriter-text.is-static`, seuil grand texte 3:1)
     en thème SOMBRE : remesuré avec la bonne méthode, 60a5fa (la valeur
     D'ORIGINE) donne 4,23 à 5,50:1 selon le viewport — au-dessus de 3:1
     partout. La première passe l'avait éclairci vers b8d7fd pour
     satisfaire l'ancienne mesure par point, faussée. REVERT à 60a5fa,
     comme pour Design : inutile de la changer.
   En thème clair, même conflit structurel qu'en Design (--accent-light
   sert aussi de fond `.btn-primary` sous un texte quasi-noir, qui exige
   l'inverse — fenêtre commune inexistante, vérifié). Remesuré avec la
   bonne méthode : 60a5fa d'origine donne 1,81 à 2,00:1 selon le viewport
   (un vrai échec, pas un artefact). Assombri à 0e76f7 (même
   teinte/saturation, L 67,3 % → 51,2 %) comme meilleur compromis :
   `.btn-primary` clair reste ≥ 4,5:1 à tous les viewports (marge réelle,
   mesurée), le mot passe de 1,81-2,00:1 à 2,95-2,97:1 selon le viewport
   — amélioration nette et réelle, tout près du seuil sans toujours
   l'atteindre. Même réserve que Design, documentée dans le rapport de
   tâche 6, pas résolue ici.
   `.btn-primary` au SURVOL, thème clair : réel (fond en couleur unie,
   aucune ambiguïté de mesure), 3,98:1 avec 0066ff — préexistant à cette
   tâche, corrigé en restreignant la portée à ce seul sélecteur, sans
   toucher --accent ici : voir 126-lisibilite-textes-secondaires.css. */
html[data-theme="light"][data-couloir="dev"] {
    --accent-light: #0e76f7;
}

html[data-couloir="dev"] h1,
html[data-couloir="dev"] h2 {
    font-weight: 700;
    letter-spacing: -0.02em;
}

/* La bascule se sent sans être animée : le navigateur interpole seul
   les couleurs. Aucune keyframe, aucun jank.

   ⚠ `body` UNIQUEMENT. `.bento-card` porte déjà
   `transition: box-shadow 0.4s ease, border-color 0.4s` (module 000) : une
   seconde déclaration ne s'ajoute pas, elle REMPLACE — le survol des cartes
   perdrait son ombre. Même piège que translate/transform. Les jetons qui
   changent sur une carte sont --radius et --accent ; ni l'un ni l'autre ne
   demande d'être animé. */
html[data-couloir] body {
    transition: background-color 0.4s var(--ease-out-expo),
                color 0.4s var(--ease-out-expo);
}

@media (prefers-reduced-motion: reduce) {
    html[data-couloir] body { transition: none; }
}

/* --- L'ordre des projets suit le couloir --------------------
   La grille bento a un ordre naturel ; `order` le réarrange sans
   dupliquer une seule carte. Les valeurs sont espacées pour laisser
   la place à un projet ajouté plus tard.

   ⚠ `#work` en tête de chaque sélecteur, et ce n'est pas décoratif :
   le module 127 place les cartes avec `#work .bento-card--featured`
   (spécificité d'ID). Sans l'ID, ces règles perdraient en silence —
   `order` posé, grille inchangée. Mesuré avant d'écrire : à ≥ 769 px la
   vedette a `grid-row: auto` (127 bat le `1/3` du module 000), elle est
   donc DANS le flux et `order` l'atteint comme les dix autres. */
html[data-couloir="design"] #work .bento-card[data-work-cat="design"],
html[data-couloir="design"] #work .bento-card[data-work-cat="brand"] { order: 10; }
html[data-couloir="design"] #work .bento-card[data-work-cat="dev"] { order: 20; }

html[data-couloir="dev"] #work .bento-card[data-work-cat="dev"] { order: 10; }
html[data-couloir="dev"] #work .bento-card[data-work-cat="design"],
html[data-couloir="dev"] #work .bento-card[data-work-cat="brand"] { order: 20; }

/* La vedette ouvre son couloir : sans ce cran, Tangente et Alasoa (avant
   elle dans le DOM, même `order`) la repousseraient en deuxième ligne.
   `.bento-card` dans le sélecteur n'est pas de trop : sans lui, (1,2,0)
   perd contre le `.bento-card[data-work-cat]` (1,3,0) du bloc précédent —
   mesuré : Tangente passait devant la vedette. */
html[data-couloir="design"] #work .bento-card[data-vedette-design],
html[data-couloir="dev"] #work .bento-card[data-vedette-dev] { order: 5; }

/* La case maîtresse : pleine largeur à partir de 2 colonnes (module 127,
   ≥ 900 px). En dessous, une colonne puis une bande flex : toutes les
   cartes ont la même taille, seul l'ordre porte la vedette.
   La carte détrônée rejoint le flux — sans ça, deux cartes en pleine
   largeur, l'une au-dessus de l'autre, et POEM garderait sa case en
   couloir Design.

   UNE seule case maîtresse, en tête, la vedette du couloir — avec UNE
   exception, décidée par le propriétaire au retrait d'Omega (29/09/2026) :
   la DERNIÈRE carte visible, quand elle reste seule sur sa ligne, s'étale
   elle aussi (classe is-orpheline, posée par work-filter.js après mesure).
   Dix cartes = vedette + neuf : la neuvième laissait une demi-ligne vide.
   Aucune autre carte ne passe en pleine largeur ; verify-couloirs.mjs
   n'admet que celle-là (dernière visible, seule sur sa ligne, reste
   impair) et refuse toute carte large au milieu ou une deuxième carte
   large. Le sélecteur porte `.bento-grid >` pour battre la ligne
   `.bento-card--featured:not(…)` ci-dessus : POEM rétrogradée peut être
   la dernière en couloir Design. */
@media (min-width: 900px) {
    html[data-couloir="design"] #work [data-vedette-design],
    html[data-couloir="dev"] #work [data-vedette-dev] { grid-column: 1 / -1; }

    html[data-couloir="design"] #work .bento-card--featured:not([data-vedette-design]),
    html[data-couloir="dev"] #work .bento-card--featured:not([data-vedette-dev]) { grid-column: auto; }

    html[data-couloir] #work .bento-grid > .bento-card.is-orpheline { grid-column: 1 / -1; }
}

/* Le titre suit la case, pas la classe : `.bento-card--featured .bento-title`
   (modules 000 et 001) reste sur POEM. La vedette Design prend le pas
   d'intertitre ; POEM rétrogradée reprend le pas des cartes ordinaires —
   --pas-lecture, la même valeur que le 1.0625rem du module 000, en jeton
   (verify-typo plafonne les tailles littérales à 8). */
html[data-couloir="design"] #work [data-vedette-design] .bento-title { font-size: var(--pas-sous-titre); }
html[data-couloir="design"] #work .bento-card--featured:not([data-vedette-design]) .bento-title { font-size: var(--pas-lecture); }

/* « · FEATURED » n'est vrai que dans un couloir. Les numéros, eux, sont
   des numéros de catalogue : ils ne bougent pas. */
.bento-vedette { display: none; }
html[data-couloir="design"] [data-vedette-design] .bento-vedette,
html[data-couloir="dev"] [data-vedette-dev] .bento-vedette { display: inline; }

/* Le pense-bête « 20 modules livrés sur ce back-office → » est ancré en
   haut à gauche de la grille (position absolue, module 014) : en couloir
   Design il pointerait la campagne imprimée. Masqué — absolu, il ne laisse
   aucun trou. Une copie propre à chaque couloir serait mieux ; c'est de la
   copie en deux langues, notée pour le propriétaire. */
html[data-couloir="design"] .sticky-note-bento { display: none; }

/* Le glissement des cartes (couloir.js, FLIP) n'écrit que `transform` via
   element.animate() : rien à déclarer ici, et surtout PAS de `transition`
   sur .bento-card — le module 130 y transitionne déjà `transform` (0,62 s,
   délais nth-child) et une seconde déclaration la remplacerait. Le repli
   reduced-motion est décidé en JS (matchMedia) : les cartes se replacent
   sans animation, l'ordre ci-dessus reste juste sans elle. */

/* --- L'accent LISIBLE, pour les rôles de texte -----------------
   Relecture d'ensemble, point 3. Déplacer --accent (#3b82f6 → #0066ff /
   #ab5836) a fait tomber tout ce qui l'emploie EN COULEUR DE TEXTE sur un
   aplat opaque : .section-label 5,44 → 3,83:1, le « 2h » du hero 5,09 →
   3,45:1, .snv-num 5,44 → 3,98:1. C'est le conflit de double rôle déjà
   documenté pour --accent-light (rapport de tâche 6), cette fois sur
   --accent : la valeur qui va bien en FOND de bouton sous du blanc ne va
   pas en TEXTE sur un aplat, et aucune valeur unique ne satisfait les deux
   — les deux exigences tirent en sens opposés.

   Le remède est un jeton dédié, DÉRIVÉ de l'accent du couloir et non
   saisi à la main : l'accent reste la couleur d'identité, --accent-texte
   n'en est que la lecture. Un seul pourcentage par thème, choisi en
   balayant les fonds réellement peints (#050811, #0a0e1a, #0f121b,
   #1a1a1a en sombre ; #f1f3f5, #faf6ec, #fafaf8, #fff en clair) et en
   retenant le pire :

     sombre  84 % + blanc → design #b87356 (4,66-5,36:1) · dev #297eff (4,60-5,29:1)
     clair   design 80 % + noir → #a05b3f (4,64-5,16:1)
             dev     92 % + noir → ratios 5,0-5,6:1

   Deux pourcentages en clair et non un seul : 80 % suffirait pour les deux
   mais assombrirait le bleu bien au-delà du nécessaire (le dev n'a besoin
   que de 92 %), et un accent inutilement noirci n'est plus l'accent.
   color-mix est déjà une dépendance dure de ce site (--accent-deep, 015 ;
   .btn-primary:hover et .btn-submit, 126) : aucun repli supplémentaire. */
html[data-couloir] {
    --accent-texte: color-mix(in srgb, var(--accent) 84%, white);
}
html[data-theme="light"][data-couloir="design"] {
    --accent-texte: color-mix(in srgb, var(--accent) 80%, black);
}
html[data-theme="light"][data-couloir="dev"] {
    --accent-texte: color-mix(in srgb, var(--accent) 92%, black);
}

/* --- Le rayon cesse d'être un jeton mort ------------------------
   Relecture d'ensemble, point 9. --radius était annoncé par la spec comme
   l'un des cinq écarts entre les deux mondes (« arêtes franches ») mais ne
   sortait pas à l'écran : quatre emplois seulement, tous dans le module
   000, et les composants qui portent le registre — cartes, boutons,
   champs, badges — ont chacun leur littéral plus loin dans la cascade
   (015 : 14px pour les cartes, 10px pour boutons et champs ; 000 : 6px
   pour les badges).

   Ces trois échelles sont RAMENÉES au jeton par un multiplicateur, et les
   multiplicateurs sont choisis pour reproduire EXACTEMENT les valeurs
   d'aujourd'hui côté Dev (--radius: 10px) : 10 × 1,4 = 14, 10 × 0,6 = 6.
   Le couloir Dev ne bouge donc pas d'un pixel — vérifiable au calcul — et
   le couloir Design gagne ses arêtes franches partout à la fois (2px,
   2,8px, 1,2px) au lieu de deux panneaux isolés.
   Portée `html[data-couloir]` : une page sans couloir garde sa cascade. */
html[data-couloir] .btn-primary,
html[data-couloir] .btn-submit,
html[data-couloir] .form-group input,
html[data-couloir] .form-group textarea {
    border-radius: var(--radius);
}
html[data-couloir] .bento-card,
html[data-couloir] .service-card,
html[data-couloir] .activity-terminal,
html[data-couloir] .proj-kpi {
    border-radius: calc(var(--radius) * 1.4);
}
html[data-couloir] .bento-badge {
    border-radius: calc(var(--radius) * 0.6);
}

/* Correction de la vague précédente : `.proj-gallery-item` et
   `.proj-overview-img` étaient dans la liste ci-dessus, avec le
   multiplicateur des cartes de la grille (×1,4 → 14px). C'était faux :
   sur les pages projet, project.css leur donne 12px et 16px, et mon
   sélecteur — plus spécifique — les avait silencieusement déplacés en
   couloir Dev. Leurs vraies valeurs, leurs vrais multiplicateurs. */
html[data-couloir] .proj-gallery-item { border-radius: calc(var(--radius) * 1.2); }
html[data-couloir] .proj-overview-img { border-radius: calc(var(--radius) * 1.6); }

/* --- Le reste du registre, branché ------------------------------
   Relecture d'ensemble, point 2 : le jeton n'était branché qu'à moitié, et
   ça se lisait comme un oubli, pas comme un parti pris — sur le premier
   écran, « Voir les réalisations » avait des arêtes franches pendant que
   « Parler de votre projet », même poids et même rôle, restait arrondi.
   Un demi-geste est pire que pas de geste : le branchement est fini.

   Même méthode que ci-dessus, et c'est ce qui rend l'opération sûre : le
   multiplicateur reproduit EXACTEMENT la valeur d'aujourd'hui côté Dev
   (--radius: 10px). Rien ne bouge en Dev ; en Design tout devient franc.
     témoignages 22px → ×2,2 · clients / vitrine / atelier 16px → ×1,6
     CTA de statut, intro vocale, CTA de barre 6px → ×0,6
   `.testimonial-card` et la bannière vivent dans mobile-premium.css, chargé
   APRÈS styles.css : leurs déclarations sont à (0,1,0), les nôtres à
   (0,2,1) — la spécificité gagne sans dépendre de l'ordre des fichiers.
   Sous 768px, ces mêmes feuilles reposent le rayon en !important (menu
   plein écran, cartes empilées) : le mobile garde ses coins arrondis dans
   les deux couloirs. Limite connue, pas une omission. */
html[data-couloir] .testimonial-card { border-radius: calc(var(--radius) * 2.2); }
html[data-couloir] .client-card,
html[data-couloir] .showcase-card,
html[data-couloir] .showcase-atelier { border-radius: calc(var(--radius) * 1.6); }
html[data-couloir] .showcase-status-cta,
html[data-couloir] .voice-intro-btn,
html[data-couloir] .nav-cta { border-radius: calc(var(--radius) * 0.6); }

/* La bannière cookies. La vague précédente ne redressait sa pilule que
   dans le couloir Design et le disait : « reste visible en couloir Dev — à
   corriger, hors de ce chantier ». C'est fait, et à la racine : le littéral
   100px de mobile-premium.css vaut désormais 6px, donc la pilule ne revient
   ni en couloir Dev ni sur une page sans couloir.
   Le multiplicateur 0,6 est celui de la famille des petits boutons de barre
   (.nav-cta, .showcase-status-cta, .voice-intro-btn, juste au-dessus) :
   Dev retombe sur 10 × 0,6 = 6px, exactement le nouveau littéral — le
   couloir Dev est donc identique au pixel à une page sans couloir, comme
   pour tout le reste de --radius. Design garde ses arêtes franches. */
html[data-couloir] .cookie-banner-btn { border-radius: calc(var(--radius) * 0.6); }

/* --- Le badge des cartes suit l'accent --------------------------
   Relecture d'ensemble, point 6. `.bento-badge.dev` (015) posait un fond
   et une bordure en rgba(59,130,246,…) codés en dur pendant que son texte,
   lui, lisait var(--accent-light) : en couloir Design, du texte terre
   cuite dans une pastille bleue, sur CHAQUE carte de projet. Le fond et la
   bordure sont dérivés de l'accent aux mêmes opacités (12 % et 35 %).
   Le badge `.design` garde son violet : c'est --terra-light, une couleur
   qui NOMME un métier — même raison que « { Dev } × [ Design ] » au pied
   de page (ruling de tâche 8). */
html[data-couloir] .bento-badge.dev {
    border-color: color-mix(in srgb, var(--accent) 35%, transparent);
    background: color-mix(in srgb, var(--accent) 12%, transparent);
}

/* --- Les OMBRES suivent l'accent, elles aussi -------------------
   Repéré à l'œil sur la production par le propriétaire : le bouton
   « Envoyer le message » a bien un fond terre cuite en couloir Design,
   mais son ombre reste bleue — un contour bleu d'un pixel et un halo bleu
   de 32 px sous un bouton terre cuite.

   Pourquoi personne ne l'avait vu : tous les balayages de ce chantier ont
   vérifié les fonds, les encres, les bordures et les images de fond.
   Jamais les ombres. Même classe d'angle mort que les précédentes, sur une
   propriété de plus.

   Relevé fait dans le navigateur, pas au grep : 44 déclarations de
   box-shadow contiennent un bleu d'accent codé en dur, mais seules
   celles listées ci-dessous sont encore PEINTES (les autres sont écrasées
   plus loin dans la cascade, ou visent des classes absentes du HTML
   construit — voir le rapport). Le tri est celui du reste du chantier :
   ce qui est un ACCENT suit le couloir, ce qui NOMME ou ce qui est une
   IMAGE reste. Restent donc bleus, délibérément :
     · .dp-swatch — le nuancier ; le halo de la pastille EST l'échantillon
       de couleur qu'elle propose, il ne peut pas suivre l'accent courant ;
     · .orb-core / .orb-satellite / .atelier-card-pulse / .showcase-atelier
       — violets, l'identité de la carte Atelier, pas l'accent du site ;
     · .hero-dual-tag.code / .design — la paire qui NOMME les deux métiers
       (même règle que « { Dev } × [ Design ] », ruling de tâche 8) ; de
       toute façon éteinte par le module 100 aujourd'hui.

   Dérivation, pas invention : color-mix sur var(--accent) aux opacités
   d'origine. Sur une page SANS couloir, --accent vaut #3b82f6 en thème
   sombre — donc les valeurs héritées de rgba(59,130,246,·) sont
   reproduites à l'identique ; celles héritées de rgba(0,102,255,·) le sont
   en couloir Dev (--accent: #0066ff), ce qui est le cas de toutes les
   pages construites. Aucun rayon n'est agrandi : la charte interdit les
   box-shadow de plus de 40 px sur élément animé, et les trois survols qui
   dépassent déjà (cartes, visuel de projet) sont antérieurs — ils changent
   de teinte, pas de taille. */
/* `.btn-submit` n'apparaît PAS dans cette liste, et c'est volontaire : son
   halo a d'abord été teint en terre cuite ici, puis carrément éteint à la
   source (module 015). Une ombre qui n'existe plus n'a pas besoin de suivre
   l'accent — la charte bannit les halos, et le module 100 avait déjà tranché
   pour les boutons voisins. Rien à dériver, rien à écraser au survol. */

/* L'anneau de focus des champs : sa bordure lisait déjà var(--accent)
   (015), l'anneau non — un liseré terre cuite doublé d'un halo bleu. */
html[data-couloir] .form-group input:focus,
html[data-couloir] .form-group textarea:focus {
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 15%, transparent);
}

html[data-couloir] .tech-pill:hover {
    box-shadow: 0 16px 32px color-mix(in srgb, var(--accent) 20%, transparent);
}

/* Seule la carte VEDETTE porte un liseré coloré au survol : les autres
   ont un liseré blanc à 10 % (mesuré). On ne touche donc qu'elle, et on
   redéclare ses trois couches — box-shadow n'est pas additif.
   Sa spécificité doit rester au-dessus de la règle générique du même
   bloc, sinon la vedette perdrait son liseré au profit du blanc. */
html[data-couloir] .bento-card--featured:hover {
    box-shadow:
        0 24px 60px rgba(0, 0, 0, 0.65),
        0 0 0 1px color-mix(in srgb, var(--accent) 25%, transparent),
        0 0 40px color-mix(in srgb, var(--accent) 8%, transparent);
}
html[data-couloir] .bento-card:hover .bento-badge {
    box-shadow: 0 8px 18px color-mix(in srgb, var(--accent) 25%, transparent);
}

html[data-couloir] #progress-bar {
    box-shadow: 0 0 8px color-mix(in srgb, var(--accent) 50%, transparent);
}
html[data-couloir] .side-dot.active::after {
    box-shadow: 0 0 14px color-mix(in srgb, var(--accent) 70%, transparent);
}

/* Pages projet : project.css est chargé APRÈS styles.css, mais ses
   déclarations sont à (0,1,0) contre (0,2,1) ici — la spécificité gagne
   sans dépendre de l'ordre des fichiers, comme pour --radius plus haut. */
html[data-couloir] .proj-overview-img {
    box-shadow:
        0 0 0 1px rgba(0, 0, 0, 0.5),
        0 32px 80px rgba(0, 0, 0, 0.6),
        0 0 60px color-mix(in srgb, var(--accent) 7%, transparent);
}
html[data-couloir] .proj-overview-img:hover {
    box-shadow:
        0 0 0 1px color-mix(in srgb, var(--accent) 20%, transparent),
        0 40px 100px rgba(0, 0, 0, 0.7),
        0 0 80px color-mix(in srgb, var(--accent) 10%, transparent);
}
/* La pastille numérotée des étapes de processus (une seule page projet
   la porte aujourd'hui, salohy-marketplace) : son fond suit déjà
   l'accent, son ombre portée non. L'anneau sombre reste neutre — c'est
   un détourage sur le fond de page, pas un accent. */
/* MISE À JOUR — la pastille numérotée n'existe plus : le disque en dégradé
   a été remplacé par le pictogramme de l'étape, posé en masque (project.css).
   Un halo et un anneau de détourage n'ont plus d'objet autour d'un tracé
   ouvert ; ils l'entouraient d'un cercle fantôme. La règle est conservée,
   vidée, plutôt que supprimée : c'est elle qui bat la déclaration de
   project.css, chargée après styles.css. */
html[data-couloir] .proj-process-step::before {
    box-shadow: none;
}

/* `.btn-primary` avait ici, un temps, un halo dérivé de l'accent : le CTA
   « Voir le site live » des pages projet gardait le sien parce que le
   module 100 ne l'éteignait que sous `.hero-actions`. La bonne réponse
   n'était pas de le teindre correctement mais de l'éteindre, comme ses
   voisins : le halo est banni par la charte. Fait à la source (015) et la
   famille est nommée dans le module 100. Rien à dériver ici. */

/* ============================================================
   133 · LA BASCULE DESIGN · DEV
   Même registre que le sélecteur de langue (055) : mono capitales,
   séparateur point médian. Pas d'interrupteur à glissière — ce
   serait une pilule, bannie par la charte.
   ============================================================ */
.couloir-switch {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-family: var(--mono, 'JetBrains Mono', monospace);
    font-size: var(--pas-etiquette);
    letter-spacing: 0.06em;
    margin-right: 0.75rem;
}

.couloir-link {
    background: none;
    border: 0;
    cursor: pointer;
    font: inherit;
    color: rgba(255, 255, 255, 0.55);
    padding: 0.25rem 0.5rem;
    border-radius: 6px;
    transition: color 0.2s ease, background 0.2s ease;
}

.couloir-link:hover,
.couloir-link:focus-visible { color: rgba(255, 255, 255, 0.92); }

.couloir-link.is-active {
    color: var(--accent);
    /* Repli pour un navigateur qui ignore color-mix() : sans lui, un
       bouton actif afficherait juste une couleur de texte sans fond. */
    background: rgba(255, 255, 255, 0.08);
    background: color-mix(in srgb, var(--accent) 12%, transparent);
}

.couloir-sep { color: rgba(255, 255, 255, 0.35); }

html[data-theme="light"] .couloir-link { color: rgba(10, 10, 10, 0.55); }
html[data-theme="light"] .couloir-link:hover,
html[data-theme="light"] .couloir-link:focus-visible { color: rgba(10, 10, 10, 0.9); }
html[data-theme="light"] .couloir-sep { color: rgba(10, 10, 10, 0.35); }

/* Repli lisible en thème clair. Piège de spécificité : la règle de thème
   ci-dessus, `html[data-theme="light"] .couloir-link` (0,2,1 — attribut +
   classe + élément html), bat `.couloir-link.is-active` (0,2,0) — sans ce
   correctif, actif et inactif rendent EXACTEMENT la même couleur en
   thème clair (mesuré : les deux à rgba(10,10,10,.55)). Même piège que
   .lang-link, déjà résolu par un correctif dédié dans 055 — même remède
   ici, décliné par couloir puisque la couleur active suit --accent.

   Et var(--accent) brut ne suffirait pas non plus : mesuré sur le fond
   RÉEL du bouton actif (capture d'écran, pixel échantillonné au bord du
   bouton — pas un calcul sur le jeton) —
     design #c8724f sur fond mesuré rgb(244,230,216) → 2,87:1
     dev    #0066ff sur fond mesuré rgb(220,228,238) → 3,77:1
   tous deux sous le seuil AA (4,5:1). Teintes assombries ci-dessous,
   mesurées sur les mêmes fonds réels, avec marge : */
html[data-theme="light"][data-couloir="design"] .couloir-link.is-active {
    color: #8a4726;   /* 5,70:1 sur rgb(244,230,216) mesuré */
}
html[data-theme="light"][data-couloir="dev"] .couloir-link.is-active {
    color: #0047b3;   /* 6,42:1 sur rgb(220,228,238) mesuré */
}

/* Tâche 6, passe de correction 1 (coordinateur) — le thème SOMBRE n'avait
   jamais eu son propre correctif : seul le clair, ci-dessus, était mesuré.
   Trouvé en assombrissant --accent (design) pour corriger .btn-primary
   (132-couloirs-jetons.css) : ce bouton lit aussi var(--accent) — sa
   couleur a bougé avec lui, sans que personne ne l'ait mesuré ici.
   Mesuré sur le fond RÉEL du bouton actif en thème sombre (capture,
   pixel échantillonné, même méthode que ci-dessus) :
     design c8724f (état d'avant tâche 6) sur fond réel rgb(25,18,21) → 5,25:1 — passait
     design ab5836 (le nouvel --accent, plus sombre) sur le même fond → 3,67:1 — cassé par ricochet
     dev    0066ff (inchangé par cette tâche) sur fond réel rgb(4,19,45) → 3,82:1 — DÉJÀ sous le seuil AVANT cette tâche
   Design restauré à sa couleur d'avant (c8724f), qui passait déjà — sans
   dédoubler --accent pour ce seul élément. Dev, repéré au passage comme
   préexistant (pas causé par cette tâche), corrigé par la même occasion :
   même teinte assombrie du strict nécessaire pour repasser le seuil. */
html:not([data-theme="light"])[data-couloir="design"] .couloir-link.is-active {
    color: #c8724f;   /* 5,25:1 sur rgb(25,18,21) mesuré — valeur d'avant tâche 6, restaurée */
}
html:not([data-theme="light"])[data-couloir="dev"] .couloir-link.is-active {
    color: #257cff;   /* 4,76:1 sur rgb(4,19,45) mesuré */
}

/* Sous 768px le menu plein écran prend le relais de la barre (048,
   114, .nav-links dans 000) : la bascule doit basculer au même point,
   pas à 640px — sinon entre 641 et 767px elle reste alignée « barre »
   dans un menu qui, lui, est déjà en plein écran.

   Alignement : PAS de justify-content:center. .lang-switch juste en
   dessous n'en a pas non plus — .nav-links (000) stretch déjà les deux à
   100% de large via align-items:stretch, donc la largeur n'était jamais
   le problème ; center créait un bord gauche qui sautait d'une ligne à
   l'autre (DESIGN · DEV centré, FR · EN à gauche), en rupture avec le
   reste de la colonne (NAVIGATION, Réalisations…), lui aussi aligné à
   gauche. Corrigé en laissant le justify-content par défaut
   (flex-start), identique à .lang-switch. */
@media (max-width: 768px) {
    .couloir-switch { margin: 0 0 0.75rem; }
}

/* ============================================================
   LA MÊME BASCULE, DANS LA BARRE RÉDUITE  (.couloir-switch--sobre)

   Pages projet, /marketplace, /atelier, pages légales et 404. Leur barre
   est délibérément dépouillée — elle ne porte même pas le sélecteur de
   langue. Une relecture avait objecté qu'y poser DESIGN · DEV seul
   inverserait la hiérarchie : un sélecteur de métier plus proéminent
   qu'un sélecteur de langue absent. Le propriétaire a tranché en faveur
   de la bascule ; l'objection reste vraie, donc elle est traitée ici.

   Ce qui est GARDÉ de la barre d'accueil, et ne doit pas diverger :
   la famille (mono), le pas (--pas-etiquette), les capitales, le point
   médian, les mêmes classes, les mêmes couleurs d'état actif déjà
   mesurées plus haut. C'est le même contrôle, pas un cousin.

   Ce qui est RETIRÉ : l'aplat teinté du bouton actif. Sur l'accueil il
   se justifie — la bascule y a un frère, .lang-switch, traité à
   l'identique : deux petits blocs teintés côte à côte se lisent comme
   une paire de commandes. Ici ce frère n'existe pas, et .nav-link n'a ni
   fond ni cadre : un aplat teinté serait le SEUL bloc rempli de la barre
   après le bouton Contact, et il pèserait plus que « ← Projets ». L'état
   actif se dit donc par l'encre (accent contre blanc à 55 %) et par la
   graisse (500 contre 400) — deux signaux, dont un non chromatique.

   Le contrat « jamais plus visible que le lien de retour » est
   VÉRIFIABLE, pas décrété. La bascule est en dessous de .nav-link sur les
   trois axes, et le contraste est mesuré au pixel sur le fond RÉEL de la
   barre (capture d'un liseré de 4px sous la bascule, moyenne des pixels) :

     barre d'une page projet, fond mesuré rgb(5,8,17) en sombre,
     rgb(250,246,236) en clair
       sombre   actif   5,70:1 (design #c8724f) · 5,15:1 (dev #257cff)
                inactif 6,28:1  ·  .nav-link 11,14:1
       clair    actif   6,46:1 (design #8a4726) · 7,62:1 (dev #0047b3)
                inactif 4,32:1  ·  .nav-link 8,46:1
     taille  12px contre 14px  ·  aire  aucun aplat, aucun cadre

   Tous les états actifs et inactifs passent AA (4,5:1) dans les quatre
   combinaisons, et aucun n'approche le contraste du lien de retour.
   Le contour de focus clavier reste celui du module 117 (2px currentColor,
   offset 3, vérifié en tabulant pour de vrai — .focus() seul ne déclenche
   pas :focus-visible et rendrait un faux négatif) : la sobriété ne coûte
   rien à l'accessibilité.

   ⚠ Deux barres ont leur propre fond et sortent de ces chiffres, chacune
   traitée là où elle vit :
     · /atelier reste SOMBRE en thème clair (règle html.editorial-page,
       plus spécifique que le correctif clair d'atelier-v4.css). Sans
       correctif, la bascule y héritait de l'encre sombre du thème clair :
       1,20:1 mesuré, illisible. Corrigé dans atelier-v4.css, portée
       #header, avec deux teintes actives éclaircies pour son fond propre.
     · le menu burger est toujours charbon (048) : le module 114 s'en
       charge, et la variante sobre y reste sobre.
   ============================================================ */
.couloir-switch--sobre .couloir-link.is-active {
    background: none;
    font-weight: 500;
}

/* La couleur d'état actif reste celle des quatre combinaisons mesurées
   plus haut (thème × couloir) : elle a été calibrée sur le fond réel de
   la barre, et ce fond ne change pas ici — seul l'aplat disparaît, ce qui
   ne peut qu'augmenter le contraste. Rien à redéclarer. */

/* ============================================================
   134 · DEUX MONDES, PAS DEUX TEINTES

   Constat du propriétaire, vérifié au relevé : entre les couloirs
   Design et Dev, seuls l'accent, l'ordre des projets et le rayon
   changeaient vraiment. --mesure (30 vs 34rem) et les graisses de
   titre (800/-0,04em vs 700/-0,02em) sont sous le seuil de
   perception à un mètre. On basculait de monde et la page restait
   la même page, repeinte.

   Ce module déplace la différence là où elle se voit d'abord : la
   VOIX typographique, le RYTHME et la SURFACE.

     Design · l'affiche      Syne, très grand, très serré, muet
     Dev    · la console     JetBrains Mono, cadencé, annoté

   Aucune police ajoutée — JetBrains Mono est déjà chargée (400 et
   500, fichier variable) et sert aujourd'hui aux étiquettes. Lui
   confier les titres du couloir Dev ne coûte pas un octet et fait
   basculer la page dans un autre registre d'un seul coup.

   Portée : `html[data-couloir="…"]` (0,2,0 + le sélecteur cible).
   Les pages sans couloir gardent leur cascade intacte.
   ============================================================ */

/* ------------------------------------------------------------
   1 · L'ÉCHELLE, par couloir

   Les tailles restent des JETONS, jamais des littéraux : le garde-fou
   verify-typo plafonne les tailles écrites en dur, et une échelle
   redéclarée ici se propage à tout ce qui la lit déjà.
   ------------------------------------------------------------ */
html[data-couloir="design"] {
    /* L'affiche : un cran au-dessus, et une mesure courte pour que la
       ligne casse tôt — c'est la casse qui fait l'éditorial. */
    --pas-titre: clamp(2.3rem, 5vw, 3.8rem);
    --pas-sous-titre: 1.4rem;
    --tenue-titre: -0.05em;      /* chasse resserrée */
    --tenue-corps: 0.94;         /* interlignage des grands titres */
    --tenue-etiquette: 0.4em;    /* étiquettes très ouvertes */
    --respire: 3.2rem;           /* air sous les en-têtes de section */
    --pas-titre-etroit: clamp(1.7rem, 2.6vw, 2.2rem);  /* colonne de 320px */
}

html[data-couloir="dev"] {
    /* La console : plus petit, plus régulier, plus proche du texte.
       Un titre mono ne se gonfle pas — il s'aligne. */
    --pas-titre: clamp(1.7rem, 3vw, 2.4rem);
    --pas-sous-titre: 1.15rem;
    --tenue-titre: -0.01em;
    --tenue-corps: 1.16;
    --tenue-etiquette: 0.16em;
    --respire: 1.9rem;
    --pas-titre-etroit: clamp(1.5rem, 2.4vw, 2rem);
}

/* ------------------------------------------------------------
   2 · LES TITRES DE SECTION — la rupture principale

   `.section-title` est redéclaré par cinq modules antérieurs, dont
   certains à spécificité d'ID. D'où `html[data-couloir] h2.section-title`
   (0,2,2) : assez pour passer devant les redéclarations de classe sans
   recourir à !important, et vérifié dans le navigateur, pas au jugé.
   ------------------------------------------------------------ */
html[data-couloir="design"] h2.section-title,
html[data-couloir="design"] .hero-title {
    font-family: var(--display);
    font-weight: 800;
    font-size: var(--pas-titre);
    letter-spacing: var(--tenue-titre);
    line-height: var(--tenue-corps);
}

html[data-couloir="dev"] h2.section-title {
    font-family: var(--mono);
    font-weight: 500;
    font-size: var(--pas-titre);
    letter-spacing: var(--tenue-titre);
    line-height: var(--tenue-corps);
}

/* Le sous-titre suit la même voix que son titre — sinon la rupture
   s'arrête à la première ligne et le reste trahit l'autre monde. */
html[data-couloir="dev"] .work-sub,
html[data-couloir="dev"] .section-sub {
    font-family: var(--mono);
    letter-spacing: 0.01em;
}

/* ------------------------------------------------------------
   3 · LES ÉTIQUETTES DE SECTION

   Design : Syne 400 très ouverte — la respiration d'une couverture.
   Dev    : mono, précédée d'un `//`. Le commentaire de code EST le
            registre du couloir ; il annonce la section comme une ligne
            de source annonce un bloc.

   `::before` est remis en display sciemment : le module 041 l'éteint
   (`.section-label::before { display: none }`) pour toutes les pages.
   Le `content` ne porte pas de texte traduisible — `//` se lit à
   l'identique en français comme en anglais, il n'a rien à faire dans
   le dictionnaire i18n.
   ------------------------------------------------------------ */
html[data-couloir="design"] .section-label {
    font-family: var(--display);
    font-weight: 400;
    letter-spacing: var(--tenue-etiquette);
    text-transform: uppercase;
}

html[data-couloir="dev"] .section-label {
    font-family: var(--mono);
    font-weight: 500;
    letter-spacing: var(--tenue-etiquette);
    text-transform: lowercase;
}
html[data-couloir="dev"] .section-label::before {
    display: inline;
    content: "//";
    color: var(--accent-texte);
    /* `.section-label` est un conteneur flex `align-items: center` (module
       130) : le `::before` y devient un ITEM, et un centrage de boîte
       décale le `//` de 4px sous le texte — mesuré. `baseline` recale les
       deux sur la même ligne d'écriture, ce que le centrage ne fait pas.
       L'espacement vient du `gap: 0.625rem` déjà posé sur le conteneur,
       pas d'une espace dans le `content` (elle serait mangée). */
    align-self: baseline;
    line-height: 1;
}

/* ------------------------------------------------------------
   4 · LE RYTHME

   L'air sous un en-tête de section dit le tempo mieux qu'une
   gouttière : Design laisse respirer, Dev enchaîne. Posé sur les
   en-têtes plutôt que sur les `padding` des sections — ceux-ci sont
   écrits à spécificité d'ID dans les modules 016/018, les reprendre
   ici ouvrirait une guerre de cascade pour un gain nul.
   ------------------------------------------------------------ */
html[data-couloir] .work-header,
html[data-couloir] .skills-intro,
html[data-couloir] .testimonials-head {
    margin-bottom: var(--respire);
}

/* ⚠ L'échelle d'affiche suppose une colonne large. `.skills-intro` n'en
   est pas une : elle vit dans une grille `320px 585px` (la planche du
   stack occupe la droite). Mesuré à 1280px, « Ce avec quoi je construis »
   y tombait sur CINQ lignes et 304px de haut.

   Le défaut est antérieur à ce module — au pas d'origine (52px) le titre
   faisait déjà cinq lignes, sur 260px — mais l'échelle Design l'aggravait.
   Une colonne étroite reçoit donc son propre cran, dans les deux couloirs :
   trois lignes, 100px. Ce n'est pas un rabais, c'est la taille juste pour
   cette largeur.

   `#stack` dans le sélecteur n'est pas décoratif : un module antérieur
   pose `#stack .skills-intro .section-title` (1,2,1) sous la même
   condition de largeur. Sans l'ID, la règle ci-dessous plafonne à (0,3,2)
   et perd en silence — mesuré, le titre restait à 60,8px. Même condition
   `min-width: 1000px` que lui, car c'est à partir de là que la grille
   `320px 585px` se forme : en dessous, la colonne reprend toute la largeur
   et n'a plus besoin d'un cran réduit. */
@media (min-width: 1000px) {
    html[data-couloir] #stack .skills-intro h2.section-title {
        font-size: var(--pas-titre-etroit);
    }
}

/* La STRUCTURE de l'en-tête sépare les deux mondes plus nettement que
   n'importe quelle teinte. `.work-header` est une rangée flex de trois
   items (étiquette · titre · sous-titre) en `space-between` : une double
   page de magazine, l'étiquette dans la gouttière.

   Design garde cette rangée, et l'aligne sur la LIGNE D'ÉCRITURE plutôt
   que sur la boîte : avec un titre de deux lignes, un alignement de
   boîte laissait l'étiquette flotter à mi-hauteur.

   Dev l'empile à gauche : étiquette, titre, sous-titre, l'un sous
   l'autre, au fer. C'est la mise en page d'une page de documentation —
   et c'est ce qui se voit en premier quand on bascule. */
/* ⚠ La rangée magazine est réservée au grand écran. Mesuré à 390px :
   en rangée, l'étiquette mange la largeur et « Projets sélectionnés »
   sortait du cadre, coupé net au bord droit. Sous 769px les deux
   couloirs empilent ; c'est la VOIX (Syne contre mono), pas la
   structure, qui porte le couloir sur un petit écran. */
@media (min-width: 769px) {
    html[data-couloir="design"] .work-header {
        flex-direction: row;
        align-items: baseline;
    }
}

html[data-couloir="dev"] .work-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
}

/* ------------------------------------------------------------
   5 · LA SURFACE DES CARTES

   Design : la carte s'efface, l'image porte. Pas de liseré, pas de
            pastille — une légende posée, comme sous une planche.
   Dev    : la carte s'assume comme un composant. Liseré net, badge
            en pastille, numéro de catalogue en évidence.

   Le survol n'est pas touché : `.bento-card` porte déjà sa transition
   (module 000) et `box-shadow`/`border-color` y sont animés — une
   seconde déclaration de `transition` REMPLACE la première, elle ne
   s'y ajoute pas. On ne redéclare donc que des valeurs au repos.
   ------------------------------------------------------------ */
html[data-couloir="design"] .bento-card {
    border-color: transparent;
}
html[data-couloir="design"] .bento-title {
    font-family: var(--display);
    font-weight: 800;
    letter-spacing: -0.03em;
}
html[data-couloir="design"] .bento-badge {
    border-color: transparent;
    background: transparent;
    padding-inline: 0;
    letter-spacing: 0.24em;
}
html[data-couloir="design"] .bento-num {
    font-family: var(--display);
    letter-spacing: 0.2em;
}

html[data-couloir="dev"] .bento-card {
    border-color: color-mix(in srgb, var(--accent) 22%, transparent);
}
html[data-couloir="dev"] .bento-title,
html[data-couloir="dev"] .bento-num {
    font-family: var(--mono);
    font-weight: 500;
    letter-spacing: -0.01em;
}
html[data-couloir="dev"] .bento-num {
    color: var(--accent-texte);
    letter-spacing: 0.08em;
}

/* ------------------------------------------------------------
   6 · LE REPLI

   Sous 768px la grille devient une bande défilante et les titres
   descendent au plancher de leur clamp : l'écart d'échelle se resserre
   tout seul. La voix, elle, reste — c'est elle qui porte le couloir sur
   un petit écran, pas la taille.
   ------------------------------------------------------------ */
@media (max-width: 768px) {
    /* 0,4em de chasse sur une étiquette qui occupe déjà toute la largeur
       la fait passer à la ligne. */
    html[data-couloir="design"] { --respire: 2.2rem; --tenue-etiquette: 0.26em; }
    html[data-couloir="dev"] { --respire: 1.5rem; }

    html[data-couloir="design"] .work-header {
        flex-direction: column;
        align-items: flex-start;
        gap: 0.4rem;
    }
}

/* ⚠ Constat pour la suite, pas une omission : sous 768px, un module
   antérieur fixe la taille des titres de section en dur, et en
   !important. --pas-titre n'y peut donc rien, et l'écart d'ÉCHELLE
   entre les deux couloirs n'existe pas sur mobile — dans un couloir
   comme dans l'autre. Vérifié au navigateur : les deux titres y font
   37px. Ce n'est pas ce module qui ira poser un second !important
   pour gagner cette bataille ; à cette taille, le changement de
   police suffit à dire le couloir.
   (La valeur exacte n'est pas recopiée ici : verify:typo lit aussi
   les commentaires et l'aurait comptée comme une taille littérale
   de ce fichier.) */

/* ============================================================
   135 · LE MOUVEMENT DIT LE COULOIR

   Constat du propriétaire : « je ne vois pas trop la diff entre
   les deux à part couleurs ». Les jetons (132) et la typographie
   (134) changent l'ASPECT ; ce module change la GESTUELLE. Les
   deux couloirs ne bougent plus pareil :

   · DESIGN — la main : les lettres se posent comme des traits de
     plume (rotation depuis la ligne de base, retombée souple,
     cadence irrégulière), les images arrivent derrière un bord
     déchiré (polygone à dents interpolé), les cartes dérivent
     avec une assiette légèrement inclinée puis se redressent.
     Ça accompagne l'en-tête « magazine » du module 134.

   · DEV — la machine : tout est QUANTIFIÉ. Les mêmes distances,
     mais parcourues en crans (`steps()`), à cadence métronome.
     Les lettres mono montent au cliquet, les images se remplissent
     par bandes comme un framebuffer, les cartes se calent en trois
     crans. Ça accompagne la page de documentation du 134.

   AUCUNE animation nouvelle : chaque règle re-voix une signature
   qui EXISTE déjà (008 split-char, motion-system .ch, 130 cartes
   et guillemets, 007 clip des images, 121 planche du stack). La
   charte « 1 signature par section » reste à 1 — c'est la même
   signature, dite dans la langue du couloir.

   Propriétés animées : transform, translate, rotate, opacity,
   clip-path. Rien d'autre — aucune propriété de mise en page,
   aucun filtre, aucun flou, aucun will-change permanent ajouté.

   La SIGNATURE DE LA BASCULE (le voile d'encre / la passe de
   compilation, éléments .couloir-passage créés par
   couloir-mouvement.js au clic) vit en bas de ce fichier : c'est
   un geste de page déclenché par l'utilisateur, pas une signature
   de section — il joue une fois et se détruit.
   ============================================================ */

@media (prefers-reduced-motion: no-preference) {

/* ─────────────────────────────────────────────────────────────
   1 · LES TITRES DE SECTION (.split-char — module 008)

   Le vrai mécanisme des titres en production n'est PAS
   .reveal-line : script.js éclate .hero-title/.section-title/
   .proj-title en .split-char et pose .is-split, qui ÉTEINT le
   translate de .reveal-line (008, !important). On re-voix donc
   .split-char. Sous reduced-motion script.js ne splitte pas, et
   ce bloc entier est hors média : rien à neutraliser.

   Spécificités mesurées avant d'écrire :
   · départ : html[data-couloir] .split-char = (0,2,1) — bat le
     .split-char (0,1,0) du 008 ;
   · arrivée : .reveal-wrap.visible .split-char et
     .is-split.visible .split-char = (0,3,0) — battent (0,2,1),
     l'état final du 008 reste maître et REMPLACE tout le
     transform (la rotation de départ est donc bien effacée).
   ───────────────────────────────────────────────────────────── */

/* DESIGN · le trait de plume : chaque lettre pivote depuis sa
   ligne de base (origine en bas à gauche) et retombe avec un
   léger dépassement — le bézier sort au-dessus de 1. La cadence
   suit --i mais s'écarte du métronome plus bas (mod()). */
html[data-couloir="design"] .split-char {
    transform: translateY(122%) rotate(12deg);
    transform-origin: 12% 96%;
    transition: transform 1.05s cubic-bezier(0.26, 1.24, 0.32, 1);
    transition-delay: calc(var(--i, 0) * 0.032s);
}

/* La main n'est pas un métronome : 41 et 89 sont premiers entre
   eux, le reste de la division parcourt donc des valeurs
   pseudo-aléatoires stables (0-88 ms) — les lettres se posent
   presque dans l'ordre, jamais exactement. Repli : sans mod(),
   la déclaration ci-dessus (cadence régulière) reste en place. */
@supports (width: mod(7px, 2px)) {
    html[data-couloir="design"] .split-char {
        transition-delay: calc(var(--i, 0) * 0.026s + mod(var(--i, 0) * 41ms, 89ms));
    }
}

/* DEV · le cliquet : même montée, mais en 4 crans nets (~27 %
   par cran, 110 ms le cran) et cadence STRICTE. Sur les titres
   passés en JetBrains Mono par le 134, la ligne semble avancer
   d'un pas de machine, pas glisser. */
html[data-couloir="dev"] .split-char {
    transform: translateY(108%);
    transition: transform 0.44s steps(4, jump-end);
    transition-delay: calc(var(--i, 0) * 0.02s);
}

/* ─────────────────────────────────────────────────────────────
   2 · LE HERO (.ch — motion-system.js::splitChars, la SEULE
   décomposition en caractères autorisée par la charte pour
   data-split="chars")

   Départ (0,3,1) : il bat l'état final du motion-system
   ([data-split="chars"].is-split-in .ch, (0,3,0)) — l'état
   final est donc redéclaré plus bas à (0,4,1), sinon les
   lettres resteraient figées à leur position de départ.

   Pas de frappe lettre à lettre ni de curseur clignotant côté
   Dev : ces marqueurs ont été retirés à la passe dé-IA (ruling
   conservé dans script.js) et ne reviennent pas. Le registre
   machine passe par la quantification du geste, pas par la
   parodie de terminal. */
html[data-couloir="design"] [data-split="chars"] .ch {
    transform: translate3d(0, 0.72em, 0) rotate(10deg);
    transform-origin: 20% 100%;
    transition: opacity 0.6s ease, transform 0.94s cubic-bezier(0.28, 1.3, 0.34, 1);
    transition-delay: calc(var(--i, 0) * 0.034s);
}
@supports (width: mod(7px, 2px)) {
    html[data-couloir="design"] [data-split="chars"] .ch {
        transition-delay: calc(var(--i, 0) * 0.027s + mod(var(--i, 0) * 53ms, 96ms));
    }
}

html[data-couloir="dev"] [data-split="chars"] .ch {
    transform: translate3d(0, 0.5em, 0);
    transition: opacity 0.28s steps(2, jump-end), transform 0.4s steps(3, jump-end);
    transition-delay: calc(var(--i, 0) * 0.024s);
}

/* L'arrivée, commune : transform remplacé en bloc, la rotation
   de départ du couloir Design disparaît avec lui. */
html[data-couloir] [data-split="chars"].is-split-in .ch {
    opacity: 1;
    transform: translate3d(0, 0, 0);
}

/* ─────────────────────────────────────────────────────────────
   3 · #WORK — les cartes projet (module 130) et leurs images
   (modules 007/008)

   Le FLIP de couloir.js passe par element.animate() : il
   surplombe la cascade le temps du vol puis s'efface — il ne
   voit jamais ces transitions, exactement comme avec le 130.
   Au repos (.visible) le transform vaut `none` des deux côtés :
   l'attribut peut changer sans déclencher un seul mouvement.
   ───────────────────────────────────────────────────────────── */

/* DESIGN · les planches se posent : chaque carte arrive avec une
   assiette inclinée (±0,9°, alternée) et se redresse en se
   posant — des tirages qu'une main aligne sur une table. Rythme
   par COUPLES : deux cartes tombent presque ensemble, une
   respiration, les deux suivantes. */
html[data-couloir="design"] .bento-grid .bento-card {
    transform: translateY(30px) rotate(0.9deg);
    transition: transform 0.9s cubic-bezier(0.3, 1.18, 0.36, 1);
}
html[data-couloir="design"] .bento-grid .bento-card:nth-child(even) {
    transform: translateY(24px) rotate(-0.9deg);
}
html[data-couloir="design"] .bento-grid.visible .bento-card:nth-child(2)  { transition-delay: 0.09s; }
html[data-couloir="design"] .bento-grid.visible .bento-card:nth-child(3)  { transition-delay: 0.14s; }
html[data-couloir="design"] .bento-grid.visible .bento-card:nth-child(4)  { transition-delay: 0.31s; }
html[data-couloir="design"] .bento-grid.visible .bento-card:nth-child(5)  { transition-delay: 0.36s; }
html[data-couloir="design"] .bento-grid.visible .bento-card:nth-child(6)  { transition-delay: 0.52s; }
html[data-couloir="design"] .bento-grid.visible .bento-card:nth-child(7)  { transition-delay: 0.58s; }
html[data-couloir="design"] .bento-grid.visible .bento-card:nth-child(8)  { transition-delay: 0.74s; }
html[data-couloir="design"] .bento-grid.visible .bento-card:nth-child(9)  { transition-delay: 0.81s; }
html[data-couloir="design"] .bento-grid.visible .bento-card:nth-child(10) { transition-delay: 0.95s; }
html[data-couloir="design"] .bento-grid.visible .bento-card:nth-child(11) { transition-delay: 1.02s; }

/* DEV · le raster : mêmes 24px, mais en 3 crans de 8px (120 ms
   le cran) et un métronome exact de 60 ms entre cartes. */
html[data-couloir="dev"] .bento-grid .bento-card {
    transform: translateY(24px);
    transition: transform 0.36s steps(3, jump-end);
}
html[data-couloir="dev"] .bento-grid.visible .bento-card:nth-child(2)  { transition-delay: 0.06s; }
html[data-couloir="dev"] .bento-grid.visible .bento-card:nth-child(3)  { transition-delay: 0.12s; }
html[data-couloir="dev"] .bento-grid.visible .bento-card:nth-child(4)  { transition-delay: 0.18s; }
html[data-couloir="dev"] .bento-grid.visible .bento-card:nth-child(5)  { transition-delay: 0.24s; }
html[data-couloir="dev"] .bento-grid.visible .bento-card:nth-child(6)  { transition-delay: 0.30s; }
html[data-couloir="dev"] .bento-grid.visible .bento-card:nth-child(7)  { transition-delay: 0.36s; }
html[data-couloir="dev"] .bento-grid.visible .bento-card:nth-child(8)  { transition-delay: 0.42s; }
html[data-couloir="dev"] .bento-grid.visible .bento-card:nth-child(9)  { transition-delay: 0.48s; }
html[data-couloir="dev"] .bento-grid.visible .bento-card:nth-child(10) { transition-delay: 0.54s; }
html[data-couloir="dev"] .bento-grid.visible .bento-card:nth-child(11) { transition-delay: 0.60s; }

/* L'arrivée, commune. Nécessaire : nos départs sont à (0,3,1) et
   (0,4,1), l'arrivée du 130 (.bento-grid.visible .bento-card,
   (0,3,0)) perdrait — les cartes resteraient penchées. Le 130
   garde ses propres délais pour une page SANS couloir. */
html[data-couloir] .bento-grid.visible .bento-card {
    transform: none;
}

/* Les IMAGES · Design — le bord déchiré. Le 007 les révèle par
   un inset() gauche→droite ; ici le front devient un polygone à
   12 dents dont le tracé diffère entre départ et arrivée : le
   navigateur interpole sommet par sommet (même nombre de points,
   mêmes unités) et le bord ONDULE en traversant — un papier
   qu'on déchire, pas un store qu'on lève. Les délais nth-child
   du 007 restent maîtres (0,4,1 contre 0,2,2 ici). */
html[data-couloir="design"] .bento-card img {
    clip-path: polygon(
        0% 0%,
        2.5% 0%, 0.8% 9%, 2.9% 18%, 1.1% 27%, 3.2% 36%, 0.9% 45%,
        2.6% 55%, 1.2% 64%, 3% 73%, 0.7% 82%, 2.8% 91%, 1.4% 100%,
        0% 100%);
    transition:
        transform 0.9s cubic-bezier(0.22, 1, 0.36, 1),
        filter 0.6s ease,
        clip-path 1.15s cubic-bezier(0.5, 0.06, 0.18, 1);
}
html[data-couloir="design"] .bento-grid.fade-in.visible .bento-card img {
    clip-path: polygon(
        0% 0%,
        104% 0%, 101.5% 9%, 105% 18%, 102% 27%, 106% 36%, 101.8% 45%,
        104.5% 55%, 102.2% 64%, 105.5% 73%, 101.6% 82%, 104.8% 91%, 102.6% 100%,
        0% 100%);
}

/* Les IMAGES · Dev — le framebuffer : le même inset() du 007,
   mais en 7 bandes discrètes. L'image ne glisse pas, elle se
   REMPLIT. Le shorthand reprend transform/filter du 008 à
   l'identique — une transition redéclarée remplace, elle ne
   s'ajoute pas (piège documenté aux modules 130/132). */
html[data-couloir="dev"] .bento-card img {
    transition:
        transform 0.9s cubic-bezier(0.22, 1, 0.36, 1),
        filter 0.6s ease,
        clip-path 0.55s steps(7, jump-end);
}

/* ─────────────────────────────────────────────────────────────
   4 · #TESTIMONIALS — le guillemet (module 130)

   Le 130 le fait grandir depuis 0.88 ; ici il prend l'accent du
   couloir. Ses délais nth-child (0,5,0) restent maîtres sur nos
   shorthands. Le translate du 118 (0 10px → 0) est une propriété
   INDÉPENDANTE : il compose, on n'y touche pas.
   Arrivée redéclarée à (0,4,1) : celle du 130 (0,3,0) perdrait
   contre nos départs (0,3,1). */

/* DESIGN · le paraphe : le guillemet tourne depuis son attache
   basse et se déploie — un geste de plume qui ouvre la parole. */
html[data-couloir="design"] .testimonial-card .testimonial-mark {
    transform: scale(0.62) rotate(-16deg) translateY(6px);
    transform-origin: 25% 75%;
    transition: transform 0.75s cubic-bezier(0.3, 1.4, 0.4, 1) 0.2s;
}

/* DEV · le composteur : le glyphe descend d'un cran, deux temps,
   posé. Pas d'élan, pas de rebond — un caractère qu'on assied. */
html[data-couloir="dev"] .testimonial-card .testimonial-mark {
    transform: scale(1.22);
    transition: transform 0.3s steps(2, jump-end) 0.2s;
}

html[data-couloir] .testimonial-card.visible .testimonial-mark {
    transform: none;
}

/* ─────────────────────────────────────────────────────────────
   5 · #STACK — la planche (module 121)

   Le 121 dépose les pastilles du haut (translate 0 -10px) au
   métronome de 26 ms. Les délais restent en place dans les deux
   couloirs ((0,5,0), imbattables sans recopier 27 règles — assumé,
   c'est le GESTE qui change, pas l'ordre de pose).

   `rotate` est une propriété indépendante : le transform du survol
   (016, scale + translateY) compose avec elle sans conflit, et au
   repos elle vaut 0deg — le survol est indemne. */

/* DESIGN · posées à la main : elles tombent d'un peu plus haut,
   légèrement en biais, et se redressent en touchant la planche. */
html[data-couloir="design"] .tech-board-grid .tech-pill {
    translate: 0 -16px;
    rotate: -2.2deg;
    transition:
        opacity 0.5s ease,
        translate 0.62s cubic-bezier(0.3, 1.3, 0.4, 1),
        rotate 0.62s cubic-bezier(0.3, 1.3, 0.4, 1);
}
html[data-couloir="design"] .tech-board-grid .tech-pill:nth-child(even) {
    rotate: 1.8deg;
}
html[data-couloir="design"] .tech-board.visible .tech-board-grid .tech-pill {
    rotate: 0deg;
}

/* DEV · clippées : deux crans d'opacité, trois crans de descente.
   Avec le métronome du 121, la planche se remplit comme une
   matrice de pixels, case par case. */
html[data-couloir="dev"] .tech-board-grid .tech-pill {
    translate: 0 -12px;
    transition:
        opacity 0.3s steps(2, jump-end),
        translate 0.34s steps(3, jump-end);
}

} /* fin @media (prefers-reduced-motion: no-preference) */

/* ============================================================
   6 · LA SIGNATURE DE LA BASCULE — .couloir-passage

   Créé par couloir-mouvement.js au clic DESIGN/DEV, détruit à la
   fin du geste (~0,7 s). Le JS n'anime que transform, opacity et
   clip-path via element.animate() (composité, aucun rAF) ; tout
   ce qui est ici est de la géométrie STATIQUE — jamais animée.

   · Design : deux bandes d'encre au bord déchiré (polygones
     statiques) traversent l'écran en diagonale — le lavis
     d'abord, le plein par-dessus.
   · Dev : une trame écran (scanlines + colonnes) se révèle du
     haut vers le bas en 9 crans puis s'efface, pendant qu'une
     tête de lecture de 2px descend la page en 18 pas.

   z-index 9000 : au-dessus du contenu et de la barre (101), sous
   le curseur personnalisé (9998/9999). pointer-events none : le
   voile ne vole aucun clic ; fixed + overflow hidden : les bandes
   plus grandes que l'écran ne créent aucun défilement.
   ============================================================ */
.couloir-passage {
    position: fixed;
    inset: 0;
    z-index: 9000;
    pointer-events: none;
    overflow: hidden;
}
.couloir-passage i {
    position: absolute;
    display: block;
}

/* Les bandes d'encre : plus hautes et larges que l'écran pour que
   la rotation de -8° (posée par les keyframes JS) ne découvre
   jamais un coin. Le bord déchiré est un polygone STATIQUE — seul
   le transform bouge, le tracé, lui, est figé. */
.passage-encre {
    top: -22%;
    left: -30%;
    width: 175%;
    height: 145%;
    background: var(--accent);
}
.passage-encre--pleine {
    opacity: 0.85;
    clip-path: polygon(
        66% 0%, 69% 9%, 64% 18%, 71% 28%, 65% 37%, 70% 47%,
        63% 56%, 70% 66%, 64% 75%, 71% 84%, 65% 93%, 68% 100%,
        34% 100%, 30% 92%, 35% 83%, 29% 74%, 36% 64%, 31% 55%,
        37% 45%, 30% 36%, 35% 27%, 29% 17%, 36% 8%, 31% 0%);
}
.passage-encre--lavis {
    opacity: 0.3;
    clip-path: polygon(
        74% 0%, 76% 11%, 70% 21%, 77% 33%, 71% 44%, 76% 56%,
        69% 67%, 75% 78%, 70% 89%, 74% 100%,
        26% 100%, 23% 90%, 29% 79%, 22% 67%, 28% 55%, 24% 43%,
        30% 32%, 23% 21%, 28% 10%, 25% 0%);
}

/* La trame : scanlines horizontales serrées + colonnes discrètes.
   Les alphas vivent dans les couleurs, le fond n'est JAMAIS animé
   — c'est le clip-path du conteneur qui la révèle. */
.passage-trame {
    inset: 0;
    background:
        repeating-linear-gradient(to bottom,
            color-mix(in srgb, var(--accent) 45%, transparent) 0 1px,
            transparent 1px 9px),
        repeating-linear-gradient(to right,
            color-mix(in srgb, var(--accent) 14%, transparent) 0 1px,
            transparent 1px 72px);
}

/* La tête de lecture : un simple trait plein. Pas de halo — la
   charte a déjà éteint ceux des boutons, on n'en rallume pas. */
.passage-tete {
    top: 0;
    left: 0;
    right: 0;
    height: 2px;
    background: var(--accent);
}

/* Ceinture et bretelles : le JS vérifie déjà matchMedia avant de
   créer le voile ; si un voile existait au moment où la préférence
   change, il disparaît. Les re-voix des sections vivent toutes
   dans le média no-preference ci-dessus : sous reduce, ce module
   n'existe pas — les replis des modules d'origine s'appliquent. */
@media (prefers-reduced-motion: reduce) {
    .couloir-passage { display: none !important; }
}

/* ============================================================
   136 · LES DEUX PANNEAUX DU STACK PERDENT LEUR FILET DÉGRADÉ

   Signalé par le propriétaire : « dans stack de page réalisation
   accueil je vois accent de couleur sur leur top des divs, à
   retirer ».

   Relevé au navigateur avant d'écrire — c'est bien un dégradé, pas
   une bordure unie :

     .skills-dev::before     linear-gradient(90deg, #10b981, rgba(0,102,255,.5))
     .skills-design::before  linear-gradient(90deg, #ab5836, #c084fc)
     tous deux : 2px de haut, calés à top:0, opacity .7

   C'est exactement le motif déjà écarté ailleurs sur ce site : une
   barre dégradée bleu→vert posée au-dessus d'une carte. Elle ne dit
   rien, elle date, et elle contredit la règle d'un accent unique —
   le panneau Dev y mélangeait du vert à du bleu, le panneau Design
   du violet à de la terre cuite, deux teintes qui n'existent nulle
   part ailleurs dans la palette.

   Le `::before` n'est pas neutralisé par `background: none` : il
   resterait une boîte de 2px à peindre pour rien. `content: none`
   supprime la pseudo-boîte entièrement.

   Portée volontairement sans `html[data-couloir]` : le filet doit
   disparaître partout, y compris sur une page qui n'aurait pas de
   couloir. Spécificité (0,2,0) contre les (0,1,1) du module 005 —
   la classe supplémentaire suffit, aucun !important nécessaire.
   ============================================================ */
.skills-dev.skills-dev::before,
.skills-design.skills-design::before {
    content: none;
}

/* ============================================================
   137 · « RÉSULTATS & LIVRABLES » — LES CARTES S'ALIGNENT

   Signalé par le propriétaire : « dans résultat livrable il y a des
   textes coupés ». Deux causes distinctes, mesurées avant d'écrire :

   1. Des phrases s'arrêtaient sur des points de suspension
      (« …Formations… », « …chatbot… »). Réglé dans le contenu, pas
      ici : les listes sont désormais terminées.

   2. Un chiffre en deux mots passe à la ligne. « 8 mois » sur deux
      lignes rendait sa carte plus haute que ses voisines, et les
      libellés des quatre cartes ne s'alignaient plus — l'œil lit ça
      comme un texte coupé.

   Pourquoi PAS `white-space: nowrap` : mesuré au canvas, à la taille
   réelle (41,6px) et dans la largeur réelle de la carte (201px),
   « 6 semaines » réclame 382px. Il faudrait descendre à 21,6px pour
   tenir sur une ligne — la moitié du corps actuel. Le grand chiffre
   ne serait plus grand. Le retour à la ligne n'est pas le défaut :
   le désalignement l'est.

   La grille répond donc à la vraie question. `.proj-stats` est DÉJÀ
   un grid de 4 colonnes (relevé, module project.css) ; on lui déclare
   trois rangées et chaque carte devient un sous-grid qui les épouse.
   Les trois lignes — chiffre, libellé, précision — se calent alors
   sur la même hauteur d'une carte à l'autre, quel que soit le nombre
   de lignes du chiffre. Aucune hauteur figée, aucun blanc réservé
   pour rien : si aucun chiffre ne passe à la ligne, la rangée reste
   compacte.

   `row-gap` est redéclaré sur la carte : un sous-grid hérite sinon
   du `gap: 20px` du conteneur, qui écarterait le chiffre de son
   libellé de 20px au lieu des 5 d'origine.

   `align-content: start` empêche les trois lignes de se répartir
   dans la hauteur quand une carte voisine est plus haute — sans lui,
   le contenu des cartes courtes flotterait au milieu.
   ============================================================ */
@supports (grid-template-rows: subgrid) {
    .proj-stats {
        grid-template-rows: auto auto auto;
        grid-auto-rows: auto;
    }

    .proj-stats .proj-stat {
        display: grid;
        grid-template-rows: subgrid;
        grid-row: span 3;
        row-gap: 0.35rem;
        align-content: start;
    }
}

/* ============================================================
   138 · LES PASTILLES D'ÉTAT DEVIENNENT DES PICTOGRAMMES

   Demandé par le propriétaire : « ajouter des icônes au lieu de
   juste un rond ». Quatre pastilles étaient concernées, relevées
   avant d'écrire :

     .hero-badge-dot     7px  #22c55e   « BONJOUR, À … »
     .showcase-pulse     9px  --green   « EN LIGNE · … · 11:00 »
     .contact-avail-dot  6px  --green   la disponibilité, en pied
     .atelier-card-pulse 6px  #a78bfa   « MONOGRAPHIE · N° 01 »

   Chacune reçoit le signe de ce qu'elle annonce : une épingle pour
   le lieu, une onde pour « en ligne », une coche pour la
   disponibilité, une planche pour la monographie. Un rond ne disait
   rien ; ces quatre-là se lisent sans légende.

   ── Pourquoi un MASQUE CSS et pas un <svg> dans le HTML ──────────
   La ligne du hero et celle du showcase vivent dans
   portfolio-legacy.html, dont chaque ligne est une clé du
   dictionnaire i18n. Y insérer un SVG changerait la ligne FR, la
   paire deviendrait orpheline et la version anglaise afficherait du
   français à cet endroit. Le masque laisse le HTML intact : c'est le
   fond de l'élément qui prend la forme du pictogramme.

   ── Pourquoi elles GRANDISSENT ──────────────────────────────────
   À 6 ou 7px, un pictogramme est une tache. Les quatre passent à
   12-13px : c'est le plancher en dessous duquel une épingle ne se
   distingue plus d'un point. Elles restent des marqueurs, pas des
   boutons.

   ── Ce qui remplace le halo ─────────────────────────────────────
   Les pulsations d'origine gonflaient un `box-shadow` circulaire —
   un anneau rond autour d'une épingle n'a aucun sens — et
   `live-pulse-core` (mobile-premium.css) animait `filter:
   brightness/saturate`, un filtre sur élément animé, que la charte
   proscrit. Les deux sont remplacés par une respiration d'OPACITÉ :
   composité, sans repeint, et le signe continue de vivre.

   Le `!important` sur la couleur n'est pas un raccourci : la règle
   `.hero-badge-dot.is-pulsing` de mobile-premium.css impose
   `background: #4ade80 !important`, et cette feuille est chargée
   APRÈS styles.css. Rien d'autre ne peut la reprendre.
   ============================================================ */

/* Classe doublée partout : `.hero-badge-dot.is-pulsing` (0,2,0) vit dans
   mobile-premium.css, chargée APRÈS styles.css — à spécificité égale elle
   gagnerait, et le halo circulaire resterait autour du pictogramme. */
.hero-badge-dot.hero-badge-dot,
.showcase-pulse.showcase-pulse,
.contact-avail-dot.contact-avail-dot,
.atelier-card-pulse.atelier-card-pulse {
    border-radius: 0;
    box-shadow: none;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-position: center;
    mask-position: center;
    -webkit-mask-size: contain;
    mask-size: contain;
}

/* ── L'épingle · le lieu d'où l'on écrit ──────────────────────── */
.hero-badge-dot.hero-badge-dot,
.hero-badge-dot.hero-badge-dot.is-pulsing {
    width: 13px;
    height: 13px;
    background: #22c55e !important;
    -webkit-mask-image: var(--signe-epingle);
    mask-image: var(--signe-epingle);
    animation: pastille-souffle 2.4s ease-in-out infinite;
}

/* ── L'onde · en ligne ────────────────────────────────────────── */
.showcase-pulse.showcase-pulse,
.showcase-pulse.showcase-pulse.is-pulsing {
    width: 13px;
    height: 13px;
    /* Redéclaré ici et pas seulement dans le bloc commun : celui-ci est à
       (0,2,0), à égalité avec `.showcase-pulse.is-pulsing` de
       mobile-premium.css, chargée après — le halo vert survivait. */
    box-shadow: none;
    background: var(--green) !important;
    -webkit-mask-image: var(--signe-onde);
    mask-image: var(--signe-onde);
    animation: pastille-souffle 2.2s ease-in-out infinite;
}

/* ── La coche · disponible ──────────────────────────────────────
   Celle-ci ne respire PAS, et c'est voulu : le module 100 (la passe
   « dé-IA ») a éteint sa pulsation avec `animation: none !important`.
   Une pastille qui clignote en pied de page était jugée comme un
   tic de gabarit. On lui donne le signe, on ne lui rend pas le
   battement — la décision précédente tient. */
.contact-avail-dot.contact-avail-dot {
    width: 12px;
    height: 12px;
    background: var(--green) !important;
    -webkit-mask-image: var(--signe-coche);
    mask-image: var(--signe-coche);
}

/* ── La planche · monographie ─────────────────────────────────── */
.atelier-card-pulse.atelier-card-pulse {
    width: 12px;
    height: 12px;
    background: #a78bfa !important;
    -webkit-mask-image: var(--signe-planche);
    mask-image: var(--signe-planche);
    animation: pastille-souffle 2.8s ease-in-out infinite;
}

/* L'anneau qui s'écartait autour du rond n'a plus d'objet : il
   cerclerait une épingle. */
.hero-badge-dot.hero-badge-dot.is-pulsing::after,
.showcase-pulse.showcase-pulse.is-pulsing::after,
.contact-avail-dot.contact-avail-dot::after,
.atelier-card-pulse.atelier-card-pulse::after {
    content: none;
}

/* Une respiration, pas un clignotement : l'opacité seule, composée
   par le compositeur, sans repeint ni filtre. */
@keyframes pastille-souffle {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.45; }
}

@media (prefers-reduced-motion: reduce) {
    .hero-badge-dot.hero-badge-dot,
    .showcase-pulse.showcase-pulse,
    .contact-avail-dot.contact-avail-dot,
    .atelier-card-pulse.atelier-card-pulse {
        animation: none;
        opacity: 1;
    }
}

/* ── Les tracés ───────────────────────────────────────────────────
   Déclarés en jetons pour qu'un pictogramme se remplace en un seul
   endroit. Contours à 2,4-3 d'épaisseur sur une grille de 24 : en
   dessous, le trait se casse à 12px de rendu. */
:root {
    --signe-epingle: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000'%3E%3Cpath fill-rule='evenodd' d='M12 1.8c-4 0-7.2 3.2-7.2 7.2 0 5.3 7.2 13.2 7.2 13.2s7.2-7.9 7.2-13.2c0-4-3.2-7.2-7.2-7.2zm0 9.9a2.7 2.7 0 1 1 0-5.4 2.7 2.7 0 0 1 0 5.4z'/%3E%3C/svg%3E");
    --signe-onde: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round'%3E%3Ccircle cx='12' cy='12' r='2.4' fill='%23000' stroke='none'/%3E%3Cpath d='M6.4 6.4a8 8 0 0 0 0 11.2'/%3E%3Cpath d='M17.6 6.4a8 8 0 0 1 0 11.2'/%3E%3C/svg%3E");
    --signe-coche: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4.5 12.6l5.2 5.2L19.8 6.4'/%3E%3C/svg%3E");
    --signe-planche: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linejoin='round'%3E%3Crect x='3.4' y='4.6' width='17.2' height='14.8' rx='1.6'/%3E%3Cpath d='M3.4 15.4l4.6-4.6 3.4 3.4 3-3 6.2 6.2'/%3E%3C/svg%3E");
}

/* ============================================================
   139 · DEUX ENCRES QUI NE PASSAIENT PAS LE SEUIL

   `npm run verify:rendu` — qui ne pouvait pas tourner jusqu'ici,
   playwright manquait dans node_modules — sort deux échecs réels,
   mesurés sur les PIXELS PEINTS, pas sur les jetons déclarés.

   ── 1 · a.cb-presta ─────────────────────────────────────────────
   4,06:1 pour un seuil AA de 4,5:1 (18px/700, « PrestAvenue » sur
   #fafaf8, couloir Dev en thème clair). Elle lisait --accent-light,
   qui est d'abord une couleur de FOND de bouton. --accent-texte
   existe précisément pour les rôles d'encre : il est dérivé du même
   accent, assombri du strict nécessaire (module 132). Mesuré à
   5,1:1 sur ce fond.

   ── 2 · .typewriter-text ────────────────────────────────────────
   Le mot du hero, 70px/700, seuil grand texte 3:1. Mesuré entre
   1,69 et 2,99:1 selon le couloir, le thème, la largeur et
   l'INSTANT — il est posé sur le maillage WebGL, qui bouge.

   Pourquoi aucune teinte d'accent ne s'en sortait, calculs à
   l'appui sur les fonds réellement relevés par l'outil :

     thème sombre  le maillage monte jusqu'à L=0,159 ([125,109,78])
                   → il faut une encre de L ≥ 0,576
     thème clair   il descend jusqu'à L=0,375 ([173,164,145])
                   → il faut une encre de L ≤ 0,092

   Un fond de TON MOYEN bat une encre claire ET une encre sombre :
   c'est la situation classique où il n'existe pas de valeur unique.
   L'accent d'origine vaut L=0,408 en sombre et L=0,140 en clair —
   des deux côtés du mauvais côté de la limite.

   La sortie n'est donc pas de choisir mieux, c'est d'aller à
   l'extrême, par thème : une teinte PÂLE de l'accent sur le
   maillage sombre, une teinte PROFONDE sur le maillage clair. La
   teinte reste celle du couloir — c'est la même couleur, poussée
   là où elle se lit.

   Dérivé par color-mix depuis --accent, jamais saisi à la main :
   changer l'accent d'un couloir met ces deux encres à jour toutes
   seules. color-mix est déjà une dépendance dure du site
   (modules 015, 126, 132) — aucun repli supplémentaire.

   Portée limitée à ces deux sélecteurs : --accent-light garde ses
   58 autres points d'appel intacts. On corrige les deux encres
   fautives, pas le jeton qui sert aussi de fond ailleurs.
   ============================================================ */

/* ── 1 · le lien PrestAvenue du mur de clients ────────────────── */
.clients-brands-label .cb-presta {
    color: var(--accent-texte, var(--accent-light));
}

/* ── 2 · le mot du hero, sur le maillage ──────────────────────────
   `.is-static` dans le sélecteur : c'est l'état que le module 099
   colore (le mot posé, une fois la frappe finie), et il faut le
   battre. Le mot en cours de frappe hérite de la même encre par la
   règle générique juste au-dessus. */
html:not([data-theme="light"]) .typewriter-text,
html:not([data-theme="light"]) .typewriter-text.is-static {
    /* pâle : L ≈ 0,63 mesuré, soit 3,3:1 sur le point le plus clair
       du maillage sombre — le seuil est à 3:1. */
    color: color-mix(in srgb, var(--accent) 28%, white);
}

html[data-theme="light"] .typewriter-text,
html[data-theme="light"] .typewriter-text.is-static {
    /* profonde : L ≈ 0,07 mesuré, soit 3,5:1 sur le point le plus
       sombre du maillage clair. */
    color: color-mix(in srgb, var(--accent) 55%, black);
}
