/* ===================================================================
   feed.css — Feed Unitydom / Flow
   Variables attendues depuis index.php :
   --k-cyan --k-green --k-yellow --k-pink --k-violet
   --bg --surface --line --txt-dim --keys-gradient
   =================================================================== */

:root {
    --ud-head-h: 0px;
    --ud-nav-h: 62px;
    --fd-r: 16px;                       /* rayon de reference */
    --fd-ease: cubic-bezier(.22, .9, .3, 1);
}

/* ===================================================================
   1. CONTENEUR
   C'est lui qui scrolle, pas le body : sinon le snap se bat avec le
   scroll de page et les elements fixes tremblent sur iOS.
   =================================================================== */
.fd-feed {
    /* --- LE CONTENEUR DE DEFILEMENT ---------------------------------
       Ces quatre declarations avaient disparu. Sans elles, .fd-feed
       est une div ordinaire : elle ne defile pas, et surtout elle n'a
       pas de hauteur definie.

       C'est la cause du tremblement. `.fd-card { height: 100% }` se
       resout contre le parent ; un parent de hauteur indefinie renvoie
       `auto`. Les cartes n'ont donc jamais exactement la hauteur de
       l'ecran. Or scroll-snap cale le HAUT d'une carte, puis constate
       que le bas ne tombe pas au bon endroit, et recale. En boucle.
       Ca oscille precisement au moment ou la carte se positionne.

       `position: fixed` cale au viewport et donne au conteneur une hauteur
       definie ET stable. C'est preferable a `height: 100vh` (qui vaut
       la hauteur barre d'URL masquee, donc trop grande quand elle est
       visible) et a `100dvh` (qui se recalcule PENDANT que la barre
       glisse — chaque recalcul retire le snap, ce qui refabrique le
       tremblement qu'on vient d'eliminer). Un element fixe ne depend
       d'aucune des deux. */
    position: fixed;
    /* Axes separes volontairement, pas `inset: 0`.
       ---------------------------------------------------------------
       L'axe vertical est structurel : le conteneur doit occuper toute
       la hauteur visible, sur tous les ecrans, sinon les cartes en
       `height: 100%` perdent leur reference et le snap se remet a
       osciller.

       L'axe horizontal, lui, change selon l'ecran : pleine largeur sur
       mobile, colonne de 430px centree sur desktop. Le raccourci
       `inset: 0` melangeait les deux et rendait le second difficile a
       surcharger proprement. */
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1;
    overflow-y: auto;
    overflow-x: hidden;

    /* --- Le calage ---------------------------------------------------
       Maintenant que les cartes font exactement la hauteur visible,
       `mandatory` est tenable : le navigateur trouve toujours une
       position qui satisfait la contrainte, et s'y arrete du premier
       coup. feed.js le detecte (SNAP_NATIF) et ne recale plus rien. */
    scroll-snap-type: y mandatory;

    /* Le geste ne remonte pas a la page : ni pull-to-refresh declenche
       par erreur, ni rebond iOS qui decale le conteneur en plein
       calage. */
    overscroll-behavior-y: contain;

    /* Le defilement natif reste instantane. `smooth` ici animerait
       CHAQUE ajustement, y compris les repositionnements internes du
       JS : garanti tremblant. */
    scroll-behavior: auto;

    -webkit-overflow-scrolling: touch;
    background: #000;
}

/* Reaffirme par identifiant les seules proprietes dont la perte casse
   le defilement : le conteneur en a deja perdu la definition une fois.

   ATTENTION — ce bloc ne doit JAMAIS toucher a l'axe horizontal.
   Il l'a fait, avec `inset: 0`. Un selecteur a identifiant pese
   1-1-0 : il ecrasait la media query desktop (0-1-0) qui centre la
   colonne avec `left: 50%`. Le feed se retrouvait colle a gauche et
   pleine largeur, tandis que les barres haute et basse restaient
   centrees a 430px. Tout paraissait decale.

   `left` et `right` sont donc absents ici, et le restent : ils
   appartiennent aux regles de classe, que les media queries doivent
   pouvoir surcharger. */
#ud-feed.fd-feed {
    position: fixed;
    top: 0;
    bottom: 0;
    overflow-y: auto;
    scroll-snap-type: y mandatory;
}

.fd-feed::-webkit-scrollbar { display: none; }

@media (min-width: 769px) {
    .fd-feed {
        /* La colonne centrale. La regle de base a pose left: 0 et
           right: 0 ; on libere la droite pour que la largeur fixe et
           le centrage par transform prennent la main. */
        left: 50%;
        right: auto;
        transform: translateX(-50%);
        width: min(430px, 100vw);
        border-left: 1px solid var(--line);
        border-right: 1px solid var(--line);

        /* La carte n'est plus l'ecran : c'est cette colonne. Le rail se
           dimensionne dessus (voir --fd-ico), donc la variable doit
           suivre la meme valeur, a l'identique. */
        --fd-carte-l: min(430px, 100vw);
    }
}


/* ===================================================================
   LOADER DISCRET — n'agite pas l'écran
   =================================================================== */
.fd-loader.is-charge {
    height: 30px;
    opacity: 0.6;
    transition: opacity 0.3s;
}
.fd-loader.is-charge span {
    height: 12px;
    animation-duration: 0.8s;
}
.fd-loader.is-charge.is-panne {
    opacity: 1;
}
.fd-loader.is-charge.is-panne span {
    display: none;
}
.fd-loader.is-charge.is-panne::after {
    content: "⏳ Recharge...";
    font-size: 12px;
    color: var(--k-yellow);
}
.fd-loader.is-done::after {
    content: "Tu es à jour";
    font-size: 11px;
    font-weight: 800;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--txt-dim);
}
/* ===================================================================
   2. CARTE
   =================================================================== */
.fd-card {
    position: relative;
    width: 100%;
    height: 100%;
    flex: 0 0 auto;
    scroll-snap-align: start;
    scroll-snap-stop: always;
    overflow: hidden;
    background: #000;
    isolation: isolate;

    /* AJOUTE : le balayage horizontal (profil / retour, voir feed.js)
       a besoin que le navigateur lui laisse l'horizontale. `pan-y` dit
       exactement cela : le defilement vertical reste natif — c'est lui
       qui porte tout le fil, on n'y touche surtout pas — et seul le
       geste lateral remonte au JavaScript. Sans cette ligne, le
       navigateur peut decider en cours de route que le geste lui
       appartient et couper les evenements en plein milieu. */
    touch-action: pan-y;
    contain: layout paint;
}

.fd-stage {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;

    /* CORRIGE : c'etait `background: #000`.
       ---------------------------------------------------------------
       Un fond noir pur ne pardonne rien. Des qu'un media manque — une
       affiche vide, une vignette morte, un type de post qu'on n'a pas
       prevu — la carte devient un rectangle noir indistinguable d'un
       bug d'affichage. On ne peut meme pas dire, en la regardant, si
       elle est en train de charger ou si elle est cassee.

       Le meme fond que la carte lien : deux teintes du site en
       diagonale. Une image posee dessus le recouvre entierement, donc
       ca ne change rien au cas normal — mais une carte sans media
       reste lisible au lieu de disparaitre. */
    background:
        radial-gradient(90% 62% at 12% 0%, rgba(0,217,255,.14) 0%, transparent 62%),
        radial-gradient(80% 58% at 92% 100%, rgba(139,92,246,.16) 0%, transparent 58%),
        linear-gradient(158deg, #16161f 0%, #0b0b12 62%);
}

.fd-stage .fd-img,
.fd-stage video {
    width: 100%; height: 100%;
    object-fit: cover;
    display: block;
}
.fd-stage video.is-large { object-fit: contain; }

/* -------------------------------------------------------------------
   LES BANDES NOIRES DES VIDEOS PAYSAGE
   -------------------------------------------------------------------
   AJOUTE. Une video plus large que haute est volontairement affichee
   entiere (`contain` ci-dessus) plutot que rognee — c'est le bon choix,
   recadrer en 9:16 couperait la moitie de l'image. Mais ca laisse deux
   bandes noires mortes, en haut et en bas, souvent un bon tiers de la
   carte. Le fil perd d'un coup son effet plein cadre : au lieu d'une
   image qui remplit l'ecran, on regarde une petite fenetre posee sur du
   vide.

   Tous les fils verticaux repondent pareil : ils remplissent les bandes
   avec une copie floutee et agrandie de l'image. Le cadre redevient
   plein, la video garde ses proportions exactes, et le flou ne dispute
   jamais l'attention au sujet.

   Le point delicat est le COUT. La solution evidente — une seconde
   <video> en fond — est justement celle qu'il ne faut pas prendre ici :
   un lecteur monte consomme un decodeur materiel, et sur iOS il n'y en
   a qu'un seul (c'est deja la raison pour laquelle le prechauffage
   passe par une requete HTTP et non par un second <video>, voir
   feed_player.js). Deux lecteurs par carte, et la video principale
   n'aurait plus de decodeur.

   On se sert donc de l'AFFICHE, qui est deja chargee et decodee : une
   image fixe, floutee une fois par le compositeur, jamais reanimee.
   C'est une image arretee la ou un fil natif anime le fond — mais le
   flou est tel qu'a l'usage la difference ne se voit pas, et ca ne
   coute pas un seul decodeur de plus.
   ------------------------------------------------------------------- */
.fd-card.a-bandes .fd-stage::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 0;
    background-image: var(--fd-fond);
    background-size: cover;
    background-position: center;
    /* Agrandi : le flou delave les bords, l'agrandissement les pousse
       hors cadre pour qu'on ne voie jamais de lisiere claire. */
    transform: scale(1.18);
    filter: blur(26px) saturate(1.35) brightness(.5);
    pointer-events: none;
}
/* La video repasse au-dessus du fond floute. */
.fd-card.a-bandes .fd-stage video { position: relative; z-index: 1; }

@media (prefers-reduced-motion: reduce) {
    /* Rien a desactiver ici (le fond est fixe), mais on allege le flou
       sur les machines qui demandent la sobriete. */
    .fd-card.a-bandes .fd-stage::before { filter: blur(18px) brightness(.45); }
}
.fd-embed { position: absolute; inset: 0; }
.fd-embed iframe { width: 100%; height: 100%; border: 0; }

/* --- Squelette : evite le flash noir avant l'image ---------------
   REECRIT POUR LA FLUIDITE.

   Deux problemes dans la version precedente, qui se cumulaient :

   1. Elle animait `background-position` sur un degrade de 300% de
      large, en plein cadre. Une propriete de peinture : le navigateur
      redessine la surface entiere a chaque frame. Le compositeur ne
      peut rien mettre sur le GPU.

   2. Elle tournait sur TOUTES les cartes sans `is-pret` — donc sur
      tout le tampon, jusqu'a vingt cartes hors ecran. Vingt repeintes
      plein cadre par frame, pour des cartes que personne ne regarde.

   Maintenant : un pseudo-element qu'on translate. `transform` est
   compositee, elle ne coute pas de repeinte. Et l'animation est en
   pause par defaut : elle ne tourne que sur la carte visible et la
   suivante — les deux seules ou un squelette a un sens. */
