*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* Tous les tokens de design sont injectés au boot dans :root par
   design-tokens.js → injectCssVariables(). SOURCE UNIQUE = le JS.
   Ne pas définir de token de design ici, ni de valeur en dur.

   ══ LA TYPO TIENT EN 4 AXES INDÉPENDANTS (cf. TYPO, design-tokens.js) ══
     1. TAILLE   --t1 … --t6   6 rangs, en em → RELATIFS à leur conteneur
     2. FONTE    --font (Georgia, défaut) · --font-alt (Inter, lisibilité)
     3. GRAISSE  --fw-light (300) · --fw-regular (400) · --fw-bold (700)
     4. STYLE    --style-italic · --style-upper
   ⚠️ Le GRAS est une GRAISSE, pas un style : un seul axe le porte.
   ⚠️ Un rang ne dit PAS où il sert — un nom d'usage ment dès qu'on s'en sert
      ailleurs. C'est ce qui a tué les échelles précédentes (une globale + une propre aux cartes).

   ══ LA PALETTE TIENT EN 7 VALEURS (cf. PALETTE, design-tokens.js) ══
     --grey1 --grey2 --grey3 · --black --white · --gold --gold-light
   ⚠️ Nommées par LOGIQUE (un rang), jamais par usage : `--parchment` ou
      `--card-ink` mentaient dès qu'on s'en servait ailleurs, et chaque écran
      ajoutait son nom pour la même couleur — d'où les 20 teintes d'avant.
   ⚠️ Une couleur absente de la table n'existe pas. Pas de #hex en dur.

   Seuls fallbacks statiques ci-dessous : les POLICES (évitent un flash avant
   le boot JS ; les valeurs du JS les surchargent — garder les deux identiques). */
:root {
  --font: Georgia, 'Times New Roman', serif;
  --font-alt: 'Inter', system-ui, sans-serif;
}

/* DÉRIVÉS des 7 valeurs — pas des couleurs de plus : un fond de panneau est le
   noir rendu translucide, une bordure est le doré très atténué, une ombre de
   lisibilité est ce même noir diffusé. Ils vivent ici (et non dans la palette)
   pour que la table reste à 7. */
:root {
  --panel-bg: color-mix(in srgb, var(--black) 82%, transparent);
  --border:   color-mix(in srgb, var(--gold) 25%, transparent);
  /* Ombres du texte POSÉ SUR UNE ILLUSTRATION (légende de l'écran d'identité).
     Elles ne décorent pas : elles garantissent la lisibilité d'un blanc sur une
     image dont on ne maîtrise pas la valeur. Deux rangs, comme la typo qu'elles
     accompagnent — le titre porte plus loin que les lignes secondaires.
     Dérivées de --black (et non d'un rgba en dur) : la table reste à 7. */
  --shadow-title: 0 2px 8px color-mix(in srgb, var(--black) 70%, transparent);
  --shadow-text:  0 1px 5px color-mix(in srgb, var(--black) 70%, transparent);
}
html, body {
  width: 100%; height: 100%;
  background: var(--black);
  color: var(--grey1);
  font-family: var(--font);
  overflow: hidden;
}

canvas#canvas {
  display: block;
  width: 100%; height: 100%;
  position: fixed; inset: 0;
}

/* ── UI overlay ─────────────────────────────────────────────────── */
#ui {
  position: fixed; inset: 0;
  pointer-events: none;
}

#title {
  position: absolute;
  top: 28px; left: 36px;
}
#title .age-label {
  font-size: var(--t1);
  letter-spacing: var(--ls-5);
  text-transform: uppercase;
  color: var(--gold);
  opacity: 0.8;
}
#title h1 {
  font-size: var(--t5);
  font-weight: var(--fw-light);
  letter-spacing: var(--ls-3);
  color: var(--grey1);
  text-shadow: 0 0 24px var(--gold-light);
}

/* ── Tooltip léger ──────────────────────────────────────────────── */
#map-tooltip {
  position: fixed;
  background: var(--panel-bg);
  border: 1px solid var(--border);
  padding: 8px 14px;
  backdrop-filter: blur(8px);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s;
  max-width: 200px;
  z-index: 10;
}
#map-tooltip.visible { opacity: 1; }
#map-tooltip .tt-name {
  font-size: var(--t2);
  color: var(--gold-light);
  display: block;
}
#map-tooltip .tt-type {
  font-size: var(--t1);
  letter-spacing: var(--ls-3);
  text-transform: uppercase;
  color: var(--grey1);
  opacity: 0.5;
}
/* Sous-titre vide (entité sans type) → pas de ligne fantôme sous le nom. */
#map-tooltip .tt-type:empty { display: none; }

/* ═══════════════════════════════════════════════════════════════════
   CARD PANEL — panneau 2D gauche (remplace la carte 3D "sur la table")
   ═══════════════════════════════════════════════════════════════════ */
#card-panel {
  position: absolute;
  /* Centrage vertical par auto-margin (pas de transform → aucun sub-pixel blur). */
  top: 0;
  bottom: 0;
  margin-top: auto;
  margin-bottom: auto;
  left: 20px;
  /* ⚠️ LA TAILLE VIENT DE CARD_SIZES.PANEL (card-frame.js), posée en --cp-w par
     card-panel.js. Elle est dérivée de la HAUTEUR d'écran : à 460px de large la
     carte faisait 757px de haut, soit 84% d'un écran 900 — et un débordement pur
     et simple sur un portable (768). Ne pas remettre une largeur en dur ici :
     c'est --cp-w qui pilote AUSSI l'échelle du texte (--cs-*). */
  width: var(--cp-w);
  height: fit-content;
  pointer-events: auto;
  display: flex;
  flex-direction: column;
  gap: 12px;
  z-index: 20;
}
#card-panel.hidden { display: none; }

/* ── Animation d'entrée / sortie — glissement depuis la gauche ────
   Séquence au REMPLACEMENT (solution A) : l'ancienne carte sort à gauche,
   puis la nouvelle entre depuis la gauche. Piloté par card-panel.js.
   Réglages : CARD_ANIM (card-panel.js) — garder la durée synchro avec
   la transition ci-dessous. */
#card-panel {
  transition: transform var(--cp-anim, 260ms) cubic-bezier(0.4, 0.0, 0.2, 1),
              opacity   var(--cp-anim, 260ms) cubic-bezier(0.4, 0.0, 0.2, 1);
}
/* Hors champ à gauche : sert d'état de départ (entrée) ET d'arrivée (sortie). */
#card-panel.cp-out {
  transform: translateX(calc(-100% - 20px));
  opacity: 0;
}
/* Pendant une animation, la carte ne doit pas capter les clics. */
#card-panel.cp-animating { pointer-events: none; }

#cp-viewport {
  flex: 0 1 auto;
  /* Ratio natif du châssis (618/1017) — la hauteur suit la largeur, sinon les
     ornements du cadre se déforment. Plafonnée à la hauteur dispo. */
  aspect-ratio: 618 / 1017;
  width: 100%;
  height: auto;
  max-height: 100%;
  margin: 0 auto;
  perspective: 1400px;
  min-height: 0;
}
#cp-flip {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  transition: transform 0.55s cubic-bezier(0.4, 0.0, 0.2, 1);
  cursor: pointer;
}
.cp-face {
  position: absolute; inset: 0;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}

