/**
 * spa-fluidity.css — Couche finale d'orchestration du chargement SPA forum.
 *
 * Objectifs :
 *   • Premier paint instantané sur les topics (skeleton SSR cohérent
 *     dans tous les thèmes).
 *   • Crossfade route propre, pas de flash, pas de double-anim.
 *   • Mobile : slide-in plus snappy, gpu only.
 *   • Light & dark themes : shimmer adapté pour rester lisible.
 *
 * Charge APRÈS performance-optimizations.css / fluid-perf.css /
 * spa-transitions.css → c'est la couche finale qui ajuste l'effet
 * sans réécrire la structure des composants.
 */

/* ─── 1. SKELETON — théming unifié ────────────────────────────────────
   Plusieurs blocs concurrents existent dans 2-layout.css ; on impose
   ici un look unique, sans toucher aux fichiers historiques. Variables
   thème-aware (`--bg-surface`, `--bg-surface-alt`, `--border-color`)
   pour rester correct sur light, dark, deep-ether, grey, custom.
*/
.skeleton-post {
    background: var(--bg-surface, #1a1d22);
    border: 1px solid var(--border-color, rgba(255, 255, 255, 0.045));
    border-radius: var(--radius-md, 12px);
    /* On laisse JS dicter min-height (variabilité réaliste). On supprime
       la contrainte fixe à 140 px qui causait un layout shift quand les
       vrais posts arrivaient plus hauts. */
    height: auto;
    padding: var(--space-md, 14px);
    margin-bottom: var(--space-sm, 10px);
    /* `position: relative` ancre les ::after du shimmer global de la card
       sur la card elle-même, pas sur le viewport. */
    position: relative;
    overflow: hidden;
    contain: paint style;
    isolation: isolate;
    /* Pas d'animation fixe sur la card elle-même — le shimmer vit dans
       les ::after pour rester GPU-only. La règle `animation: none !important`
       neutralise les keyframes héritées (skeletonFadeIn, skeleton-pulse,
       premiumShimmer, etc.) déclarées dans 2-layout.css. */
    animation: none !important;
}

.skeleton-line,
.skeleton-avatar,
.skeleton-header {
    /* Base couleur : surface-alt pour s'aligner sur le fond du post.
       Le shimmer translucide passe par-dessus via ::after. */
    background: var(--bg-surface-alt, rgba(255, 255, 255, 0.045)) !important;
    background-image: none !important;
    background-size: auto !important;
    /* Annule les keyframes JS-driven concurrentes (shimmer / skeletonShimmer
       / premiumShimmer) qui se battaient en cascade. La nouvelle anim
       vit uniquement dans le ::after, GPU-only via translateX. */
    animation: none !important;
    position: relative;
    overflow: hidden;
    border-radius: var(--radius-xs, 6px);
}

.skeleton-avatar {
    border-radius: 50%;
}

/* Shimmer GPU : translateX d'un ::after, jamais background-position. */
.skeleton-line::after,
.skeleton-avatar::after,
.skeleton-header::after,
.skeleton-post::after {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(
        100deg,
        transparent 0%,
        var(--skeleton-shimmer-tint, rgba(255, 255, 255, 0.07)) 45%,
        var(--skeleton-shimmer-tint, rgba(255, 255, 255, 0.07)) 55%,
        transparent 100%
    );
    transform: translate3d(-100%, 0, 0);
    animation: spa-skel-shimmer 1.25s cubic-bezier(0.4, 0, 0.2, 1) infinite;
    will-change: transform;
}

.skeleton-post::after {
    /* Le shimmer global de la card est plus diffus pour ne pas concurrencer
       ceux des lignes / avatar à l'intérieur. */
    opacity: 0.55;
    border-radius: inherit;
    animation-duration: 1.45s;
    animation-delay: 80ms;
}

@keyframes spa-skel-shimmer {
    0%   { transform: translate3d(-100%, 0, 0); }
    60%  { transform: translate3d(100%, 0, 0); }
    100% { transform: translate3d(100%, 0, 0); }
}

/* Light themes : remonter la luminance du shimmer pour rester visible
   sur fond pâle. Le `:root` par défaut est light (poire light-ether). */
:root,
[data-theme="light-ether"] {
    --skeleton-shimmer-tint: rgba(20, 24, 32, 0.08);
}

/* Dark themes : tint plus discret pour ne pas exploser le contrast. */
[data-theme="dark"],
[data-theme="grey"],
[data-theme="custom"],
[data-theme="deep-ether"] {
    --skeleton-shimmer-tint: rgba(255, 255, 255, 0.075);
}

/* prefers-reduced-motion : on coupe la translation, on garde un pulse
   très léger pour signaler que c'est encore en cours de chargement. */
@media (prefers-reduced-motion: reduce) {
    .skeleton-line::after,
    .skeleton-avatar::after,
    .skeleton-header::after,
    .skeleton-post::after {
        animation: spa-skel-pulse 2s ease-in-out infinite;
        transform: none;
    }
    @keyframes spa-skel-pulse {
        0%, 100% { opacity: 0.55; }
        50%      { opacity: 0.85; }
    }
}

/* ─── 2. ENTRÉE DU CONTENU — anti-double-animation ────────────────────
   `topicPostMaterialize` (3-components.css) et `postReveal` (2-layout.css)
   tournaient en parallèle sur les premiers posts → animation un peu
   "pataude". On garde uniquement la première (staggered) sur le batch
   initial via `is-route-ready`, et on annule la seconde sur la même
   fenêtre.
*/
.main-content.topic-layout.is-route-ready #posts-container > .post:nth-child(-n + 12):not(.skeleton-post) {
    animation-name: topicPostMaterialize;
    animation-duration: 0.22s;
    animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
    animation-fill-mode: both;
}

