/* ═══════════════════════════════════════════════════════════════════
   TIMELINE — ACCORDÉON chronologique
   ───────────────────────────────────────────────────────────────────
   Voir CHANTIER-EVENTS.md §7 et timeline.js (le pourquoi de la forme).

   ⚠️ RIEN N'EST JAMAIS REMPLACÉ : les niveaux s'INSÈRENT sous leur
   parent (.tl-kids). Le contexte est à l'écran, dans l'imbrication —
   c'est ce qui rend inutiles le fil d'Ariane et la règle latérale des
   versions précédentes.

   REGISTRE : technique et minimaliste, à l'opposé des cartes qui sont
   picturales. Les cartes sont des objets du monde, la timeline est un
   instrument de lecture. Traits fins, sobriété.

   ⚠️ TOUT EST EN `--font` (Georgia), y compris les chiffres.
   Une première version passait les libellés et les comptes en monospace
   au nom du « registre instrument » — une police système sans caractère : la
   timeline ne ressemblait à rien du reste. Il n'existe plus de token `mono`
   (le HUD utilise Inter en chiffres tabulaires). « Millénaire », « 22e siècle »
   sont des MOTS, pas des chiffres alignés.

   ⚠️ Les libellés de la timeline sont de la STRUCTURE (ils nomment une
   période), donc `--font`. Ne pas les basculer en `--font-alt` : Inter y
   porte le CONTENU qu'on lit, pas les repères qu'on parcourt.

   Les deux règles de card-gallery.css s'appliquent telles quelles :
   1. ANIMER `transform` ET `opacity`, RIEN D'AUTRE — la scène Three.js
      tourne derrière ; animer une hauteur ou une ombre ferait tout
      tomber. (Le dépliage passe par un keyframe sur opacity+transform,
      pas par une transition de height.)
   2. Tokens de design-tokens.js — pas de couleur ni de taille en dur.
   ═══════════════════════════════════════════════════════════════════ */

/* ⚠️ `pointer-events: none` À L'ÉTAT FERMÉ — INDISPENSABLE.
   La racine est en `position: fixed; inset: 0` : elle couvre TOUT l'écran
   en permanence. `visibility: hidden` la rend invisible mais ne suffit
   pas ici, car les enfants qui portent `pointer-events: auto` la
   réactivent — l'écran restait donc couvert par une surface morte, et le
   CANVAS ne recevait plus aucun clic, timeline fermée comprise. C'est ce
   qui bloquait le clic dans le vide du terrain (`pointerup` sur le
   canvas, main.js), et donc toute fermeture. */
.tl-root {
  position: fixed;
  inset: 0;
  z-index: 4000;                 /* au-dessus de #ui, sous les cartes (5000+) */
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--tl-build, 460ms) var(--tl-ease, ease),
              visibility 0s linear var(--tl-build, 460ms);
}
.tl-root.is-open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity var(--tl-build, 460ms) var(--tl-ease, ease),
              visibility 0s;
}

/* ── Le voile ──────────────────────────────────────────────────────
   PARTIEL, comme celui de la galerie : le terrain reste perceptible
   dessous — on ne quitte pas le monde, il passe au second plan. */
.tl-veil {
  position: absolute;
  inset: 0;
  background: radial-gradient(120% 80% at 50% 40%,
              color-mix(in srgb, var(--black) 84%, transparent) 0%,
              color-mix(in srgb, var(--black) 95%, transparent) 60%,
              color-mix(in srgb, var(--black) 99%, transparent) 100%);
  backdrop-filter: blur(7px) saturate(.75);
  -webkit-backdrop-filter: blur(7px) saturate(.75);
}

/* ── Le cadre ─────────────────────────────────────────────────────*/
/* ⚠️ `pointer-events: none` SUR LE CADRE, réactivé sur ses enfants.
   Le cadre est en `inset: 0` : il recouvre tout l'écran, y compris le
   voile posé dessous. Sans ça il interceptait CHAQUE clic, et cliquer
   « à côté » ne fermait jamais rien — le voile était inatteignable. */