.fd-skel {
    position: absolute;
    inset: 0;
    overflow: hidden;
    background: #0e0e14;
}
.fd-skel::before {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(100deg,
        rgba(25,25,34,0) 20%,
        rgba(25,25,34,1) 50%,
        rgba(25,25,34,0) 80%);
    transform: translate3d(-100%, 0, 0);
    animation: fd-shim 1.4s linear infinite;
    animation-play-state: paused;
}
@keyframes fd-shim { to { transform: translate3d(100%, 0, 0); } }

/* Seules la carte active et la suivante font miroiter leur squelette. */
.fd-card.is-active .fd-skel::before,
.fd-card.is-active + .fd-card .fd-skel::before {
    animation-play-state: running;
}

.fd-card.is-pret .fd-skel {
    opacity: 0;
    transition: opacity .35s;
}

/* AJOUTE — « des videos sans image ».
   Une carte video dont la vignette manque (videos.thumbnail vide, la
   vignette est facultative a la publication) n'a rien a montrer avant
   sa premiere image decodee. Comme elle n'a aucune ressource a
   attendre, elle passe « is-pret » immediatement et le squelette
   s'efface : il ne reste qu'un rectangle noir, parfois plusieurs
   secondes. On garde le miroitement jusqu'a la premiere image, que
   feed.js signale avec « a-image » (ecouteur loadeddata). */
.fd-card[data-kind="video"]:not([data-poster]).is-pret:not(.a-image) .fd-skel {
    opacity: 1;
}
.fd-card[data-kind="video"]:not([data-poster]):not(.a-image) .fd-skel::before {
    animation-play-state: running;
}
.fd-card.is-pret .fd-skel::before { animation: none; }

/* --- Images : fondu a l'arrivee, pas d'apparition seche --- */
.fd-img {
    opacity: 0;
    transition: opacity .45s var(--fd-ease);
}
.fd-img.is-charge { opacity: 1; }

/* --- Carte son : pochette floue en fond, disque net au centre --- */
.fd-flou {
    position: absolute;
    inset: -8%;
    width: 116%; height: 116%;
    filter: blur(38px) saturate(1.5) brightness(.5);
    transform: scale(1.1);
}
.fd-disque {
    position: relative;
    width: min(68vw, 290px);
    aspect-ratio: 1;
    border-radius: 20px;
    overflow: hidden;
    box-shadow: 0 26px 70px rgba(0,0,0,.75);
    margin-bottom: 12vh;
}
.fd-disque .fd-img { border-radius: 20px; }

/* Onde : 4 barres qui pulsent quand ca joue */
.fd-onde {
    position: absolute;
    bottom: 12px; left: 12px;
    display: flex; align-items: flex-end; gap: 3px;
    height: 18px;
    padding: 5px 7px;
    border-radius: 999px;
    background: rgba(0,0,0,.5);
    backdrop-filter: blur(6px);
    opacity: 0;
    transition: opacity .3s;
}
.fd-card.is-active .fd-onde { opacity: 1; }
.fd-onde i {
    display: block;
    width: 3px; height: 100%;
    border-radius: 2px;
    background: var(--k-cyan);
    transform-origin: bottom;
    animation: fd-onde .9s ease-in-out infinite;
}
.fd-onde i:nth-child(2) { background: var(--k-green);  animation-delay: .15s; }
.fd-onde i:nth-child(3) { background: var(--k-yellow); animation-delay: .3s; }
.fd-onde i:nth-child(4) { background: var(--k-pink);   animation-delay: .45s; }
@keyframes fd-onde {
    0%, 100% { transform: scaleY(.3); }
    50%      { transform: scaleY(1); }
}

/* --- Voile de lisibilite --- */
.fd-scrim {
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
    background:
        linear-gradient(to top, rgba(0,0,0,.93) 0%, rgba(0,0,0,.5) 24%, rgba(0,0,0,0) 50%),
        linear-gradient(to bottom, rgba(0,0,0,.5) 0%, rgba(0,0,0,0) 17%);
}

/* ===================================================================
   3. BADGE — une touche de couleur par famille de contenu
   =================================================================== */
.fd-badge {
    position: absolute;
    top: calc(env(safe-area-inset-top, 0px) + 58px);
    left: 14px;
    z-index: 20;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px 4px 8px;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: rgba(0,0,0,.5);
    backdrop-filter: blur(10px);
    font-size: 10px;
    font-weight: 800;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: #fff;
}
.fd-badge::before {
    content: "";
    width: 7px; height: 7px;
    border-radius: 2px;
    background: var(--fd-key, var(--k-cyan));
}
.fd-card[data-kind="video"]   { --fd-key: var(--k-pink); }
.fd-card[data-kind="youtube"] { --fd-key: var(--k-pink); }
.fd-card[data-kind="audio"]   { --fd-key: var(--k-cyan); }
.fd-card[data-kind="image"]   { --fd-key: var(--k-yellow); }
.fd-card[data-kind="texte"]   { --fd-key: var(--k-violet); }

/* ===================================================================
   4. RAIL D'ACTIONS
   =================================================================== */
/* -------------------------------------------------------------------
   LA TAILLE DU RAIL, ET POURQUOI ELLE NE PEUT PAS ETRE FIXE
   -------------------------------------------------------------------
   Tout le rail etait en pixels durs : pastilles de 44, icones de 23,
   ecarts de 17, avatar de 46. Or ce rail est ancre EN BAS et grandit
   VERS LE HAUT, tandis que le bouton du son est ancre en haut (58 px).
   Rien ne les empechait de se rencontrer : sur un ecran un peu court —
   une fenetre de navigateur sur ordinateur, un telephone compact — le
   haut du rail monte dans le bouton du son, et l'avatar se retrouve
   dessine par-dessus. C'est exactement ce qu'on voit sur la capture :
   le haut-parleur et l'avatar se chevauchent.

   Deux dimensions comptent donc, pas une :

   - LA LARGEUR DE LA CARTE, pour la proportion. Attention, ce n'est pas
     toujours la largeur de l'ecran : au-dessus de 769 px la carte
     devient une colonne de 430 px au milieu d'un grand ecran (voir la
     regle .fd-feed plus haut). D'ou --fd-carte-l, qui suit exactement
     cette bascule ; s'appuyer sur vw donnerait des icones enormes sur
     ordinateur.

   - LA HAUTEUR DISPONIBLE, pour que les sept elements tiennent sans
     jamais atteindre le bouton du son.

   On prend le plus petit des deux : la contrainte la plus serree gagne.
   Les bornes de clamp() gardent la pastille tapable en bas (34 px) et
   l'empechent de devenir absurde sur un grand moniteur (46 px).

   Tout le reste du rail — ecarts, icones, chiffres, avatar, bouton
   suivre — est desormais exprime en proportion de cette pastille, dans
   les memes rapports qu'avant (23/44, 17/44, 46/44...). Une seule
   valeur a regler, et l'ensemble reste coherent a toutes les tailles.
   ------------------------------------------------------------------- */
/* Filet : ces variables sont AUSSI posees sur :root.
   Sans ca, un element du rail qui se retrouverait hors de .fd-feed
   verrait var(--fd-ico) invalide — et un var() invalide ne retombe PAS
   sur la declaration en pixels ecrite juste au-dessus : la propriete
   entiere est jetee et vaut `unset`, donc `auto` pour une largeur. Le
   rail s'effondre alors completement (avatar en pleine page, pastilles
   sans forme). Verifie au rendu : c'est exactement ce qui s'est produit
   au premier essai. D'ou la ceinture (ici) et les bretelles (le repli
   ecrit dans chaque var(), plus haut). */
:root {
    --fd-carte-l: 100vw;
    --fd-ico: clamp(34px, min(calc(100vw * .115), 6vh), 46px);
    --fd-rail-ecart: .34;
}

.fd-feed {
    --fd-carte-l: 100vw;
    --fd-ico: clamp(34px, min(calc(var(--fd-carte-l, 100vw) * .115), 6vh), 46px);
    --fd-rail-ecart: .34;
}

.fd-rail {
    position: absolute;
    right: 8px;                              /* repli pour l'ancien moteur */
    right: calc(var(--fd-ico, 44px) * .18);
    /* Remonte au-dessus de la barre du bas. Sans ca, le bouton du bas
       du rail — le partage — tombe derriere la barre et devient
       intouchable. */
    bottom: calc(var(--ud-nav-h, 0px) + 26px);
    z-index: 20;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 17px;
    gap: calc(var(--fd-ico, 44px) * var(--fd-rail-ecart, .34));
}

/* Ecran court : on retire les trois ETIQUETTES du rail — « Favori »,
   « Partager », « Signaler ». Ce sont des mots, pas des compteurs :
   contrairement aux chiffres des trois premiers boutons, ils
   n'apprennent rien que l'icone ne dise deja (et les lecteurs d'ecran
   gardent leur aria-label). Les supprimer rend une cinquantaine de
   pixels a la verticale, precisement la ou ils manquent, plutot que de
   rapetisser encore des cibles tactiles deja au minimum. */
/* Le palier est a 700 px et non a 620 : mesure faite dans un vrai
   navigateur, un Android compact (360x640) ne laissait que 12 px entre
   le haut du rail et le bouton du son, et la fenetre de la capture
   (1366x660) 20 px. Ca ne se chevauche plus, mais il suffit d'une barre
   systeme un peu plus haute pour que ca recommence. Un iPhone SE (667)
   et toutes les fenetres de bureau courtes tombent dans le meme cas :
   ils passent donc tous ici, ou la marge remonte au-dela de 60 px. */
@media (max-height: 700px) {
    .fd-feed { --fd-ico: clamp(32px, min(calc(var(--fd-carte-l, 100vw) * .115), 6vh), 46px);
               --fd-rail-ecart: .24; }
    .fd-fav .fd-n,
    .fd-share .fd-n,
    .fd-report .fd-n { display: none; }
}

/* Ecran tres bas — un telephone tenu a l'horizontale, pour l'essentiel.
   Il faut le dire franchement : sept actions empilees ne tiennent pas
   dans 390 px de haut une fois retirees l'en-tete et la barre du bas,
   quelle que soit la taille des pastilles (il resterait moins de 27 px
   chacune, soit une cible intapable). On ne fait donc pas semblant :
   on descend au plus bas raisonnable, on retire tous les compteurs, et
   on plafonne la hauteur du rail pour qu'il reste DANS la carte plutot
   que d'aller se dessiner par-dessus l'en-tete. Si ca ne suffit pas, il
   defile — c'est le seul cas ou ce defilement peut disputer le geste au
   fil, et c'est un compromis assume : mieux vaut un rail qui glisse
   qu'un rail dont la moitie est hors de l'ecran. */
@media (max-height: 480px) {
    .fd-feed { --fd-ico: clamp(30px, min(calc(var(--fd-carte-l, 100vw) * .115), 6.5vh), 40px);
               --fd-rail-ecart: .2; }
    .fd-act .fd-n { display: none; }
    .fd-rail {
        max-height: calc(100% - var(--ud-nav-h, 0px) - 26px
                         - (env(safe-area-inset-top, 0px) + 104px));
        overflow-y: auto;
        scrollbar-width: none;
    }
    .fd-rail::-webkit-scrollbar { display: none; }
}