/* Stagger plus serré : tous les posts visibles entrent en moins de 80 ms,
   au lieu de 90 + 240 = 330 ms cumulés. On reste sous le seuil de
   perception du retard (~100 ms). */
.main-content.topic-layout.is-route-ready #posts-container > .post:nth-child(1) { animation-delay: 0ms; }
.main-content.topic-layout.is-route-ready #posts-container > .post:nth-child(2) { animation-delay: 14ms; }
.main-content.topic-layout.is-route-ready #posts-container > .post:nth-child(3) { animation-delay: 28ms; }
.main-content.topic-layout.is-route-ready #posts-container > .post:nth-child(4) { animation-delay: 42ms; }
.main-content.topic-layout.is-route-ready #posts-container > .post:nth-child(5) { animation-delay: 56ms; }
.main-content.topic-layout.is-route-ready #posts-container > .post:nth-child(n + 6):nth-child(-n + 12) {
    animation-delay: 70ms;
}

/* La règle générique de 2-layout.css applique `postReveal` à TOUS les
   posts (y compris au-delà des 12 premiers), ce qui re-jouait l'anim
   à chaque virtualisation / append. On ne garde l'animation que sur le
   batch initial (gérée ci-dessus), pas sur les batches suivants. */
.main-content.topic-layout.is-route-ready #posts-container > .post:not(.skeleton-post):nth-child(n + 13),
.main-content.topic-layout:not(.is-route-ready) #posts-container > .post:not(.skeleton-post) {
    animation: none !important;
}

/* Le coupe-feu sur `.topic-card.is-opening` une fois le topic monté est
   géré dans la section 10 (via `body.spa-topic-mounted`). Le router
   retire aussi la classe `.is-opening` côté JS dans le finally de
   loadRoute pour les autres cartes — double sécurité. */

/* ─── 3. ROUTE TRANSITIONS — crossfade subtil + mobile snappy ──────── */

/* La couche `is-route-shell` doit être plus discrète que la version
   originale (translateY 10 px → 4 px) pour ne pas attirer l'œil :
   c'est juste le squelette, le focus reste sur le contenu réel. */
.main-content.topic-layout.is-route-shell .topic-route-shell {
    animation-name: spa-route-shell-in;
    animation-duration: 0.16s;
    animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
    animation-fill-mode: both;
}