.tl-frame {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  /* ⚠️ LE BAS RÉSERVE LA PLACE DE LA NAV.
     Le cadre est en `inset: 0`, donc il descend jusqu'au bord de l'écran
     — la fin d'une longue liste passait sous les 3 pastilles (à 52 px du
     bas, 44 px de haut, plus leur libellé), qui vivent à un z-index
     supérieur : les dernières lignes devenaient illisibles et
     incliquables. 116 px couvrent la nav complète. */
  padding: clamp(16px, 2.6vh, 30px) clamp(20px, 4vw, 60px) 116px;
  transform: translateY(14px);
  transition: transform var(--tl-build, 460ms) var(--tl-ease, ease);
  pointer-events: none;
}
/* ⚠️ `.tl-scroll` DOIT rester `auto` : sans lui la molette ne défile plus.
   Il réintercepte donc les clics — c'est son écouteur (timeline.js) qui
   distingue « sur une ligne » de « dans le vide » et ferme en conséquence. */
.tl-head, .tl-scroll, .tl-foot, .tl-close { pointer-events: auto; }
.tl-root.is-open .tl-frame { transform: translateY(0); }

/* ── En-tête ──────────────────────────────────────────────────────*/
.tl-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding-bottom: 10px;
}
.tl-title {
  font-family: var(--font);
  font-size: var(--t4);
  letter-spacing: var(--ls-3);
  text-transform: uppercase;
  color: var(--grey1);
  opacity: .85;
}
.tl-close {
  background: none; border: 0; cursor: pointer; padding: 6px;
  line-height: 0; color: var(--gold);
  opacity: .8;
  transition: opacity 180ms var(--tl-ease, ease),
              transform 240ms var(--tl-ease, ease);
}
.tl-close:hover { opacity: 1; transform: rotate(90deg); }
.tl-close svg { width: 24px; height: 24px; }
.tl-close path { stroke: currentColor; stroke-width: 1.6; fill: none; }

/* ── La scène ─────────────────────────────────────────────────────*/
.tl-stage {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
}

/* ── La colonne scrollable ────────────────────────────────────────
   Le scroll n'est PAS la navigation : c'est du scroll. On descend dans
   la liste quand le dépliage la fait déborder, rien de plus. */
.tl-scroll {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  min-width: 0;   /* sinon le contenu pousse le rail hors du cadre */
  /* Conteneur FLEX en colonne : c'est ce qui rend le `margin-block: auto`
     de `.tl-rows` opérant (centrage tant que ça tient, ancrage en haut
     dès que ça déborde). Sans `display: flex`, la marge auto verticale
     est simplement ignorée. */
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  overflow-x: hidden;
  /* ⚠️ GOUTTIÈRE SYMÉTRIQUE — c'est ce qui garde l'axe AU MILIEU.
     Une scrollbar prend de la largeur À DROITE sans équivalent à gauche :
     la zone utile n'est plus centrée sur l'écran, et l'axe de la timeline
     tombe à côté du milieu. `both-edges` réserve la même gouttière des
     deux côtés, donc le centre reste le centre — et il ne bouge pas non
     plus quand la scrollbar apparaît ou disparaît selon la longueur de la
     liste. */
  scrollbar-gutter: stable both-edges;
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--gold) 35%, transparent) transparent;
  /* ⚠️ PAS de grand `padding-bottom` ici (40vh auparavant).
     Il servait à laisser respirer la dernière ligne d'une longue liste,
     mais il s'applique AUSSI à l'état d'ouverture : 4 millénaires sur
     ~280 px se retrouvaient suivis de 40 % de hauteur d'écran vide, donc
     une scrollbar dès l'ouverture, sur un contenu qui tenait largement.
     Une valeur fixe et modeste suffit — elle ne crée jamais de débordement
     à elle seule. */
  padding: 6px 0 24px;
}
.tl-scroll::-webkit-scrollbar { width: 6px; }
.tl-scroll::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--gold) 35%, transparent); border-radius: 3px;
}

/* ⚠️ AUCUNE MARGE LATÉRALE ICI.
   Elle réservait la place de la mini ligne du temps (supprimée). Même
   symétrique, elle décentrait l'ensemble : la scrollbar occupe déjà de la
   largeur à DROITE sans équivalent à gauche, donc l'axe tombait à côté du
   milieu de l'écran. Les lignes se centrent d'elles-mêmes
   (`.tl-row { margin-inline: auto }`). */