/* ═══════════════════════════════════════════════════════════════════
   CHÂSSIS DE CARTE — 3 couches (cf. HISTORIQUE.md § Chantier Cartes › Châssis : les 3 couches)
   ───────────────────────────────────────────────────────────────────
     [2] .cf-overlay  → l'ornement (visuel), pointer-events:none
     [1] .cf-window   → clip-path(masque SVG) → DÉCOUPE LE CONTENU QUI SLIDE
     [0] .cp-frame    → couleur de catégorie pleine (opacité de la carte)

   ⚠️ LE MASQUE EST LE CŒUR DU SYSTÈME — pas un détail de finition.
   Le contenu glisse horizontalement en continu ; c'est le clip-path qui le
   découpe à la forme exacte de l'ornement (coins chanfreinés + encoche des
   dots en bas + encoche de l'étoile en haut-gauche). Sans lui, le contenu
   apparaît/disparaît dans les angles et L'ILLUSION DU GLISSEMENT CASSE.

   ⚠️ ÉCHELLE : le path est en coords de la CARTE ENTIÈRE (618×1017). Il doit
   donc être porté par un élément qui couvre TOUTE la carte (inset:0), pas par
   une fenêtre déjà encastrée — sinon il est comprimé deux fois (bug "masque
   trop petit"). Les marges de la fenêtre viennent du PATH, pas du PNG.
   ═══════════════════════════════════════════════════════════════════ */
.cp-frame.has-frame {
  padding: 0;                 /* le padding passe au contenu */
  box-shadow: none;
  /* Bords arrondis de la CARTE : le rayon + overflow:hidden découpent le fond,
     l'ornement et le contenu d'un coup (aucune couche ne dépasse aux angles).
     Réglable via le cran `radius` de CARD_SCALE (card-frame.js) : il suit la
     taille de la carte, sinon une flottante réduite garde des coins de grande
     carte — proportion cassée sur la pièce la plus visible du châssis. */
  border-radius: var(--cs-radius);
  overflow: hidden;
  /* Fond de la carte : couleur de catégorie × texture. Il vit ICI (couche du
     bas) et non sur la fenêtre : celle-ci est en inset:0, donc un fond opaque
     dessus recouvrirait l'ornement. Ordre : fond → ornement → contenu. */
  background-color: var(--accent);
  background-image: url('../../data/raw/cards/card-bg-alpha.jpg');
  /* `cover` et non `100% 100%` : la carte a souvent une hauteur fractionnaire
     (ex. 625.4px) qu'un fond étiré ne couvre pas au demi-pixel près → il restait
     une LIGNE de couleur nue sous la texture, en bas de carte. */
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}
/* Texture papier toujours en multiply, quelle que soit la carte (overlay
   éclaircissait les teintes moyennes et faussait la couleur pure). */
.cp-frame.has-frame { background-blend-mode: multiply; }

/* ── PLUS DE CONTRASTE AUTOMATIQUE ────────────────────────────────
   Le mécanisme --ink* / data-ink a été SUPPRIMÉ : il faisait basculer tout le
   contenu en blanc ou en noir selon la clarté de la carte. Il n'a plus d'objet
   — la fenêtre est blanche en permanence (cf. .cf-window), donc le texte y est
   toujours sombre. Deux jeux de couleurs pour un seul cas réel.
   Le contenu utilise les tokens de palette directement. */

/* [0b] L'ORNEMENT — EN DESSOUS de tout le contenu.
   C'est le clip-path de .cf-window qui découpe le rail à la forme de la carte :
   c'est LUI qui crée l'illusion de glissement, pas un recouvrement par le PNG.
   Provisoire : PNG de travail, sera remplacé. */
.cf-overlay {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image: url('../../data/raw/cards/carte_cadre.png');
  /* Étiré à la carte, mais avec un demi-pixel de débord : évite la ligne nue en
     bas quand la hauteur de carte est fractionnaire (cf. .cp-frame ci-dessus).
     L'ornement DOIT rester étiré (pas `cover`) pour ne pas se déformer. */
  background-size: calc(100% + 1px) calc(100% + 1px);
  background-position: -0.5px -0.5px;
  background-repeat: no-repeat;
  mix-blend-mode: multiply;   /* prend la couleur de catégorie portée dessous */
}
/* Variantes personnages : ornement dédié par alignement.
   🔄 **DÉ-SCOPÉ le 26/08** (HISTORIQUE.md § Chantier Cartes v2 › Décisions) — ces trois ornements
   ne s'appliquaient que dans la galerie, si bien qu'un même personnage portait
   un cadre dans la pile et un autre sur le terrain. Ils valent désormais
   partout : c'est LA carte du personnage. */
/* ?v=N = cache-bust : bump le nombre à chaque remplacement de PNG pour forcer
   le navigateur à retélécharger (les fichiers ont le même nom d'un design à
   l'autre, sinon Chrome sert la version cachée). */
.cp-frame[data-card-type="character"][data-alignment="light"] .cf-overlay {
  background-image: url('../../data/raw/cards/carte_cadre_character_light.png?v=2');
  /* Multiply désactivé : cadre light rendu tel quel, pas teinté par l'accent. */
  mix-blend-mode: normal;
}
.cp-frame[data-card-type="character"][data-alignment="neutral"] .cf-overlay {
  background-image: url('../../data/raw/cards/carte_cadre_character_neutral.png?v=2');
}
.cp-frame[data-card-type="character"][data-alignment="shadow"] .cf-overlay {
  background-image: url('../../data/raw/cards/carte_cadre_character_shadow.png?v=2');
  /* Multiply désactivé : cadre shadow rendu tel quel, pas teinté par l'accent. */
  mix-blend-mode: normal;
}

/* [1] LA FENÊTRE — couvre toute la carte et porte le masque, donc le path est
   appliqué à son échelle native (618×1017). Le contenu qui slide vit dedans et
   se fait découper à la forme de l'ornement. */