@keyframes spa-route-shell-in {
    from {
        opacity: 0;
        transform: translate3d(0, 4px, 0);
    }
    to {
        opacity: 1;
        transform: translate3d(0, 0, 0);
    }
}

/* La couche `is-route-ready` n'a plus besoin de redessiner le shell —
   la View Transition desktop fait le boulot, et mobile slide-in masque
   la transition. On allège l'animation à 0.12s pour ne pas créer un
   second beat visuel après le slide mobile. */
.main-content.topic-layout.is-route-ready .topic-route-ready {
    animation-name: spa-route-ready-in;
    animation-duration: 0.12s;
    animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
    animation-fill-mode: both;
}

@keyframes spa-route-ready-in {
    from { opacity: 0.94; }
    to   { opacity: 1; }
}

/* ─── 4. TOPIC HEADER & REPLY FORM — paint isolation ──────────────── */

/* `will-change: transform` permanent (fluid-perf.css :545) entretient
   un layer GPU pour rien sur idle. On bascule sur `transform: translateZ(0)`
   seul : il promeut une seule fois (au mount) puis le compositor garde
   le layer si besoin, sans pénalité VRAM continue. */
.topic-header,
.mobile-bottom-nav {
    will-change: auto;
}
.topic-header {
    transform: translateZ(0);
}

/* Quand on scrolle, on autorise temporairement will-change. Toggle posé
   par topic_view.js / scroll-lock si besoin — sinon laisse `auto`. */
body.topic-scrolling .topic-header {
    will-change: transform;
}

/* ─── 5. SIDEBAR — feedback de prefetch / clic plus net ──────────── */

/* `.topic-card.is-prefetched` : signale visuellement que la carte est
   déjà chaude (cache HIT garanti). Très subtil — juste une accent-line
   à gauche. Pas de hover full background pour ne pas saturer la liste. */