/* ⚠️ CENTRÉ VERTICALEMENT TANT QUE ÇA TIENT, ancré en haut ensuite.
   `margin: auto 0` sur un enfant de conteneur flex centre le contenu
   court — au repos, 4 millénaires flottaient en haut d'un écran vide.
   Dès que la liste dépasse la hauteur disponible, la marge automatique
   se réduit à zéro d'elle-même et le contenu repart du haut : on ne perd
   pas le début de la liste, ce qu'un `justify-content: center` aurait
   fait (il rogne le haut, inatteignable au scroll). */
.tl-rows {
  position: relative;
  z-index: 1;
  margin-block: auto;
}

/* ── L'axe central ────────────────────────────────────────────────
   Le repère vertical du croquis. Les colonnes latérales étant de
   largeur ÉGALE, la colonne des points est exactement au milieu du
   conteneur : 50 % tombe donc juste, quelle que soit la profondeur. */
.tl-axis {
  position: absolute;
  left: 50%;
  top: 0; bottom: 0;
  width: 0;
  border-left: 1px dashed color-mix(in srgb, var(--gold) 32%, transparent);
  pointer-events: none;
  z-index: 0;
}

/* ── Une ligne ────────────────────────────────────────────────────
   ⚠️ LA LIGNE EST CENTRÉE SUR L'AXE, PAS ALIGNÉE À GAUCHE.
   Deux étages empilés et centrés : le LIBELLÉ au-dessus, les POINTS
   dessous. La file grandit donc symétriquement de part et d'autre de
   l'axe, et c'est cette symétrie qui produit la silhouette en losange —
   la densité se lit d'un coup d'œil, sans lire un seul chiffre.
   Aligner à gauche (essayé) détruit cette lecture : les files partent
   toutes du même bord et on ne compare plus que des longueurs. */
/* ⚠️ `width: fit-content` + `margin: auto` : la ligne ne fait que la
   largeur de SON CONTENU, pas celle de la colonne. En pleine largeur
   (le défaut d'un bloc flex), cliquer à 300 px à gauche d'un libellé
   tombait encore « sur la ligne » : le clic dans le vide ne marchait
   qu'en DEHORS de la timeline, jamais dedans. */
.tl-row {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: fit-content;
  margin-inline: auto;
  gap: 6px;
  /* L'air respecte la hiérarchie : un millénaire s'entoure de silence,
     un jour s'inscrit dans une liste serrée. */
  padding: var(--tl-pad, 8px) 0;
  cursor: default;
  /* ⚠️ PIÈGE RÉSOLU — DEUX ÉLÉMENTS, DEUX RÔLES.
     L'animation d'entrée est en `forwards` : sa valeur de fin FIGE
     l'opacité et écrase toute autre règle (`.is-empty` et `.is-off`
     restaient à 1 sans raison visible), et un survol ne peut plus rien
     changer. On sépare donc les deux mécanismes :
       • .tl-row      porte l'ÉTAT (opacité, en transition) ;
       • .tl-row > *  porte l'ENTRÉE (l'animation, une fois).
     Chacun son propriétaire, plus de conflit possible. */
  opacity: var(--tl-o, 1);
  /* ⚠️ Valeur de repos EXPLICITE : une transition ne peut pas partir
     d'une propriété jamais déclarée. Sans ce `translateY(0)`, la sortie
     au refiltrage (`.tl-out`) sautait à sa position finale au lieu de
     glisser — seul le fondu se voyait. Même piège que `.floating-card`,
     qui déclare aussi ses valeurs de repos pour cette raison. */
  transform: translateY(0);
  transition: opacity 260ms var(--tl-ease, ease);
}
.tl-row > * {
  opacity: 0;
  transform: translateY(-4px);
  animation: tl-in var(--tl-open, 420ms) var(--tl-ease, ease) forwards;
  animation-delay: var(--d, 0ms);
}
@keyframes tl-in { to { opacity: 1; transform: translateY(0); } }

/* ── Sortie au refiltrage ─────────────────────────────────────────
   Symétrique de l'entrée : les lignes montent en s'effaçant, décalées
   les unes après les autres (le JS pose la classe en cascade), puis les
   nouvelles entrent par le bas. Même geste que le refiltrage du
   carrousel des personnages (`cg-out`) — deux écrans frères doivent
   répondre pareil.
   ⚠️ `!important` sur l'opacité : la ligne porte son état via `--tl-o`
   (vide, hors chemin…) et cet état gagnerait sur la sortie. Pendant
   qu'elle s'en va, plus rien d'autre ne doit la peindre. */