.fd-act {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
    padding: 0;
    border: 0;
    background: none;
    color: #fff;
    cursor: pointer;
    text-decoration: none;
    -webkit-tap-highlight-color: transparent;
}
.fd-act .fd-ico {
    width: 44px; height: 44px;               /* repli pour l'ancien moteur */
    width: var(--fd-ico, 44px); height: var(--fd-ico, 44px);
    display: flex; align-items: center; justify-content: center;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: transparent;
    backdrop-filter: blur(10px);
    transition: transform .16s var(--fd-ease), background .16s, border-color .16s;
}
.fd-act:active .fd-ico { transform: scale(.88); }
.fd-act svg {
    width: 23px; height: 23px;               /* repli pour l'ancien moteur */
    width: calc(var(--fd-ico, 44px) * .52); height: calc(var(--fd-ico, 44px) * .52);

    /* AJOUTE : sur telephone, la regle @media (max-width: 768px) plus
       bas retire le flou ET le fond des pastilles (`backdrop-filter:
       none; background: transparent`) — pour de bonnes raisons de
       performance. Il ne reste alors qu'un trait blanc de 1,8 px pose
       directement sur l'image. Sur une video sombre c'est parfait ;
       sur un plan clair — ciel, neige, mur blanc, un visage en
       surexposition — les icones disparaissent purement et simplement.
       Le voile de lisibilite (.fd-scrim) ne couvre que le haut et le
       bas de la carte : le rail, lui, est au milieu, dans la zone
       laissee transparente.

       Une ombre portee coute une passe de compositeur, suit exactement
       la forme du trait, et rend le rail lisible sur n'importe quel
       fond. C'est ce que font tous les fils verticaux. */
    filter: drop-shadow(0 1px 3px rgba(0,0,0,.55));
}
.fd-act .fd-n {
    font-size: 11px;                         /* repli pour l'ancien moteur */
    font-size: clamp(9px, calc(var(--fd-ico, 44px) * .25), 12px);
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    text-shadow: 0 1px 4px rgba(0,0,0,.9);
}

.fd-like.is-on { color: var(--k-pink); }
.fd-like.is-on .fd-ico {
    background: rgba(255,0,85,.16);
    border-color: rgba(255,0,85,.5);
}
.fd-like.is-on .fd-n { color: var(--k-pink); }

/* Petit rebond a l'activation */
@keyframes fd-pop {
    0% { transform: scale(1); } 45% { transform: scale(1.28); } 100% { transform: scale(1); }
}
.fd-act.is-pop .fd-ico { animation: fd-pop .38s var(--fd-ease); }

.fd-avatar {
    position: relative;
    display: block;
    width: 46px; height: 46px;               /* repli pour l'ancien moteur */
    width: calc(var(--fd-ico, 44px) * 1.05); height: calc(var(--fd-ico, 44px) * 1.05);
    margin-bottom: 2px;
}
.fd-avatar img {
    width: 100%; height: 100%;
    object-fit: cover;
    border: 2px solid #fff;
    border-radius: 999px;
    background: var(--surface);
}
.fd-avatar .fd-plus {
    position: absolute;
    bottom: -6px; left: 50%;
    transform: translateX(-50%);
    width: 19px; height: 19px;               /* repli pour l'ancien moteur */
    width: calc(var(--fd-ico, 44px) * .43); height: calc(var(--fd-ico, 44px) * .43);
    display: flex; align-items: center; justify-content: center;
    border: 2px solid #000;
    border-radius: 999px;
    background: var(--k-pink);
    color: #fff;
    font-size: 11px; font-weight: 900; line-height: 1;
}

/* ===================================================================
   5. BLOC INFO
   =================================================================== */