.topic-card.is-prefetched {
    position: relative;
}
.topic-card.is-prefetched::before {
    content: '';
    position: absolute;
    left: 0;
    top: 8px;
    bottom: 8px;
    width: 2px;
    border-radius: 2px;
    background: linear-gradient(
        180deg,
        color-mix(in srgb, var(--accent-primary, #a8c545) 36%, transparent),
        color-mix(in srgb, var(--accent-primary, #a8c545) 12%, transparent)
    );
    pointer-events: none;
    opacity: 0.7;
}

/* ─── 6. MOBILE SLIDE-IN — snappier, easing iOS-like ────────────── */

@media (max-width: 768px) {
    /* La transition globale `.main-content` (transform 0.35s) est posée
       inline par spa-router.js pour conserver le timing exact pendant
       le slide. On override quand la classe `.spa-snappy-slide` est
       présente — c'est elle qu'on ajoute à la mount. */
    body.spa-snappy-slide .main-content {
        transition: transform 0.28s cubic-bezier(0.16, 1, 0.3, 1) !important;
    }

    /* Pendant le slide, on cache la sidebar de la liste pour éviter un
       compositing avec le contenu en mouvement. Les classes existantes
       le font déjà, on ajoute juste une protection contre le scroll
       parasite. */
    body.spa-snappy-slide {
        overscroll-behavior: contain;
    }
}

/* ─── 7. SSR SKELETON — fade-out propre quand les posts arrivent ── */

/* Le SSR skeleton est retiré "atomically" avec l'insertion des vrais
   posts (même frame). Pour que le passage soit moins sec, on lui ajoute
   un fade trigger via la classe `.is-leaving`. Si topic_view.js l'utilise,
   on a une transition de 120 ms ; sinon le `remove()` direct reste en
   place sans changement de comportement. */
#posts-initial-skeleton.is-leaving {
    opacity: 0;
    transform: translate3d(0, -2px, 0);
    transition: opacity 0.12s ease-out, transform 0.12s ease-out;
    pointer-events: none;
}

/* Le shell SPA construit dynamiquement utilise la classe `topic-skeleton-stack`
   sur `#posts-container`. Quand le router pose `is-route-ready`, on déclenche
   un fade-out simultané au fade-in du contenu réel. Le swap reste atomique
   (innerHTML), mais ça évite les flickers résiduels si un thème ajoute de
   l'opacity sur une transition CSS générique. */
.main-content.topic-layout.is-route-ready .topic-skeleton-stack {
    opacity: 0;
    transition: opacity 0.1s ease-out;
}

/* ─── 8. PROGRESS DISCREET — micro-indicateur en haut ────────────── */

/* `.spa-loading` est posé sur <body> par spa-router.js dès le début d'une
   navigation. Une fine barre dégradée se dessine en haut de l'écran,
   GPU-composited, presque invisible mais rassurante. La barre est
   `opacity: 0` à l'instant T0 et passe en `opacity: 0.85` après une
   `transition-delay: 80ms`. Sur cache HIT (mount synchrone < 80 ms),
   la classe `.spa-loading` est retirée AVANT que le delay ne s'écoule
   → la barre n'apparaît jamais. C'est l'effet voulu : on n'attire
   l'attention que si l'utilisateur attend réellement. */
body::before {
    content: '';
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 2px;
    z-index: 99999;
    background: linear-gradient(
        90deg,
        transparent 0%,
        color-mix(in srgb, var(--accent-primary, #a8c545) 80%, transparent) 35%,
        color-mix(in srgb, var(--accent-primary, #a8c545) 100%, transparent) 50%,
        color-mix(in srgb, var(--accent-primary, #a8c545) 80%, transparent) 65%,
        transparent 100%
    );
    background-size: 200% 100%;
    pointer-events: none;
    opacity: 0;
    transform: translate3d(0, -2px, 0);
    /* Pas de delay côté hide → quand .spa-loading disparaît, la barre
       fond instantanément. Le delay 80 ms n'est appliqué qu'au show
       via la règle .spa-loading::before. */
    transition: opacity 0.15s ease-out, transform 0.15s ease-out;
    /* L'animation slide ne tourne que quand la barre est visible —
       sinon on consommerait des frames CPU pour rien. */
    animation: none;
}

body.spa-loading::before {
    opacity: 0.85;
    transform: translate3d(0, 0, 0);
    /* Delay 80 ms sur transition ET animation : pour les nav < 80 ms
       (cache HIT, mount synchrone), .spa-loading est retiré avant que
       la barre n'ait commencé à apparaître. */
    transition: opacity 0.18s ease-out 80ms, transform 0.18s ease-out 80ms;
    animation: spa-loading-slide 1.1s linear infinite 80ms;
}

@keyframes spa-loading-slide {
    from { background-position: 200% 0; }
    to   { background-position: -200% 0; }
}

@media (prefers-reduced-motion: reduce) {
    body.spa-loading::before {
        animation: none;
        opacity: 0.6;
    }
}

/* ─── 9. CONTAINER PROMOTION — éviter reflows pendant le swap ──── */

/* Pendant le swap innerHTML, le navigateur peut recalculer le layout
   de l'arborescence parente. `contain: layout` sur la zone topique
   isole ce coût (le sidebar + header restent inchangés). On le pose
   uniquement sur la phase de chargement pour ne pas masquer les
   menus / overlays qui débordent légitimement après le mount. */
.main-content.topic-layout.is-route-shell {
    contain: layout style;
}

/* ─── 10. THEME-AWARE TOPIC CARD ACTIVE STATE ──────────────────────
   Quand on clique sur une carte, on a déjà l'effet `.is-opening`. Sur
   cache HIT, la carte mount le topic avant que les 1600 ms d'animation
   ne se terminent. On ajoute une coupure nette via la classe `.spa-mounted`
   sur <body> : dès que le topic est interactif, l'animation s'arrête.
*/
body.spa-topic-mounted .topic-card.is-opening::after {
    animation: none;
    opacity: 0;
    transition: opacity 0.18s ease-out;
}