.tl-row.tl-out {
  opacity: 0 !important;
  transform: translateY(-14px);
  transition: opacity var(--tl-open, 420ms) var(--tl-ease, ease),
              transform var(--tl-open, 420ms) var(--tl-ease, ease);
  pointer-events: none;
}

.tl-row.can-open { cursor: pointer; }
/* Le survol se marque par le libellé et les points, pas par un pavé de
   fond : sur une mise en page centrée, une bande pleine largeur écrase
   la silhouette qu'on cherche justement à faire lire. */
.tl-row.can-open:hover .tl-lb { text-shadow: 0 0 12px color-mix(in srgb, var(--gold-light) 45%, transparent); }

/* ── Les quatre états d'opacité, par ordre de priorité ────────────
   Une ligne vide DIT que rien ne s'est passé : on l'atténue, on ne la
   masque pas — la masquer donnerait une fausse continuité.

   ⚠️ Quand une ligne est dépliée, ses SŒURS reculent (`is-off`) : le
   regard suit la descente au lieu de se partager entre trois
   millénaires. Elles restent lisibles et CLIQUABLES — c'est un recul,
   pas une désactivation : on doit pouvoir changer de branche d'un clic. */
.tl-row.is-empty        { --tl-o: .3; }
.tl-row.is-off          { --tl-o: var(--tl-off, .28); }
.tl-row.is-off.is-empty { --tl-o: .12; }   /* pas un cumul : une valeur choisie */
.tl-row.is-empty .tl-lb { color: color-mix(in srgb, var(--grey1) 55%, transparent); }

/* Le survol ramène une ligne en retrait au premier plan. */
.tl-row.is-off:hover    { --tl-o: .9; }


/* ── Le libellé — AU-DESSUS des points, sur l'axe ─────────────────
   ⚠️ Il ne porte PAS de compte : le nombre est déjà DANS la boule quand
   la ligne est chargée, et quand elle ne l'est pas, les points sont
   comptables à l'œil. Répéter le chiffre à côté était du bruit.
   La taille décroît avec la profondeur : un millénaire s'annonce, un
   jour se murmure. C'est ce qui hiérarchise sans indentation. */
.tl-lb {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font);
  font-size: var(--tl-lb-size, 18px);
  color: var(--grey1);
  opacity: .62;
  letter-spacing: var(--ls-1);
  white-space: nowrap;
  transition: color 180ms var(--tl-ease, ease), opacity 180ms var(--tl-ease, ease);
}

/* ⚠️ LA HIÉRARCHIE DE TAILLE EST LA SEULE MARQUE DE PROFONDEUR.
   La mise en page étant centrée, il n'y a pas d'indentation pour dire
   le niveau : c'est l'échelle typographique qui le fait. Un millénaire
   s'annonce, un jour se murmure. Un cran par niveau, jamais deux
   niveaux à la même taille — sinon l'imbrication cesse de se lire. */
/* Les POINTS suivent la même descente que le texte : un point de 10 px
   sous un libellé de 32 px se lirait comme une poussière. */
/* ⚠️ TAILLES PRISES DANS L'ÉCHELLE DU DESIGN SYSTEM (`--t1…--t6`), pas en dur.
   Une première version posait 32/26/21/18/15/13 px inventés pour l'occasion :
   six valeurs hors système, qui ne suivaient plus si l'échelle bougeait.
   L'échelle compte justement 6 crans (t6 → t1, avec t1 répété au bas) et la
   hiérarchie en compte 6 : elles se recouvrent.
   Voir DESIGN-TOKENS.md — aucune taille de police en dur dans le projet. */