.cf-window {
  position: absolute;
  inset: 0;                   /* ← même repère que le path : pas de compression */
  z-index: 2;
  overflow: hidden;
  clip-path: url(#card-window-mask);
  /* Alignement fin du masque sur l'ornement : translate 1.4px X + scaleX 1.01
     (mesuré au HUD de dev puis figé). Origine centre. */
  transform: translate(1.4px, 0) scaleX(1.01);
  transform-origin: 50% 50%;
  /* Le clip est posé sur la fenêtre STATIQUE (pas sur le contenu mobile) :
     limite la recomposition pendant scroll/slide. */
  contain: paint;
  /* Fond BLANC × texture papier — clippé à la forme de la fenêtre par le
     clip-path ci-dessus. La couleur d'accent posée sur .cp-frame ne reste
     donc visible QUE dans la bande de bord (autour de la fenêtre) = zone du
     cadre orné. Le centre garde le blanc/texture d'origine. */
  background-color: var(--white);
  background-image: url('../../data/raw/cards/card-bg-alpha.jpg');
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

/* ═══════════════════════════════════════════════════════════════════
   SLIDER — rail horizontal de N écrans dans la fenêtre masquée
   ───────────────────────────────────────────────────────────────────
   Le rail glisse SOUS l'ornement fixe ; c'est le clip-path de .cf-window
   qui le découpe → illusion de glissement à l'intérieur de la carte.
   Réglages : CARD_SLIDER_CONFIG (card-slider.js).
   ═══════════════════════════════════════════════════════════════════ */

.cs-rail {
  display: flex;
  width: 100%;
  height: 100%;
  will-change: transform;     /* le rail est la seule couche qui bouge */
}
.cs-screen {
  flex: 0 0 100%;             /* 1 écran = toute la largeur de la fenêtre */
  width: 100%;
  height: 100%;
  overflow-y: auto;           /* scroll vertical propre à chaque écran (étape 3) */
  overflow-x: hidden;
  overscroll-behavior: contain;
  /* Masque la scrollbar → aucun décalage à droite quand la liste dépasse. */
  scrollbar-width: none;

  /* Marges de la zone utile — issues du PATH du masque (47/48/47/50 sur
     618×1017), pas du PNG : le masque fait autorité sur la forme.
     Le BAS est élargi pour dégager la barre de dots + les flèches de
     l'ornement (à partir de y≈906) — sinon le dernier élément est mangé.
     Le HAUT reste à la marge du masque : chaque écran gère lui-même la
     réserve de l'étoile (haut-gauche) selon son contenu. */
  padding: 4.72% 7.61% 20% 7.61%;
}
.cs-screen::-webkit-scrollbar { width: 0; height: 0; }

/* ── Chrome de navigation ─────────────────────────────────────────
   Les VISUELS (croix, chevrons, barre de dots) sont dessinés dans l'ornement.
   Ces éléments ne sont donc que des ZONES CLIQUABLES transparentes posées
   pile dessus. Tout est en % du repère natif 618×1017 → suit la taille de la
   carte (grande ou petite) sans recalage.

   Géométrie fournie (px sur 618×1017) :
     close  right 16, top 20,    ⌀68
     back   left  16, bottom 20, ⌀68
     next   right 16, bottom 20, ⌀68
   → 16/618 = 2.59%  ·  20/1017 = 1.97%  ·  ⌀68 = 11.00% de large
   ═══════════════════════════════════════════════════════════════ */
.cs-arrow, .cs-dots, .cf-close, .cf-locate {
  position: absolute;
  z-index: 4;                 /* au-dessus du rail (2) et de l'ornement (0) */
}

/* Les 4 boutons ronds — UN SVG chacun, chargé en MASQUE CSS : l'alpha du
   fichier sert de pochoir, la couleur vient de background-color. Les opacités
   du SVG (0.3 sur les cercles) sont préservées comme niveaux de transparence.
   Le survol ne change QUE la couleur. */
.cs-arrow, .cf-close, .cf-locate {
  position: absolute;
  width: 11%;                 /* ⌀68 / 618 */
  aspect-ratio: 1;
  padding: 0;
  border: none;
  background: transparent;
  cursor: pointer;
  transition: opacity 0.15s;
}
.cs-arrow::before,
.cf-close::before,
.cf-locate::before {
  content: '';
  position: absolute;
  inset: 0;
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  transition: background-color 0.18s ease;
  pointer-events: none;
}
/* ⚠️ UN SEUL SVG PAR BOUTON, COLORISÉ EN CSS — pas deux fichiers par teinte.
   Il existait `close_card_hover.svg` ET `close_card_hover_light.svg` : le MÊME
   dessin, à la couleur près (#313131 vs #fff). Deux fichiers pour une valeur
   CSS, à maintenir en double à chaque retouche du picto.
   (Les 8 fichiers `*_card_hover*.svg` sont rangés dans `data/raw/cards/_unused/` :
   plus référencés, gardés au cas où ce soient des sources à reprendre.)
   Le SVG sert de POCHOIR (mask-image) et la couleur vient de background-color :
   une seule source, autant de teintes qu'on veut.

   ⚠️ Icône CLAIRE au repos : les 4 boutons vivent sur la BANDE DU CADRE (la
   couleur de catégorie), pas dans la fenêtre blanche — c'est le seul endroit de
   la carte où le fond reste foncé, une icône sombre s'y perdrait. */
.cs-arrow::before, .cf-close::before, .cf-locate::before {
  background-color: color-mix(in srgb, var(--white) 82%, transparent);
  opacity: 1;
}
/* Hover : même pochoir, couleur pleine. */
.cs-arrow:not(:disabled):hover::before,
.cf-locate:not(:disabled):hover::before,
.cf-close:hover::before {
  background-color: var(--white);
}
/* ⚠️ Cadre "light" (personnage à l'alignement clair) : SON ornement est blanc,
   pas la bande de catégorie — l'icône blanche s'y efface. Seul cas où le cadre
   n'est pas sombre (voir data-alignment plus haut) ; on repasse en encre sombre. */
.cp-frame[data-alignment="light"] .cs-arrow::before,
.cp-frame[data-alignment="light"] .cf-close::before,
.cp-frame[data-alignment="light"] .cf-locate::before {
  background-color: color-mix(in srgb, var(--grey3) 82%, transparent);
}
.cp-frame[data-alignment="light"] .cs-arrow:not(:disabled):hover::before,
.cp-frame[data-alignment="light"] .cf-locate:not(:disabled):hover::before,
.cp-frame[data-alignment="light"] .cf-close:hover::before {
  background-color: var(--grey3);
}
/* Positions + mask/image par bouton. */
.cf-close  { top: 1.97%; right: 2.59%; }
.cf-locate { top: 1.97%; left:  2.59%; }
.cs-prev   { bottom: 1.97%; left:  2.59%; }
.cs-next   { bottom: 1.97%; right: 2.59%; }

.cf-close::before  { -webkit-mask-image: url('../../data/raw/cards/close_card.svg');    mask-image: url('../../data/raw/cards/close_card.svg'); }
.cf-locate::before { -webkit-mask-image: url('../../data/raw/cards/locate_card.svg');   mask-image: url('../../data/raw/cards/locate_card.svg'); }
.cs-prev::before   { -webkit-mask-image: url('../../data/raw/cards/previous_card.svg'); mask-image: url('../../data/raw/cards/previous_card.svg'); }
.cs-next::before   { -webkit-mask-image: url('../../data/raw/cards/next_card.svg');     mask-image: url('../../data/raw/cards/next_card.svg'); }

.cs-arrow:disabled,
.cf-locate:disabled { opacity: 0.28; cursor: default; }

/* Pastilles de pagination — calées sur la barre dessinée dans l'ornement
   (mesurée : x 227..395, y 956..990 sur 618×1017). */
.cs-dots {
  bottom: 2.45%;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7%;
  width: 27.35%;
  height: 3.44%;
}
.cs-dot {
  width: 7px; height: 7px;
  flex: 0 0 auto;
  padding: 0;
  border: none;
  border-radius: 50%;
  /* Toujours blanches, quelle que soit la carte : elles sont posées sur la barre
     sombre dessinée dans l'ornement, pas sur le fond de carte. */
  background: color-mix(in srgb, var(--white) 30%, transparent);
  cursor: pointer;
  transition: background 0.2s, transform 0.2s;
}
.cs-dot:hover  { background: color-mix(in srgb, var(--white) 65%, transparent); }
.cs-dot.active { background: var(--white); transform: scale(1.3); }
/* Cadre "light" : même raison que les 4 boutons ronds juste au-dessus — la
   barre de pagination se lit sur un ornement blanc, pas sur la bande sombre. */
.cp-frame[data-alignment="light"] .cs-dot {
  background: color-mix(in srgb, var(--grey3) 30%, transparent);
}
.cp-frame[data-alignment="light"] .cs-dot:hover  { background: color-mix(in srgb, var(--grey3) 65%, transparent); }
.cp-frame[data-alignment="light"] .cs-dot.active { background: var(--grey3); }

/* ── Contenu des écrans ───────────────────────────────────────────
   Réutilise au maximum les styles de carte existants (cp-title, cp-rows,
   cp-list, cp-link, cp-section-title, cp-desc…). Seuls les écarts sont
   définis ici. Les couleurs viennent directement des tokens de palette. */

/* ══ RÉPARTITION DES DEUX POLICES (cf. TYPO.fonts, design-tokens.js) ══
   La STRUCTURE reste en Georgia (--font, hérité de .cp-frame) : ce qui NOMME —
   noms, titres, clés de table, labels de section.
   Le CONTENU passe en Inter (--font-alt) : ce qui SE LIT — descriptions,
   valeurs, items de liste, méta.

   ⚠️ Règle posée en un seul endroit plutôt que sélecteur par sélecteur : un
   écran ajouté plus tard hérite du bon régime sans qu'on y pense. Les éléments
   de structure la reprennent explicitement juste après.

   ⚠️ La GRAISSE va avec la police, pas séparément : Inter est nettement plus
   dense que Georgia à graisse égale. Le semibold hérité du serif l'alourdissait
   franchement sur les listes. Tout le contenu passe donc en --fw-light (300). */
.cs-screen .cp-desc,
.cs-screen .cp-list,
.cs-screen .cp-list .cp-link,
.cs-alias {
  font-family: var(--font-alt);
  font-weight: var(--fw-light);
}
/* ⚠️ La méta (l'ANNÉE d'un événement) garde le GRAS — elle est hors du groupe
   ci-dessus, qui l'aurait écrasée en --fw-light par spécificité (3 classes
   contre 1 sur `.cp-meta`). Elle ouvre la ligne : c'est le repère qu'on balaye. */
.cs-screen .cp-list .cp-meta {
  font-family: var(--font-alt);
  font-weight: var(--fw-bold);
}

/* Écran 0 — identité */
.cs-identity { display: flex; flex-direction: column; height: 100%; }

/* Illustration : nom + alias posés dessus.
   Surcharge de .cp-illus (dont l'aspect-ratio 16/9 ne convient pas ici). */
/* Double classe : .cp-illus est défini PLUS BAS dans le fichier et,
   à spécificité égale, la règle la plus tardive l'emporterait — la bordure
   d'accent et l'aspect-ratio 16/9 reprenaient le dessus. */
.cp-illus.cs-illus {
  position: relative;
  /* L'image fait 75 % de la hauteur de la carte ; la table d'attributs occupe le
     quart du bas (card-render.js). Le padding de .cs-screen[identity] est à 0 (plus
     bas) : .cs-identity fait toute la hauteur visible, donc ces 75 % se mesurent sur
     ce qu'on voit. Valeur calée à l'œil — à ne pas « corriger » par un calcul.
     ⚠️ `flex-basis`, PAS `height` : dans un conteneur flex, une hauteur en % est une
     SUGGESTION — le calcul de flex la reprend et la boîte se cale sur son contenu.
     La base flex, elle, fait autorité. */
  flex: 0 0 75%;
  /* Aucune contrainte de ratio : l'image est un BACKGROUND en `cover`, elle se
     recadre d'elle-même quelle que soit la boîte. Le ratio ne servait qu'à
     déformer la hauteur qu'on vient de fixer. */
  aspect-ratio: auto;
  min-height: 0;             /* sinon un flex-item refuse de descendre sous son contenu */
  display: flex;
  align-items: flex-end;
  border: none;              /* pas de liseré d'accent autour de l'image */
  margin-bottom: 0;          /* la table enchaîne directement sous l'image */
}
/* Voile bas pour garantir la lisibilité du nom quelle que soit l'image. */
.cs-illus-caption {
  width: 100%;
  padding: calc(var(--cs-gap) * 2.17) calc(var(--cs-gap) * 1.17) calc(var(--cs-gap) * 1.17);
  /* Dégradé en trois arrêts conservé de la v2 : la légende ne porte plus la table
     (elle est sous l'image depuis le 06/10), mais un titre d'événement peut
     encore tenir sur 3 lignes — l'arrêt médian garde la zone dense sous le texte.
     ⚠️ Calibrer sur une illustration CLAIRE : sur une image sombre tout passe
     (HISTORIQUE.md § Chantier Cartes v2 › Risques du gabarit (lisibilité du dégradé)). */
  background: linear-gradient(to top,
    var(--black) 0%,                /* opaque : se fond dans le fond noir de la table */
    color-mix(in srgb, var(--black) 78%, transparent) 45%,
    transparent 100%);
  text-align: center;
}
.cs-illus-caption .cp-title {
  color: var(--white);
  text-align: center;
  padding-bottom: calc(var(--cs-gap) * 0.83);
  font-size: var(--t6);  /* nom perso : gros titre */
  line-height: 0.9;          /* serré : compact quand le nom passe sur 2 lignes */
  text-shadow: var(--shadow-title);
}
/* ⚠️ ÉVÉNEMENTS : le titre est une PHRASE, pas un nom propre.
   « Frodo stabbed by a Morgul-knife below Weathertop » à --fs-6 (44px) déborde
   l'illustration et mange toute la carte. Le gabarit est calibré pour
   « Gandalf » ou « Minas Tirith » — un événement demande sa propre échelle.
   `data-card-type` est posé par wrapInFrame (card-render.js). */
/* ⚠️ Une bataille EST un événement : les deux règles restent identiques.
   Le titre garde un cran de moins que le hero (--t6) — c'est une PHRASE de
   59 caractères en moyenne, pas un nom propre : à la taille d'un nom il
   déborde l'illustration et mange la carte. */
.cp-frame[data-card-type="battle"] .cs-illus-caption .cp-title,
.cp-frame[data-card-type="event"]  .cs-illus-caption .cp-title {
  /* --t4 et non --t5 : à 24px, « Isildur's youngest, and only surviving son,
     Valandil… » (110 car., le plus long de la base) prenait 5 lignes et poussait
     le bloc jusqu'au milieu de la carte — l'illustration disparaissait derrière
     son propre libellé (HISTORIQUE.md, chantier cartes v2). */
  font-size: var(--t4);
  line-height: 1.15;        /* desserré : une phrase se lit sur 3-4 lignes */
  /* ⚠️ MARGIN, PAS PADDING — l'écart doit être HORS de la boîte clampée.
     `-webkit-line-clamp` coupe à la hauteur de N lignes ; un padding intérieur
     s'ajoute DANS cette hauteur et rogne la dernière ligne par le milieu (la
     3e ligne apparaissait tranchée, à moitié cachée derrière la table).
     En marge, l'écart se pose après la coupe : les 3 lignes restent entières. */
  padding-bottom: 0;
  margin-bottom: calc(var(--cs-gap) * 0.5);
  /* ⚠️ PLAFOND À 3 LIGNES — la vraie sécurité. Réduire le corps ne fait que
     déplacer le seuil : il y aura toujours un titre plus long. Au-delà de
     3 lignes le texte est coupé par une ellipse ; le titre complet reste lisible
     en entier sur l'écran Description, comme les valeurs tronquées de la table.
     `-webkit-line-clamp` est le seul mécanisme multi-lignes supporté partout —
     il exige les trois déclarations ensemble, sinon il ne s'applique pas. */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* Badge de catégorie / alignement — centré juste au-dessus du titre.
   Rendu blanc via mask + background-color (le SVG sert de pochoir). */
.cs-badge {
  width: var(--cs-badge);
  aspect-ratio: 1;
  /* ⚠️ Marge basse POSITIVE : elle était négative (-0.33), ce qui remontait le
     titre sur le badge. Elle suit --cs-gap, donc l'écart se conserve quelle que
     soit la taille de la carte. */
  margin: 0 auto var(--cs-gap);
  background-color: var(--white);
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  filter: drop-shadow(0 2px 6px color-mix(in srgb, var(--black) 55%, transparent));
}
/* 2e ligne : les alias, sous le nom. */
.cs-alias {
  margin: calc(var(--cs-gap) * 0.25) 0 calc(var(--cs-gap) * 0.83);
  font-size: var(--t1);
  font-family: var(--font-alt);
  font-style: var(--style-italic);
  letter-spacing: var(--ls-2);
  color: color-mix(in srgb, var(--white) 80%, transparent);
  text-shadow: var(--shadow-text);
}

/* 3e ligne optionnelle sous le nom, portée par `data.tagline`.
   ⚠️ PLUS AUCUNE SOURCE NE LA FOURNIT depuis le 26/08 : la galerie s'en servait
   pour « Race · Foyer » quand la table d'attributs était masquée, la table est
   revenue et le dirait deux fois (HISTORIQUE.md § Chantier Cartes v2 › Méthode et réglages). Le style et son
   emplacement dans `screenIdentity` sont conservés : c'est un emplacement libre
   pour une ligne d'identité, si un type en demande une un jour. */
.cs-tagline {
  margin: calc(var(--cs-gap) * 0.25) 0 0;
  font-size: var(--t2);
  font-family: var(--font-alt);
  letter-spacing: var(--ls-2);
  color: color-mix(in srgb, var(--white) 88%, transparent);
  text-shadow: var(--shadow-text);
}

/* ── ÉCRAN D'IDENTITÉ — LA COUVERTURE, TOUS TYPES, TOUS CHEMINS ───
   Même gabarit dans la pile galerie et sur le terrain (une entité = une carte).
   Image à 75 % de la hauteur avec badge + titre + alias dessus, table d'attributs
   dans le quart du bas, fond noir (06/10/2026). Le padding de .cs-screen est mis à
   zéro sur CET écran.
   ⚠️ Ce padding n'est pas une marge esthétique : c'est la réserve du bas (20%)
   qui dégageait la barre de dots et les flèches DESSINÉES DANS L'ORNEMENT. Sur la
   couverture, la TABLE reprend cette réserve (voir plus bas).
   Aucun risque de débordement : .cf-window porte le clip-path du masque, donc
   l'image est découpée à la silhouette exacte de la carte.
   Sélecteur par data-screen-id : les écrans liste et description gardent leur
   réserve, sinon leur dernière ligne passerait sous le chrome de l'ornement. */
.cs-screen[data-screen-id="identity"] {
  padding: 0;
  /* 06/10 : la COUVERTURE est sur fond noir, les autres écrans gardent le
     blanc/papier de .cf-window. Le noir se pose ICI (et non sur .cf-window) : il
     suit l'écran qui glisse. Le dégradé de la légende part de --black opaque. */
  background: var(--black);
}
/* La TABLE D'ATTRIBUTS PORTE la réserve du bas, puisque .cs-screen n'en a plus.
   ⚠️ Les % se réfèrent à la LARGEUR de la carte (règle CSS du padding), pas à
   sa hauteur — 13.27% de 312px valent 41px, pas 68. Ne pas les lire comme une
   fraction de la hauteur.
   La valeur POSE l'écart entre la dernière ligne et la barre « VOIR PLUS »
   dessinée dans l'ornement. 16% = ~30px, réglé à l'œil par Mathieu (26/08, quand
   cette réserve était portée par la légende) — à recaler à l'œil maintenant que
   le quart du bas ne contient plus que la table.
   La légende, elle, ne garde qu'un petit retrait : elle finit sur l'image. */
.cs-illus-caption {
  padding: calc(var(--cs-gap) * 2.17)
           calc(7.61%  + var(--cs-gap) * 1.17)
           calc(var(--cs-gap) * 1.17);
}
/* Le quart du bas : la table, sous l'image. `flex: 1 1 0` + `min-height: 0` la
   font tenir dans ce qui reste de .cs-identity (jamais plus) ; l'excédent est
   coupé plutôt que de pousser l'image. Texte sombre sur le blanc de .cf-window
   (règles `.cf-window .cs-attrs` plus bas). */
.cs-identity > .cp-rows.cs-attrs {
  flex: 1 1 0;
  min-height: 0;
  overflow: hidden;
  align-content: center;       /* 3-4 lignes centrées dans le quart, pas collées en haut */
  /* Mêmes marges que les écrans suivants (.cs-screen : 4.72% 7.61% 20%), plus le
     retrait de texte de la table (--cs-gutter) — comme .cp-desc / .cp-list. Posé
     ici parce que .cs-screen[identity] est à padding 0. Les % = LARGEUR de la carte. */
  padding: 4.72% calc(7.61% + var(--cs-gutter) * 1.07) 20%;
}
/* 06/10 — typo de la table en BLANC (la couverture est sur fond noir).
   Valeur : blanc plein. Clé : --grey2 (le médian tient les deux fonds, comme en v2).
   Séparateurs : pointillé clair. Sélecteurs à 4 classes pour l'emporter sur
   `.cf-window .cs-attrs li > …` (bordures sombres, plus haut). Les autres écrans,
   sur blanc, ne sont pas touchés. */
.cs-identity > .cp-rows.cs-attrs .cs-key { color: var(--grey2); }
.cs-identity > .cp-rows.cs-attrs .cs-val { color: var(--white); }
.cs-identity > .cp-rows.cs-attrs .cs-val-link:hover { color: var(--grey1); }
.cs-identity > .cp-rows.cs-attrs li > .cs-key,
.cs-identity > .cp-rows.cs-attrs li > .cs-val {
  border-bottom-color: color-mix(in srgb, var(--white) 22%, transparent);
  /* Interligne un peu plus aéré sur la couverture (base : 0.08 sur les autres écrans). */
  padding: calc(var(--cs-gap) * 0.2) 0;
}

/* ── LA COUCHE QUI PORTE L'IMAGE ──────────────────────────────────
   ⚠️ CE ::before N'EST PAS DÉCORATIF — il PEINT l'illustration. Le supprimer
   laisserait la carte sans image.
   `background-image: inherit` reprend les couches posées en style inline par
   screenIdentity (illustration réelle EMPILÉE sur le placeholder de famille) —
   pas de second chemin d'URL à tenir à jour ici.
   Il existe séparément de .cs-illus parce que cette boîte CONTIENT la légende :
   tout effet appliqué à elle emmènerait le texte avec lui.
   Aucun overflow:hidden nécessaire : .cf-window porte le clip-path du masque,
   l'image débordante est donc coupée à la silhouette de la carte.
   🔄 **Zoom au survol RETIRÉ le 26/08** (demande de Mathieu) : l'image grossissait
   de 10% au survol d'une carte de pile. `transform` et sa transition sont partis
   avec lui — ils n'existaient que pour cet effet. */
.cp-illus.cs-illus::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  background-image: inherit;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}
/* ⚠️ La légende doit être POSITIONNÉE pour repasser au-dessus. Le ::before est
   en position:absolute, donc peint au-dessus de tout frère NON positionné —
   sans ce z-index, l'image recouvrirait le nom. */
.cs-illus-caption {
  position: relative;
  z-index: 1;
}
/* Table d'attributs : 2 colonnes clé/valeur.
   Double classe : .cp-rows est défini plus bas dans le fichier et l'emporterait
   à spécificité égale (fond sombre, couleur figée). */
/* ⚠️ GRILLE, PAS FLEX — c'est ce qui donne une VRAIE colonne.
   Chaque <li> en flex est un contexte INDÉPENDANT : les clés ne se voient pas
   entre elles, donc la seule façon de les aligner était une largeur fixe
   (l'ancien --cs-keyCol, 80px) — arbitraire, et fausse dès que le libellé
   changeait : trop large elle laissait un vide, trop étroite elle tronquait.
   En grille posée sur la LISTE, `max-content` mesure la plus longue clé et
   toutes s'y alignent. La colonne connaît enfin son contenu. */
.cp-rows.cs-attrs {
  flex: 0 0 auto;
  display: grid;
  /* Colonne 1 = la plus longue CLÉ, colonne 2 = tout le reste.
     ⚠️ Ne pas envelopper `max-content` dans un min()/minmax() calculé : le
     navigateur y perd la mesure par colonne et rabat toute la grille sur une
     seule piste — clé et valeur passaient alors l'une SOUS l'autre. C'est
     `auto` qui donne le bon comportement ici : il se cale sur le contenu de SA
     colonne tout en acceptant de céder si la place manque. */
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: var(--cs-gap);
  /* ⚠️ CENTRAGE VERTICAL des deux cellules d'une même ligne. Le défaut
     (`stretch`, qui retombe sur la ligne de base du texte) ne convient pas ici :
     la clé est en --t1 et sa valeur en --t3, deux corps très différents. Alignés
     par la base, le petit label paraissait « posé » sous sa valeur au lieu de
     lui faire face. */
  align-items: center;
  background: none;            /* pas de fond : le texte pose sur la carte */
  border-radius: 0;
  padding: 0 calc(var(--cs-gutter) * 1.07);  /* padding gauche/droite seulement */
  color: var(--grey3);           /* texte sombre sur la fenêtre blanche */
  line-height: 1.35;           /* .cp-rows est à 1.7 → trop aéré ici */
}
/* Le <li> ne met plus ses enfants en page : il s'efface et laisse la clé et la
   valeur se poser directement dans la grille du parent (`display: contents`).
   Sans ça, chaque li resterait une boîte et rien ne s'alignerait d'une ligne
   à l'autre. */
/* ⚠️ SÉLECTEUR DOUBLÉ (.cp-rows.cs-attrs li) — obligatoire.
   `.cp-rows li` est déclaré PLUS BAS dans ce fichier : à spécificité égale la
   règle la plus tardive gagne, elle réimposait donc `display: list-item` et
   annulait le `contents` ci-dessous — les cellules retombaient en liste
   empilée au lieu de rejoindre la grille. Même piège que .cp-illus/.cp-rows
   plus haut. */
.cp-rows.cs-attrs li {
  display: contents;
}
/* ⚠️ Le SÉPARATEUR passe sur les cellules, pas sur le li : avec
   `display: contents` le li n'a plus de boîte, donc plus de bordure possible.
   Les deux cellules le portent ensemble → le trait traverse toute la ligne. */
.cp-rows.cs-attrs li > .cs-key,
.cp-rows.cs-attrs li > .cs-val {
  padding: calc(var(--cs-gap) * 0.08) 0;   /* lignes resserrées */
  border-bottom: 1px dotted color-mix(in srgb, var(--black) 28%, transparent);
}
.cs-key {
  font-family: var(--font);    /* STRUCTURE : la clé NOMME le champ */
  /* Le label est un REPÈRE, pas une information : plus petit cran de l'échelle
     + capitales. Il se distingue de sa valeur par la FORME, plus par la taille —
     à taille égale les deux colonnes se disputaient l'attention. */
  font-size: var(--t2);
  color: var(--grey2);         /* le label s'efface derrière sa valeur */
  font-weight: var(--fw-regular);
  letter-spacing: var(--ls-3);
  text-transform: var(--style-upper);
  opacity: 1;
}
.cs-val {
  font-family: var(--font-alt);   /* CONTENU : la valeur est de la donnée */
  min-width: 0;                /* ⚠️ sans ça une cellule de grille refuse de
                                  rétrécir sous la largeur de son contenu :
                                  l'ellipsis ne se déclenche jamais et la valeur
                                  déborde (même piège qu'en flex). */
  text-align: left;            /* colonne de droite alignée à gauche */
  color: var(--black);         /* la valeur porte l'information : contraste plein */
  font-size: var(--t4);
  font-weight: var(--fw-light);   /* Inter : 300, cf. règle de contenu plus haut */
  text-transform: capitalize;  /* forcer majuscule initiale (light → Light) */
  /* UNE SEULE LIGNE par entrée : les valeurs du lore sont des énumérations qui
     peuvent faire 4 lignes (les 5 titres d'Aragorn, les 5 langues de Galadriel)
     et faire déborder la table sous l'illustration. Le texte complet reste
     accessible au survol (title) et sur l'écran Description. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Valeurs venant du lore (titres, langues, souverains…) : déjà capitalisées à la
   source. Le `capitalize` ci-dessus est pensé pour un mot-clé isolé ; sur une
   énumération il majusculerait CHAQUE mot (« King Of Gondor And Arnor »). */
.cs-val.cs-val-lore { text-transform: none; }

/* Valeur CLIQUABLE d'une ligne d'attribut (Foyer d'un personnage, Région d'un
   lieu). C'est un <button> qui EST la cellule .cs-val — il en reprend donc
   taille, police et couleur, et ne s'en distingue que par le GRAS.
   Le reset ci-dessous efface l'apparence native du bouton, rien de plus. */
.cs-val-link {
  appearance: none;
  padding: 0;
  margin: 0;
  background: none;
  border: 0;
  font-weight: var(--fw-bold);
  text-decoration: none;
  cursor: pointer;
}
.cs-val-link:hover { color: var(--grey2); }
/* Le contenu INTÉRIEUR du cadre reste toujours en --grey3, quelle que soit la
   carte : le centre du cadre est blanc en permanence (cf. .cf-window). */
.cf-window .cs-name,
.cf-window .cs-screen .cp-section-title,
.cf-window .cs-screen .cp-desc,
.cf-window .cs-screen .cp-list,
.cf-window .cs-screen .cp-list li,
.cf-window .cs-screen .cp-list .cp-link,
.cf-window .cs-screen .cp-list .cp-meta { color: var(--grey3); }
/* Le fond hover conserve ses couleurs custom (bg noir + texte blanc). */
.cf-window .cs-screen .cp-list .cp-link:hover,
.cf-window .cs-screen .cp-list .cp-link:hover * { color: var(--white); }
/* Exception : la vignette garde son initiale lisible (le `*` ci-dessus la
   passerait en blanc sur un cercle clair). */
.cf-window .cs-screen .cp-list .cp-link:hover .cp-avatar { color: color-mix(in srgb, var(--black) 62%, transparent); }

/* Séparateurs intérieurs : forcés sombres (fond blanc constant à l'intérieur). */
.cf-window .cs-header-sep { background-color: var(--grey3); }
.cf-window .cs-screen .cp-list li { border-bottom-color: color-mix(in srgb, var(--black) 15%, transparent); }
/* ⚠️ Sur les CELLULES, pas sur le li : `display: contents` lui a retiré sa
   boîte, donc une bordure posée dessus ne se dessine plus. */
.cf-window .cs-attrs li > .cs-key,
.cf-window .cs-attrs li > .cs-val { border-bottom-color: color-mix(in srgb, var(--black) 15%, transparent); }

/* ── Écrans 1-3 (listes, description) ─────────────────────────────
   Structure commune : nom du personnage + label de section + corps. */

/* Le nom. Retrait à gauche pour dégager l'étoile de l'ornement (haut-gauche,
   ≈14% de la carte) : le titre passait dessous. Le slide 0 n'en a pas besoin,
   son illustration occupe toute la largeur. */
.cs-name {
  margin: calc(var(--cs-gap) * 2.5) 0 calc(var(--cs-gap) * 0.17);
  padding: 0 8% calc(var(--cs-gap) * 1.67);  /* symétrique : dégage l'étoile sans décentrer */
  text-align: center;
  color: var(--grey3);
  font-size: var(--t5);
}
/* Même raison que pour le titre sur l'illustration : le nom repris en tête des
   écrans secondaires est une phrase entière sur une carte d'événement. */
.cp-frame[data-card-type="battle"] .cs-name,
.cp-frame[data-card-type="event"]  .cs-name {
  font-size: var(--t4);
  line-height: 1.25;
}

/* Séparateur ornemental sous le titre des slides secondaires — SVG en mask
   → couleur = background-color (--grey3). viewBox 505.3×8 (ratio ~63:1). */
.cs-header-sep {
  width: 84%;
  aspect-ratio: 505.3 / 8;
  margin: 0 auto calc(var(--cs-gap) * 1.33);
  background-color: var(--grey3);
  -webkit-mask-image: url('../../data/raw/cards/separateur.svg');
  mask-image: url('../../data/raw/cards/separateur.svg');
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  opacity: 0.65;
}

/* Le label de section (Description, Événements, Objets…). Reprend
   .cp-section-title mais retire uppercase + filet vertical + letter-spacing :
   ici --font (Georgia) bold, casse naturelle (data JS déjà capitalisée). */
.cs-screen .cp-section-title {
  margin: 0 0 calc(var(--cs-gap) * 1.17);
  padding: 0 var(--cs-gutter);
  font-family: var(--font);    /* STRUCTURE : le label NOMME la section */
  border-left: none;
  color: color-mix(in srgb, var(--black) 62%, transparent);
  text-transform: none;
  letter-spacing: 0;
  font-size: var(--t4);
  font-weight: var(--fw-bold);
}

/* La description longue (écran 2) — CONTENU, donc --font-alt (Inter). */
.cs-screen .cp-desc {
  margin-top: 0;
  padding: 0 var(--cs-gutter);
  font-size: var(--t3);
  font-weight: var(--fw-light);
  color: var(--grey3);
}

/* Les listes — même taille/graisse que les valeurs de l'écran Description (--t3 / --fw-light). */
/* Padding retiré du ul → li (donc séparateurs et fond hover) spannent 100%.
   Les 15px reviennent uniquement sur le bouton .cp-link : le texte garde
   sa position d'origine. */
.cs-screen .cp-list { padding: 0; color: var(--grey3); font-size: var(--t3); font-weight: var(--fw-light); }
/* Items : fine ligne 1px entre chaque, dernier sans ligne.
   Padding retiré du li → migré vers .cp-link, pour que le fond de survol
   remplisse toute la hauteur entre les 2 lignes de séparation. */
.cs-screen .cp-list li {
  border-bottom: 1px solid color-mix(in srgb, var(--black) 28%, transparent);
  padding: 0;
}
.cs-screen .cp-list li + li { margin-top: 0; }
.cs-screen .cp-list li:last-child { border-bottom: none; }
/* Padding vertical réel sur les boutons de lien (typo héritée : --font-alt).
   Padding horizontal 15px = ancienne inset du ul, pour que le texte garde sa
   position pendant que le fond hover s'étend à 100% de la largeur. */
.cs-screen .cp-list .cp-link {
  font-size: var(--t3);
  font-weight: var(--fw-light);
  padding: calc(var(--cs-gap) * 0.67) var(--cs-gutter);
  border: 0;
  border-radius: 0;
  /* Vignette + nom alignés à GAUCHE sur une ligne. `width:100%` + text-align
     pour que le fond de survol couvre toute la largeur, comme avant. */
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: calc(var(--cs-gap) * 0.83);
  width: 100%;
  text-align: left;
}
/* Items non cliquables : même alignement, l'inset du gutter vient du li. */
.cs-screen .cp-list li { display: flex; align-items: center; }
.cs-screen .cp-list li > .cp-avatar { margin-left: var(--cs-gutter); }
/* Le libellé hérite la typo du lien — ne rien redéfinir ici,
   sinon on désynchronise les items cliquables et non cliquables. */
.cp-link-label { min-width: 0; flex: 1 1 auto; }

/* ── Vignette ronde d'une entité dans une liste ──────────────────
   L'image est un BACKGROUND : si l'illustration n'existe pas (≈40 fichiers pour
   ~900 personnages), rien ne casse — le cercle et son initiale restent visibles.
   Repli silencieux, même principe que les icônes PNG par type de lieu.
   ⚠️ L'image est posée en PREMIER plan de la pile de backgrounds, la couleur en
   dernier : quand le fichier existe il recouvre l'initiale (qui est du texte,
   donc dessous) ; quand il manque, seule la couleur et l'initiale subsistent. */
.cp-avatar {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background-size: cover;
  background-position: center 22%;   /* défaut : portraits, visage en haut */
  background-repeat: no-repeat;
  background-color: color-mix(in srgb, var(--black) 28%, transparent);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--t1);
  font-weight: var(--fw-bold);
  line-height: 1;
  color: color-mix(in srgb, var(--black) 62%, transparent);
  overflow: hidden;
  /* L'initiale est du TEXTE : elle se dessine au-dessus des backgrounds. Elle
     n'est donc visible que par transparence — en pratique masquée dès qu'une
     image (réelle ou placeholder) couvre le cercle. On la garde comme ultime
     repli si même le placeholder manque. */
  position: relative;
}
/* Cadrage selon la nature du visuel : un portrait 16:9 met le visage dans le
   tiers haut, un objet est composé au centre. Un cadrage unique couperait
   soit les têtes, soit les objets. */
.cp-avatar[data-kind="item"] { background-position: center center; }
/* ⚠️ Scopé à .cp-frame : .cp-avatar sert AUSSI hors des cartes (liste de hub),
   où la taille en px est légitime — ces éléments-là ne vivent pas dans un
   châssis qui change de taille. Seule la vignette EN CARTE suit l'échelle. */
.cp-frame .cp-avatar {
  width: var(--cs-avatar);
  height: var(--cs-avatar);
  font-size: var(--t1);
}

/* Picto de section : mask SVG → couleur = currentColor (suit le texte). */
.cs-section-picto {
  display: inline-block;
  width: 1em;
  height: 1em;
  margin-right: 8px;
  vertical-align: -0.15em;
  background-color: currentColor;
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}
.cs-screen .cp-link { color: var(--grey3); }
.cs-screen .cp-link:hover { background: var(--grey3); color: var(--white); }
.cs-screen .cp-link:hover * { color: var(--white); }

/* ── Contenu carte ─────────────────────────────────────────────── */
/* ══ Carte : UNE texture neutre (card-bg-alpha.jpg) × couleur de catégorie ══
   Le fond = --accent (couleur dynamique, posée par le JS depuis CATEGORY_COLORS)
   mélangée à la texture via blend-mode :
   Toujours en `multiply` (le grain assombrit la couleur de catégorie).
   Plus de fichier par type/entité : 1 seule texture, la couleur fait le reste. */
.cp-frame {
  --accent: var(--gold);
  width: 100%; height: 100%;
  /* Padding du châssis SANS ornement (has-frame le remet à 0) — proportionnel
     comme le reste : à taille réduite, 39/40px mangeaient la moitié de la carte. */
  padding: calc(var(--cs-gap) * 3.25) calc(var(--cs-gap) * 3.33);
  color: var(--grey3);
  font-family: var(--font);
  display: flex; flex-direction: column;
  overflow: visible;
  position: relative;
  border-radius: 22px;
  box-shadow: 0 12px 32px color-mix(in srgb, var(--black) 55%, transparent), 0 3px 10px color-mix(in srgb, var(--black) 40%, transparent);
  background-color: var(--accent);
  background-image: url('../../data/raw/cards/card-bg-alpha.jpg');
  background-size: 100% 100%;
  background-repeat: no-repeat;
}
/* Toujours multiply — cohérent avec .cp-frame.has-frame (l'overlay éclaircissait
   les teintes moyennes et faussait la couleur d'accent). */
.cp-frame { background-blend-mode: multiply; }

/* Illustration — aplat --grey2 (ou image de fond via .has-illus en JS) */
.cp-illus {
  flex: 0 0 auto;
  aspect-ratio: 16 / 9;
  background: var(--grey2);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  border: 2px solid var(--accent);
  margin-bottom: 14px;
}
.cp-rows {
  list-style: none;
  padding: 12px 16px; margin: 0 0 4px 0;
  background: color-mix(in srgb, var(--black) 28%, transparent);
  border-radius: 6px;
  color: var(--grey3);
  font-size: var(--t2);
  line-height: 1.7;
}
.cp-rows li {
  border-bottom: 1px dotted color-mix(in srgb, var(--black) 28%, transparent);
  padding: 3px 0;
}
.cp-rows li:last-child { border-bottom: none; }
/* Description en texte brut */
.cp-desc {
  font-size: var(--t2);
  line-height: 1.55;
  color: var(--grey3);
  margin-top: 14px;
  white-space: pre-wrap;
}

.cp-section-title {
  font-size: var(--t2);
  letter-spacing: var(--ls-3);
  color: var(--grey3);
  text-transform: uppercase;
  margin-bottom: 10px;
  font-weight: var(--fw-bold);
  border-left: 4px solid color-mix(in srgb, var(--black) 28%, transparent);
  padding-left: 10px;
}
.cp-list {
  list-style: none;
  padding: 0; margin: 0;
  font-size: var(--t2);
  line-height: 1.5;
  color: var(--grey3);
}
.cp-meta {
  color: inherit;                /* suit la couleur du texte */
  font-weight: var(--fw-bold);
  flex-shrink: 0;
}
.cp-more {
  color: var(--grey3);
  font-style: italic;
  font-size: var(--t1);
  padding-top: 6px;
}

/* ═══════════════════════════════════════════════════════════════════
   FLOATING CARD — cartes draggables (spawnées au clic sur un lien)
   ═══════════════════════════════════════════════════════════════════ */
.floating-card {
  position: fixed;
  pointer-events: auto;
  perspective: 1400px;
  z-index: 40;
  /* ⚠️ LA HAUTEUR DÉRIVE DE LA LARGEUR (26/08). Elle était posée en PIXELS par
     le JS au spawn : la largeur, elle, est une expression `clamp(...vh...)` que
     le navigateur réévalue seul. La carte s'étirait donc en largeur au
     redimensionnement de la fenêtre, mais gardait la hauteur du jour de sa
     création — le châssis se déformait.
     Même mécanique que #cp-viewport, qui n'a jamais eu le problème. */
  aspect-ratio: 618 / 1017;
  /* Valeurs de repos explicites : une transition ne peut pas partir d'une
     propriété jamais déclarée. */
  opacity: 1;
  translate: 0 0;
  /* ⚠️ `scale` FIGURE DANS LA LISTE (chantier v2) — sans elle, une carte qui
     naît à l'échelle de la pile saute à sa taille finale pendant que le
     déplacement se déroule : le glissement paraît finir avant d'avoir commencé.
     Même piège que `translate` en galerie, documenté dans COMPONENTS.md. */
  scale: 1;
  transition: box-shadow 0.15s,
              opacity   var(--fc-in, 620ms) var(--fc-ease, ease),
              translate var(--fc-in, 620ms) var(--fc-ease, ease),
              scale     var(--fc-in, 620ms) var(--fc-ease, ease);
}
/* Apparition / disparition — même geste que les cartes de la galerie : fondu +
   léger déplacement vertical.
   ⚠️ L'animation passe par `translate` et NON par left/top : ceux-ci portent la
   position de la carte et sont réécrits pendant le drag. Les animer ferait
   sauter la carte au moment où on l'attrape. */
.floating-card.fc-in {
  opacity: 0;
  translate: 0 var(--fc-rise, 46px);
}
.floating-card.fc-out {
  opacity: 0;
  translate: 0 calc(var(--fc-rise, 46px) * -0.55);
  transition: opacity   var(--fc-out, 420ms) var(--fc-ease, ease),
              translate var(--fc-out, 420ms) var(--fc-ease, ease);
}
/* Pendant le drag, aucune transition : la carte doit coller au curseur. */
.floating-card.dragging {
  user-select: none;
  transition: none;
}
/* ── DRAG : toute la carte s'attrape ──────────────────────────────
   Plus de poignée dédiée (`.fc-drag-handle`, supprimée) : c'était une zone
   invisible qu'il fallait deviner, ou du chrome de fenêtre au-dessus d'un objet
   du monde. Le curseur porte l'affordance sur toute la surface.
   ⚠️ Les éléments interactifs et les écrans scrollables gardent leur curseur
   propre — la liste NO_DRAG de floating-card.js et ces règles doivent rester
   cohérentes. */
/* ⚠️ PAS DE SÉLECTION DE TEXTE — sinon deux gestes se disputent le même
   mouvement : partir d'un mot lançait à la fois une sélection et un drag.
   La carte est un OBJET qu'on déplace, pas un document dont on copie le texte.
   Aucune exception : une zone sélectionnable au milieu d'une carte draggable
   rendrait le geste imprévisible selon l'endroit où l'on appuie. */
/* ⚠️ `touch-action: none` — au tactile, sans lui le navigateur prend le début
   d'un geste de pan pour un défilement et émet `pointercancel` : le drag est tué.
   Le défilement des listes n'est pas touché : `.cs-screen` est lui-même un
   conteneur scrollable (overflow-y: auto), et le touch-action d'un ancêtre
   situé AU-DELÀ du conteneur qui défile ne s'applique pas à son pan. */
.floating-card { cursor: grab; user-select: none; -webkit-user-select: none; touch-action: none; }
.floating-card.dragging { cursor: grabbing; }
.floating-card button,
.floating-card a,
.floating-card [data-action] { cursor: pointer; }
.fc-viewport {
  width: 100%; height: 100%;
  perspective: 1400px;
}
.fc-flip {
  position: relative;
  width: 100%; height: 100%;
  transform-style: preserve-3d;
  transition: transform 0.55s cubic-bezier(0.4, 0.0, 0.2, 1);
}

/* Boutons de lien dans les listes (persos/events/…) */
.cp-link {
  width: 100%;
  display: flex; justify-content: space-between; align-items: baseline; gap: 8px;
  padding: 5px 8px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 3px;
  color: var(--grey3);
  font: inherit;
  font-size: var(--t2);
  font-weight: var(--fw-bold);
  text-align: left;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.cp-link:hover {
  background: color-mix(in srgb, var(--black) 28%, transparent);
  border-color: transparent;
  color: var(--grey3);
}
/* Item de liste : layout par défaut (texte nu) ; .cp-link (bouton) le surcharge */
.cp-list li {
  display: flex; justify-content: space-between; align-items: baseline; gap: 8px;
  padding: 4px 8px;
  color: var(--grey3);
}
.cp-list li + li { margin-top: 2px; }
.cp-list li:has(.cp-link) { padding: 0; }   /* le bouton gère son propre padding */

/* ═══════════════════════════════════════════════════════════════════
   INTRO LOADER — écran de chargement puis fade-out
   ═══════════════════════════════════════════════════════════════════ */
#intro-loader {
  position: fixed;
  inset: 0;
  background: var(--black);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 9999;
  transition: opacity 0.8s ease-out;
  pointer-events: all;
}
#intro-loader.fade-out {
  opacity: 0;
  pointer-events: none;
}
/* hide-inner : cache titre/spinner/hint mais garde le fond noir opaque (hold visuel) */
#intro-loader.hide-inner .il-inner {
  opacity: 0;
  transition: opacity 0.8s ease-out;
}
#intro-loader .il-inner {
  transition: opacity 0.8s ease-out;
}
#intro-loader .il-inner {
  text-align: center;
  color: var(--grey1);
}
#intro-loader .il-title {
  font-family: var(--font);
  font-size: var(--t6);
  letter-spacing: var(--ls-5);
  color: var(--gold-light);
  text-shadow: 0 0 32px var(--gold);
  margin-bottom: 32px;
}
#intro-loader .il-spinner {
  width: 46px;
  height: 46px;
  border: 2px solid color-mix(in srgb, var(--gold-light) 15%, transparent);
  border-top-color: var(--gold);
  border-radius: 50%;
  margin: 0 auto 20px;
  animation: il-spin 1s linear infinite;
}
#intro-loader .il-hint {
  font-family: var(--font);
  font-size: var(--t2);
  letter-spacing: var(--ls-4);
  text-transform: uppercase;
  opacity: 0.55;
}
@keyframes il-spin { to { transform: rotate(360deg); } }