.fd-meta {
    position: absolute;
    left: 0; right: 68px;
    bottom: calc(var(--ud-nav-h, 0px) + 22px);
    z-index: 20;
    padding: 0 14px;
    color: #fff;
}
.fd-who {
    display: flex; align-items: center; gap: 7px;
    margin-bottom: 5px;
}
.fd-who a {
    font-size: 15px; font-weight: 900;
    color: #fff; text-decoration: none;
    text-shadow: 0 1px 6px rgba(0,0,0,.9);
}
.fd-when {
    font-size: 10px; font-weight: 800;
    letter-spacing: .06em;
    color: rgba(255,255,255,.6);
    font-variant-numeric: tabular-nums;
}
.fd-titre {
    font-size: 15px; font-weight: 800; line-height: 1.25;
    margin-bottom: 3px;
    text-shadow: 0 1px 6px rgba(0,0,0,.9);
}
.fd-sous {
    font-size: 13px; line-height: 1.45;
    color: rgba(255,255,255,.82);
    text-shadow: 0 1px 5px rgba(0,0,0,.9);
}
.fd-sous.is-ouvert {
    max-height: 42vh;
    overflow-y: auto;
    scrollbar-width: none;

    /* CORRIGE : c'etait `contain`.
       overscroll-behavior: contain empeche le defilement de se
       propager au parent — donc une fois le texte deplie, le doigt
       reste prisonnier du paragraphe et le feed ne bouge plus, meme
       arrive en bas. Le meme piege que l'ancienne vitrine, en plus
       discret. En `auto`, le feed reprend la main au bout. */
    overscroll-behavior: auto;
}
.fd-sous.is-ouvert::-webkit-scrollbar { display: none; }
.fd-plus-txt {
    margin-left: 5px;
    padding: 0;
    border: 0; background: none;
    color: rgba(255,255,255,.65);
    font: inherit; font-weight: 800;
    cursor: pointer;
}
.fd-plus-txt:hover { color: #fff; }

.fd-cta {
    display: inline-flex; align-items: center; gap: 5px;
    margin-top: 11px;
    padding: 9px 17px;
    border-radius: 999px;
    background: #fff; color: #000;
    font-size: 12px; font-weight: 900; letter-spacing: .02em;
    text-decoration: none;
    transition: transform .15s var(--fd-ease);
}
.fd-cta:active { transform: scale(.95); }

/* Carte texte : la typo est le visuel */
.fd-card[data-kind="texte"] .fd-stage {
    background:
        radial-gradient(120% 90% at 10% 0%,  rgba(139,92,246,.30) 0%, transparent 58%),
        radial-gradient(120% 90% at 90% 100%, rgba(0,217,255,.20) 0%, transparent 58%),
        var(--bg);
}
.fd-quote {
    /* Le texte est centre dans la carte pleine hauteur : sa reserve du
       bas doit couvrir la barre ET le bloc d'infos qui la surplombe. */
    padding: 3rem 1.5rem calc(var(--ud-nav-h, 0px) + 9rem);
    font-size: 26px; font-weight: 800; line-height: 1.26;
    letter-spacing: -.02em;
    color: #fff;
    max-height: 100%;
    overflow: hidden;
    border: 0;
}
/* AJOUTE : ces deux proprietes vivaient dans le style en ligne du
   gabarit (voir feed_card.php, carte texte). Elles remontent ici avec
   le reste, sinon un texte long ne se coupe plus proprement. */
.fd-quote { text-align: center; word-break: break-word; }

/* AJOUTE : les mots-cles et mentions cliquables des legendes (voir
   lierMots() dans feed.js). Assez marques pour se voir sur une image
   claire, assez sobres pour ne pas manger la legende. */
.fd-mot {
    color: var(--k-cyan, #00d9ff);
    font-weight: 800;
    text-decoration: none;
    /* Le mot reste dans le fil du texte : pas de fond, pas de pastille
       — sinon une legende de trois mots-cles devient un damier. */
    text-shadow: 0 1px 3px rgba(0,0,0,.6);
}
.fd-mot.est-membre { color: #fff; }

/* ===================================================================
   TIRER POUR RAFRAICHIR
   -------------------------------------------------------------------
   AJOUTE (voir rafraichirFil() dans feed.js). Le rond se remplit a
   mesure que le doigt descend : on voit ou est le seuil au lieu de le
   deviner. Il vit dans le fil, au-dessus de la premiere carte, et sous
   la barre du haut.
   =================================================================== */
.fd-ptr {
    position: fixed;
    top: calc(env(safe-area-inset-top, 0px) + 58px);
    left: 50%;
    z-index: 30;
    width: 34px;
    height: 34px;
    transform: translate(-50%, 0);
    opacity: 0;
    pointer-events: none;
    border-radius: 50%;
    background: rgba(0, 0, 0, .55);
    backdrop-filter: blur(8px);
    box-shadow: 0 2px 10px rgba(0, 0, 0, .35);
}
.fd-ptr svg { width: 100%; height: 100%; display: block; }
.fd-ptr circle {
    fill: none;
    stroke-width: 2.4;
    transform: rotate(-90deg);
    transform-origin: 50% 50%;
}
.fd-ptr .fd-ptr-fond { stroke: rgba(255, 255, 255, .18); }
.fd-ptr .fd-ptr-arc  {
    stroke: var(--k-cyan, #00d9ff);
    stroke-linecap: round;
    stroke-dasharray: 57;          /* 2 x pi x 9, le tour du cercle */
    stroke-dashoffset: 57;
}
.fd-ptr.est-pret { background: rgba(0, 217, 255, .22); }

/* ===================================================================
   AMORCES DES PREMIERES SECONDES  (voir montrerAmorce dans feed.js)
   -------------------------------------------------------------------
   Une pastille discrete, posee au-dessus du bloc d'infos pour ne rien
   recouvrir, et qui s'efface d'elle-meme. Elle ne doit jamais avoir
   l'air d'une fenetre a fermer : pas de croix, pas de bouton.
   =================================================================== */
.fd-amorce {
    position: fixed;
    left: 50%;
    bottom: calc(var(--ud-nav-h, 56px) + 132px);
    z-index: 25;
    display: flex;
    align-items: center;
    gap: 8px;
    max-width: 78vw;
    padding: 9px 15px;
    border: 1px solid rgba(255, 255, 255, .14);
    border-radius: 999px;
    background: rgba(0, 0, 0, .62);
    backdrop-filter: blur(10px);
    color: #fff;
    font-size: 13px;
    font-weight: 700;
    white-space: nowrap;
    pointer-events: none;      /* jamais dans le chemin du doigt */
    opacity: 0;
    transform: translate(-50%, 8px);
    transition: opacity .28s var(--fd-ease, ease), transform .28s var(--fd-ease, ease);
}
.fd-amorce.est-la { opacity: 1; transform: translate(-50%, 0); }
.fd-amorce svg { width: 17px; height: 17px; flex-shrink: 0; fill: currentColor; }
/* La fleche monte doucement : c'est elle qui dit le geste, pas le mot. */
.fd-amorce.est-geste svg { animation: fd-amorce-monte 1.4s ease-in-out infinite; }
@keyframes fd-amorce-monte {
    0%, 100% { transform: translateY(2px); opacity: .75; }
    50%      { transform: translateY(-3px); opacity: 1; }
}
@media (max-height: 640px) { .fd-amorce { bottom: calc(var(--ud-nav-h, 56px) + 104px); } }
@media (prefers-reduced-motion: reduce) {
    .fd-amorce { transition: opacity .28s linear; }
    .fd-amorce.est-geste svg { animation: none; }
}
.fd-ptr.est-pret .fd-ptr-arc { stroke: #fff; }
/* Pendant le chargement, le rond tourne : c'est le seul moment ou il
   s'anime tout seul. */
.fd-ptr.tourne svg { animation: fd-ptr-tour .9s linear infinite; }
@keyframes fd-ptr-tour { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
    .fd-ptr.tourne svg { animation: none; }
}

/* ===================================================================
   MENU D'UN POST + TOAST AVEC ACTION
   -------------------------------------------------------------------
   AJOUTE (voir ouvrirMenuPost() et toast() dans feed.js). Une feuille
   basse, comme le reste du fil : on la remonte du bas, on la ferme en
   touchant a cote. Elle se place AU-DESSUS de la barre du bas, sinon
   la derniere entree tomberait sous les onglets du site.
   =================================================================== */
.fd-menu {
    position: fixed;
    inset: 0;
    z-index: 1400;
    display: flex;
    align-items: flex-end;
}
.fd-menu-fond {
    position: absolute;
    inset: 0;
    background: rgba(0,0,0,.5);
    opacity: 0;
    transition: opacity .2s var(--fd-ease, ease);
}
.fd-menu.is-on .fd-menu-fond { opacity: 1; }
.fd-menu-corps {
    position: relative;
    width: 100%;
    padding: 8px 10px calc(var(--ud-nav-h, 56px) + 10px);
    background: rgba(18,18,26,.98);
    backdrop-filter: blur(14px);
    border-top: 1px solid rgba(255,255,255,.09);
    transform: translateY(100%);
    transition: transform .24s var(--fd-ease, ease);
}
.fd-menu.is-on .fd-menu-corps { transform: translateY(0); }
.fd-menu-item {
    display: flex;
    align-items: center;
    gap: 13px;
    width: 100%;
    /* 56 px de haut : on vise ce menu au pouce, souvent en marchant. */
    min-height: 56px;
    padding: 10px 12px;
    border: 0;
    background: none;
    color: #fff;
    text-align: left;
    cursor: pointer;
    border-radius: 14px;
}
.fd-menu-item:active { background: rgba(255,255,255,.08); }
.fd-menu-item span { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.fd-menu-item b { font-size: 14.5px; font-weight: 700; }
.fd-menu-item i { font-style: normal; font-size: 12px; color: #8b95a5; }
.fd-menu-item svg { flex-shrink: 0; }
.fd-menu-annuler {
    width: 100%;
    min-height: 48px;
    margin-top: 6px;
    border: 1px solid rgba(255,255,255,.14);
    border-radius: 999px;
    background: rgba(255,255,255,.06);
    color: #fff;
    font-size: 13px;
    font-weight: 800;
    cursor: pointer;
}
.fd-menu-annuler:active { transform: scale(.98); }
@media (prefers-reduced-motion: reduce) {
    .fd-menu-fond, .fd-menu-corps { transition: none; }
}

/* Le toast peut porter une action (« Annuler »). Il passe alors en
   rangee, et son bouton garde une cible confortable. */
.fd-toast.a-action {
    display: flex;
    align-items: center;
    gap: 14px;
}
.fd-toast-act {
    flex-shrink: 0;
    padding: 6px 12px;
    border: 0;
    border-radius: 999px;
    background: rgba(255,255,255,.16);
    color: #fff;
    font-size: 12px;
    font-weight: 900;
    letter-spacing: .02em;
    cursor: pointer;
}
.fd-toast-act:active { opacity: .7; }
.fd-mot:active { opacity: .6; }
/* Le lien ne doit pas casser la troncature de la legende. */
.fd-sous .fd-mot, .fd-titre .fd-mot { display: inline; }
@media (prefers-reduced-motion: reduce) { .fd-mot:active { opacity: 1; } }
.fd-quote.is-sm { font-size: 20px; }
.fd-quote.is-xs { font-size: 16px; font-weight: 700; line-height: 1.45; }

/* ===================================================================
   6. SIGNATURE — la barre "touches Unitydom"
   =================================================================== */
.fd-bar {
    position: absolute;
    left: 0; right: 0;
    /* Elle sert aussi de poignee de navigation dans la video
       (pointerdown dans feed.js) : derriere la barre du bas, elle
       serait invisible ET inutilisable. */
    bottom: var(--ud-nav-h, 0px);
    height: 3px;
    z-index: 30;
    background: var(--keys-gradient);
    opacity: .2;

    /* CORRIGE : c'etait `touch-action: none`.
       ---------------------------------------------------------------
       Cette barre porte le scrubbing video (pointerdown +
       setPointerCapture dans feed.js). Tant qu'elle etait a
       `bottom: 0`, elle dormait sous la barre du bas : inatteignable,
       donc inoffensive. En la remontant au-dessus, je l'ai posee
       pile dans la zone du pouce.

       `none` dit au navigateur : ne gere AUCUN geste ici. Le
       defilement vertical natif est donc coupe des que le doigt
       effleure la barre, le pointeur part en capture, et le swipe se
       transforme en seek video. Le feed part, se fait voler le geste,
       repart : ca tremble.

       `pan-y` rend le geste vertical au navigateur et ne laisse au
       scrubbing que l'horizontal — ce dont il a besoin, et rien de
       plus. */
    touch-action: pan-y;

    /* Et par defaut la barre ne capte rien du tout : seules les cartes
       qui ont une duree (donc une video) ont quelque chose a scrubber.
       Ailleurs, c'est une decoration — elle n'a pas a intercepter. */
    pointer-events: none;

    transition: height .18s var(--fd-ease), opacity .18s;
}
.fd-bar::after {
    content: "";
    position: absolute;
    top: 0; left: 0; bottom: 0;
    width: var(--fd-progress, 0%);
    background: var(--keys-gradient);
    background-size: 100vw 100%;
    opacity: 0;

    /* AJOUTE : la barre avancait par sauts visibles.
       ---------------------------------------------------------------
       `--fd-progress` est repose par l'evenement `timeupdate` de la
       video (feed.js), qui ne se declenche qu'environ quatre fois par
       seconde. La barre ne bougeait donc que quatre fois par seconde :
       sur une video de vingt secondes, ce sont des bonds de 1,25 % —
       assez gros pour se voir, et ca donne au fil un air de vieille
       page web la ou tout le reste glisse.

       Plutot qu'une boucle d'animation en JavaScript (une frame toutes
       les 16 ms, tenue en permanence, pour deplacer une barre de trois
       pixels de haut), on laisse le navigateur interpoler entre deux
       mises a jour. C'est gratuit, ca s'execute sur le compositeur, et
       ca ne consomme rien quand la video est en pause puisque plus
       aucune valeur ne change.

       280 ms, soit un peu plus que l'intervalle entre deux `timeupdate` :
       l'interpolation n'a jamais le temps de finir avant la valeur
       suivante, donc la barre glisse sans jamais marquer d'arret. */
    transition: width .28s linear;
}

/* Deux cas ou il ne faut surtout PAS interpoler, sous peine de voir la
   barre glisser doucement en ARRIERE : le retour a zero quand la video
   boucle, et le deplacement du doigt pendant un scrub — la ou l'on veut
   au contraire que la barre colle au doigt a l'image pres. */
.fd-bar.is-scrub::after,
.fd-card.is-saut .fd-bar::after { transition: none; }
.fd-buf {
    position: absolute;
    top: 0; left: 0; bottom: 0;
    width: var(--fd-buffer, 0%);
    background: rgba(255,255,255,.22);
    opacity: 0;
}
.fd-card.has-time .fd-bar { opacity: 1; background: rgba(255,255,255,.14); }
.fd-card.has-time .fd-bar::after,
.fd-card.has-time .fd-buf { opacity: 1; }

.fd-bar.is-scrub { height: 10px; }
.fd-card.has-time .fd-bar { pointer-events: auto; }

@media (hover: hover) { .fd-bar:hover { height: 8px; } }

.fd-time {
    position: absolute;
    right: 14px;
    bottom: calc(var(--ud-nav-h, 0px) + 9px);
    z-index: 25;
    font-size: 10px; font-weight: 800;
    letter-spacing: .05em;
    font-variant-numeric: tabular-nums;
    color: rgba(255,255,255,.55);
    opacity: 0;
    transition: opacity .2s;
}
.fd-card.has-time .fd-time { opacity: 1; }
.fd-bar.is-scrub ~ .fd-time { opacity: 1; color: #fff; }

/* ===================================================================
   7. FEEDBACK
   =================================================================== */
.fd-pulse {
    position: absolute;
    top: 50%; left: 50%;
    z-index: 22;
    width: 76px; height: 76px;
    display: flex; align-items: center; justify-content: center;
    border-radius: 999px;
    background: rgba(0,0,0,.42);
    backdrop-filter: blur(6px);
    transform: translate(-50%, -50%) scale(.6);
    opacity: 0;
    pointer-events: none;
    transition: opacity .24s, transform .24s var(--fd-ease);
}
.fd-pulse svg { width: 34px; height: 34px; fill: #fff; }
.fd-pulse.show { opacity: 1; transform: translate(-50%, -50%) scale(1); }

/* AJOUTE : la meme bulle sert de repere de vitesse pendant un appui
   long (voir pulseVitesse() dans feed.js). En plein centre elle
   masquerait le sujet pendant toute la duree de l'appui — c'est
   precisement le moment ou l'on regarde. On la remonte donc sous
   l'en-tete et on la reduit a une pastille de texte. */
.fd-pulse.est-vitesse {
    top: calc(env(safe-area-inset-top, 0px) + 100px);
    width: auto; height: auto;
    padding: 7px 15px;
    font: 800 13px/1 system-ui, -apple-system, sans-serif;
    letter-spacing: .08em;
    color: #fff;
    background: rgba(0,0,0,.62);
}
.fd-pulse.est-vitesse.show { transform: translate(-50%, -50%) scale(1); }

/* --- Bandeau hors ligne ------------------------------------------- */
.fd-horsligne {
    position: fixed;
    left: 50%;
    /* 58 px, comme .fd-badge et .fd-mute : c'est la reserve que tout ce
       fichier laisse a l'en-tete du site (logo FLOW, onglets, avatar).
       A 12 px, ce bandeau — en z-index 1200, donc au-dessus d'un
       en-tete a 100 — se posait pile sur les onglets « Pour toi /
       Recents ». Il ne bloquait rien (pointer-events: none) mais il les
       masquait. */
    top: calc(env(safe-area-inset-top, 0px) + 58px);
    transform: translateX(-50%) translateY(-16px);
    z-index: 1200;
    padding: 8px 16px;
    border-radius: 999px;
    border: 1px solid rgba(255,255,255,.14);
    background: rgba(20,20,28,.92);
    backdrop-filter: blur(12px);
    color: #fff;
    font: 700 12.5px/1 system-ui, -apple-system, sans-serif;
    letter-spacing: .02em;
    box-shadow: 0 10px 30px rgba(0,0,0,.6);
    opacity: 0;
    pointer-events: none;
    transition: opacity .22s, transform .22s var(--fd-ease);
}
.fd-horsligne.show { opacity: 1; transform: translateX(-50%) translateY(0); }
.fd-pulse.spin {
    opacity: 1;
    background: none;
    backdrop-filter: none;
    animation: fd-spin .85s linear infinite;
}
@keyframes fd-spin { to { transform: translate(-50%, -50%) rotate(360deg); } }

.fd-burst {
    position: absolute;
    z-index: 40;
    width: 110px; height: 110px;
    margin: -55px 0 0 -55px;
    pointer-events: none;
    animation: fd-burst .78s var(--fd-ease) forwards;
}
@keyframes fd-burst {
    0%   { transform: scale(0)    rotate(-16deg); opacity: 0; }
    32%  { transform: scale(1.18) rotate(7deg);   opacity: 1; }
    100% { transform: scale(1) translateY(-30px) rotate(0);  opacity: 0; }
}

.fd-mute {
    position: absolute;
    top: calc(env(safe-area-inset-top, 0px) + 58px);
    right: 14px;
    z-index: 25;
    width: 38px; height: 38px;               /* repli pour l'ancien moteur */
    width: calc(var(--fd-ico, 44px) * .86); height: calc(var(--fd-ico, 44px) * .86);
    display: none;
    align-items: center; justify-content: center;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: rgba(0,0,0,.5);
    backdrop-filter: blur(8px);
    color: #fff;
    cursor: pointer;
}
.fd-card.has-sound .fd-mute { display: flex; }
.fd-mute svg { width: 18px; height: 18px; fill: #fff; }

/* ===================================================================
   8. FIN DE FEED / VIDE
   =================================================================== */
.fd-loader {
    height: 84px;
    display: flex; align-items: center; justify-content: center;
    gap: 5px;
    color: var(--txt-dim);
    scroll-snap-align: none;
}
.fd-loader span {
    width: 5px; height: 22px;
    border-radius: 2px;
    background: var(--k-cyan);
    animation: fd-keys 1s ease-in-out infinite;
}
.fd-loader span:nth-child(2) { background: var(--k-green);  animation-delay: .1s; }
.fd-loader span:nth-child(3) { background: var(--k-yellow); animation-delay: .2s; }
.fd-loader span:nth-child(4) { background: var(--k-pink);   animation-delay: .3s; }
.fd-loader span:nth-child(5) { background: var(--k-violet); animation-delay: .4s; }
@keyframes fd-keys {
    0%, 100% { transform: scaleY(.35); opacity: .4; }
    50%      { transform: scaleY(1);   opacity: 1; }
}
.fd-loader.is-done span { display: none; }
.fd-loader.is-done::after {
    content: "Tu es à jour · appuie pour re-vérifier";
    font-size: 11px; font-weight: 800;
    letter-spacing: .12em; text-transform: uppercase;
}
/* AJOUTE : l'etat de fin est desormais actionnable (voir feed.js). Le
   fil coupe volontairement le tirer-pour-rafraichir du navigateur ; ce
   bouton est ce qui le remplace. Il doit donc en avoir l'air. */
.fd-loader.is-done { cursor: pointer; }
/* CORRIGE — l'etat de fin ne s'affiche plus.
   -------------------------------------------------------------------
   Les deux regles ci-dessus ecrivaient « TU ES A JOUR ». Sur une page
   d'un seul post (?post=...), le bout du fil est atteint des l'arrivee :
   le bandeau apparaissait a chaque ouverture. Et de facon generale, dire
   a quelqu'un qu'il a tout vu est une invitation a partir — aucun fil
   de ce genre ne le fait. Le chargeur disparait, sans mot ni pastille ;
   la relance automatique (tenterRelance dans feed.js) continue son
   travail derriere. */
.fd-loader.is-done { display: none; }
.fd-loader.is-done::after { content: none; }
.fd-loader.is-panne span { display: none; }
.fd-loader.is-panne::after {
    content: "Connexion perdue — touche pour réessayer";
    font-size: 11px; font-weight: 700;
    color: var(--k-yellow);
    cursor: pointer;
}

.fd-empty {
    height: 100%;
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    gap: 12px; padding: 2rem; text-align: center;
    scroll-snap-align: start;
}
.fd-empty-mark { display: flex; align-items: flex-end; gap: 3px; height: 26px; margin-bottom: 6px; }
.fd-empty-mark i { display: block; width: 4px; border-radius: 2px; }
.fd-empty-mark i:nth-child(1) { height: 55%;  background: var(--k-cyan); }
.fd-empty-mark i:nth-child(2) { height: 100%; background: var(--k-green); }
.fd-empty-mark i:nth-child(3) { height: 40%;  background: var(--k-yellow); }
.fd-empty-mark i:nth-child(4) { height: 78%;  background: var(--k-pink); }
.fd-empty-mark i:nth-child(5) { height: 32%;  background: var(--k-violet); }
.fd-empty h2 { font-size: 18px; font-weight: 900; color: #fff; }
.fd-empty p  { font-size: 13px; color: var(--txt-dim); max-width: 28ch; line-height: 1.5; }

/* ===================================================================
   9. PANNEAU COMMENTAIRES
   Un seul panneau dans la page, reutilise pour toutes les cartes.
   =================================================================== */
.fd-sheet {
    position: fixed;
    inset: 0;
    z-index: 900;
    display: flex;
    align-items: flex-end;
    justify-content: center;
}
.fd-sheet[hidden] { display: none; }

.fd-sheet-fond {
    position: absolute;
    inset: 0;
    background: rgba(0,0,0,.6);
    opacity: 0;
    transition: opacity .28s var(--fd-ease);
}
.fd-sheet.is-ouvert .fd-sheet-fond { opacity: 1; }

.fd-sheet-corps {
    position: relative;
    width: 100%;
    max-width: 560px;
    height: 72svh;
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border-radius: var(--fd-r) var(--fd-r) 0 0;
    transform: translateY(100%);
    transition: transform .3s var(--fd-ease);
    overflow: hidden;
}
.fd-sheet.is-ouvert .fd-sheet-corps { transform: none; }
.fd-sheet-corps.is-drag { transition: none; }

.fd-sheet-corps::before {
    content: "";
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 3px;
    background: var(--keys-gradient);
}

.fd-sheet-poignee {
    width: 38px; height: 4px;
    margin: 10px auto 2px;
    border-radius: 2px;
    background: rgba(255,255,255,.28);
    flex-shrink: 0;
}

.fd-sheet-tete {
    display: flex; align-items: center; justify-content: center;
    position: relative;
    padding: 8px 52px 12px;
    flex-shrink: 0;
}
.fd-sheet-tete h2 {
    font-size: 14px; font-weight: 800;
    letter-spacing: .02em;
    color: #fff;
}
.fd-sheet-x {
    position: absolute;
    right: 12px; top: 2px;
    width: 32px; height: 32px;
    display: flex; align-items: center; justify-content: center;
    border: 0; border-radius: 999px;
    background: none;
    color: var(--txt-dim);
    font-size: 15px;
    cursor: pointer;
}
.fd-sheet-x:hover { color: #fff; background: rgba(255,255,255,.07); }

.fd-sheet-liste {
    flex: 1;
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    padding: 4px 16px 16px;
}

.fd-com {
    display: flex;
    gap: 11px;
    padding: 11px 0;
    animation: fd-in .3s var(--fd-ease);
}
@keyframes fd-in { from { opacity: 0; transform: translateY(7px); } }
.fd-com img {
    width: 34px; height: 34px;
    flex-shrink: 0;
    border-radius: 999px;
    object-fit: cover;
    background: var(--bg);
}
.fd-com-corps { min-width: 0; flex: 1; }
.fd-com-tete {
    display: flex; align-items: baseline; gap: 7px;
    margin-bottom: 2px;
}
.fd-com-tete b {
    font-size: 12px; font-weight: 800;
    color: rgba(255,255,255,.9);
}
.fd-com-tete .fd-com-moi {
    font-size: 9px; font-weight: 900;
    letter-spacing: .08em; text-transform: uppercase;
    color: #000;
    background: var(--k-cyan);
    padding: 1px 5px; border-radius: 3px;
}
.fd-com-tete time {
    font-size: 10px;
    color: var(--txt-dim);
    font-variant-numeric: tabular-nums;
}
.fd-com-txt {
    font-size: 13.5px; line-height: 1.45;
    color: rgba(255,255,255,.88);
    overflow-wrap: anywhere;
    white-space: pre-wrap;
}

.fd-sheet-etat {
    padding: 44px 20px;
    text-align: center;
    font-size: 13px;
    color: var(--txt-dim);
    line-height: 1.5;
}
.fd-sheet-etat b { display: block; color: #fff; font-size: 14px; margin-bottom: 5px; }

/* Squelettes de commentaires pendant le chargement */
.fd-com-skel { display: flex; gap: 11px; padding: 11px 0; }
/* La keyframe fd-shim anime desormais une transform : elle ne
   conviendrait plus ici. Ces squelettes-la sont petits, peu nombreux,
   et visibles uniquement panneau ouvert — une pulsation d'opacite
   suffit, et elle est compositee. */
@keyframes fd-pulse-skel { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
.fd-com-skel i, .fd-com-skel u {
    display: block;
    background-color: #1b1b25;
    animation: fd-pulse-skel 1.4s ease-in-out infinite;
    border-radius: 4px;
}
.fd-com-skel i { width: 34px; height: 34px; border-radius: 999px; flex-shrink: 0; }
.fd-com-skel u { height: 11px; margin-bottom: 7px; }
.fd-com-skel div { flex: 1; padding-top: 4px; }

/* Formulaire */
.fd-sheet-form {
    flex-shrink: 0;
    display: flex;
    align-items: flex-end;
    gap: 9px;
    padding: 11px 14px calc(11px + env(safe-area-inset-bottom, 0px));
    border-top: 1px solid var(--line);
    background: var(--bg);
}
.fd-sheet-form textarea {
    flex: 1;
    max-height: 110px;
    padding: 10px 14px;
    border: 1px solid var(--line);
    border-radius: 19px;
    background: var(--surface);
    color: #fff;
    font: inherit;
    font-size: 14px;
    line-height: 1.4;
    resize: none;
    outline: none;
    transition: border-color .2s;
}
.fd-sheet-form textarea:focus { border-color: rgba(0,217,255,.45); }
.fd-sheet-form textarea::placeholder { color: var(--txt-dim); }

.fd-sheet-form button[type="submit"] {
    width: 38px; height: 38px;
    flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
    border: 0; border-radius: 999px;
    background: var(--k-cyan);
    color: #000;
    cursor: pointer;
    transition: opacity .2s, transform .15s var(--fd-ease);
}
.fd-sheet-form button[type="submit"] svg { width: 18px; height: 18px; }
.fd-sheet-form button[type="submit"]:disabled {
    opacity: .3; cursor: default;
}
.fd-sheet-form button[type="submit"]:not(:disabled):active { transform: scale(.9); }

.fd-sheet-invite {
    align-items: center;
    justify-content: space-between;
    font-size: 13px;
    color: var(--txt-dim);
}
.fd-sheet-invite .fd-cta { margin: 0; padding: 8px 15px; font-size: 11px; }

/* ===================================================================
   10. TOAST — retours discrets (copie, erreur, quota)
   =================================================================== */
.fd-toast {
    position: fixed;
    left: 50%;
    bottom: calc(var(--ud-nav-h) + 18px);
    z-index: 1200;
    transform: translate(-50%, 14px);
    max-width: min(90vw, 400px);
    padding: 11px 17px;
    border: 1px solid var(--line);
    border-radius: 11px;
    background: rgba(20,20,28,.96);
    backdrop-filter: blur(12px);
    box-shadow: 0 12px 40px rgba(0,0,0,.6);
    color: #fff;
    font-size: 13px; font-weight: 600;
    text-align: center;
    opacity: 0;
    pointer-events: none;
    transition: opacity .25s, transform .25s var(--fd-ease);
}
.fd-toast.show { opacity: 1; transform: translate(-50%, 0); }
.fd-toast.is-err { border-color: rgba(250,204,21,.4); }

/* ===================================================================
   11. QUALITE DE BASE
   =================================================================== */
.fd-act:focus-visible,
.fd-cta:focus-visible,
.fd-avatar:focus-visible,
.fd-mute:focus-visible,
.fd-plus-txt:focus-visible,
.fd-sheet-x:focus-visible,
.fd-sheet-form button:focus-visible {
    outline: 2px solid var(--k-cyan);
    outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
    .vt-story.is-urgent .vt-compte::before { animation: none; }
    .fd-burst, .fd-loader span, .fd-skel, .fd-onde i, .fd-tampon i, .fd-com-skel i, .fd-com-skel u {
        animation: none;
    }
    .fd-img { transition: none; opacity: 1; }
    .fd-pulse, .fd-sheet-corps, .fd-sheet-fond, .fd-toast { transition-duration: .01ms; }
    .fd-bar::after { transition: none; }
}

/* ===================================================================
   12. PIED DU PANNEAU COMMENTAIRES
   Trois formulaires servis, un seul visible. Le serveur choisit via
   data-mode : membre connecte, visiteur avec pseudo, ou lecture seule.
   =================================================================== */
.fd-sheet-pied { flex-shrink: 0; }

.fd-sheet-pied[data-mode="membre"] .fd-sheet-invite,
.fd-sheet-pied[data-mode="invite"] .fd-sheet-invite,
.fd-sheet-pied[data-mode="ferme"]  .fd-sheet-form,
.fd-sheet-pied[data-mode="membre"] .fd-com-nom { display: none; }

.fd-sheet-form { display: block; }
.fd-sheet-ligne { display: flex; align-items: flex-end; gap: 9px; }

/* CORRIGE — le formulaire de commentaire pouvait se retrouver casse.

   Historique : plus haut dans ce fichier, `.fd-sheet-form` etait une
   rangee (`display:flex`) contenant le champ de texte (`flex:1`) et le
   bouton d'envoi. Un remaniement l'a passe en `display:block` juste
   au-dessus, en deplacant la mise en rangee sur un `.fd-sheet-ligne`
   cense envelopper ces deux elements — pour pouvoir glisser un champ
   « pseudo » pleine largeur au-dessus quand la personne n'est pas
   connectee (voir .fd-com-nom et .fd-sheet-pied[data-mode]).

   Le probleme : `.fd-sheet-ligne` n'existe QUE dans ce fichier CSS.
   Aucun des fichiers du projet ne produit ce balisage — le formulaire
   vient du gabarit racine du site. Si ce gabarit n'a pas ete mis a jour
   en meme temps, il n'y a pas de rangee du tout : le parent est un
   bloc, le `flex:1` du champ de texte devient inerte, et celui-ci
   retombe a sa largeur par defaut (une vingtaine de caracteres) avec le
   bouton colle a cote — au lieu d'un champ pleine largeur.

   Ce correctif marche dans LES DEUX CAS, sans avoir a trancher :
   le formulaire redevient un conteneur flex, mais qui passe a la ligne.
   Les elements qui doivent occuper toute la largeur le demandent
   explicitement (flex-basis 100%).

     - Si `.fd-sheet-ligne` existe : il prend une ligne entiere et se
       comporte exactement comme avant, avec sa rangee interne. Aucun
       changement visible.
     - S'il n'existe pas : le champ de texte retrouve un `flex:1` actif
       et s'etire, le bouton reste a 38px a sa droite. Repare.

   L'espacement vertical est desormais assure par `gap`, d'ou la marge
   du champ pseudo remise a zero pour ne pas s'additionner. */
.fd-sheet-form {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 9px;
}
.fd-sheet-ligne { flex: 1 1 100%; }
.fd-com-nom { flex: 1 1 100%; margin-bottom: 0; }

/* Filet : la regle qui masque le formulaire en mode "ferme" l'emporte
   deja par specificite (0,3,0 contre 0,1,0), elle n'a donc rien perdu
   au passage. On la repete quand meme, explicitement, pour qu'un futur
   remaniement de ces selecteurs ne puisse pas faire reapparaitre la
   zone de saisie sur un fil de commentaires ferme. */
.fd-sheet-pied[data-mode="ferme"] .fd-sheet-form { display: none; }

.fd-com-nom {
    width: 100%;
    margin-bottom: 8px;
    padding: 8px 13px;
    border: 1px solid var(--line);
    border-radius: 17px;
    background: var(--surface);
    color: #fff;
    font: inherit;
    font-size: 13px;
    font-weight: 700;
    outline: none;
    transition: border-color .2s;
}
.fd-com-nom:focus { border-color: rgba(0,217,255,.45); }
.fd-com-nom::placeholder { color: var(--txt-dim); font-weight: 400; }

.fd-com-invite {
    font-size: 9px; font-weight: 900;
    letter-spacing: .08em; text-transform: uppercase;
    color: var(--txt-dim);
    border: 1px solid var(--line);
    padding: 1px 5px; border-radius: 3px;
}

/* ===================================================================
   13. JALON DE PALIER
   Le feed ne s'arrete jamais : quand le stock frais s'epuise, il
   elargit. Ce separateur l'annonce sans interrompre le defilement.
   =================================================================== */
.fd-jalon {
    height: 88px;
    display: flex;
    align-items: center;
    justify-content: center;
    scroll-snap-align: none;
    padding: 0 24px;
}
.fd-jalon span {
    position: relative;
    padding: 7px 16px;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: rgba(20,20,28,.9);
    color: var(--txt-dim);
    font-size: 11px;
    font-weight: 800;
    letter-spacing: .07em;
    text-transform: uppercase;
    text-align: center;
}
.fd-jalon::before,
.fd-jalon::after {
    content: "";
    flex: 1;
    height: 1px;
    background: linear-gradient(to right, transparent, var(--line), transparent);
}


/* ===================================================================
   AJOUT — ETAT DE TAMPON DU LECTEUR
   -------------------------------------------------------------------
   UDPlayer met la carte en `is-tampon` pendant une pause volontaire :
   celle qu'il s'impose AVANT de caler. Une pause annoncee de 800 ms se
   remarque bien moins qu'un saccadement, a condition de l'annoncer.

   REMPLACE : l'anneau blanc qui tournait sur `.fd-pulse`. Ce n'etait
   pas un loader dedie mais la bulle de feedback de tap (double-tap
   like, play/pause) detournee pour l'occasion — les deux partageant
   le meme element, un tap juste avant un calage affichait l'icone de
   tap ET l'anneau de chargement empiles.

   `.fd-tampon` est un element a part (cree par feed_player.js), sur
   le motif des « cinq touches » deja utilise partout ailleurs sur le
   site (fd-onde, fd-loader, fd-empty-mark, vt-mark) : le loader video
   se reconnait maintenant comme un loader Unitydom, pas comme un
   spinner generique. */
.fd-tampon {
    position: absolute;
    top: 50%; left: 50%;
    z-index: 22;
    display: flex;
    align-items: flex-end;
    gap: 4px;
    height: 22px;
    padding: 8px 10px;
    border-radius: 999px;
    background: rgba(0,0,0,.5);
    backdrop-filter: blur(6px);
    transform: translate(-50%, -50%);
    opacity: 0;
    pointer-events: none;
    transition: opacity .2s;
}
.fd-card.is-tampon .fd-tampon { opacity: 1; }
.fd-tampon i {
    display: block;
    width: 3px; height: 100%;
    border-radius: 2px;
    background: var(--k-cyan);
    transform-origin: bottom;
    animation: fd-onde .9s ease-in-out infinite;
}
.fd-tampon i:nth-child(2) { background: var(--k-green);  animation-delay: .15s; }
.fd-tampon i:nth-child(3) { background: var(--k-yellow); animation-delay: .3s; }
.fd-tampon i:nth-child(4) { background: var(--k-pink);   animation-delay: .45s; }
.fd-tampon i:nth-child(5) { background: var(--k-violet); animation-delay: .6s; }

/* La barre de progression s'estompe pendant le remplissage : on montre
   que rien n'avance, plutot que de laisser croire a un blocage. */
.fd-card.is-tampon .fd-bar { opacity: .35; }


/* ===================================================================
   AJOUT — CARTE EN ATTENTE DE SES RESSOURCES
   -------------------------------------------------------------------
   Une carte qui n'est pas complete ne prend pas son tour : feed.js la
   descend d'un cran. Le temps que ca arrive, elle ne doit pas clignoter.
   =================================================================== */

/* CORRIGE : la regle s'appliquait meme quand feed.js n'avait pas
   encore demarre.
   ---------------------------------------------------------------------
   `.fd-card:not(.is-pret) .fd-img { opacity: 0 }` prend effet au
   premier octet de CSS. Or `is-pret` n'est pose que par feed.js, qui
   est en `defer` et attend donc la fin du HTML — et, avant ce
   correctif, le telechargement d'axios depuis un CDN.

   Entre les deux, TOUTES les images etaient masquees. Si le CDN
   repondait en trois secondes, le feed restait noir trois secondes,
   images deja chargees ou non. C'est la « premiere carte vide ».

   Le masquage ne s'applique donc plus qu'une fois que feed.js a pris
   la main : il pose `js` sur le conteneur en premiere instruction.
   Sans JavaScript du tout, les images s'affichent normalement — un feed
   degrade vaut mieux qu'un feed noir. */
.fd-feed.js .fd-card:not(.is-pret) .fd-img { opacity: 0; }
.fd-card .fd-img { transition: opacity .28s ease; }
.fd-card.is-pret .fd-img { opacity: 1; }


/* ===================================================================
   AJOUT — LA VITRINE, EN CARTE PLEIN ECRAN
   -------------------------------------------------------------------
   Une actualite ou un riddim, dans la meme forme qu'un post.

   AUCUN overflow ICI. C'est la regle principale de ce bloc.
   L'ancienne vitrine avait `overflow-y: auto` : un conteneur qui
   defile a l'interieur d'un feed en scroll-snap capte le geste
   vertical, le feed ne bouge plus tant qu'on n'a pas atteint le bas
   du conteneur, et le chargement infini ne se declenche jamais.

   Le rail horizontal de riddims est parti pour la meme raison : sur
   mobile, un geste un peu oblique sur un rail horizontal est avale
   par le rail. Chaque riddim a maintenant sa propre carte.
   =================================================================== */

.vt-story {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    overflow: hidden;            /* hidden, jamais auto */
    background: #0b0b12;
}

/* Fond floute : remplit le 9:16 sans deformer la pochette carree. */
.vt-flou {
    position: absolute;
    inset: -8%;
    width: 116%;
    height: 116%;
    object-fit: cover;
    filter: blur(34px) saturate(1.35) brightness(.5);
    transform: scale(1.06);
}

/* L'image nette, entiere, dans le tiers haut. */
.vt-nette {
    position: absolute;
    left: 50%;
    top: calc(var(--ud-head-h, 0px) + 12%);
    transform: translateX(-50%);
    width: min(74%, 330px);
    height: auto;
    max-height: 46%;
    object-fit: contain;
    border-radius: 18px;
    box-shadow: 0 24px 60px rgba(0,0,0,.6);
}

.vt-voile {
    position: absolute;
    inset: 0;
    background: linear-gradient(to top,
        #000 0%,
        rgba(0,0,0,.82) 22%,
        rgba(0,0,0,.25) 52%,
        rgba(0,0,0,.05) 100%);
}

.vt-corps {
    position: relative;
    z-index: 2;
    padding: 0 26px calc(var(--ud-nav-h, 0px) + 74px);
    text-align: center;
}

.vt-badge {
    display: inline-block;
    background: var(--k-yellow, #facc15);
    color: #000;
    font-size: 10px;
    font-weight: 900;
    letter-spacing: .2em;
    text-transform: uppercase;
    padding: 6px 14px;
    border-radius: 999px;
    margin-bottom: 14px;
}
/* La couleur du badge dit l'etat sans qu'on ait a lire.
   Vert = tu peux participer. Violet = ca arrive. Cyan = a ecouter.
   Jaune = actualite. C'est la palette du site, pas une invention. */
.vt-riddim_ouvert .vt-badge { background: var(--k-green, #22c55e); }
.vt-riddim_prepa  .vt-badge { background: var(--k-violet, #8b5cf6); color: #fff; }
.vt-riddim_dispo  .vt-badge { background: var(--k-cyan, #00d9ff); }
.vt-riddim        .vt-badge { background: var(--k-green, #22c55e); }
.vt-post          .vt-badge { background: var(--k-cyan, #00d9ff); }

/* Riddim ouvert : le bouton prend la couleur de l'action.
   Un « Je participe » blanc au milieu de six autres boutons blancs ne
   se distingue pas ; ici il doit se distinguer. */
.vt-riddim_ouvert .vt-cta {
    background: var(--k-green, #22c55e);
    color: #04210f;
    box-shadow: 0 10px 34px rgba(34,197,94,.4);
}

/* Moins de 48 h avant la cloture. Le point qui bat est le seul
   mouvement de la carte : discret, mais on le remarque. */
.vt-story.is-urgent .vt-compte {
    border-color: rgba(255,0,85,.5);
    background: rgba(255,0,85,.16);
    color: #ffd7e3;
}
.vt-story.is-urgent .vt-compte::before {
    content: '';
    display: inline-block;
    width: 7px; height: 7px;
    border-radius: 50%;
    background: var(--k-pink, #ff0055);
    margin-right: 8px;
    vertical-align: middle;
    animation: vt-bat 1.6s ease-in-out infinite;
}
@keyframes vt-bat {
    0%, 100% { opacity: 1;  transform: scale(1); }
    50%      { opacity: .35; transform: scale(.8); }
}

.vt-corps h2 {
    font-size: clamp(21px, 6.2vw, 30px);
    font-weight: 900;
    line-height: 1.14;
    letter-spacing: -.02em;
    margin-bottom: 12px;
    text-shadow: 0 2px 18px rgba(0,0,0,.85);

    /* Trois lignes maximum. Un titre plus long pousserait le bouton
       sous la barre du bas, ou l'ecraserait. */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.vt-corps p {
    font-size: 14px;
    line-height: 1.5;
    color: rgba(255,255,255,.78);
    margin-bottom: 18px;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.vt-compte {
    display: inline-block;
    background: rgba(0,0,0,.6);
    border: 1px solid rgba(255,255,255,.16);
    backdrop-filter: blur(6px);
    padding: 8px 16px;
    border-radius: 999px;
    font-size: 13px;
    font-weight: 700;
    margin-bottom: 18px;
}

.vt-cta {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    background: #fff;
    color: #000;
    font-size: 14.5px;
    font-weight: 800;
    padding: 13px 26px;
    border-radius: 999px;
    text-decoration: none;
    box-shadow: 0 10px 30px rgba(0,0,0,.45);
    transition: transform .16s var(--fd-ease, ease);
}
.vt-cta:active { transform: scale(.96); }

/* Les cinq touches : ceci vient du site, pas d'un membre. */
.vt-mark {
    position: absolute;
    left: 50%;
    bottom: calc(var(--ud-nav-h, 0px) + 30px);
    transform: translateX(-50%);
    z-index: 2;
    display: flex;
    gap: 3px;
    height: 13px;
    align-items: flex-end;
    opacity: .85;
}
.vt-mark i { display: block; width: 3px; border-radius: 1px; }
.vt-mark i:nth-child(1) { height: 60%;  background: var(--k-cyan, #00d9ff); }
.vt-mark i:nth-child(2) { height: 100%; background: var(--k-green, #22c55e); }
.vt-mark i:nth-child(3) { height: 45%;  background: var(--k-yellow, #facc15); }
.vt-mark i:nth-child(4) { height: 80%;  background: var(--k-pink, #ff0055); }
.vt-mark i:nth-child(5) { height: 35%;  background: var(--k-violet, #8b5cf6); }

@media (min-width: 700px) {
    .vt-nette { max-height: 40%; }
    .vt-corps { padding-left: 48px; padding-right: 48px; }
}


/* ===================================================================
   AJOUT — CARTE DONT LE MEDIA EST MORT
   -------------------------------------------------------------------
   Avec le reglage par defaut, elle est retiree du feed avant meme
   d'etre montree. Ce style ne sert que si tu mets
   « Carte dont tout le media est mort » a 0, pour la garder visible :
   dans ce cas, autant qu'elle ne fasse pas croire a un bug.
   =================================================================== */

.fd-card.is-casse .fd-stage {
    background:
        repeating-linear-gradient(45deg,
            rgba(255,255,255,.022) 0 12px,
            rgba(255,255,255,0)    12px 24px),
        #101018;
}
.fd-card.is-casse .fd-stage::after {
    content: 'Média indisponible';
    position: absolute;
    left: 50%; top: 50%;
    transform: translate(-50%, -50%);
    font-size: 12.5px;
    font-weight: 600;
    color: rgba(255,255,255,.34);
    letter-spacing: .02em;
}


/* ===================================================================
   AJOUT — LA CARTE LIEN
   -------------------------------------------------------------------
   Une actualite, un article de presse : un titre, un chapo, un bouton,
   et pas d'affiche utilisable.

   Avant, ces cartes etaient rendues comme des cartes « image » avec
   une affiche vide ou morte. Resultat : un rectangle noir plein ecran,
   le titre perdu tout en bas dans le bloc d'infos. C'est le « article
   tout noir » — et comme la presse est souvent bien notee, c'etait
   souvent la premiere carte.

   Deux facons d'y arriver, meme rendu :
     .fd-lien          le serveur savait des le depart qu'il n'y a pas
                       d'image (kind = lien)
     .is-sans-image    l'image existait mais n'a jamais charge
   =================================================================== */

.fd-lien,
.fd-card.is-sans-image .fd-stage {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: 0 30px calc(var(--ud-nav-h, 0px) + 150px);

    /* Un fond qui n'est pas noir. Deux teintes du site, en diagonale,
       plus un voile de touches en haut : on reconnait Unitydom, et
       surtout on voit que la carte est vivante. */
    background:
        radial-gradient(90% 62% at 12% 0%, rgba(0,217,255,.16) 0%, transparent 62%),
        radial-gradient(80% 58% at 92% 100%, rgba(139,92,246,.18) 0%, transparent 58%),
        linear-gradient(158deg, #16161f 0%, #0b0b12 62%);
}

.fd-card.is-sans-image .fd-img,
.fd-card.is-sans-image .fd-skel,
.fd-card.is-sans-image .fd-disque { display: none; }

.fd-lien-source {
    align-self: flex-start;
    font-size: 10.5px;
    font-weight: 900;
    letter-spacing: .2em;
    text-transform: uppercase;
    color: #04212a;
    background: var(--k-cyan, #00d9ff);
    padding: 5px 13px;
    border-radius: 999px;
    margin-bottom: 18px;
}

/* Le titre EST le contenu de la carte : il prend la place qu'il merite.
   Six lignes maximum — au-dela on empiete sur le bloc d'infos, qui
   porte deja le titre complet et le bouton. */
.fd-lien-titre {
    font-size: clamp(23px, 6.6vw, 34px);
    font-weight: 900;
    line-height: 1.16;
    letter-spacing: -.025em;
    color: #fff;
    text-wrap: balance;

    display: -webkit-box;
    -webkit-line-clamp: 6;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.fd-lien-mark {
    display: flex;
    gap: 3px;
    height: 15px;
    align-items: flex-end;
    margin-top: 22px;
    opacity: .9;
}
.fd-lien-mark i { display: block; width: 3px; border-radius: 1px; }
.fd-lien-mark i:nth-child(1) { height: 60%;  background: var(--k-cyan, #00d9ff); }
.fd-lien-mark i:nth-child(2) { height: 100%; background: var(--k-green, #22c55e); }
.fd-lien-mark i:nth-child(3) { height: 45%;  background: var(--k-yellow, #facc15); }
.fd-lien-mark i:nth-child(4) { height: 80%;  background: var(--k-pink, #ff0055); }
.fd-lien-mark i:nth-child(5) { height: 35%;  background: var(--k-violet, #8b5cf6); }

/* Le voile du bas est inutile ici : il n'y a pas d'image a assombrir,
   et il ne fait qu'eteindre le fond. */
.fd-card[data-kind="lien"] .fd-scrim,
.fd-card.is-sans-image .fd-scrim { opacity: .55; }

/* Le titre est deja en grand dans la scene : on ne le repete pas en
   petit juste en dessous. Le chapo et le bouton, eux, restent. */
.fd-card[data-kind="lien"] .fd-meta .fd-titre { display: none; }


/* ===================================================================
   AJOUT — MODE DEBUG   (?fd_debug=1)
   -------------------------------------------------------------------
   Colle sur chaque carte ce qu'elle est vraiment : son type, son
   identifiant, ses ressources et leur etat. C'est ce qu'il faut quand
   une carte s'affiche mal et qu'on ne sait pas laquelle c'est.
   N'apparait jamais sans le parametre dans l'URL.
   =================================================================== */

.fd-dbg {
    position: absolute;
    top: calc(var(--ud-head-h, 0px) + 62px);
    left: 12px;
    right: 12px;
    z-index: 60;
    padding: 10px 12px;
    border-radius: 10px;
    background: rgba(0,0,0,.86);
    border: 1px solid rgba(0,217,255,.45);
    font: 11px/1.5 ui-monospace, monospace;
    color: #9fe8ff;
    white-space: pre-wrap;
    word-break: break-all;
    pointer-events: none;
}
.fd-dbg b { color: #fff; }
.fd-dbg .ko { color: #ff6b93; }
.fd-dbg .ok { color: #6ee7a0; }
/* --- Avatar + bouton suivre (TikTok) --- */
.fd-avatar-wrapper {
    position: relative;
    display: inline-block;
    margin-bottom: 2px;
}

/* Cette declaration ecrase celle de la section 4 : elle doit donc
   porter la meme regle proportionnelle, sinon l'avatar resterait seul
   en taille fixe au milieu d'un rail qui, lui, s'adapte. */
.fd-avatar {
    display: block;
    width: 46px;                             /* repli pour l'ancien moteur */
    height: 46px;
    width: calc(var(--fd-ico, 44px) * 1.05);
    height: calc(var(--fd-ico, 44px) * 1.05);
}
.fd-avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border: 2px solid #fff;
    border-radius: 999px;
    background: var(--surface);
}

.fd-follow-btn {
    position: absolute;
    bottom: -6px;
    left: 50%;
    transform: translateX(-50%);
    width: 22px;                             /* repli pour l'ancien moteur */
    height: 22px;
    width: calc(var(--fd-ico, 44px) * .5);
    height: calc(var(--fd-ico, 44px) * .5);
    border: 2px solid #000;
    border-radius: 50%;
    background: #ff0055;   /* rouge TikTok */
    color: #fff;
    font-size: 14px;
    font-size: calc(var(--fd-ico, 44px) * .32);
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    padding: 0;
    transition: background 0.2s, transform 0.15s;
}
/* CORRIGE — une cible de 15 px.
   -------------------------------------------------------------------
   Ce bouton fait la moitie de la pastille du rail (--fd-ico x .5). En
   paysage sur telephone, --fd-ico tombe a 32 px : le bouton
   « s'abonner » ne mesurait plus que 15x15 px. Mesure faite dans un
   navigateur, a 844x390. C'est la plus petite commande de l'ecran, et
   celle qu'on vise le plus mal : posee a cheval sur l'avatar, un rate
   ouvre le profil.
   On agrandit la ZONE TACTILE sans toucher au dessin, meme technique
   que dans cam.php : un ::before transparent qui depasse. On garde
   aussi un plancher de dessin a 18 px pour que le « + » reste lisible. */
.fd-follow-btn { min-width: 22px; min-height: 22px; }
.fd-follow-btn::before {
    content: "";
    position: absolute;
    /* CORRIGE (mesure au banc d'essai). Cette zone etait etendue de
       11 px DANS TOUS LES SENS — y compris vers le haut, c'est-a-dire
       par-dessus l'avatar, qui ne fait que 40 a 47 px. Resultat mesure
       sur trois ecrans : a partir de la moitie de la photo de profil,
       le doigt tombait sur « suivre ». Appuyer sur la photo de
       quelqu'un l'abonnait au lieu d'ouvrir son profil.
       Mesure de ce qu'il y a autour : au-dessus, l'avatar (qui est
       peint PAR-DESSUS et garde ses propres appuis) ; 2 px en dessous,
       le bouton « j'aime ». Il n'y a donc aucune place a prendre en
       hauteur sans voler un voisin — et voler le « j'aime » serait
       pire. On elargit donc sur les cotes, ou il n'y a rien, et on
       grossit la pastille elle-meme (22 px au lieu de 18 dans les
       dispositions serrees) : environ 26 px de haut et 48 de large,
       sans mordre ni sur l'avatar ni sur le rail. */
    top: -1px; right: -13px; bottom: -3px; left: -13px;
    border-radius: 50%;
}

/* État suivi : fond VERT */
.fd-follow-btn.is-following {
    background: #22c55e;   /* vert */
}
.fd-follow-btn.is-following .fd-follow-check {
    display: block;
}
.fd-follow-btn.is-following .fd-follow-plus {
    display: none;
}
.fd-follow-btn:not(.is-following) .fd-follow-check {
    display: none;
}
.fd-follow-btn:not(.is-following) .fd-follow-plus {
    display: block;
}

/* Effet pop (identique au like) */
.fd-follow-btn.is-pop {
    animation: fd-pop .38s var(--fd-ease);
}

/* ===================================================================
   MOBILE — LE DEFILEMENT
   -------------------------------------------------------------------
   Place en fin de fichier volontairement. Le bloc precedent etait en
   tete et se faisait ecraser par les regles generales : a specificite
   egale, c'est le dernier ecrit qui gagne, et une media query
   n'ajoute aucune specificite.
   =================================================================== */

@media (max-width: 768px) {

    /* --- La hauteur --------------------------------------------------
       Aucune unite de viewport ici, volontairement. Le conteneur est
       `position: fixed; inset: 0` : sa hauteur est deja celle de la
       zone visible, et elle ne bouge pas quand la barre d'URL glisse.
       `height: 100%` se resout donc exactement, au pixel pres.

       J'avais d'abord mis `100dvh` ici. C'etait une erreur : `dvh` se
       recalcule en continu pendant l'animation de la barre d'URL, et
       chaque recalcul retire le snap. Ca refabriquait le tremblement
       par l'autre bout. */
    .fd-card {
        height: 100%;
        scroll-snap-align: start;
        scroll-snap-stop: always;
    }

    /* --- Le loader, en fin de flux ------------------------------------
       En snap `mandatory`, le navigateur DOIT se poser sur une zone de
       snap. Le loader est le dernier element : s'il n'en est pas une,
       descendre jusqu'a lui fait rebondir la vue sur la derniere carte.
       Un rebond en fin de feed — c'est-a-dire exactement la ou le
       chargement infini se declenche, donc tout le temps.

       On en fait une zone de snap legitime : la vue peut s'y arreter. */
    .fd-loader {
        scroll-snap-align: start;
        scroll-snap-stop: normal;
    }

    /* Le jalon, lui, est encadre par deux cartes : il n'a pas besoin
       d'etre une zone de snap, on le traverse. */
    .fd-jalon {
        scroll-snap-align: none;
        scroll-snap-stop: normal;
    }

    /* --- Le texte deplie ------------------------------------------
       Une zone qui defile a l'interieur d'une carte qui defile : le
       navigateur doit choisir a chaque frame lequel bouge. Sur un
       geste vertical un peu oblique, il hesite — et l'hesitation se
       voit. On borne la zone et on laisse la main au feed des qu'on
       touche le bout (`auto`, jamais `contain`). */
    .fd-sous.is-ouvert {
        max-height: 34vh;
        overscroll-behavior: auto;
    }

    /* --- Le panneau commentaires ----------------------------------
       Lui, au contraire, doit garder le geste : il est par-dessus le
       feed, pas dedans. Sans `contain`, fermer la liste en bas fait
       defiler le feed derriere. */
    .fd-sheet-liste {
        overscroll-behavior: contain;
    }
}

/* La carte active porte cette classe pendant un recalage programme du
   JS (reordonnancement d'une carte pas prete). Le temps du recalage,
   on coupe les transitions internes : sinon l'animation d'opacite se
   rejoue en plein saut de scroll et redouble l'effet de saccade. */
.fd-feed.is-recale .fd-card,
.fd-feed.is-recale .fd-card * {
    transition: none !important;
    animation-play-state: paused !important;
}

/* ===================================================================
   FLUIDITE — LE COUT DES FLOUS DE FOND
   -------------------------------------------------------------------
   `backdrop-filter` oblige le navigateur a relire la surface deja
   composee derriere l'element, la flouter, puis la recomposer. A
   chaque frame ou quelque chose bouge dessous. Pendant un
   defilement, c'est-a-dire : a chaque frame.

   Compte par carte : le badge, les cinq a six pastilles du rail,
   l'onde du son, le bouton muet. Environ huit. Trois cartes dans le
   compositeur pendant un swipe font une vingtaine de floutages par
   frame — sur un telephone, c'est la depense qui fait tomber sous les
   60 images par seconde.

   Ces pastilles sont posees sur du media sombre, sous un voile
   (`.fd-scrim`) qui assombrit deja le bas de la carte. Le flou n'y
   apporte presque rien. On le retire sur mobile et on compense par un
   fond un peu plus dense : a l'oeil, la difference est minime ; au
   compositeur, elle est totale.

   Le desktop garde le flou : la surface est plus petite et la machine
   plus large.
   =================================================================== */

@media (max-width: 768px) {
    .fd-badge {
        backdrop-filter: none;
        background: rgba(0, 0, 0, .62);
    }
    .fd-act .fd-ico {
        backdrop-filter: none;
        background:transparent;
    }
    .fd-onde {
        backdrop-filter: none;
        background: rgba(0, 0, 0, .62);
    }
    .fd-mute {
        backdrop-filter: none;
        background: rgba(0, 0, 0, .62);
    }
    .fd-pulse {
        backdrop-filter: none;
        background: rgba(0, 0, 0, .55);
    }
    .fd-tampon {
        backdrop-filter: none;
        background: rgba(0, 0, 0, .62);
    }
}

/* La barre du bas floute toute la largeur de l'ecran, en permanence,
   au-dessus d'un conteneur qui defile — donc a chaque frame.

   Or son fond est deja `rgba(0,0,0,.94)` : quatre-vingt-quatorze pour
   cent de noir opaque. Ce qui passe a travers pour etre floute est
   imperceptible. On paie un floutage plein ecran par frame pour un
   resultat que personne ne peut voir.

   Selecteur par identifiant : la regle vient du <style> d'index.php,
   qui est charge APRES feed.css. A specificite egale, c'est lui qui
   gagnerait. */
#bottom-nav {
    backdrop-filter: none;
}

/* Bouton Favori – étoile transparente par défaut, jaune quand active */
.fd-fav .fd-ico svg {
    stroke: #fff;
    fill: none;
    transition: fill .2s, stroke .2s;
}
.fd-fav.is-on .fd-ico svg {
    fill: currentColor;
    stroke: currentColor;
}
.fd-fav.is-on {
    color: #ffd700;
}
.fd-fav.is-on .fd-n {
    color: #ffd700;
}
.fd-fav.is-on .fd-ico {
    background: rgba(255, 215, 0, 0.16);
    border-color: rgba(255, 215, 0, 0.5);
}
.fd-fav.is-pop .fd-ico {
    animation: fd-pop .38s var(--fd-ease);
}/* Animation renforcée de l'étoile au clic */
.fd-fav.is-pop .fd-ico svg {
    animation: fd-fav-star .5s var(--fd-ease);
}
.fd-fav.is-pop .fd-ico {
    box-shadow: 0 0 0 6px rgba(255, 215, 0, .3);
    transition: box-shadow .3s;
}

@keyframes fd-fav-star {
    0%   { transform: scale(1) rotate(0deg); }
    40%  { transform: scale(1.45) rotate(15deg); }
    70%  { transform: scale(.9) rotate(-10deg); }
    100% { transform: scale(1) rotate(0deg); }
}

/* AJOUTE : Bouton Repost — jusqu'ici seul le rebond generique fd-pop
   s'appliquait (comme sur tous les boutons), sans etat "actif" persistant
   ni animation propre, contrairement a J'aime (rose) et Favori (or +
   rotation de l'etoile). Meme principe que le bloc Favori ci-dessus :
   couleur verte persistante une fois reposte, et rotation de la fleche
   au moment du clic. */
.fd-repost.is-on {
    color: var(--k-green, #22c55e);
}
.fd-repost.is-on .fd-n {
    color: var(--k-green, #22c55e);
}
.fd-repost.is-on .fd-ico {
    background: rgba(34, 197, 94, 0.16);
    border-color: rgba(34, 197, 94, 0.5);
}
.fd-repost.is-pop .fd-ico svg {
    animation: fd-repost-spin .5s var(--fd-ease);
}

@keyframes fd-repost-spin {
    0%   { transform: rotate(0deg) scale(1); }
    60%  { transform: rotate(360deg) scale(1.15); }
    100% { transform: rotate(360deg) scale(1); }
}

/* Particules d'étoiles */
.fd-burst-etoile {
    position: fixed;
    z-index: 2000;
    pointer-events: none;
    animation: burst-etoile 0.7s ease-out forwards;
}

.fd-burst-etoile span {
    position: absolute;
    font-size: 14px;
    animation: etoile-vol 0.7s ease-out forwards;
}

@keyframes burst-etoile {
    from { opacity: 1; }
    to { opacity: 0; }
}

@keyframes etoile-vol {
    from { transform: rotate(var(--deg)) translateY(0) scale(1); }
    to { transform: rotate(var(--deg)) translateY(-40px) scale(0.5); opacity: 0; }
}