.tl-row[data-lv="0"] { --tl-lb-size: var(--t6); --tl-dot: 15px; --tl-gap: 9px; --tl-pad: 18px; }
.tl-row[data-lv="1"] { --tl-lb-size: var(--t5); --tl-dot: 13px; --tl-gap: 8px; --tl-pad: 14px; }
.tl-row[data-lv="2"] { --tl-lb-size: var(--t4); --tl-dot: 12px; --tl-gap: 7px; --tl-pad: 11px; }
.tl-row[data-lv="3"] { --tl-lb-size: var(--t2); --tl-dot: 11px; --tl-gap: 7px; --tl-pad:  9px; }
/* ⚠️ Mois et jour partagent `--t1` : l'échelle n'a que 6 crans et le plus
   petit (t1, 10 px) est déjà atteint. La profondeur se lit alors sur les
   autres marqueurs — taille des points, air autour de la ligne — plutôt que
   d'inventer un 7e cran hors système. */
.tl-row[data-lv="4"] { --tl-lb-size: var(--t1); --tl-dot: 10px; --tl-gap: 6px; --tl-pad:  7px; }
.tl-row[data-lv="5"] { --tl-lb-size: var(--t1); --tl-dot:  9px; --tl-gap: 6px; --tl-pad:  6px; }
.tl-row.can-open:hover .tl-lb { opacity: 1; color: var(--gold-light); }

/* ⚠️ Une ligne ouverte n'affiche PLUS ses points : ils seraient le
   doublon de ce qui se déplie juste en dessous (une boule « 2335 » au-
   dessus des 10 siècles qui la détaillent, c'est deux fois la même
   information — et on ne sait plus laquelle lire).
   Le libellé reste : il dit où l'on est, et c'est lui qui referme.
   Il devient donc le SEUL marqueur d'état — d'où l'accent appuyé. */
.tl-row.is-open .tl-lb {
  color: var(--gold-light);
  opacity: 1;
}
/* Un filet sous le libellé ouvert : il tient la place laissée par les
   points et sépare l'en-tête de ce qu'il déplie. */
.tl-row.is-open .tl-lb::after {
  content: '';
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: -6px;
  width: calc(var(--tl-lb-size, 18px) * 1.6);
  border-bottom: 1px solid currentColor;
  opacity: .5;
}
.tl-lb { position: relative; }

/* ── Les points ───────────────────────────────────────────────────
   CENTRÉS : la file grandit symétriquement de part et d'autre de l'axe.
   C'est la LONGUEUR de la file qui dit la densité, et sa symétrie qui
   fait apparaître la silhouette d'un millénaire à l'autre. */
.tl-dots {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--tl-gap, 6px);
  /* ⚠️ Les points REPASSENT À LA LIGNE. Une feuille « Sans date précise »
     vaut 343 événements pour la seule année 3019 (207 en 2941) : en
     `nowrap` ils sortaient de l'écran, donc devenaient inatteignables.
     Enveloppés, ils forment un bloc dont la hauteur dit la quantité —
     et chacun reste survolable et cliquable. */
  flex-wrap: wrap;
  max-width: min(720px, 62vw);
  row-gap: var(--tl-gap, 6px);
}

.tl-dot {
  flex: 0 0 auto;
  width: var(--tl-dot, 10px);
  height: var(--tl-dot, 10px);
  border-radius: 50%;
  border: 0;
  padding: 0;
  position: relative;
  cursor: pointer;
  background: var(--gold);
  /* transform + opacity uniquement : la scène 3D tourne derrière */
  transition: transform 180ms var(--tl-ease, ease),
              background-color 180ms var(--tl-ease, ease);
}
.tl-dot:hover,
.tl-dot:focus-visible {
  transform: scale(1.6);
  background: var(--gold-light);
  outline: none;
  z-index: 2;
}

/* Un point-groupe : plus gros, il porte son compte.
   ⚠️ C'est ce qui rend l'an 3019 (623 events) représentable. */
/* ⚠️ Le groupe est une PASTILLE, pas un cercle : « 1472 » ne tient pas
   dans un disque de 28 px. Elle s'élargit à son contenu et garde une
   hauteur fixe, donc elle reste sur la ligne de base des points. */
/* ⚠️ SA TAILLE PORTE UNE INFORMATION : `--cs` vient du JS et vaut la
   densité de la ligne RAPPORTÉE À SES SŒURS (échelle en racine). Le
   siècle le plus chargé d'un millénaire est visiblement le plus gros —
   on voit où creuser avant même d'avoir lu un chiffre.
   La hauteur suit `--cs`, la largeur s'adapte au nombre (« 1472 » ne
   tient pas dans un disque), donc la pastille reste une pastille. */
.tl-dot.is-cluster {
  /* ⚠️ `max()` avec un plancher : le chiffre est désormais à taille FIXE
     (--t1), donc la plus petite pastille doit rester assez haute pour le
     contenir. Sans ce plancher, une fratrie très calme produisait des
     pastilles plus courtes que leur propre texte. */
  --cs-h: max(calc(var(--t1) * 1.9),
              calc(var(--tl-dot, 10px) * 1.9 * var(--cs, 1)));
  width: auto;
  min-width: var(--cs-h);
  height: var(--cs-h);
  padding: 0 calc(var(--cs-h) * .28);
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--gold) 40%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--gold-light) 60%, transparent);
}
.tl-dot.is-cluster:hover { background: color-mix(in srgb, var(--gold-light) 60%, transparent); }
.tl-dot-n {
  font-family: var(--font);
  /* ⚠️ Taille STANDARD de l'échelle (`--t1`, le cran des micro-labels),
     pas une fraction de la pastille. La version précédente la calculait en
     `--cs-h * .42` : le chiffre changeait de taille d'une pastille à
     l'autre, donc la même information se lisait à six tailles différentes
     dans une seule fratrie. C'est la pastille qui porte la densité, pas
     son chiffre — celui-ci doit rester constant pour être comparable. */
  font-size: var(--t1);
  line-height: 1;
  color: var(--grey1);
  pointer-events: none;
}

/* ── Le bloc d'enfants dépliés ────────────────────────────────────
   Pas de filet latéral ici : la mise en page étant CENTRÉE, un filet à
   gauche pointerait à côté de tout. C'est l'axe central (.tl-axis) qui
   tient ce rôle, et le retrait du libellé qui dit la profondeur. */
.tl-kids {
  position: relative;
}

/* Les lignes « sans date » / « sans année » : détachées et grisées,
   elles se lisent comme EN ATTENTE, pas comme une unité de temps. */
.tl-row.is-undated { margin-top: clamp(10px, 1.6vh, 22px); }
.tl-row.is-undated .tl-lb { color: color-mix(in srgb, var(--grey2) 90%, transparent); font-style: italic; }
.tl-row.is-undated .tl-dot { background: color-mix(in srgb, var(--grey2) 55%, transparent); }
.tl-row.is-undated .tl-dot.is-cluster {
  background: color-mix(in srgb, var(--grey2) 30%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--grey2) 50%, transparent);
}

/* ── Le cartouche au survol ───────────────────────────────────────
   Rouge, comme sur le croquis : il tranche volontairement sur le cuivre.
   C'est le seul élément qui PARLE au milieu d'un instrument.
   ⚠️ Le rouge vient de `--cat-battle`, la couleur de catégorie du bucket
   « bataille » (design-tokens.js), et non d'un `#9d1f1f` inventé pour
   l'occasion : la timeline ne définit aucune palette parallèle. */
.tl-tip {
  position: absolute;
  top: 0; left: 0;
  max-width: 300px;
  padding: 11px 15px 13px;
  border-radius: 4px;
  background: var(--cat-battle);
  color: var(--white);
  pointer-events: none;
  opacity: 0;
  transition: opacity 160ms var(--tl-ease, ease);
  z-index: 5;
  box-shadow: 0 10px 30px color-mix(in srgb, var(--black) 45%, transparent);
}
.tl-tip.is-on { opacity: 1; }
.tl-tip-when {
  display: flex; align-items: center; gap: 8px;
  font-family: var(--font);
  font-size: var(--t1);
  letter-spacing: var(--ls-2);
  text-transform: uppercase;
  opacity: .92;
  margin-bottom: 5px;
}
.tl-tip-when i {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--white); flex: 0 0 auto;
}
.tl-tip-what {
  display: block;
  font-family: var(--font);
  font-size: var(--t2);
  font-weight: 700;
  line-height: 1.32;
}

/* ── Pied ─────────────────────────────────────────────────────────*/
.tl-foot {
  flex: 0 0 auto;
  padding-top: 10px;
  text-align: right;
}
.tl-count {
  font-family: var(--font);
  font-size: var(--t1);
  letter-spacing: var(--ls-4);
  text-transform: uppercase;
  color: var(--grey1);
  opacity: .45;
}
