/* ══════════════════════════════════════════════════════════
   SELF-HOSTED FONTS — fonts/ (WOFF2, latin subset, all SIL OFL,
   see fonts/LICENSES). No CDN: the app stays fully offline.
   ══════════════════════════════════════════════════════════ */
@font-face{font-family:'Space Grotesk';src:url('fonts/space-grotesk-var.woff2') format('woff2');font-weight:300 700;font-display:swap;}
@font-face{font-family:'Inter';src:url('fonts/inter-var.woff2') format('woff2');font-weight:100 900;font-display:swap;}
@font-face{font-family:'Chakra Petch';src:url('fonts/chakra-petch-400.woff2') format('woff2');font-weight:400;font-display:swap;}
@font-face{font-family:'Chakra Petch';src:url('fonts/chakra-petch-600.woff2') format('woff2');font-weight:600;font-display:swap;}
@font-face{font-family:'Chakra Petch';src:url('fonts/chakra-petch-700.woff2') format('woff2');font-weight:700;font-display:swap;}
@font-face{font-family:'IBM Plex Sans';src:url('fonts/ibm-plex-sans-var.woff2') format('woff2');font-weight:100 700;font-display:swap;}
@font-face{font-family:'Fraunces';src:url('fonts/fraunces-var.woff2') format('woff2');font-weight:100 900;font-display:swap;}
@font-face{font-family:'Source Sans 3';src:url('fonts/source-sans-3-var.woff2') format('woff2');font-weight:200 900;font-display:swap;}
@font-face{font-family:'Manrope';src:url('fonts/manrope-var.woff2') format('woff2');font-weight:200 800;font-display:swap;}
@font-face{font-family:'Syne';src:url('fonts/syne-var.woff2') format('woff2');font-weight:400 800;font-display:swap;}
@font-face{font-family:'DM Sans';src:url('fonts/dm-sans-var.woff2') format('woff2');font-weight:100 1000;font-display:swap;}
@font-face{font-family:'Racing Sans One';src:url('fonts/racing-sans-one-400.woff2') format('woff2');font-weight:400;font-display:swap;}
@font-face{font-family:'Orbitron';src:url('fonts/orbitron-var.woff2') format('woff2');font-weight:400 900;font-display:swap;}

/* ── Font themes: --font-d (display) / --font-b (body), selected via
   data-font on <html> (Settings), persisted as f1uno_font.
   Driver numbers (.dn-num / .dn-*) keep their own identity fonts and
   are NOT affected by these variables. ── */
:root{--font-d:'Space Grotesk',sans-serif;--font-b:'Inter',sans-serif;}
:root[data-font="sprint"]{--font-d:'Chakra Petch',sans-serif;--font-b:'IBM Plex Sans',sans-serif;}
:root[data-font="prestige"]{--font-d:'Fraunces',serif;--font-b:'Source Sans 3',sans-serif;}
:root[data-font="minimal"]{--font-d:'Manrope',sans-serif;--font-b:'Manrope',sans-serif;}
:root[data-font="original"]{--font-d:'Syne',sans-serif;--font-b:'DM Sans',sans-serif;}

:root {
  /* ── Échelle de neutres — 4 surfaces perceptuellement régulières
     (fond < carte < élément surélevé < survol), hairlines plutôt
     qu'ombres lourdes, 3 niveaux de texte tous ≥ AA. La palette
     sémantique (raretés, accents stats) est figée et n'est PAS ici. ── */
  --bg:#F4F1ED;--surface:#FFFFFF;--surface2:#EFEBE6;--surface3:#E3DED7;
  --border:rgba(0,0,0,0.07);--border-s:rgba(0,0,0,0.13);
  --sh-sm:0 1px 2px rgba(0,0,0,0.05);--sh-md:0 4px 12px rgba(0,0,0,0.07);--sh-lg:0 12px 40px rgba(0,0,0,0.12);
  --tx1:#191716;--tx2:#615B57;--tx3:#67615C; /* 1.35.0 : neutres clair assombris — AA sur surface2 */
  /* Carte non possédée : négatif volontaire (sombre en thème clair,
     clair en thème sombre) — l'absence saute aux yeux. */
  --not-owned:#3A3739;
  /* Nav bead — dégradés dérivés de l'accent existant */
  --nav-edge-top:rgba(0,0,0,0.16);--nav-edge-bot:rgba(0,0,0,0.04);
  --nav-fill-top:#FFFFFF;--nav-fill-bot:#F6F3EF;
  --nav-bead-hi:#FF3355;--nav-bead-lo:#C9002A;
  --nav-glow:232,0,45;--nav-glow-a1:0.16;--nav-glow-a2:0.05;
  --gold:#B8860B;
  --red:#E8002D;--red-s:#FFE8EC;--red-m:#FFBEC8;--red-glow:rgba(232,0,45,0.18);
  --green:#34C759;--green-s:#E8F5E9;--green-m:#C8E6C9;
  --orange:#FF9500;--orange-s:#FFF3E0;--orange-m:#FFE0B2;
  --blue:#007AFF;--blue-s:#E3F2FD;--blue-m:#BBDEFB;
  --purple:#AF52DE;--purple-s:#F3E5F5;--purple-m:#E1BEE7;
  --r-sm:10px;--r-md:16px;--r-lg:24px;--r-xl:32px;
  /* spacing scale (base 4px) + missing shape/size tokens — pure
     refactor: every var resolves to the exact px it replaced */
  --space-1:4px;--space-2:8px;--space-3:12px;--space-4:16px;--space-5:20px;
  --space-6:24px;--space-7:28px;--space-8:32px;--space-10:40px;--space-12:48px;
  /* Half-steps: recurring 6/10/14px values in dense areas (chips, cells,
     lists). Named rather than rounded — snapping them to the 4px scale
     would shift 61 declarations by 1-2px for scale purity alone. */
  --space-6px:6px;--space-10px:10px;--space-14px:14px;
  /* Clearance under the floating bottom tab bar — was repeated
     verbatim in the four scrolling views. */
  --nav-clear:calc(107px + env(safe-area-inset-bottom)); /* 14 marge + 58 barre + 35 débord pastille */
  --r-pill:100px;--content-max:800px;--tap-min:44px;
  --ease:all 0.2s cubic-bezier(0.25,0.46,0.45,0.94);
  --ease-fast:all 0.15s cubic-bezier(0.25,0.46,0.45,0.94);
  /* Stats semantic accents — palette validated for contrast & CVD
     separation on the light surface (see Stats view) */
  --st-owned:#00A94F;--st-wish:#EC4899;--st-doubles:#0EA5B5;
  --st-missing:#E4002B;--st-fav:#F43F5E;--st-copies:#7C4DFF;
  --pr-low:#E8002D;--pr-mid:#C77F00;
  /* ── FOILS — intensité « moyen » (1.46.0) ───────────────────
     Trois niveaux avaient été comparés sur la grille réelle ;
     celui-ci est le retenu. Pour re-régler, ces six variables
     suffisent — léger : .34/.15/10s, .40/.20/9s, .35/10s ;
     actuel (avant 1.46.0) : .92/.50/3.8s, 1/.60/3s, .75/4s.
     Le vrai levier de confort n'est pas l'opacité mais le CYCLE
     DE SERVICE : la bande passe sur 45 % du cycle, puis attend. */
  --f-peak:0.55;        /* crête de la bande */
  --f-mid:0.26;         /* flancs de la bande */
  --f-dur:6.5s;         /* période du balayage */
  --f-promo-peak:0.62;--f-promo-mid:0.34;--f-promo-dur:6s;
  --f-beam-a:0.50;      /* alpha des faisceaux coniques wild/nitro */
  --f-beam-dur:7s;
  --f-nitro-dur:10s;--f-nitro-a:0.62;
}

/* Thème sombre */
[data-theme="dark"] {
  /* Sombre : très sombre sans être noir pur, teinte légèrement chaude
     (R>G>B) accordée au rouge accent — pas le gris neutre clinique
     d'avant. Pas de blanc pur en texte (#F3F0F1 ≈ 95 %). */
  --gold:#FACC15;
  --bg:#100E0F;--surface:#1A1719;--surface2:#232022;--surface3:#2E2A2C;
  --border:rgba(255,255,255,0.08);--border-s:rgba(255,255,255,0.14);
  --sh-sm:0 1px 2px rgba(0,0,0,0.4);--sh-md:0 4px 14px rgba(0,0,0,0.5);--sh-lg:0 16px 44px rgba(0,0,0,0.65);
  --tx1:#F3F0F1;--tx2:#A5A0A2;--tx3:#8A8487;
  --not-owned:#DEDADC;
  --nav-edge-top:rgba(255,255,255,0.22);--nav-edge-bot:rgba(255,255,255,0.04);
  --nav-fill-top:#242023;--nav-fill-bot:#171416;
  --nav-bead-hi:#FF7A88;--nav-bead-lo:#E4243C;
  --nav-glow:255,71,87;--nav-glow-a1:0.30;--nav-glow-a2:0.09;
  --red:#FF4757;--red-s:#2D1B1B;--red-m:#3D2B2B;--red-glow:rgba(255,71,87,0.22);
  --green:#4CD964;--green-s:#1B2D1B;--green-m:#2B3D2B;
  --orange:#FFB347;--orange-s:#2D241B;--orange-m:#3D342B;
  --blue:#64B5F6;--blue-s:#1B2430;--blue-m:#2B3440;
  --purple:#CE93D8;--purple-s:#2D1B30;--purple-m:#3D2B40;
  /* Stats semantic accents — dark-mode steps, validated against the
     dark surface (not an automatic flip of the light set) */
  --st-owned:#1DAF54;--st-wish:#F472B6;--st-doubles:#22C3D6;
  --st-missing:#FF3D54;--st-fav:#FB7185;--st-copies:#A78BFA;
  --pr-low:#FF3D54;--pr-mid:#C08300;
}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
html{font-size:16px;-webkit-font-smoothing:antialiased;}
body{font-family:var(--font-b);background:var(--bg);color:var(--tx1);min-height:100vh;overflow-x:hidden;width:100%;max-width:none;margin:0;padding:0;}
::-webkit-scrollbar{width:6px;height:6px;}
::-webkit-scrollbar-track{background:transparent;}
::-webkit-scrollbar-thumb{background:var(--surface3);border-radius:var(--r-pill);}

/* LOGIN SCREEN */
/* Hidden by default: the pre-paint script in <head> stamps
   data-boot on <html>, so the right screen is up on the very
   first frame — no flash in either direction. */
#login-screen {
  display: none;
  position: fixed; inset: 0;
  background: var(--bg);
  align-items: center; justify-content: center;
  z-index: 10000;
  flex-direction: column;
  gap:0;
}
#login-screen::before {
  content: '';
  position: absolute; inset: 0;
  background:
    radial-gradient(ellipse 60% 40% at 30% 20%, rgba(232,0,45,0.07) 0%, transparent 60%),
    radial-gradient(ellipse 50% 50% at 80% 80%, rgba(232,0,45,0.06) 0%, transparent 60%);
  pointer-events: none;
}
.login-box {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  /* Largeur FLUIDE — elle était figée à 380 px. Sur un écran de 320,
     la boîte débordait de 60 px (de −30 à gauche à +350 à droite) :
     ses bords arrondis, sa bordure et son ombre étaient coupés, et
     body{overflow-x:hidden} masquait le symptôme sans le corriger.
     C'est l'écran que 100 % des nouveaux utilisateurs voient en
     premier — choix de langue, installation, déverrouillage.
     Le remplissage suit, sinon rétrécir la boîte réduirait la largeur
     utile : la grille des langues a besoin de 252 px pour ses deux
     colonnes, et 48 px de marge de chaque côté n'en laisseraient que
     192 sur un écran de 320. */
  padding:52px clamp(20px, 6vw, var(--space-12)) 44px;
  width: min(380px, 100vw - 24px);
  box-shadow: 0 32px 80px rgba(0,0,0,0.7), 0 0 0 1px rgba(232,0,45,0.08);
  display: flex; flex-direction: column; align-items: center; gap:0;
  animation: loginReveal 0.5s ease both;
}
@keyframes loginReveal {
  from { opacity: 0; transform: translateY(24px) scale(0.97); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
.pin-label {
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--tx2);
  margin-bottom: var(--space-4);
  align-self: flex-start;
}
@keyframes shake {
  0%,100% { transform: translateX(0); }
  20%      { transform: translateX(-6px); }
  40%      { transform: translateX(6px); }
  60%      { transform: translateX(-4px); }
  80%      { transform: translateX(4px); }
}
/* ── Volet D : écran PIN au standard du composant segmenté ── */
.pin-lock{width:34px;height:34px;color:var(--tx3);margin:0 auto var(--space-2);display:block;}
.pin-segs{margin-bottom:var(--space-8);}
.pin-segs .otp-box{flex-basis:46px;min-width:36px;height:54px;font-size:22px;border-radius:12px;}
.pin-segs .otp-boxes{gap:10px;}
.pin-flow-title{font-family:var(--font-d);font-size:20px;font-weight:700;color:var(--tx1);margin-bottom:var(--space-1);text-align:center;}
.pin-flow-step{font-size:13px;color:var(--tx2);margin-bottom:var(--space-5);text-align:center;}
.pin-flow-num{font-variant-numeric:tabular-nums;font-weight:700;color:var(--tx3);background:var(--surface2);border:1px solid var(--border);border-radius:var(--r-pill);padding:1px 8px;margin-left:4px;font-size:11px;}
.pin-flow-cancel{background:none;border:1.5px solid var(--border);border-radius:var(--r-pill);color:var(--tx2);font-size:12px;font-weight:600;padding:7px 18px;cursor:pointer;font-family:var(--font-b);margin-top:var(--space-4);}
.sec-chip{display:inline-block;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:0.5px;color:var(--tx3);background:var(--surface2);border:1px solid var(--border);border-radius:var(--r-pill);padding:2px 8px;margin-left:6px;vertical-align:2px;}
.sec-chip.on{color:var(--green);background:var(--green-s);border-color:var(--green-m);}

.pin-keypad {
  display: grid;
  /* minmax(0,…) et pas 1fr seul : `1fr` vaut `minmax(auto, 1fr)`, donc le
     contenu le plus large ÉLARGIT sa colonne. Mesuré avec un libellé
     « Effacer » sur la 3e touche : colonnes 70 / 70 / 86 px, chiffres
     décalés. C'est aussi pourquoi cette touche porte un pictogramme et
     non un mot — « Löschen », « Wissen », « 清除 » n'ont pas la même
     largeur, et la grille ne doit pas dépendre de la langue. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap:var(--space-10px);
  width: 100%;
}
.pin-key {
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: var(--space-4);
  font-family: var(--font-b);
  font-size: 1.2rem;
  font-weight: 500;
  color: var(--tx1);
  cursor: pointer;
  transition: all 0.15s;
  text-align: center;
  user-select: none;
}
.pin-key:hover { background: var(--surface3); border-color: var(--red); color: var(--red); }
.pin-key:active { transform: scale(0.94); background: rgba(232,0,45,0.1); }
.pin-key.del { font-size: 1rem; color: var(--tx3); }
.pin-key.del:hover { color: var(--red); border-color: var(--red); }
.pin-key.zero { grid-column: 2; }
/* ── « Tout effacer » — la case libre du pavé (1.57.0) ──
   Un rôle, toujours le même. Désactivée quand il n'y a rien à effacer :
   à sa place, sans effet, plutôt qu'apparaissant et disparaissant — une
   touche qui va et vient déplace les voisines sous le doigt. */
.pin-key.clear { display: flex; align-items: center; justify-content: center; color: var(--tx3); }
.pin-key.clear .ic { width: 20px; height: 20px; display: block; }
.pin-key.clear:hover:not([disabled]) { color: var(--red); border-color: var(--red); }
.pin-key.clear[disabled] { opacity: 0.35; cursor: default; }
.pin-key.clear[disabled]:hover { color: var(--tx3); border-color: var(--border); background: var(--surface2); }
.pin-key.clear[disabled]:active { transform: none; background: var(--surface2); }
/* Navigation d'étape : sous le pavé, en texte — jamais dans la grille. */
.pin-flow-actions { display: flex; gap: var(--space-2); justify-content: center; flex-wrap: wrap; }
.pin-error-msg {
  margin-top: var(--space-4);
  font-size: 0.8rem;
  color: var(--red);
  text-align: center;
  min-height: 20px;
  font-weight: 500;
}

/* VIEWER BROWSE BUTTON */
/* Le libellé vit dans un <span data-i18n>, PAS sur le bouton : applyLanguage()
   écrit dans textContent, ce qui effacerait l'icône SVG à chaque changement de
   langue. C'est la même raison qui rendait l'ancien émoji invisible partout
   sauf en français — il ne survivait pas au premier rendu. */
.viewer-browse-btn{
  display:inline-flex;align-items:center;gap:var(--space-2);
  margin-top:var(--space-4);
  padding:var(--space-10px) var(--space-7);
  border-radius:var(--r-pill);
  border:1.5px solid var(--border);
  background:transparent;
  font-family:var(--font-b);font-size:13px;font-weight:600;
  color:var(--tx2);cursor:pointer;
  transition:var(--ease);
}
.viewer-browse-btn:hover{background:var(--surface2);border-color:var(--blue);color:var(--blue);}

/* PIN CHANGE FORM (in settings) */
.pin-form-error{font-size:12px;color:var(--red);min-height:16px;font-weight:500;}

/* IMPORT DIALOG */
.import-dialog-overlay{
  position:fixed;inset:0;background:rgba(0,0,0,0.6);
  z-index:500;display:flex;align-items:center;justify-content:center;
}
.import-dialog{
  background:var(--surface);border:1px solid var(--border);
  border-radius:var(--r-xl);padding:var(--space-7) var(--space-8);
  max-width:360px;width:90%;
  box-shadow:var(--sh-lg);
  display:flex;flex-direction:column;gap:var(--space-4);
}
.import-dialog-title{font-family:var(--font-d);font-weight:800;font-size:18px;}
.import-dialog-sub{font-size:13px;color:var(--tx2);line-height:1.5;}
.import-dialog-btns{display:flex;gap:var(--space-10px);flex-wrap:wrap;}
.import-dialog-btn{
  flex:1;padding:var(--space-10px) var(--space-4);border-radius:var(--r-pill);
  border:1.5px solid var(--border);
  font-family:var(--font-b);font-size:13px;font-weight:600;
  cursor:pointer;transition:var(--ease);background:var(--surface2);color:var(--tx1);
}
.import-dialog-btn:hover{border-color:var(--red);color:var(--red);}
.import-dialog-btn.primary{background:var(--red);border-color:var(--red);color:white;}
.import-dialog-btn.primary:hover{opacity:0.85;}

/* SETUP SCREEN */
.setup-title{font-family:var(--font-d);font-weight:800;font-size:20px;text-align:center;margin:var(--space-4) 0 var(--space-2);color:var(--tx1);}
.setup-sub{font-size:13px;color:var(--tx2);text-align:center;line-height:1.5;margin-bottom:var(--space-6);}
.setup-btns{display:flex;flex-direction:column;gap:var(--space-10px);width:100%;}
.setup-btn{
  padding:var(--space-3);border-radius:var(--r-pill);
  font-family:var(--font-b);font-size:14px;font-weight:600;
  cursor:pointer;transition:var(--ease);width:100%;
}
.setup-btn.primary{background:var(--red);border:none;color:white;}
.setup-btn.primary:hover{opacity:0.85;}
.setup-btn.secondary{background:transparent;border:1.5px solid var(--border);color:var(--tx2);}
.setup-btn.secondary:hover{background:var(--surface2);color:var(--tx1);}

/* ── First-launch language choice ──
   Language-neutral screen (7 native names + the prompt in all 7),
   styled like the setup screen; theme-aware through the CSS vars. */
.lang-globe{font-size:34px;text-align:center;margin:var(--space-14px) 0 2px;line-height:1;}
.lang-prompt{font-size:11px;color:var(--tx3);text-align:center;line-height:1.7;margin:0 0 var(--space-5);}
.lang-grid{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-10px);width:100%;}
.lang-opt{display:flex;align-items:center;gap:var(--space-10px);padding:var(--space-3) var(--space-14px);border-radius:var(--r-md);border:1.5px solid var(--border);background:var(--surface2);cursor:pointer;transition:var(--ease);text-align:left;}
.lang-opt:hover,.lang-opt:focus-visible{border-color:var(--red);background:var(--surface);transform:translateY(-1px);box-shadow:var(--sh-sm);outline:none;}
.lang-opt:active{transform:none;}
/* La 7e langue occupe toute la largeur — et reste alignée à GAUCHE
   comme les six autres. Centrée, elle se lisait comme un orphelin au
   milieu du vide ; alignée, sa pastille ISO tombe dans la même colonne
   que les autres et la dernière ligne devient une ligne comme les
   autres, simplement plus large.
   Écartées après mesure à 320×568 : une grille à 3 colonnes (la 3e est
   coupée — .login-box n'offre que 284 px utiles, voir POINTS-SIGNALES)
   et une liste à 7 lignes (boîte de 640 px pour un écran de 568, donc
   défilement sur le tout premier écran de l'app). */
.lang-opt:last-child{grid-column:1 / -1;justify-content:flex-start;}
.lang-name{font-family:var(--font-b);font-size:14px;font-weight:600;color:var(--tx1);}
@media (prefers-reduced-motion:reduce){.lang-opt:hover{transform:none;}}

/* ── "Install the app" (PWA) ──
   Settings row bits + a discreet dismissable banner above the bottom
   nav (shown only when the native install prompt is available). */
.install-ok{font-size:13px;font-weight:600;color:var(--tx2);padding:var(--space-1) 0;}
.install-ins{font-size:13px;line-height:1.55;color:var(--tx2);background:var(--surface2);border:1px solid var(--border);border-radius:var(--r-md);padding:var(--space-10px) var(--space-3);}
.install-banner{position:fixed;left:50%;transform:translateX(-50%);bottom:92px;z-index:400;display:flex;align-items:center;gap:var(--space-10px);max-width:min(480px,calc(100vw - 24px));background:var(--surface);border:1px solid var(--border);border-radius:var(--r-pill);box-shadow:var(--sh-lg);padding:var(--space-2) var(--space-10px) var(--space-2) var(--space-14px);animation:instIn 0.25s ease;}
@keyframes instIn{from{opacity:0;transform:translate(-50%,8px)}to{opacity:1;transform:translate(-50%,0)}}
.install-banner-icon{font-size:18px;line-height:1;}
.install-banner-text{flex:1;font-size:12.5px;font-weight:600;color:var(--tx1);line-height:1.3;}
@media (max-width:480px){.install-banner{width:calc(100vw - 24px);border-radius:18px;}}
.install-banner-btn{background:var(--red);border:none;color:#fff;font-family:var(--font-b);font-size:12px;font-weight:700;border-radius:var(--r-pill);padding:var(--space-2) var(--space-14px);cursor:pointer;transition:var(--ease);white-space:nowrap;}
.install-banner-btn:hover{filter:brightness(1.08);}
.install-banner-close{background:none;border:none;color:var(--tx3);font-size:13px;cursor:pointer;padding:var(--space-6px) var(--space-2);border-radius:50%;transition:var(--ease);line-height:1;}
.install-banner-close:hover{color:var(--tx1);background:var(--surface2);}
@media (prefers-reduced-motion:reduce){.install-banner{animation:none;}}

/* ── App updates (banner) + changelog dialog + version badge ──
   The update/what's-new pills reuse the install-banner look; when an
   install banner is also present they stack above it. */
.update-banner{bottom:150px;z-index:401;}
/* Soutien — un lien, dans la grammaire des boutons existants. Aucun
   traitement racoleur : ni couleur d'accent, ni pastille, ni animation.
   Le cœur suffit à dire ce que c'est. */
/* UNE LIGNE = UNE ACTION, y compris ici. Les deux destinations avaient
   été empilées dans une seule ligne (texte au-dessus, boutons dessous),
   ce qui faisait de « À propos » la seule carte à deux grammaires.
   Elles ont maintenant chacune la leur, comme Version, Nouveautés et
   Vérifier les mises à jour.
   Mesuré avant de trancher : remettre les DEUX boutons à droite d'une
   seule ligne tenait à 375 px, mais à 320 la colonne de texte tombait
   de 173 à 127 px, le titre passait sur 3 lignes, les boutons
   s'enroulaient quand même — et la carte devenait PLUS HAUTE qu'avant.
   L'irrégularité n'était pas supprimée, elle passait du bouton au
   texte. */
a.setv-btn{display:inline-flex;align-items:center;justify-content:center;
  text-decoration:none;min-height:36px;}
#supportRow .setv-row-label .ic{color:var(--red);margin-right:2px;}
/* Hors ligne : visiblement inerte, et le sous-titre de CHAQUE ligne
   l'explique (le texte n'est plus partagé entre les deux). */
a.setv-btn.is-off{opacity:0.45;pointer-events:none;cursor:default;}

.setv-version{font-family:var(--font-b);font-size:13px;font-weight:700;color:var(--tx2);background:var(--surface2);border:1px solid var(--border);border-radius:var(--r-pill);padding:var(--space-1) var(--space-14px);white-space:nowrap;}
.changelog-dialog{max-width:460px;text-align:left;}
.changelog-dialog .import-dialog-title{text-align:center;}
.changelog-list{max-height:min(55vh,420px);overflow-y:auto;display:flex;flex-direction:column;gap:var(--space-4);margin:var(--space-3) 0 var(--space-4);}
.changelog-ver{font-weight:800;font-size:14px;color:var(--tx1);}
.changelog-date{font-weight:600;font-size:12px;color:var(--tx3);margin-left:var(--space-2);}
.changelog-entry ul{margin:var(--space-6px) 0 0;padding-left:var(--space-5);display:flex;flex-direction:column;gap:var(--space-1);}
.changelog-entry li{font-size:12.5px;line-height:1.5;color:var(--tx2);}

/* ── Cloud backup (Settings) ── */
.cloud-login-row{display:flex;gap:var(--space-2);flex-wrap:wrap;align-items:center;}
.cloud-email{flex:1;min-width:180px;min-height:44px;padding:var(--space-2) var(--space-14px);border-radius:var(--r-pill);border:1.5px solid var(--border);background:var(--surface2);font-family:var(--font-b);font-size:13px;color:var(--tx1);outline:none;transition:var(--ease);}
.cloud-email:focus{border-color:var(--red);}
.cloud-msg{font-size:12.5px;line-height:1.5;color:var(--tx2);min-height:0;}
.cloud-msg.ok{color:#00A94F;}
.cloud-msg.err{color:var(--red);}
[data-theme="dark"] .cloud-msg.ok{color:#1DAF54;}
.cloud-status{display:flex;align-items:center;gap:var(--space-2);font-size:13.5px;color:var(--tx1);}
.cloud-actions{display:flex;gap:var(--space-2);flex-wrap:wrap;}
.cloud-dot{width:9px;height:9px;border-radius:50%;background:var(--tx3);flex:none;}
.cloud-dot.on{background:#00A94F;}
[data-theme="dark"] .cloud-dot.on{background:#1DAF54;}

/* ── Backup content checkboxes (export side + import dialog) ── */
.import-set-row{display:flex;align-items:flex-start;gap:var(--space-2);font-size:13px;color:var(--tx1);cursor:pointer;line-height:1.45;text-align:left;}
.import-set-row input{margin-top:2px;accent-color:var(--red);flex:none;width:20px;height:20px;}
.import-dialog-settings{display:flex;flex-direction:column;gap:var(--space-2);margin:2px 0 var(--space-14px);text-align:left;}
.import-sec-warn{font-size:12.5px;line-height:1.5;color:var(--red);background:color-mix(in srgb, var(--red) 10%, transparent);border:1px solid color-mix(in srgb, var(--red) 35%, transparent);border-radius:var(--r-md);padding:var(--space-2) var(--space-10px);}

/* VIEWER MODE */
body.viewer-mode [data-action="quickToggle"],
body.viewer-mode [data-action="changeMoQty"],
body.viewer-mode .mo-type-rows button { opacity:0.35;pointer-events:none; }

/* HEADER */
.header{background:var(--surface);border-bottom:1px solid var(--border);padding:0;position:sticky;top:0;z-index:100;box-shadow:var(--sh-sm);}
.header-inner{display:flex;align-items:center;height:56px;padding:0 var(--space-4);gap:var(--space-3);position:relative;}
.header-left{flex:0 0 auto;display:flex;justify-content:center;align-items:center;}
/* flex:1 0 auto — grows to keep the badges right-aligned, but NEVER
   shrinks below its content (children are nowrap/flex-shrink:0). */
.header-right{flex:1 0 auto;display:flex;justify-content:flex-end;align-items:center;gap:var(--space-2);}
.logo{display:flex;align-items:center;gap:var(--space-10px);flex-shrink:0;text-decoration:none;}
/* Right-side rail: every control is 28px tall → one optical line.
   The total is INFORMATION, not a control: quiet display-font text
   after a thin divider, instead of a third competing pill. */
.header-title{display:inline-flex;align-items:center;gap:var(--space-1);height:28px;padding:0 var(--space-3);border-radius:var(--r-pill);font-size:11px;font-weight:700;font-family:var(--font-d);background:var(--surface2);border:1px solid var(--border);white-space:nowrap;flex-shrink:0;}
.header-title .ht-icon{font-size:13px;}
.header-title span{max-width:130px;overflow:hidden;text-overflow:ellipsis;}
.season-pills{display:flex;gap:var(--space-1);align-items:center;flex-shrink:0;}
.season-pill{display:inline-flex;align-items:center;height:28px;padding:0 var(--space-3);border-radius:var(--r-pill);border:1.5px solid var(--border);background:transparent;font-family:var(--font-b);font-size:11px;font-weight:700;color:var(--tx2);cursor:pointer;transition:var(--ease);letter-spacing:0.5px;white-space:nowrap;}
.season-pill:hover{background:var(--surface2);color:var(--tx1);}
.season-pill.active{background:var(--red-s);color:var(--red);border-color:var(--red-m);}
.theme-switch{width:32px;height:32px;border-radius:50%;background:var(--surface2);border:1px solid var(--border);display:flex;align-items:center;justify-content:center;cursor:pointer;transition:var(--ease);}
.theme-switch:hover{background:var(--surface3);transform:scale(1.05);}
.theme-icon{font-size:16px;transition:var(--ease);}

/* ══════════════════════════════════════════════════════════
   BANDEAU DE PAGE (.ph) — le pattern commun aux 5 pages (1.45.0).
   Voir pagehead.js pour le pourquoi. Il DÉFILE : seul le header
   global reste collant, donc la hauteur se paie une fois.
   ══════════════════════════════════════════════════════════ */
.ph{padding:var(--space-5) var(--space-4) 0;display:flex;flex-direction:column;gap:var(--space-14px);}
.ph-top{display:flex;align-items:center;gap:var(--space-3);min-height:var(--tap-min);}
.ph-id{display:flex;align-items:center;gap:var(--space-3);min-width:0;flex:1;}
.ph-icon{width:38px;height:38px;flex:0 0 38px;border-radius:12px;background:var(--surface2);
  border:1px solid var(--border);display:flex;align-items:center;justify-content:center;
  color:var(--red);font-size:19px;}
.ph-txt{min-width:0;}
.ph-title{font-family:var(--font-d);font-weight:800;font-size:22px;letter-spacing:-0.5px;line-height:1.08;
  color:var(--tx1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.ph-sub{font-size:12px;color:var(--tx2);margin-top:3px;font-variant-numeric:tabular-nums;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.ph-act{display:flex;gap:var(--space-2);flex:0 0 auto;}
.ph-btn{width:44px;height:44px;border-radius:12px;border:1px solid var(--border);background:var(--surface);
  color:var(--tx2);display:flex;align-items:center;justify-content:center;cursor:pointer;
  font-size:17px;transition:var(--ease);}
.ph-btn:hover{color:var(--red);border-color:var(--red);}
.ph-metric{border-top:1px solid var(--border);padding-top:var(--space-14px);}
/* Sur Collection le bandeau porte sa propre gouttière (16 px, celle de
   .card-grid, pour que titre et tuiles s'alignent). Dans les vues déjà
   rembourrées, il laisse la vue la porter. */
.badges-view > .ph,.stats-view > .ph,.settings-view > .ph{padding-left:0;padding-right:0;}
/* Vues en display:block : pas de gap de conteneur pour séparer le
   bandeau du premier bloc — il le porte lui-même. */
.stats-view > .ph,.settings-view > .ph{margin-bottom:var(--space-5);}
@media(min-width:700px){.ph{padding-top:var(--space-6);}.ph-title{font-size:26px;}}
@media(max-width:360px){
  .ph{padding:var(--space-4) var(--space-14px) 0;}
  .ph-title{font-size:19px;}
  .ph-icon{width:32px;height:32px;flex-basis:32px;border-radius:10px;font-size:16px;}
}

/* Mesure de Collection — compacte sur mobile (une ligne), déployée
   sur desktop (barre à gauche, chiffres empilés à droite). */
.phm-coll{display:flex;flex-direction:column;gap:var(--space-10px);}
.phm-bar{height:6px;border-radius:var(--r-pill);background:var(--surface3);overflow:hidden;}
.phm-bar i{display:block;height:100%;border-radius:var(--r-pill);background:var(--green);
  transition:width 0.35s ease;}
.phm-nums{display:flex;gap:var(--space-4);flex-wrap:wrap;font-size:11px;color:var(--tx2);
  font-variant-numeric:tabular-nums;}
.phm-k{display:flex;align-items:baseline;gap:5px;}
.phm-k b{font-family:var(--font-d);font-size:13px;line-height:1.1;}
.phm-k.k-own b{color:var(--st-owned);}
.phm-k.k-mis b{color:var(--st-missing);}
.phm-k.k-dbl b{color:var(--st-doubles);}
@media(min-width:700px){
  .phm-coll{flex-direction:row-reverse;align-items:center;gap:var(--space-6);}
  .phm-coll .phm-bar{flex:1;}
  .phm-nums{flex:0 0 auto;gap:var(--space-6);}
  .phm-k{flex-direction:column;align-items:flex-start;gap:2px;}
  .phm-k b{font-size:15px;}
}

/* LAYOUT */
.app{height:calc(100vh - 56px);width:100%;max-width:none;min-width:0;overflow-x:hidden;}
/* overflow-x:hidden était posé sur body, mais l'élément qui défile est
   <html> : la page pouvait donc bien glisser de 14 px de côté sur les
   vues qui ont leur propre barre de défilement. */
.main{display:flex;flex-direction:column;gap:0;overflow-y:auto;padding:0 0 var(--nav-clear);height:100%;width:100%;max-width:none;}
.main::-webkit-scrollbar{width:8px;}
.main::-webkit-scrollbar-track{background:var(--surface2);border-radius:4px;}
.main::-webkit-scrollbar-thumb{background:var(--surface3);border-radius:4px;}
.main::-webkit-scrollbar-thumb:hover{background:var(--tx3);}


/* CARD GRID */
.card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:var(--space-14px);padding:var(--space-4);width:100%;max-width:none;}

/* CARD */
/* ══════════════════════════════════════════════════════════
   HAUTEUR DE TUILE FIXE — la boîte ne s'étire plus, le contenu
   s'adapte dedans.

   LE DÉFAUT CORRIGÉ : la hauteur dépendait du nombre de lignes du
   nom d'écurie (0 à 3) et de la présence de la pastille
   d'exemplaires. 13 hauteurs distinctes mesurées à 375 px, de
   246,69 à 292,69. Comme la grille aligne ses rangées sur la plus
   haute tuile, une seule carte à nom long allongeait TOUTE sa
   rangée et laissait ses voisines avec du vide.

   --card-h est la hauteur de CONTENU (box-sizing content-box) :
   320 déclaré rend ~332 avec bordures et remplissage.

   LE PLANCHER, REMESURÉ SUR CE CSS (août 2026) — la valeur de 300 venait
   d'une maquette et était optimiste de 15 px. En relâchant la contrainte
   tuile par tuile (height:auto) et en lisant la hauteur naturelle, le
   maximum sur 101 tuiles vaut 281,89 à 1280 px, 314,48 à 375, et 347,08
   à 320. Donc : 5,5 px de marge à 375, et une compression réelle à
   320 px (nom et résumé des exemplaires tassés de 8 à 9 px).

   On garde 320 quand même. Monter à 348 coûterait +8,75 % de défilement
   à tout le monde pour un tassement que personne n'a signalé, et un
   --card-h par palier réintroduirait la variabilité de hauteur que ce
   bloc existe pour supprimer. Détail et arbitrage : docs/POINTS-SIGNALES.md n°9.
   ══════════════════════════════════════════════════════════ */
:root{--card-h:320px;}
/* ══════════════════════════════════════════════════════════
   LE RETOUR SUR COLLECTION — ce que la mesure a corrigé

   Le défaut signalé disait : « la grille se reconstruit entièrement au
   retour sur Collection ». MESURÉ, c'est faux. Les 101 tuiles sont
   marquées avant le changement de vue et retrouvées intactes après —
   101 marques sur 101, zéro nœud ajouté, aucune tâche longue. Le DOM
   n'est pas touché.

   Ce qui coûte est ailleurs : la vue passe de `display:none` à visible,
   donc le navigateur doit REPEINDRE 101 tuiles d'un coup, avec leurs
   couches foil. C'est un coût de peinture, pas de construction — et
   c'est pour ça qu'aucune tâche longue n'apparaissait : le travail est
   dans le compositeur, pas dans le fil JS.

   `content-visibility:auto` laisse le navigateur sauter le rendu des
   tuiles hors écran. `contain-intrinsic-size` lui donne la taille à
   réserver en attendant — et ici elle est EXACTE, puisque la hauteur de
   tuile est fixe : hauteur de grille inchangée, 17 052 px à 320 et
   390 px, 7 032 à 1280, avec ou sans la règle. Le zéro-décalage tient.

   Mesuré sur les 280 ms du glissement de la pastille, retour sur
   Collection : 32-36 fps → 61 fps, pire écart entre images 83 → 17 ms.

   CONTREPARTIE, ET ELLE EST RÉELLE : une tuile dont le rendu est sauté
   n'a pas d'animation à figer. Le script de captures désactive donc
   cette règle chez lui — voir capture_screenshots.py, `new_page()`.
   ══════════════════════════════════════════════════════════ */
.card{content-visibility:auto;contain-intrinsic-size:auto var(--card-h);}
.card{background:var(--surface);border-radius:var(--r-lg);overflow:hidden;box-shadow:var(--sh-sm);transition:var(--ease);cursor:pointer;position:relative;border:1.5px solid var(--border);display:flex;flex-direction:column;height:var(--card-h);}
.card:hover{transform:translateY(-3px);box-shadow:var(--sh-lg);border-color:var(--red-m);}

/* Card visual */
.card-visual{height:120px;display:flex;align-items:center;justify-content:center;font-size:40px;position:relative;overflow:hidden;}
.card-visual.not-owned{background:var(--not-owned);}

.card-visual .circuit-svg{width:85%;height:70%;position:relative;z-index:1;}
.modal-visual .circuit-svg{width:80%;height:70%;position:relative;z-index:1;}
/* Fond P1 toujours sombre : tracé clair sur les deux thèmes (le noir
   ciblait l'ancien aplat clair des non-possédées, disparu en 1.33.0) */
.circuit-svg .track-path{stroke:rgba(255,255,255,0.85);}
/* Driver number */
/* ── LOCKUP « F1 UNO Élite » (v2.3) ──────────────────────────
   Remplace les logos F1 et UNO qui étaient hotlinkés depuis
   Wikimedia : marques déposées, dépendance externe, hors-ligne
   cassé. Uniquement de la typographie, l'accent rouge des tokens
   et une barre de vitesse abstraite — aucun élément de marque.
   Suit --font-d, donc les 5 thèmes de police. */
/* ══ SOURCE DE VÉRITÉ de la géométrie du lockup (1.61.0) ══
   Les VALEURS NOMMÉES ci-dessous sont reprises par icons/icon.svg et
   par le sceau canvas de profile-card.js. Toute divergence casse
   tests/lockup-geometry.test.js — ne changez rien ici sans y passer.

     graisse        700       (le VRAI maximum de space-grotesk-var :
                               la 800 demandée depuis 1.30.0 était un
                               FAUX GRAS synthétique du navigateur —
                               le lockup n'était pas mal dessiné, il
                               était mal rendu)
     F1–UNO         0.22em    (resserrés : un seul nom)
     nom–ÉLITE      +0.34em   (écarté : un qualificatif)
     ÉLITE          500 / 0.34em / interlettre 0.16em, compensée à
                    droite (le letter-spacing s'ajoute APRÈS chaque
                    glyphe, dernier compris : sans margin-right négatif
                    le mot n'est jamais optiquement centré)
     barre          hauteur max(2px, 0.08em) — jamais sous le pixel —
                    largeur 78 %, PLEINE (le fondu la rendait illisible)
     seuil          la barre DISPARAÎT sous 24 px : un élément qui ne
                    peut pas être net à une taille n'y apparaît pas. */
.lockup{display:inline-flex;flex-direction:column;gap:0.16em;font-family:var(--font-d);line-height:1;white-space:nowrap;}
.lk-top{display:flex;align-items:baseline;gap:0.22em;}
.lk-f1{font-weight:700;font-size:1em;letter-spacing:-0.02em;color:var(--tx1);}
.lk-uno{font-weight:700;font-size:1em;letter-spacing:-0.02em;color:var(--red);}
.lk-elite{font-weight:500;font-size:0.34em;letter-spacing:0.16em;margin-left:0.34em;margin-right:-0.16em;color:var(--tx3);text-transform:uppercase;}
.lk-bar{height:max(2px,0.08em);border-radius:1px;background:var(--red);width:78%;}
.lk-hd{font-size:20px;}                       /* header */
.lk-hd .lk-bar{display:none;}                 /* < 24 px : la barre part proprement */
/* ⚠️ EXCEPTION DOCUMENTÉE, ne pas « uniformiser » : à 20 px, la
   proportion du grand format (0.34em) donnerait 6,8 px — illisible
   hors retina. ÉLITE remonte à 0.4em (8 px réels) dans l'en-tête.
   La proportion ne survit pas au rétrécissement ; le réglage si. */
.lk-hd .lk-elite{font-size:0.4em;letter-spacing:0.12em;margin-right:-0.12em;}
.lk-lg{font-size:34px;margin-bottom:var(--space-2);}  /* PIN, langue, setup */

/* ── Monogrammes d'écurie (v2.3) ──
   Les logos officiels venaient de formula1.com. Un monogramme sur la
   couleur d'équipe identifie l'écurie sans emprunter sa marque. */
.team-mono-wrap{display:flex;align-items:center;justify-content:center;width:100%;height:100%;position:relative;z-index:1;}
.team-mono{font-family:var(--font-d);font-weight:800;letter-spacing:-0.02em;
  color:var(--tc,#fff);text-shadow:0 0 14px color-mix(in srgb,var(--tc,#fff) 45%,transparent);}
.card-visual .team-mono{font-size:40px;}
.modal-visual .team-mono{font-size:74px;}

.driver-number{display:flex;flex-direction:column;align-items:center;justify-content:center;width:100%;height:100%;position:relative;z-index:1;gap:2px;}
.dn-num{font-family:'Orbitron',sans-serif;font-weight:900;color:var(--tc,#fff);text-shadow:0 0 12px var(--tc,#fff),0 0 30px color-mix(in srgb,var(--tc,#fff) 40%,transparent);line-height:1;}
.card-visual .dn-num{font-size:28px;letter-spacing:-1px;}
.modal-visual .dn-num{font-size:56px;letter-spacing:-2px;}
.driver-number.no-img .dn-num{font-size:inherit;letter-spacing:inherit;}
.card-visual .driver-number.no-img .dn-num{font-size:52px;letter-spacing:-2px;}
.modal-visual .driver-number.no-img .dn-num{font-size:100px;letter-spacing:-4px;}
/* Per-driver logo styles */
.dn-ver{font-family:'Orbitron',sans-serif;letter-spacing:1px;text-transform:uppercase;-webkit-text-stroke:0;}
.dn-ham{font-family:'Racing Sans One',serif;font-style:italic;letter-spacing:2px;text-shadow:0 0 20px var(--tc),0 2px 8px rgba(0,0,0,0.5);}
.dn-lec{font-family:'Racing Sans One',serif;letter-spacing:3px;text-transform:uppercase;}
.dn-nor{font-family:'Orbitron',sans-serif;letter-spacing:4px;font-weight:700;}
.dn-sai{font-family:'Racing Sans One',serif;letter-spacing:6px;font-weight:700;}
.dn-pia{font-family:'Orbitron',sans-serif;letter-spacing:2px;}
.dn-rus{font-family:'Orbitron',sans-serif;letter-spacing:1px;font-weight:700;}
.dn-alo{font-family:'Racing Sans One',serif;font-style:italic;letter-spacing:2px;}
.dn-gas{font-family:'Racing Sans One',serif;letter-spacing:1px;}
.dn-oco{font-family:'Orbitron',sans-serif;letter-spacing:2px;}
.dn-alb{font-family:'Orbitron',sans-serif;letter-spacing:1px;}
.dn-tsu{font-family:'Orbitron',sans-serif;letter-spacing:3px;font-weight:700;}
.dn-str{font-family:'Racing Sans One',serif;letter-spacing:2px;}
.dn-hul{font-family:'Orbitron',sans-serif;letter-spacing:2px;font-weight:700;}
.dn-ant{font-family:'Orbitron',sans-serif;letter-spacing:2px;font-weight:700;}
.dn-bea{font-family:'Racing Sans One',serif;letter-spacing:2px;}
.dn-bor{font-family:'Racing Sans One',serif;letter-spacing:3px;font-style:italic;}
.dn-col{font-family:'Orbitron',sans-serif;letter-spacing:1px;font-weight:700;}
.dn-doo{font-family:'Orbitron',sans-serif;letter-spacing:2px;}
.dn-had{font-family:'Racing Sans One',serif;letter-spacing:2px;font-style:italic;}
.dn-law{font-family:'Orbitron',sans-serif;letter-spacing:2px;font-weight:700;}
/* Team logo */
#moEmoji{display:flex;align-items:center;justify-content:center;width:100%;height:100%;}
/* Type backgrounds */
/* ── TYPE VISUALS — "Soie vive" (P5) ──
   One shared mechanic, no seams, cheap on 101 cards:
   - BASE: rich STATIC depth (two radial light/dark plates over a
     diagonal gradient). No animation at all on plain types.
   - FOIL: the same base + ONE bright band that glides across
     periodically, always the same direction, constant speed
     (background-position on a transparent-edged overlay — the only
     animated thing, shared keyframes, GPU-friendly).
   - DUAL: the two hues FUSE through a deep blend at the junction
     (no white line), same standard foil band on top.
   - Hierarchy stays readable: plain < foil < dual < nitro/promo < wild. */
.tv-blue{background:radial-gradient(120% 150% at 25% -15%,#4a90d9 0%,transparent 55%),radial-gradient(140% 130% at 85% 120%,#081426 0%,transparent 60%),linear-gradient(135deg,#1a3a6b,#0a1830);}
.tv-green{background:radial-gradient(120% 150% at 25% -15%,#2ecc71 0%,transparent 55%),radial-gradient(140% 130% at 85% 120%,#03230d 0%,transparent 60%),linear-gradient(135deg,#0d4a1f,#052912);}
.tv-red{background:radial-gradient(120% 150% at 25% -15%,#ff5040 0%,transparent 55%),radial-gradient(140% 130% at 85% 120%,#2e0000 0%,transparent 60%),linear-gradient(135deg,#a30808,#4d0000);}
.tv-yellow{background:radial-gradient(120% 150% at 25% -15%,#ffe14d 0%,transparent 55%),radial-gradient(140% 130% at 85% 120%,#453400 0%,transparent 60%),linear-gradient(135deg,#d9ae00,#6b5200);}
.tv-blue_foil{background:radial-gradient(120% 150% at 25% -15%,#7ec2ff 0%,transparent 55%),radial-gradient(140% 130% at 85% 120%,#081426 0%,transparent 60%),linear-gradient(135deg,#2d6abf,#0a1830);}
.tv-green_foil{background:radial-gradient(120% 150% at 25% -15%,#5ee694 0%,transparent 55%),radial-gradient(140% 130% at 85% 120%,#03230d 0%,transparent 60%),linear-gradient(135deg,#1a8c3a,#052912);}
.tv-red_foil{background:radial-gradient(120% 150% at 25% -15%,#ff7a6e 0%,transparent 55%),radial-gradient(140% 130% at 85% 120%,#2e0000 0%,transparent 60%),linear-gradient(135deg,#cc1010,#4d0000);}
.tv-yellow_foil{background:radial-gradient(120% 150% at 25% -15%,#ffefa0 0%,transparent 55%),radial-gradient(140% 130% at 85% 120%,#453400 0%,transparent 60%),linear-gradient(135deg,#ffd000,#6b5200);}
/* duals: chromatic fusion at the junction (blue→violet→red / green→chartreuse→yellow) */
.tv-blue_red_foil{background:radial-gradient(130% 150% at 12% -15%,rgba(74,144,217,0.85) 0%,transparent 52%),radial-gradient(130% 150% at 92% 118%,rgba(224,64,80,0.8) 0%,transparent 52%),linear-gradient(115deg,#153a75 0%,#1d4f9c 36%,#5c2f8e 50%,#a01020 64%,#6e0812 100%);}
.tv-green_yellow_foil{background:radial-gradient(130% 150% at 12% -15%,rgba(46,204,113,0.85) 0%,transparent 52%),radial-gradient(130% 150% at 92% 118%,rgba(255,208,0,0.75) 0%,transparent 52%),linear-gradient(115deg,#0d4a1f 0%,#1a8c3a 36%,#7a9a1a 50%,#c7a800 64%,#6b5200 100%);}
/* the ONE calm foil band — same effect for simple foils AND duals,
   scoped to the visual areas so it never washes over card text */
.card-visual.tv-blue_foil::after,.card-visual.tv-green_foil::after,.card-visual.tv-red_foil::after,.card-visual.tv-yellow_foil::after,
.card-visual.tv-blue_red_foil::after,.card-visual.tv-green_yellow_foil::after,
.modal-visual.tv-blue_foil::after,.modal-visual.tv-green_foil::after,.modal-visual.tv-red_foil::after,.modal-visual.tv-yellow_foil::after,
.modal-visual.tv-blue_red_foil::after,.modal-visual.tv-green_yellow_foil::after{
  /* 1.46.0 — la bande est une COUCHE ÉTROITE TRANSLATÉE, plus un
     background-position animé : le calque est composé au lieu d'être
     repeint à chaque frame sur toute la tuile. */
  content:'';position:absolute;top:0;bottom:0;left:0;width:55%;
  pointer-events:none;z-index:2;mix-blend-mode:screen;
  background:linear-gradient(105deg,
    transparent 0%,
    rgb(255 244 235 / var(--f-mid)) 38%,
    rgb(255 255 255 / var(--f-peak)) 50%,
    rgb(255 244 235 / var(--f-mid)) 62%,
    transparent 100%);
  animation:foil-pass var(--f-dur) ease-in-out infinite;
}
/* Le passage occupe 45 % du cycle ; les 55 % restants sont un temps
   mort, pendant lequel la tuile ne bouge plus du tout. */
/* Tuile hors cadre : les effets sont mis en PAUSE, pas supprimés —
   l'état visuel est conservé et la reprise ne saute pas de phase.
   La classe est posée par l'IntersectionObserver de render.js. */
.card.fx-idle,
.card.fx-idle::before,
.card.fx-idle::after,
.card.fx-idle .card-visual::after{animation-play-state:paused;}

@keyframes foil-pass{
  0%  {transform:translateX(-100%);}
  45% {transform:translateX(300%);}
  100%{transform:translateX(300%);}
}

/* ── FULL-CARD FOIL WASH (the wild treatment, generalized) ──
   Every foil card carries its tv- class on the WHOLE card: the base
   gradient paints once, continuously — the .card-visual goes
   transparent so there is NO horizontal seam between the visual area
   and the body. Text sits on a translucent dark panel (wild's recipe);
   backdrop blur stays exclusive to wild/nitro — a grid full of blurred
   foils would be costly on mobile, plain translucency reads just as
   well. Non-foil types keep the neutral body. */
.card.tv-nitro_foil .card-visual{background:transparent !important;}
.card.tv-blue_foil .card-body,
.card.tv-green_foil .card-body,
.card.tv-red_foil .card-body,
.card.tv-yellow_foil .card-body,
.card.tv-blue_red_foil .card-body,
.card.tv-green_yellow_foil .card-body,
.card.tv-nitro_foil .card-body,
.card.tv-promo-blue .card-body,
.card.tv-promo-green .card-body,
.card.tv-promo-red .card-body,
.card.tv-promo-yellow .card-body{background:rgba(8,8,10,0.45);position:relative;z-index:1;}
/* 1.34.0 — la couleur se voit FRANCHEMENT : les dégradés foil
   concentrent leur éclat en haut de carte, recouvert depuis P1 par le
   visuel-rareté ; le corps n'héritait que des queues sombres (le jaune
   rendait noir). Le panneau devient un verre TEINTÉ à la couleur de la
   famille — sombre vers le bas, texte blanc intact. Wild et nitro
   gardent leurs mondes propres. */
.card.tv-blue_foil .card-body,
.card.tv-promo-blue .card-body{background:linear-gradient(168deg,rgba(37,99,235,0.50),rgba(8,8,12,0.66) 85%);}
.card.tv-green_foil .card-body,
.card.tv-promo-green .card-body{background:linear-gradient(168deg,rgba(22,163,74,0.50),rgba(8,8,12,0.66) 85%);}
.card.tv-red_foil .card-body,
.card.tv-promo-red .card-body{background:linear-gradient(168deg,rgba(220,38,38,0.50),rgba(8,8,12,0.66) 85%);}
.card.tv-yellow_foil .card-body,
.card.tv-promo-yellow .card-body{background:linear-gradient(168deg,rgba(202,138,4,0.55),rgba(8,8,12,0.66) 85%);}
.card.tv-blue_red_foil .card-body{background:linear-gradient(115deg,rgba(37,99,235,0.48),rgba(220,38,38,0.48)),linear-gradient(rgba(8,8,12,0.42),rgba(8,8,12,0.62));}
.card.tv-green_yellow_foil .card-body{background:linear-gradient(115deg,rgba(22,163,74,0.48),rgba(202,138,4,0.52)),linear-gradient(rgba(8,8,12,0.42),rgba(8,8,12,0.62));}
.card.tv-nitro_foil .card-body{background:rgba(8,8,10,0.70);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);z-index:3;}
.card.tv-blue_foil .card-num,.card.tv-blue_foil .card-name,.card.tv-blue_foil .card-year,.card.tv-blue_foil .card-team,
.card.tv-green_foil .card-num,.card.tv-green_foil .card-name,.card.tv-green_foil .card-year,.card.tv-green_foil .card-team,
.card.tv-red_foil .card-num,.card.tv-red_foil .card-name,.card.tv-red_foil .card-year,.card.tv-red_foil .card-team,
.card.tv-yellow_foil .card-num,.card.tv-yellow_foil .card-name,.card.tv-yellow_foil .card-year,.card.tv-yellow_foil .card-team,
.card.tv-blue_red_foil .card-num,.card.tv-blue_red_foil .card-name,.card.tv-blue_red_foil .card-year,.card.tv-blue_red_foil .card-team,
.card.tv-green_yellow_foil .card-num,.card.tv-green_yellow_foil .card-name,.card.tv-green_yellow_foil .card-year,.card.tv-green_yellow_foil .card-team,
.card.tv-nitro_foil .card-num,.card.tv-nitro_foil .card-name,.card.tv-nitro_foil .card-year,.card.tv-nitro_foil .card-team,
.card.tv-promo-blue .card-num,.card.tv-promo-blue .card-name,.card.tv-promo-blue .card-year,.card.tv-promo-blue .card-team,
.card.tv-promo-green .card-num,.card.tv-promo-green .card-name,.card.tv-promo-green .card-year,.card.tv-promo-green .card-team,
.card.tv-promo-red .card-num,.card.tv-promo-red .card-name,.card.tv-promo-red .card-year,.card.tv-promo-red .card-team,
.card.tv-promo-yellow .card-num,.card.tv-promo-yellow .card-name,.card.tv-promo-yellow .card-year,.card.tv-promo-yellow .card-team{color:#FFFFFF !important;}
/* Full-card washes: the card's translucent border reads as a colored
   ring over a gradient — kill it on foils. The GOLD liner is a promo
   exclusive, drawn with the card's own border so it hugs the 24px
   rounded corners perfectly (no extra layer, no square-cut corner). */
.card.tv-blue_foil,.card.tv-green_foil,.card.tv-red_foil,.card.tv-yellow_foil,
.card.tv-blue_red_foil,.card.tv-green_yellow_foil,.card.tv-wild_foil{
  border-color:transparent;
  /* the wash paints UNDER the transparent border by default
     (background-clip:border-box) — that thin ring of raw gradient WAS
     the colored edge. Clip to the padding box: no wash under the
     border, no ring. */
  background-clip:padding-box;
}
.card.tv-promo-blue,.card.tv-promo-green,.card.tv-promo-red,.card.tv-promo-yellow{
  border-color:rgba(255,205,90,0.85);
  box-shadow:inset 0 0 14px rgba(255,180,60,0.22), var(--sh-sm);
}
/* modal: same gold liner around the promo visual (inset shadow follows
   the header's own rounded corners) */
.modal-visual.tv-promo-blue,.modal-visual.tv-promo-green,.modal-visual.tv-promo-red,.modal-visual.tv-promo-yellow{
  box-shadow:inset 0 0 0 2px rgba(255,205,90,0.85), inset 0 0 18px rgba(255,180,60,0.25);
}

/* nitro mirrors wild fully: its grid identity covers the whole card */


/* Base de la carte — fond noir, effet sur toute la carte */
/* NITRO — the violet tier between duals and wild: deep violet base,
   its grid identity kept as a subtle overlay, and the standard foil
   band running FASTER (nitro = speed). Whole-card class keeps a glassy
   body + white text since the base is dark. */

/* PROMOS — deep type hue + a flowing GOLD band + gold liner. */
.tv-promo-blue{background:radial-gradient(120% 150% at 25% -15%,#4a90d9 0%,transparent 55%),radial-gradient(140% 130% at 85% 120%,#02101f 0%,transparent 60%),linear-gradient(135deg,#0b2a55,#04101f);}
.tv-promo-green{background:radial-gradient(120% 150% at 25% -15%,#2ecc71 0%,transparent 55%),radial-gradient(140% 130% at 85% 120%,#01180a 0%,transparent 60%),linear-gradient(135deg,#08471c,#02180b);}
.tv-promo-red{background:radial-gradient(120% 150% at 25% -15%,#ff4d5e 0%,transparent 55%),radial-gradient(140% 130% at 85% 120%,#1f0004 0%,transparent 60%),linear-gradient(135deg,#8f0413,#33000a);}
.tv-promo-yellow{background:radial-gradient(120% 150% at 25% -15%,#ffe14d 0%,transparent 55%),radial-gradient(140% 130% at 85% 120%,#241a00 0%,transparent 60%),linear-gradient(135deg,#9a7a00,#453400);}
.card-visual.tv-promo-blue::after,.card-visual.tv-promo-green::after,.card-visual.tv-promo-red::after,.card-visual.tv-promo-yellow::after,
.modal-visual.tv-promo-blue::after,.modal-visual.tv-promo-green::after,.modal-visual.tv-promo-red::after,.modal-visual.tv-promo-yellow::after{
  content:'';position:absolute;top:0;bottom:0;left:0;width:55%;
  pointer-events:none;z-index:2;mix-blend-mode:screen;
  background:linear-gradient(105deg,
    transparent 0%,
    rgb(255 209 102 / var(--f-promo-mid)) 38%,
    rgb(255 242 204 / var(--f-promo-peak)) 50%,
    rgb(255 209 102 / var(--f-promo-mid)) 62%,
    transparent 100%);
  animation:foil-pass var(--f-promo-dur) ease-in-out infinite;
}

/* ══ NITRO FOIL — like the real card: the DRIVER pops out in relief.
   Background = the wild recipe at FULL brightness (same beam alphas,
   same speed) + wild's glossy sheen baked into the base + a luminous
   inner rim (the light-catching edge). Nitro's own identity: the grid
   is INVISIBLE at rest — it only lights up inside the mauve reflection
   band that sweeps by occasionally (the light literally reveals it:
   the grid lives in a layer clipped by an animated mask that IS the
   band). Cheap: transform-only beams, one masked layer, static
   drop-shadow relief. */
.card.tv-nitro_foil,
.modal-visual.tv-nitro_foil{
  background:
    linear-gradient(135deg,rgba(255,255,255,0.10) 0%,transparent 40%,rgba(255,255,255,0.05) 100%),
    #080808;
  background-clip:padding-box;
  position:relative;overflow:hidden;isolation:isolate;
}
/* nitro's edge: promo-style MAUVE border (the card's own border, hugs
   the rounded corners) + wild's reflective halo, IDENTICAL. */
/* 1.46.0 — wild-glow animait une box-shadow (repeint pur, toute la
   tuile, en continu). Les faisceaux tournants portent déjà l'identité
   colorée : le halo devient STATIQUE. */
.card.tv-nitro_foil{border-color:rgba(176,124,232,0.62);
  box-shadow:inset 0 0 22px rgba(150,90,230,0.22),
             0 0 12px rgba(150,120,220,0.26),0 8px 32px rgba(0,0,0,0.7);}
.card.tv-nitro_foil .dn-num,
.modal-visual.tv-nitro_foil .dn-num{color:#FFFFFF !important;}
.modal-visual.tv-nitro_foil{box-shadow:inset 0 0 0 2px rgba(200,140,255,0.85), inset 0 0 18px rgba(180,110,255,0.3);}
.card.tv-nitro_foil::before,
.modal-visual.tv-nitro_foil::before{
  content:'';position:absolute;inset:-100%;pointer-events:none;z-index:0;
  background:conic-gradient(
    from 0deg,
    transparent            0deg,
    rgb(30 80 220 / var(--f-beam-a))  50deg,
    transparent            90deg,
    rgb(20 160 50 / var(--f-beam-a))  140deg,
    transparent            180deg,
    rgb(220 0 0 / var(--f-beam-a))    230deg,
    transparent            270deg,
    rgb(255 208 0 / var(--f-beam-a))  320deg,
    transparent            360deg
  );
  animation:wild-spin var(--f-beam-dur) linear infinite;
}
/* the revealed grid: mauve glow + grid lines, clipped by the moving
   light band (mask) — nothing is visible outside the band's passage */
.card.tv-nitro_foil::after,
.modal-visual.tv-nitro_foil::after{
  /* 1.46.0 — même mécanique que la bande foil : une couche étroite
     TRANSLATÉE, masque fixe. La grille est un motif périodique de
     20 px : la translater est visuellement indiscernable d'une grille
     fixe, et ça remplace un mask-position animé (repeint) par un
     transform (composé). */
  /* Bande plus étroite (42 %) et masque à bords FONDUS : à 55 % avec
     des bords francs, la grille lisait comme un panneau de verre posé
     sur la moitié de la tuile, pas comme un rai de lumière.
     Trame resserrée à 13 px (20 px était grossier sur 200 px de large)
     et teintée mauve : l'identité de nitro est violette, elle ne doit
     pas se dissoudre dans les faisceaux qu'il partage avec wild. */
  content:'';position:absolute;top:0;bottom:0;left:0;width:42%;
  pointer-events:none;z-index:2;opacity:var(--f-nitro-a);
  background:
    linear-gradient(rgba(196,150,255,0.30),rgba(196,150,255,0.30)),
    linear-gradient(180deg,rgba(238,214,255,0.85) 0 1px,transparent 1px 13px),
    linear-gradient(90deg,rgba(238,214,255,0.85) 0 1px,transparent 1px 13px);
  background-size:100% 100%,13px 13px,13px 13px;
  -webkit-mask-image:linear-gradient(105deg,transparent 0%,rgba(0,0,0,.35) 22%,#000 48%,#000 56%,rgba(0,0,0,.35) 80%,transparent 100%);
  mask-image:linear-gradient(105deg,transparent 0%,rgba(0,0,0,.35) 22%,#000 48%,#000 56%,rgba(0,0,0,.35) 80%,transparent 100%);
  animation:foil-pass var(--f-nitro-dur) ease-in-out infinite;
}
.card.tv-wild_foil {
  background: var(--bg);
  background-clip: padding-box; /* nothing under the transparent border */
  position: relative;
  overflow: hidden;
  /* isolate: keeps the composited rotating ::before clipped by the
     rounded corners (without it the layer can paint past the radius) */
  isolation: isolate;
  box-shadow: 0 0 12px rgba(150,120,220,0.30), 0 8px 32px rgba(0,0,0,0.7);
}

/* Faisceaux lumineux rotatifs sur toute la carte */
.card.tv-wild_foil::before {
  content: '';
  position: absolute;
  inset: -100%;
  background: conic-gradient(
    from 0deg,
    transparent           0deg,
    rgb(30 80 220 / var(--f-beam-a))  50deg,
    transparent           90deg,
    rgb(20 160 50 / var(--f-beam-a))  140deg,
    transparent           180deg,
    rgb(220 0 0 / var(--f-beam-a))    230deg,
    transparent           270deg,
    rgb(255 208 0 / var(--f-beam-a))  320deg,
    transparent           360deg
  );
  animation: wild-spin var(--f-beam-dur) linear infinite;
  pointer-events: none;
  z-index: 0;
}

/* Reflet brillant par-dessus */
.card.tv-wild_foil::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    135deg,
    rgba(255,255,255,0.10) 0%,
    transparent 40%,
    rgba(255,255,255,0.05) 100%
  );
  pointer-events: none;
  z-index: 2;
}

/* La partie visuelle devient transparente pour laisser passer l'effet */
.card-visual.tv-wild_foil {
  background: transparent !important;
  position: relative;
  z-index: 1;
}
.card-visual.tv-wild_foil::before {
  display: none !important;
}

/* Le corps de la carte semi-transparent pour voir l'effet en dessous */
.card.tv-wild_foil .card-body {
  background: rgba(8,8,8,0.70);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  position: relative;
  z-index: 1;
}
/* Force white text for wild foil cards in both themes */
.card.tv-wild_foil .dn-num,
.card.tv-wild_foil .card-name,
.card.tv-wild_foil .card-team,
.card.tv-wild_foil .card-year,
.card.tv-wild_foil .card-num,
.card.tv-wild_foil .card-rarity {
  color: #FFFFFF !important;
}
.card.tv-wild_foil .card-rarity {
  background: rgba(255,255,255,0.15);
}

@keyframes wild-spin {
  /* scale(1.45): a rotating RECTANGLE only covers its container when its
     inscribed radius exceeds the container's half-diagonal — true for
     cards, false for the wide modal header. The scale guarantees full
     coverage at every angle, everywhere, for free (pure transform). */
  from { transform: rotate(0deg) scale(1.45); }
  to   { transform: rotate(360deg) scale(1.45); }
}

/* ══════════════════════════════════════════════════════════
   MARQUEURS DE COIN — un socle, quatre glyphes, aucune teinte
   (1.58.0)

   POURQUOI CES JETONS NE SUIVENT PAS LE THÈME. C'est la question
   qu'on se posera en les lisant, alors autant y répondre ici : le
   fond d'un marqueur n'est pas la page, c'est le VISUEL de la
   carte — livrée rose Alpine, cyan Mercedes, tracé de circuit,
   foil, monogramme pâle. Sur une tuile Mercedes, le thème sombre
   ne rend pas le fond sombre. Un jeton qui basculerait avec le
   thème ne serait donc juste que par accident. Voile fixe + glyphe
   blanc, c'est le seul repère stable des deux côtés.

   POURQUOI PAS DE COULEUR. Les tuiles portent déjà la couleur
   d'écurie, la rareté et les foils ; un marqueur teinté de plus est
   du bruit sur une information secondaire. Les quatre se lisent par
   leur FORME — couronne anguleuse, flèches circulaires, arceau
   d'écoute, rosette pleine.

   CONTRASTE MESURÉ du glyphe sur le voile composité, pire cas =
   visuel blanc pur : 4,76 (seuil WCAG 1.4.11 pour un objet
   graphique : 3). Un voile à 0.46 n'y donnait que 3,47, soit une
   marge d'une décimale ; 0.55 la triple sans assombrir les tuiles
   sombres (19,45 → 19,72).
   ══════════════════════════════════════════════════════════ */
:root{
  --mark-ink:#FFFFFF;
  --mark-scrim:rgba(0,0,0,0.55);
  --mark-ring:rgba(255,255,255,0.20);
  --mark-shadow:0 1px 3px rgba(0,0,0,0.45);
  --mark-box:22px;
  --mark-glyph:14px;
  --mark-inset:6px;
  --mark-stack:32px;   /* 2e marqueur du même coin */
}
.card-visual .crown,
.card-visual .replacement-icon,
.card-visual .director-icon,
.card-visual .set-flag,
.modal-visual .set-flag{
  position:absolute;z-index:2;padding:0;
  width:var(--mark-box);height:var(--mark-box);
  display:flex;align-items:center;justify-content:center;
  border-radius:var(--r-pill);
  background:var(--mark-scrim);
  border:1px solid var(--mark-ring);
  box-shadow:var(--mark-shadow);
  backdrop-filter:blur(2px);
  color:var(--mark-ink);
}
.card-visual .crown .ic,
.card-visual .replacement-icon .ic,
.card-visual .director-icon .ic,
.card-visual .set-flag .ic,
.modal-visual .set-flag .ic{
  width:var(--mark-glyph);height:var(--mark-glyph);display:block;filter:none;
}
.card-visual .crown{top:var(--mark-inset);left:var(--mark-inset);}
.card-visual .replacement-icon,
.card-visual .director-icon,
.card-visual .set-flag{top:var(--mark-inset);right:var(--mark-inset);}

/* ── SET COMPLET — au moins 1 exemplaire de CHAQUE type de la carte.
   Liseré : fin double anneau NEUTRE hors de la tuile (outline décalé),
   volontairement distinct du liseré doré inset des promos et des halos
   foil. Badge : pastille 🏁 en coin du visuel, pendant de la couronne
   champion. 100 % statique — rien à couper en prefers-reduced-motion. */
.card.set-complete{outline:1.5px solid color-mix(in srgb, var(--tx1) 30%, transparent);outline-offset:3px;}
/* Un marqueur de CATÉGORIE occupe déjà le coin : le sceau glisse dessous.
   Les deux sélecteurs, pas seulement la réserve — le directeur porte le
   même rôle depuis 1.58.0, et un seul des deux aurait suffi à laisser
   passer une superposition. */
.replacement-icon ~ .set-flag,
.director-icon ~ .set-flag{top:var(--mark-stack);}
/* Fiche carte : même badge sur le visuel + liseré inset neutre (le
   liseré externe est un langage de tuile ; ici il suit le visuel) */
.modal-visual.set-complete{box-shadow:inset 0 0 0 1.5px color-mix(in srgb, var(--tx1) 30%, transparent);}
.modal-visual{--mark-box:26px;--mark-glyph:16px;}
.modal-visual .set-flag{top:10px;right:52px;}

/* ── Rareté ÉTERNEL (7e niveau) — « balayage stellaire » ──
   Liseré or (remplace le double-ring set-complet — le badge 🏁 reste),
   rai de lumière périodique (même technique que le foil nitro) et
   étoiles ✦ scintillantes. Placé APRÈS les règles set-complete pour
   que le liseré or gagne la cascade (même spécificité). */
.card.rar-eternal-fx{
  outline:2.5px solid #FACC15;outline-offset:3px;
  box-shadow:0 0 14px 2px rgba(250,204,21,0.55),0 0 34px 8px rgba(250,204,21,0.22);
  animation:eternalGlow 3s ease-in-out infinite;
}
@keyframes eternalGlow{
  50%{box-shadow:0 0 22px 5px rgba(250,204,21,0.85),0 0 50px 12px rgba(250,204,21,0.35);}
}
.modal-visual.rar-eternal-fx{box-shadow:inset 0 0 0 2.5px #FACC15,inset 0 0 26px rgba(250,204,21,0.35);}
.card.rar-eternal-fx::after,.modal-visual.rar-eternal-fx::after{
  content:'';position:absolute;inset:0;pointer-events:none;z-index:5;
  background:linear-gradient(105deg,transparent 40%,rgba(255,255,255,0.85) 49%,rgba(250,204,21,0.85) 52%,transparent 62%);
  background-size:300% 100%;background-repeat:no-repeat;background-position:-80% 0;
  animation:eternalSweep 5s ease-in-out infinite;
}
@keyframes eternalSweep{
  0%{background-position:-80% 0;}
  28%{background-position:180% 0;}
  100%{background-position:180% 0;}
}
.eternal-spark{position:absolute;z-index:6;color:#FACC15;text-shadow:0 0 6px #fff,0 0 10px #FACC15;pointer-events:none;font-size:14px;}
/* Positions choisies pour ne chevaucher AUCUN élément de la tuile :
   s1 à droite de la couronne 👑, s2 sous le badge 🏁 et au-dessus de la
   zone du bouton +, s3 sur le bord gauche à mi-hauteur. */
.eternal-spark.s1{top:6px;left:32px;animation:eternalTwinkle 2.2s ease-in-out infinite;}
.eternal-spark.s2{top:32px;right:10px;font-size:11px;animation:eternalTwinkle 2.2s ease-in-out 0.7s infinite;}
/* Fiche : le ✕ occupe le coin haut-droit — s2 descend sous lui */
.modal-visual .eternal-spark.s2{top:56px;right:14px;}
.eternal-spark.s3{top:42%;left:6px;font-size:9px;animation:eternalTwinkle 2.2s ease-in-out 1.4s infinite;}
@keyframes eternalTwinkle{0%,100%{opacity:0.25;transform:scale(0.8);}50%{opacity:1;transform:scale(1.15);}}
@media (prefers-reduced-motion: reduce){
  .card.rar-eternal-fx{animation:none;}
  .card.rar-eternal-fx::after,.modal-visual.rar-eternal-fx::after{animation:none;background:none;}
  .eternal-spark{animation:none;opacity:1;}
}
.type-badge{position:absolute;top:6px;right:6px;font-size:9px;font-weight:700;padding:2px var(--space-6px);border-radius:5px;background:rgba(0,0,0,0.55);color:white;letter-spacing:0.2px;backdrop-filter:blur(2px);}

/* Card body */
/* flex:1 1 auto + min-height:0 : le corps occupe ce qui reste sous le
   visuel et n'impose plus sa hauteur naturelle à la tuile. */
.card-body{padding:var(--space-10px) var(--space-3) var(--space-10px);position:relative;z-index:1;flex:1 1 auto;min-height:0;display:flex;flex-direction:column;}
.card-num{font-family:var(--font-d);font-weight:700;font-size:11px;letter-spacing:0.5px;color:var(--tx1);margin-bottom:2px;}
/* Borné à 2 lignes : sans ça le pire cas est dicté par un seul GP à
   rallonge (« Gran Premio Del Made In Italy E Dell'Emilia-Romagna »,
   4 lignes / 62,4 px) qui imposerait 344 px à toute la grille. Six
   cartes sont concernées ; le nom complet reste lisible dans la fiche. */
.card-name{font-family:var(--font-d);font-weight:700;font-size:13px;line-height:1.2;margin-bottom:2px;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}
.card-year{font-family:var(--font-d);font-weight:600;font-size:11px;color:var(--tx1);margin-bottom:2px;text-transform:uppercase;letter-spacing:0.5px;}
.card-team{font-size:11px;color:var(--tx1);margin-bottom:var(--space-1);line-height:1.3;}
.card-rarity{display:inline-flex;align-items:center;gap:3px;padding:2px var(--space-2);border-radius:6px;font-size:11px;font-weight:600;}
/* margin-top:auto — la rangée rareté et la pastille d'exemplaires
   tombent en BAS de la tuile. Effet de bord voulu : elles s'alignent
   d'une tuile à l'autre sur toute la rangée, ce qu'une hauteur
   variable rendait impossible. */
.card-rarity-row{display:flex;align-items:center;justify-content:space-between;margin-top:auto;}
.card-rarity-row .status-chips{display:flex;gap:3px;}

/* Owned summary */
/* min-height : l'emplacement est TOUJOURS réservé, occupé ou non —
   sinon les 61 cartes sans exemplaire seraient plus courtes. */
.card-owned-summary{display:flex;flex-wrap:wrap;gap:var(--space-1);margin-top:var(--space-1);margin-bottom:var(--space-1);min-height:20px;}
.owned-tag{font-size:11px;padding:2px var(--space-2);border-radius:6px;background:var(--surface2);color:var(--tx2);font-weight:500;display:flex;align-items:center;gap:3px;}
.owned-tag.blue{background:#E3EEF9;color:#1a3a6b;}
.owned-tag.green{background:#E3F5E9;color:#0d4a1f;}
.owned-tag.red{background:#FFE8EC;color:#cc0000;}
.owned-tag.yellow{background:#FFF8DB;color:#6b5200;}
.owned-tag.foil{background:#EDE9FE;color:#5B21B6;}
.owned-tag.nitro{background:#F3E8FF;color:#7C3AED;}
.owned-tag.promo{background:#FFE8EC;color:#E8002D;}

/* Card footer */
.status-chips{display:flex;gap:3px;}
.schip{width:24px;height:24px;border-radius:7px;border:1px solid var(--border);display:flex;align-items:center;justify-content:center;font-size:11px;cursor:pointer;transition:var(--ease);background:var(--surface);}
.schip:hover{transform:scale(1.15);}
.schip[data-s="owned"].on{background:#E8F5E9;border-color:#34C759;}
.schip[data-s="wishlist"].on{background:#FCE7F3;border-color:#EC4899;}
.schip[data-s="doubles"].on{background:#E8F0FE;border-color:#007AFF;}
.schip[data-s="favorite"].on{background:#FDE8EC;border-color:var(--red);}
.qbtn{width:20px;height:20px;border-radius:6px;border:1px solid var(--border);background:var(--surface);font-size:13px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--tx2);transition:var(--ease);font-family:var(--font-d);font-weight:700;line-height:1;}
.qbtn:hover{background:var(--red-s);color:var(--red);}

/* EMPTY */
.empty{grid-column:1/-1;text-align:center;padding:80px var(--space-10);color:var(--tx3);}

/* ══════════════════════════════════════════════════════════
   ÉTAT DU CATALOGUE DE SAISON — deux intensités, DEUX TONS
   Un catalogue partiel est un état NORMAL et temporaire (les cartes
   arrivent par boosters, la saisie s'étale). Il informe : surface
   neutre, texte secondaire, un ⓘ — aucune couleur d'alerte, aucun ⚠.
   Un catalogue vide est plus proche d'un problème de données : il peut
   être affirmatif, et il occupe la grille entière.
   ══════════════════════════════════════════════════════════ */
.season-partial{grid-column:1/-1;display:flex;gap:12px;align-items:flex-start;
  background:var(--surface2);border:1px solid var(--border);border-radius:12px;
  padding:12px 14px;margin-bottom:4px;}
.season-partial .sp-ic{flex:0 0 auto;width:20px;height:20px;color:var(--tx3);margin-top:1px;}
.season-partial .sp-txt{font-size:12px;line-height:1.45;color:var(--tx2);}
.season-partial .sp-txt b{color:var(--tx1);font-weight:700;}
.season-partial .sp-sub{display:block;margin-top:3px;color:var(--tx3);font-size:11px;}
/* display:block OBLIGATOIRE — un <span> reste inline, et `height` ne
   s'y applique pas : la barre était invisible dans la maquette. */
.season-partial .sp-bar{display:block;margin-top:7px;height:4px;border-radius:2px;
  background:var(--surface3);overflow:hidden;}
.season-partial .sp-bar i{display:block;height:4px;background:var(--tx3);border-radius:2px;}

.season-empty{grid-column:1/-1;text-align:center;padding:56px var(--space-6);}
.season-empty .se-ic{width:44px;height:44px;margin:0 auto var(--space-3);color:var(--tx3);opacity:.8;}
.season-empty .se-title{font-family:var(--font-d);font-size:17px;font-weight:700;color:var(--tx1);margin-bottom:var(--space-2);}
.season-empty .se-body{font-size:13px;line-height:1.5;color:var(--tx2);max-width:290px;margin:0 auto;}
.season-empty .se-hint{display:block;margin-top:var(--space-10px);font-size:12px;color:var(--tx3);}

/* MODAL */
.mo{position:fixed;inset:0;background:rgba(0,0,0,0.4);backdrop-filter:blur(8px);z-index:300;display:flex;align-items:center;justify-content:center;opacity:0;pointer-events:none;transition:opacity 0.2s;}
.mo.open{opacity:1;pointer-events:all;}
.modal{background:var(--surface);border-radius:var(--r-xl);width:560px;max-width:calc(100vw - 32px);max-height:calc(100vh - 60px);overflow-y:auto;box-shadow:0 24px 80px rgba(0,0,0,0.2);transform:scale(0.96) translateY(8px);transition:transform 0.2s cubic-bezier(0.25,0.46,0.45,0.94);}
.mo.open .modal{transform:scale(1) translateY(0);}
.modal-visual{height:160px;display:flex;align-items:center;justify-content:center;font-size:64px;position:relative;border-radius:var(--r-xl) var(--r-xl) 0 0;overflow:hidden;isolation:isolate;}
.modal-visual.not-owned{background:var(--not-owned);}

/* ── Modal mirrors of the card-scoped effects (wild / nitro) ──
   Their grid visuals live on .card.tv-* (whole-card conic beams), so the
   modal header re-creates the exact same treatment at its own scale.
   Simple foils, dual foils and promos need nothing here: their .tv-*
   classes are element-generic and apply to .modal-visual directly. */
.modal-visual.tv-wild_foil{background:#080808;}
.modal-visual.tv-wild_foil::before{
  content:'';position:absolute;inset:-100%;pointer-events:none;z-index:0;
  background:conic-gradient(
    from 0deg,
    transparent           0deg,
    rgb(30 80 220 / var(--f-beam-a))  50deg,
    transparent           90deg,
    rgb(20 160 50 / var(--f-beam-a))  140deg,
    transparent           180deg,
    rgb(220 0 0 / var(--f-beam-a))    230deg,
    transparent           270deg,
    rgb(255 208 0 / var(--f-beam-a))  320deg,
    transparent           360deg
  );
  animation:wild-spin var(--f-beam-dur) linear infinite;
}
.modal-visual.tv-wild_foil::after{
  content:'';position:absolute;inset:0;pointer-events:none;z-index:2;
  background:linear-gradient(135deg,rgba(255,255,255,0.10) 0%,transparent 40%,rgba(255,255,255,0.05) 100%);
}
.modal-visual.tv-wild_foil #moEmoji{position:relative;z-index:1;}
@media (prefers-reduced-motion:reduce){
  .modal-visual.tv-wild_foil::before,
  .card.tv-wild_foil::before,
  .card.tv-nitro_foil::before,
  .modal-visual.tv-nitro_foil::before{animation:none;transform:rotate(45deg) scale(1.45);}
  /* Les bandes (foil, dual, promo, grille nitro) se figent EN TRAVERS
     de la tuile — l'effet reste lisible à l'arrêt. */
  .card-visual[class*="tv-"]::after,.modal-visual[class*="tv-"]::after,
  .card.tv-nitro_foil::after,.modal-visual.tv-nitro_foil::after{
    animation:none;transform:translateX(60%);}
}
.modal-close{position:absolute;top:12px;right:12px;width:30px;height:30px;border-radius:50%;background:rgba(255,255,255,0.9);border:none;cursor:pointer;font-size:14px;display:flex;align-items:center;justify-content:center;transition:var(--ease);z-index:5;}
.modal-close:hover{transform:scale(1.1);}
.modal-body{padding:var(--space-5);}
.mo-num{font-family:var(--font-d);font-weight:700;font-size:11px;letter-spacing:1px;color:var(--tx3);text-transform:uppercase;margin-bottom:var(--space-1);}
.mo-name{font-family:var(--font-d);font-weight:800;font-size:22px;line-height:1.1;margin-bottom:var(--space-1);}
.mo-team{font-size:13px;color:var(--tx2);margin-bottom:var(--space-3);}
.mo-tags{display:flex;flex-wrap:wrap;gap:var(--space-2);margin-bottom:var(--space-4);}

/* ── Reading order: identity → rarity → status → figures → description ──
   Rarity gets its own line (it used to be buried among the champion /
   legend tags), and the status row is read-only: quantities are still
   edited from the grid below and from the card chips. */
.mo-rarity{margin-bottom:var(--space-3);}
.mtag-rarity{font-size:12px;padding:var(--space-1) var(--space-3);}
.mo-status{display:flex;flex-wrap:wrap;gap:var(--space-2);margin-bottom:var(--space-4);}
.mo-st{
  font-size:11px;font-weight:600;padding:var(--space-1) var(--space-2);
  border-radius:var(--r-pill);border:1px solid var(--border);
  background:var(--surface2);color:var(--tx3);opacity:0.5;
}
.mo-st.on{opacity:1;color:var(--tx1);border-color:var(--tx3);background:var(--surface);}
.mtag{padding:3px var(--space-2);border-radius:7px;font-size:11px;font-weight:500;background:var(--surface2);color:var(--tx2);}
.mtag.champion{background:#FFF9C4;color:#8B6914;}
.mtag.legend{background:#F3E8FF;color:#7C3AED;}
.mo-desc{font-size:13px;line-height:1.6;color:var(--tx2);padding:var(--space-3);background:var(--surface2);border-radius:var(--r-md);margin-bottom:var(--space-4);}
.mo-info-row{display:flex;gap:var(--space-2);margin-bottom:var(--space-4);flex-wrap:wrap;}
.mib{flex:1;min-width:100px;padding:var(--space-3);background:var(--surface2);border-radius:var(--r-md);}
.mib-l{font-size:11px;font-weight:600;letter-spacing:0.5px;text-transform:uppercase;color:var(--tx3);margin-bottom:var(--space-1);}
.mib-v{font-family:var(--font-d);font-weight:700;font-size:15px;}

/* MODAL — exemplaires par type (grid 4 colonnes) */
/* Single section-heading style: it used to share its size with .mo-num
   and .mib-l, which flattened the hierarchy. Now it reads as a divider. */
.mo-variants-title{
  font-size:12px;font-weight:700;letter-spacing:0.6px;text-transform:uppercase;
  color:var(--tx2);margin-bottom:var(--space-3);padding-top:var(--space-3);
  border-top:1px solid var(--border);
}
.mo-type-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--space-6px);margin-bottom:var(--space-4);}
/* minmax(0,1fr) instead of 1fr: a bare 1fr track refuses to shrink below
   its content's min size, so on a 375px screen the fourth column was
   pushed outside the modal. The cells' inner text needs min-width:0 too. */
.mo-type-cell{min-width:0;}
.mo-type-cell .mo-cell-label{max-width:100%;}
.mo-type-cell{display:flex;flex-direction:column;align-items:center;gap:var(--space-1);padding:var(--space-2) var(--space-1) var(--space-6px);border-radius:12px;border:1px solid var(--border);background:var(--surface2);transition:var(--ease);position:relative;}
.mo-type-cell:hover{border-color:var(--red-m);}
.mo-type-cell.has-qty{border-color:rgba(52,199,89,0.3);background:rgba(52,199,89,0.06);}
[data-theme="dark"] .mo-type-cell.has-qty{background:rgba(52,199,89,0.1);}
.mo-type-cell .mo-cell-icon{width:28px;height:28px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:14px;flex-shrink:0;}
.mo-type-cell .mo-cell-label{font-size:11px;font-weight:600;text-align:center;line-height:1.1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;}
.mo-type-cell .mo-qty-wrap{display:flex;align-items:center;gap:1px;background:var(--surface);border-radius:8px;padding:1px;border:1px solid var(--border);}
.mo-type-cell .mqbtn{width:22px;height:22px;border-radius:6px;border:none;background:transparent;font-size:14px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:var(--ease);font-family:var(--font-d);font-weight:700;color:var(--tx2);}
.mo-type-cell .mqbtn:hover{background:var(--red-s);color:var(--red);}
.mo-type-cell .mqbtn:active{transform:scale(0.9);}
.mo-type-cell .mqval{font-family:var(--font-d);font-weight:800;font-size:13px;min-width:20px;text-align:center;color:var(--tx1);}
.mo-grid-row-label{grid-column:1/-1;font-size:11px;font-weight:700;letter-spacing:0.5px;text-transform:uppercase;color:var(--tx3);margin-top:var(--space-1);padding:0 2px;}
.mo-type-cell.empty{visibility:hidden;}

/* BG */
.bga{position:fixed;border-radius:50%;pointer-events:none;z-index:-1;}
.bga1{width:600px;height:600px;background:radial-gradient(circle,rgba(232,0,45,0.04) 0%,transparent 70%);top:-200px;right:-100px;}
.bga2{width:400px;height:400px;background:radial-gradient(circle,rgba(0,76,151,0.04) 0%,transparent 70%);bottom:-100px;left:-100px;}

/* TOAST */
/* n°17 (POINTS-SIGNALES) — pendant la visite guidée, le toast quitte la
   zone de la nav : il recouvrait l'onglet que le projecteur désignait
   (mesuré : elementFromPoint(centre du halo) rendait le toast, pas
   l'onglet — l'utilisateur obéissant cliquait « Annuler »). 130px = la
   bande NAV_CLEAR du moteur du tutoriel. Le garde est dans
   verify_tutorial.py : si cette règle saute, le parcours échoue à
   « Direction les stats ». */
body:has(.tut-overlay) .toast{bottom:130px;}
.toast{position:fixed;bottom:24px;right:24px;background:var(--surface3);color:var(--tx1);border:1px solid var(--border-s);padding:var(--space-10px) var(--space-4);border-radius:12px;font-size:13px;font-weight:500;box-shadow:var(--sh-lg);z-index:500;opacity:0;transform:translateY(8px);transition:all 0.2s;pointer-events:none;}
.toast.show{opacity:1;transform:translateY(0);pointer-events:auto;}
.toast-action{margin-left:var(--space-3);border:none;background:none;color:#4CD964;font-weight:700;font-size:13px;cursor:pointer;font-family:inherit;padding:0;}

/* ── Quick add : bouton + sur la tuile, sélecteur de variante ── */
.qbtn{position:absolute;bottom:8px;right:8px;z-index:4;width:34px;height:34px;border-radius:50%;
  border:1px solid var(--border);background:var(--surface);color:var(--tx1);
  font-size:19px;font-weight:700;line-height:1;cursor:pointer;
  display:flex;align-items:center;justify-content:center;
  box-shadow:0 2px 8px rgba(0,0,0,0.22);transition:transform 0.12s ease,border-color 0.12s ease,color 0.12s ease;}
.qbtn:hover{transform:scale(1.1);border-color:var(--green);color:var(--green);}
.qbtn:active{transform:scale(0.94);}
.qadd-pop{position:absolute;inset:4px;z-index:6;display:flex;flex-wrap:wrap;gap:4px;
  align-content:center;justify-content:center;padding:6px;overflow-y:auto;
  background:color-mix(in srgb, var(--surface) 94%, transparent);
  border:1px solid var(--border);border-radius:12px;box-shadow:var(--sh-lg);}
.qadd-type{position:relative;width:30px;height:30px;border-radius:9px;border:1px solid var(--border);
  background:var(--surface2);font-size:14px;cursor:pointer;
  display:flex;align-items:center;justify-content:center;transition:transform 0.1s ease,border-color 0.1s ease;}
.qadd-type:hover{transform:scale(1.08);border-color:var(--green);}
.qadd-qty{position:absolute;top:-5px;right:-5px;min-width:14px;height:14px;padding:0 3px;border-radius:7px;
  background:var(--green);color:#fff;font-size:11px;font-weight:700;
  display:flex;align-items:center;justify-content:center;}

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

/* BADGES PAGE */
.badges-view{display:none;flex-direction:column;gap:var(--space-5);padding:0 var(--space-4) var(--nav-clear);overflow-y:auto;height:100%;flex:1 1 auto;min-width:0;}
.badges-view.active{display:flex;}
/* ── BADGES v2.2 — hero, prochain badge, familles, tuiles, détail ── */
.bh-row{display:flex;align-items:center;gap:var(--space-4);}
.bh-ring{position:relative;width:86px;height:86px;flex:none;}
.bh-ring svg{transform:rotate(-90deg);display:block;}
.bh-bg{stroke:var(--surface3);}
.bh-fg{stroke:var(--red);stroke-linecap:round;}
.bh-num{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;font-family:var(--font-d);}
.bh-num b{font-size:22px;line-height:1;color:var(--tx1);}
/* 10 → 11 px : le « / 120 » est le dénominateur du compteur de badges,
   donc une information. L'anneau qui l'entoure fait 86 px et ne bouge
   pas ; la pile interne passe de 36,5 à 38 px, le libellé de 24,1 à
   26,5 px de large. Aucun débordement, aucun décalage. */
.bh-num span{font-size:11px;color:var(--tx3);margin-top:2px;}
.bh-right{flex:1;min-width:0;}
.bh-row .user-title-wrap{background:none;border:none;padding:0;margin:0;flex-wrap:wrap;}
.bh-hardest{font-size:11px;color:var(--tx3);margin-top:var(--space-1);}
.bh-hardest b{color:var(--tx2);font-weight:600;}
/* prochain badge / objectif */
.bn-next{background:linear-gradient(135deg,var(--surface),var(--surface2));border:1px solid var(--border-s);border-radius:var(--r-md);padding:var(--space-3) var(--space-14px);display:flex;align-items:center;gap:var(--space-3);cursor:pointer;}
.bn-med{width:44px;height:44px;flex:none;border-radius:50%;background:var(--surface3);display:flex;align-items:center;justify-content:center;font-size:22px;}
.bn-body{flex:1;min-width:0;}
.bn-k{font-size:11px;font-weight:700;letter-spacing:0.08em;text-transform:uppercase;color:var(--red);}
.bn-n{font-weight:700;font-size:14px;margin:1px 0 5px;font-family:var(--font-d);color:var(--tx1);}
.bn-bar{height:5px;border-radius:3px;background:var(--surface3);overflow:hidden;}
.bn-bar i{display:block;height:100%;border-radius:3px;background:var(--red);}
.bn-s{font-size:11px;color:var(--tx2);margin-top:4px;}
.bn-s b{color:var(--tx1);font-variant-numeric:tabular-nums;}
.bn-empty{background:var(--surface);border:1px dashed var(--border-s);border-radius:var(--r-md);padding:var(--space-14px);text-align:center;font-size:12px;color:var(--tx2);line-height:1.5;}
.bn-empty b{color:var(--tx1);}
.bn-empty.full{border-style:solid;}
/* familles — accents par tokens sémantiques (l'or = identité Éternel) */
.badge-fam{margin-top:var(--space-2);}
.bf-parcours{--fam:var(--red);}
.bf-sets{--fam:var(--gold);}
.bf-foils{--fam:var(--purple);}
.bf-colors{--fam:var(--blue);}
.bf-passion{--fam:var(--orange);}
.bf-exp{--fam:var(--green);}
.bfh{display:flex;align-items:baseline;gap:var(--space-2);margin-bottom:var(--space-2);}
.bfh-ico{font-size:14px;}
.bfh-name{font-family:var(--font-d);font-weight:700;font-size:15px;color:var(--tx1);}
.bfh-count{font-size:11px;color:var(--tx3);font-variant-numeric:tabular-nums;margin-left:auto;}
.bfh-bar{height:3px;border-radius:2px;background:var(--surface2);margin-bottom:var(--space-3);overflow:hidden;}
.bfh-bar i{display:block;height:100%;background:var(--fam);border-radius:2px;}
/* échelle (Parcours) */
.badge-ladder{display:flex;padding:var(--space-1) 2px var(--space-2);}
.bl-rung{flex:1;display:flex;flex-direction:column;align-items:center;gap:5px;position:relative;cursor:pointer;}
.bl-rung::before{content:'';position:absolute;top:19px;left:-50%;width:100%;height:2px;background:var(--surface3);z-index:0;}
.bl-rung:first-child::before{display:none;}
.bl-rung.done::before{background:var(--green);}
.bl-dot{width:38px;height:38px;border-radius:50%;background:var(--surface2);border:2px solid var(--surface3);display:flex;align-items:center;justify-content:center;font-size:17px;position:relative;z-index:1;transition:var(--ease);}
.bl-rung.done .bl-dot{border-color:var(--green);background:var(--green-s);}
.bl-rung.cur .bl-dot{border-color:var(--red);box-shadow:0 0 0 4px var(--red-glow);}
.bl-rung.lock .bl-dot{filter:grayscale(1);opacity:0.45;}
.bl-lbl{font-size:11px;color:var(--tx3);font-variant-numeric:tabular-nums;}
.bl-rung.done .bl-lbl{color:var(--green);}
.bl-rung.cur .bl-lbl{color:var(--tx1);font-weight:700;}
/* tuiles */
.badge-tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(72px,1fr));gap:var(--space-2);}
.badge-tile{--p:0;display:flex;flex-direction:column;align-items:center;gap:5px;cursor:pointer;position:relative;padding:4px 0;border-radius:12px;}
.bt-med{width:52px;height:52px;border-radius:50%;background:var(--surface2);border:2px solid var(--border-s);display:flex;align-items:center;justify-content:center;font-size:24px;position:relative;transition:transform 0.15s ease;}
.badge-tile:hover .bt-med{transform:translateY(-2px);}
.bt-name{font-size:12px;font-weight:600;color:var(--tx2);text-align:center;line-height:1.15;max-width:100%;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;}
.badge-tile.un .bt-med{border-color:var(--fam);background:color-mix(in srgb,var(--fam) 14%,var(--surface2));}
.badge-tile.un .bt-name{color:var(--tx1);}
.badge-tile.prog .bt-med{border:none;background-image:conic-gradient(var(--fam) calc(var(--p)*1%),var(--surface3) 0);}
.badge-tile.prog .bt-med::before{content:'';position:absolute;inset:3px;border-radius:50%;background:var(--surface2);}
.badge-tile.prog .bt-med span{position:relative;}
.badge-tile.prog .bt-name{margin-top:9px;}
.bt-pp{position:absolute;top:44px;left:50%;transform:translateX(-50%);background:var(--surface3);border:1px solid var(--border-s);color:var(--tx2);font-size:11px;font-weight:700;padding:1px 5px;border-radius:8px;font-variant-numeric:tabular-nums;z-index:2;}
/* .bt-pp est passé de 8 à 11 px comme le reste du plancher. Elle porte
   une INFORMATION (« 8/25 » — l'avancement du badge), pas une décoration.
   Coût mesuré à 320 px : la pastille passe de 35,8 à 44,8 px de large sur
   une tuile de 88,7 (elle reste dedans), et l'écart avec le nom du badge
   tombe de 11 à 6 px. Zéro décalage : elle est en position:absolute, la
   hauteur de tuile ne bouge pas. */
.badge-tile.lock .bt-med{filter:grayscale(1);opacity:0.4;}
.badge-tile.lock .bt-name{color:var(--tx3);opacity:0.75;}
.bt-chk{position:absolute;top:0;right:calc(50% - 30px);width:16px;height:16px;border-radius:50%;background:var(--green);color:#fff;font-size:11px;display:flex;align-items:center;justify-content:center;border:2px solid var(--bg);z-index:2;}
.badge-tile:focus-visible{outline:2px solid var(--red);outline-offset:2px;}
/* Éternel : champ_all porte l'identité or existante */
.badge-tile.eternal .bt-med{border:2px solid transparent;background:linear-gradient(var(--surface2),var(--surface2)) padding-box,linear-gradient(120deg,var(--surface3),var(--gold),var(--surface3)) border-box;}
.badge-tile.eternal.un .bt-med{box-shadow:0 0 14px color-mix(in srgb,var(--gold) 35%,transparent);}
.badge-tile.eternal.un .bt-name{color:var(--gold);font-weight:700;}
/* détail */
.badge-detail{grid-column:1/-1;background:var(--surface);border:1px solid var(--border-s);border-radius:var(--r-md);padding:var(--space-3) var(--space-14px);font-size:12px;display:none;margin-top:var(--space-1);}
.badge-detail.open{display:block;}
.bd-name{font-family:var(--font-d);font-weight:700;font-size:14px;color:var(--tx1);margin-bottom:2px;}
.bd-desc{color:var(--tx2);margin-bottom:var(--space-2);}
.bd-date{font-size:11px;color:var(--green);font-weight:600;margin-bottom:var(--space-2);}
.bd-bar{height:5px;background:var(--surface3);border-radius:3px;overflow:hidden;margin-bottom:4px;}
.bd-bar i{display:block;height:100%;background:var(--fam,var(--red));}
.bd-sub{font-size:11px;color:var(--tx3);margin-bottom:var(--space-2);}
.bd-chips{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:var(--space-2);}
.bd-chip{background:var(--surface2);border:1px solid var(--border);border-radius:6px;padding:2px 7px;font-size:11px;color:var(--tx2);}
.bd-chip.miss{opacity:0.55;text-decoration:line-through;}
.bd-actions{display:flex;gap:var(--space-2);}
.bd-btn{background:var(--surface2);border:1px solid var(--border-s);border-radius:var(--r-pill);padding:7px 16px;font-family:var(--font-b);font-size:12px;font-weight:600;color:var(--tx2);cursor:pointer;transition:var(--ease);}
.bd-btn:hover{color:var(--red);border-color:var(--red);}
.bd-btn.primary{background:var(--red);border-color:transparent;color:#fff;}
.bd-btn.primary:hover{opacity:0.88;color:#fff;}
/* célébration d'une tuile */
.badge-tile.just-unlocked .bt-med{animation:badgeCelebrate 0.7s cubic-bezier(0.22,0.9,0.36,1) both;}
@keyframes badgeCelebrate{0%{transform:scale(1);}35%{transform:scale(1.25) rotate(-4deg);}70%{transform:scale(0.96);}100%{transform:scale(1);}}
.badge-particles{position:absolute;inset:0;pointer-events:none;overflow:hidden;z-index:5;}
.badge-particle{position:absolute;width:6px;height:6px;border-radius:50%;animation:particleFly 0.8s ease-out forwards;}
@keyframes particleFly{
  0%{opacity:1;transform:translate(0,0) scale(1);}
  100%{opacity:0;transform:translate(var(--px),var(--py)) scale(0);}
}

/* toast badge — même anatomie que le toast standard, au-dessus de la nav */
.badge-toast{position:fixed;left:50%;transform:translate(-50%,16px);bottom:calc(var(--nav-clear) + 6px);z-index:300;display:flex;align-items:center;gap:var(--space-10px);background:var(--surface);border:1px solid var(--border-s);border-radius:var(--r-md);padding:var(--space-10px) var(--space-4);box-shadow:var(--sh-lg);opacity:0;pointer-events:none;transition:opacity 0.35s ease,transform 0.35s cubic-bezier(0.22,0.9,0.36,1);cursor:pointer;}
.badge-toast.show{opacity:1;transform:translate(-50%,0);pointer-events:all;}
/* ── Invitation à laisser un avis ──────────────────────────────
   Même grammaire que .badge-toast : elle se pose là où la célébration
   vient de disparaître, avec la même carte et la même ombre. Plus haute,
   parce qu'elle porte trois actions — et chacune fait 44 px, le plancher
   tactile imposé à tout le dépôt depuis la phase C.

   Rien n'est bloquant : aucun voile, aucun piège de focus, l'app
   continue de fonctionner dessous. Une invitation qui empêche
   d'utiliser l'app ne mérite pas de réponse honnête. */
.rv-invite{position:fixed;left:50%;transform:translate(-50%,16px);
  bottom:calc(var(--nav-clear) + 6px);z-index:300;
  width:min(420px, calc(100vw - 28px));box-sizing:border-box;
  display:flex;align-items:flex-start;gap:var(--space-3);
  background:var(--surface);border:1px solid var(--border-s);border-radius:var(--r-md);
  padding:var(--space-3) var(--space-10px);box-shadow:var(--sh-lg);
  opacity:0;pointer-events:none;
  transition:opacity 0.35s ease,transform 0.35s cubic-bezier(0.22,0.9,0.36,1);}
.rv-invite.show{opacity:1;transform:translate(-50%,0);pointer-events:all;}
.rv-ic{flex:0 0 auto;width:32px;height:32px;border-radius:50%;background:var(--red-s);
  display:flex;align-items:center;justify-content:center;color:var(--red);}
.rv-ic .ic{width:17px;height:17px;}
.rv-body{flex:1;min-width:0;}
.rv-k{font-size:10px;font-weight:800;letter-spacing:0.09em;text-transform:uppercase;color:var(--red);}
.rv-t{font-size:12.5px;line-height:1.45;color:var(--tx1);margin:2px 0 8px;}
.rv-acts{display:flex;gap:6px;flex-wrap:wrap;}
.rv-btn{min-height:44px;padding:0 14px;border-radius:var(--r-pill);border:1.5px solid var(--border);
  background:var(--surface2);color:var(--tx2);font-family:var(--font-b);font-size:12px;font-weight:600;
  cursor:pointer;display:inline-flex;align-items:center;transition:var(--ease-fast);}
.rv-btn:hover{background:var(--surface3);}
.rv-btn.primary{border-color:var(--red);color:var(--red);}
.rv-x{flex:0 0 auto;width:44px;height:44px;margin:-9px -9px 0 0;border-radius:50%;
  border:none;background:none;color:var(--tx3);cursor:pointer;
  display:flex;align-items:center;justify-content:center;}
.rv-x .ic{width:14px;height:14px;}
@media (prefers-reduced-motion: reduce){
  .rv-invite{transition:opacity 0.2s ease;}
}
.bgt-med{width:36px;height:36px;border-radius:50%;background:var(--red-s);display:flex;align-items:center;justify-content:center;font-size:18px;}
.bgt-k{font-size:11px;font-weight:800;letter-spacing:0.1em;text-transform:uppercase;color:var(--red);}
.bgt-n{font-family:var(--font-d);font-weight:700;font-size:13px;color:var(--tx1);}
@media (prefers-reduced-motion: reduce){
  .badge-tile.just-unlocked .bt-med,.badge-particle{animation:none;}
  .bh-fg{transition:none !important;}
  .badge-toast{transition:opacity 0.2s ease;}
}

/* BOTTOM NAV — barre à « bead ». La silhouette (pilule + encoche) est
   un seul path SVG calculé dans render.js ; ici, uniquement l'habillage :
   dégradés, halo, états. La pastille n'a AUCUNE transition CSS — elle est
   animée avec l'encoche par la même boucle rAF, sinon les deux dérivent. */
.bottom-nav{
  position:fixed;
  bottom:calc(14px + env(safe-area-inset-bottom));
  left:50%;
  transform:translateX(-50%);
  z-index:200;
  pointer-events:none;
  background:transparent;
  filter:drop-shadow(0 8px 22px rgba(0,0,0,0.26));
}
.bead-nav-inner{position:relative;pointer-events:all;}
.bead-bar{display:block;}
.bead-bar .nav-bar-shape{fill:url(#beadBarFill);stroke:url(#beadBarEdge);stroke-width:1.25;}
.bead-bar .nav-bar-glow{fill:url(#beadGlowGrad);pointer-events:none;}
.bead{
  position:absolute;left:0;width:50px;height:50px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;
  color:#fff;z-index:3;cursor:grab;touch-action:none;
  -webkit-tap-highlight-color:transparent;
  background:radial-gradient(circle at 34% 26%, var(--nav-bead-hi), var(--nav-bead-lo) 78%);
  box-shadow:
    0 0 0 1px rgba(255,255,255,0.18) inset,
    0 0 8px 1px rgba(var(--nav-glow),0.50),
    0 0 18px 2px rgba(var(--nav-glow),0.22),
    0 6px 16px rgba(0,0,0,0.32);
}
.bead:active{cursor:grabbing;}
.bead .bn-icon{width:25px;height:25px;opacity:1;transform:none;}
.bottom-nav-pill{position:absolute;top:0;bottom:0;display:flex;background:none;border:none;padding:0;}
.bn-tab{
  flex:1;position:relative;min-width:var(--tap-min);
  display:flex;align-items:center;justify-content:center;
  border:none;background:transparent;
  font-family:var(--font-b);cursor:pointer;
  color:var(--tx3);transition:color 0.15s ease;
}
/* hover/focus : fond doux arrondi DANS la barre — jamais une forme qui
   rivalise avec la pastille */
.bn-tab::before{
  content:'';position:absolute;left:50%;top:50%;translate:-50% -50%;
  width:40px;height:40px;border-radius:50%;background:transparent;
  transition:background 0.15s ease;
}
/* Le survol est un DISQUE, pas un bloc : il reprend la forme de la
   pastille au lieu de lui opposer un rectangle. Teinte dérivée du texte
   plutôt qu'un gris opaque, qui trancherait sur le dégradé de la barre. */
.bn-tab:hover{color:var(--tx1);}
.bn-tab:hover::before{background:color-mix(in srgb, var(--tx1) 9%, transparent);}
.bn-tab.active::before{background:transparent;}
.bn-tab:focus-visible{outline:none !important;}
.bn-tab:focus-visible::before{
  background:color-mix(in srgb, var(--tx1) 9%, transparent);
  box-shadow:0 0 0 2px var(--red);
}
.bn-icon{width:22px;height:22px;transition:opacity 0.2s ease, transform 0.2s ease;}
.bn-tab.active .bn-icon{opacity:0;transform:translateY(-8px);} /* l'icône vit dans la pastille */
.bn-label{
  position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  font-size:11px;font-weight:700;line-height:1;white-space:nowrap;
  color:var(--red);text-shadow:0 1px 3px var(--surface),0 0 8px var(--surface);
  opacity:0;transform:translateY(3px);
  transition:opacity 0.2s ease, transform 0.2s ease;pointer-events:none;
}
.bn-tab.active .bn-label{opacity:1;transform:translateY(0);}
@media (prefers-reduced-motion: reduce){
  .bn-icon,.bn-label,.bn-tab,.bn-tab::before{transition:none;}
}

/* STATS VIEW */
.stats-view{display:none;padding:0 var(--space-4) var(--nav-clear);overflow-y:auto;height:100%;flex:1 1 auto;min-width:0;}
.stats-view.active{display:block;}

/* ══ Disposition « colonne principale + rail collant » (1.45.0) ══
   Avant, tout était centré à 800 px : sur un bureau de 1440 px la
   page n'occupait que 44 % de la largeur et faisait 3 500 px de
   haut. La colonne porte le récit, le rail garde donut, à la une et
   objectifs sous les yeux pendant le défilement. */
.sv-layout{display:grid;gap:var(--space-5);align-items:start;}
.sv-col,.sv-rail{display:grid;gap:var(--space-5);align-content:start;min-width:0;}
.sv-block{display:flex;flex-direction:column;min-width:0;}
.sv-breakcols{display:grid;gap:var(--space-5);grid-template-columns:repeat(auto-fit,minmax(260px,1fr));}
/* ── Répartitions (1.56.0) ──
   Sommaire de repères : rien n'est caché, on annonce seulement ce qui
   vient et on permet d'y aller. Les pastilles s'enroulent ; à 375 px
   elles tiennent sur DEUX lignes (mesuré), ce qui reste un repère et
   non un obstacle. */
.sv-jump{display:flex;flex-wrap:wrap;gap:var(--space-2);}
.sv-jump-chip{display:inline-flex;align-items:center;gap:var(--space-6px);min-height:36px;
  padding:var(--space-2) var(--space-3);border-radius:var(--r-pill);border:1px solid var(--border);
  background:var(--surface);color:var(--tx1);font-family:var(--font-b);font-size:12px;font-weight:600;
  cursor:pointer;transition:background 0.15s,border-color 0.15s;}
.sv-jump-chip:hover{background:var(--surface3);border-color:var(--tx3);}
.sv-jump-chip:focus-visible{outline:2px solid var(--red);outline-offset:2px;}
.sv-jump-n,.sv-jump-arrow{font-weight:700;color:var(--tx3);}
.sv-jump-skip{border-style:dashed;color:var(--tx2);}
/* Sous 380 px les cinq pastilles s'enroulaient sur TROIS lignes (124 px
   de hauteur mesurés à 320) : un repère qui prend trois lignes n'est plus
   un repère, c'est un obstacle de plus avant le contenu. Resserrées, elles
   tiennent sur deux lignes jusqu'à 320 px — sans rien retirer du texte. */
@media (max-width:380px){
  .sv-jump{gap:var(--space-6px);}
  .sv-jump-chip{font-size:11px;padding:var(--space-2);gap:4px;}
}
/* La colonne de 16 lignes n'est plus une colonne : pleine largeur, ses
   lignes en sous-colonnes. minmax(270px) donne 3 sous-colonnes au-delà
   de ~880 px, 2 entre ~570 et ~880, 1 en dessous — la dégradation est
   progressive, elle ne saute pas de 3 à 1. */
.sv-rows-split{display:grid;grid-template-columns:repeat(auto-fit,minmax(270px,1fr));overflow:hidden;}
.sv-rows-split .sv-row{border-bottom:1px solid var(--border);border-radius:0;}
/* 16 lignes sur 3 sous-colonnes laissent une orpheline sur le dernier
   rang : sa bordure basse doublerait celle du conteneur, juste sous elle
   et sur un tiers de largeur. */
.sv-rows-split .sv-row:last-child{border-bottom:none;}
@media(min-width:1000px){
  .sv-layout{grid-template-columns:minmax(0,1fr) 320px;}
  .sv-rail{position:sticky;top:var(--space-3);}
}

/* Stats page title */

/* Hero progress block */
.sv-progress{
  background:var(--surface);
  border:1px solid var(--border);
  border-radius:var(--r-lg);
  padding:var(--space-4) var(--space-5) var(--space-14px);
  box-shadow:var(--sh-sm);
  margin-bottom:var(--space-5);
  position:relative;
  overflow:hidden;
}
.sv-progress::before{
  content:'';position:absolute;inset:0;
  background:radial-gradient(ellipse 80% 60% at 10% 50%,rgba(232,0,45,0.05),transparent);
  pointer-events:none;
}
.sv-prog-hero{display:flex;align-items:center;gap:var(--space-14px);margin-bottom:var(--space-10px);}
.sv-prog-pct-big{font-family:var(--font-d);font-weight:900;font-size:36px;letter-spacing:-2px;color:var(--red);line-height:1;flex-shrink:0;}
.sv-prog-text{display:flex;flex-direction:column;gap:2px;}
.sv-prog-title{font-family:var(--font-d);font-weight:700;font-size:14px;color:var(--tx1);}
.sv-prog-sub{font-size:12px;color:var(--tx2);}
.sv-prog-bar{height:7px;background:var(--surface2);border-radius:var(--r-pill);overflow:hidden;}
.sv-prog-fill{height:100%;background:linear-gradient(90deg,var(--red),#FF6B35);border-radius:var(--r-pill);transition:width 0.6s cubic-bezier(0.25,0.46,0.45,0.94);}
/* Full effect */
.sv-progress-full::before{background:radial-gradient(ellipse 80% 60% at 20% 50%,rgba(52,199,89,0.1),transparent);}
.sv-progress-full{border-color:var(--green);box-shadow:0 0 0 1px var(--green),0 4px 24px rgba(52,199,89,0.2);}
.sv-progress-full .sv-prog-pct-big{color:var(--green);}
.sv-prog-fill-full{background:linear-gradient(90deg,#34C759,#30D158,#00E676,#30D158,#34C759);background-size:300% 100%;animation:prog-full-shine 2.5s linear infinite;}
@keyframes prog-full-shine{0%{background-position:100% 0;}100%{background-position:-200% 0;}}

/* Stat cards */
.sv-cards{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--space-10px);}
@media(min-width:700px){.sv-cards{grid-template-columns:repeat(6,1fr);}}
@media(max-width:400px){.sv-cards{grid-template-columns:repeat(2,1fr);}}
.sv-card{
  position:relative;overflow:hidden;
  background:linear-gradient(180deg,color-mix(in srgb,var(--acc,transparent) 9%,var(--surface)),var(--surface) 60%);
  border:1px solid color-mix(in srgb,var(--acc,var(--border)) 28%,var(--border));
  border-radius:var(--r-lg);
  padding:var(--space-14px) var(--space-4) var(--space-3);
  display:flex;flex-direction:column;gap:var(--space-1);
  box-shadow:var(--sh-sm);
}
.sv-card::before{content:'';position:absolute;inset:0 0 auto 0;height:3px;background:var(--acc,var(--tx3));}
.sv-card-value{font-family:var(--font-d);font-weight:900;font-size:30px;letter-spacing:-1.5px;line-height:1;}
.sv-card-total{font-size:16px;font-weight:600;color:var(--tx3);}
.sv-card-label{font-size:11px;font-weight:600;color:var(--tx3);text-transform:uppercase;letter-spacing:0.3px;}
.sv-card .sv-card-value{color:var(--acc,var(--tx1));}
.sv-card.owned{--acc:var(--st-owned);}
.sv-card.wish{--acc:var(--st-wish);}
.sv-card.doubles{--acc:var(--st-doubles);}
.sv-card.missing{--acc:var(--st-missing);}
.sv-card.fav{--acc:var(--st-fav);}
.sv-card.exemplaires{--acc:var(--st-copies);}

/* Section titles & rows */
.sv-section-title{font-family:var(--font-d);font-weight:700;font-size:11px;text-transform:uppercase;letter-spacing:1px;color:var(--tx3);margin:0 0 var(--space-2);}
.sv-rows-block{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);overflow:visible;box-shadow:var(--sh-sm);}
.sv-row{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--border);transition:background 0.15s;}
.sv-row:first-child{border-radius:var(--r-lg) var(--r-lg) 0 0;}
.sv-row:last-child{border-bottom:none;border-radius:0 0 var(--r-lg) var(--r-lg);}
.sv-rows-block .sv-row:only-child{border-radius:var(--r-lg);}
.sv-row-full{background:rgba(52,199,89,0.05);}
.sv-row-left{display:flex;align-items:center;gap:var(--space-2);min-width:0;flex:1;}
.sv-row-icon{font-size:15px;flex-shrink:0;width:22px;text-align:center;}
.sv-row-name{font-size:13px;font-weight:500;color:var(--tx1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:center;gap:var(--space-6px);}
.sv-row-right{display:flex;align-items:center;gap:var(--space-2);flex-shrink:0;width:140px;justify-content:flex-end;}
.sv-row-count{font-family:var(--font-d);font-weight:700;font-size:14px;color:var(--tx1);min-width:44px;text-align:right;}
.sv-row-total{font-size:11px;font-weight:500;color:var(--tx3);}
.sv-full-check{font-size:13px;color:var(--green);flex-shrink:0;width:14px;text-align:center;}
.sv-mini-bar{width:60px;height:5px;background:var(--surface3);border-radius:var(--r-pill);overflow:hidden;flex-shrink:0;}
.sv-mini-fill{height:100%;background:var(--red);border-radius:var(--r-pill);transition:width 0.4s ease;}
.sv-mini-fill-full{background:var(--green)!important;animation:prog-full-shine 2.5s linear infinite;background-size:300% 100%;}
.sv-type-dot{
  width:14px;height:14px;border-radius:4px;
  flex-shrink:0;display:inline-block;vertical-align:middle;
}
.sv-type-dot-foil{
  animation:sv-foil-dot 2.5s linear infinite;
  box-shadow:0 0 6px rgba(255,255,255,0.2);
  border-radius:5px;
}
@keyframes sv-foil-dot{
  0%  {filter:brightness(1) saturate(1.2);}
  50% {filter:brightness(1.4) saturate(2);}
  100%{filter:brightness(1) saturate(1.2);}
}
.sv-team-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0;display:inline-block;vertical-align:middle;}

/* STATS EXTRAS — highlights, rarity donut, progression curve */
.sv-empty-note{padding:var(--space-14px);border-radius:12px;background:var(--surface2);border:1px dashed var(--border);color:var(--tx3);font-size:12px;text-align:center;flex:1;}
/* Summary band: Highlights + rarity donut side by side (stacks on mobile),
   same 800px column as every other stats section. */
/* In-panel variant of the section title (no auto-centering / big top gap) */
.sv-feat{display:flex;flex-direction:column;flex:1;background:var(--surface);border:1px solid var(--border);border-radius:14px;box-shadow:var(--sh-sm);overflow:hidden;}
.sv-feat-item{padding:var(--space-3) var(--space-14px);}
.sv-feat-item + .sv-feat-item{border-top:1px solid var(--border);}
.sv-feat-label{font-size:11px;text-transform:uppercase;letter-spacing:0.5px;color:var(--tx3);margin-bottom:var(--space-6px);}
.sv-feat-name{font-weight:700;font-size:13px;color:var(--tx1);}
.sv-feat-sub{font-size:12px;font-weight:600;margin-top:var(--space-1);color:var(--tx2);}
.sv-donut-row{display:flex;align-items:center;gap:var(--space-5);flex-wrap:wrap;flex:1;background:var(--surface);border:1px solid var(--border);border-radius:14px;padding:var(--space-14px);box-shadow:var(--sh-sm);}
.sv-donut{width:165px;height:165px;flex-shrink:0;margin:0 auto;}
/* 165 et non 140 : le viewBox fait 120, donc le libellé interne de 8 px
   est rendu à 8 × 165/120 = 11 px. On agrandit le DONUT, pas le texte —
   grossir la police à l'intérieur d'un SVG déséquilibrerait le tracé.
   DETTE CONNUE, laissée telle quelle : « cartes possédées » mesure
   67,2 unités de viewBox pour un trou de 64 — le libellé mord donc
   légèrement sur l'anneau. Le défaut est ANTÉRIEUR à l'agrandissement
   (la mise à l'échelle conserve le rapport) ; il devient seulement plus
   visible. Le corriger veut dire reformuler le libellé dans les 7
   langues : c'est une décision de rédaction, pas un ajustement de CSS,
   et elle ne se prend pas en fin de chantier typographique. */
.sv-donut-big{font-size:22px;font-weight:800;fill:var(--tx1);font-family:var(--font-d);}
.sv-donut-small{font-size:8px;fill:var(--tx3);}
.sv-legend{display:flex;flex-direction:column;gap:var(--space-6px);flex:1;min-width:140px;}
.sv-leg-item{display:flex;align-items:center;gap:var(--space-2);font-size:12px;color:var(--tx2);}
.sv-leg-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0;}
.sv-leg-n{margin-left:auto;font-weight:700;color:var(--tx1);}
.sv-chart{width:100%;max-width:800px;height:auto;display:block;margin:0 auto;background:var(--surface);border:1px solid var(--border);border-radius:14px;box-shadow:var(--sh-sm);}
.sv-line{fill:none;stroke:var(--red);stroke-width:2.5;stroke-linejoin:round;stroke-linecap:round;}
.sv-area{fill:var(--red);opacity:0.08;}
.sv-dot{fill:var(--red);}
.sv-grid{stroke:var(--border);stroke-dasharray:4 4;}
.sv-axis{stroke:var(--border);}
.sv-axis-label{font-size:11px;fill:var(--tx3);font-family:var(--font-b);}
.sv-note{font-size:11px;color:var(--tx3);margin:var(--space-6px) auto 0;max-width:800px;}

/* BACKUP QR */
.bk-qr-wrap{display:flex;flex-direction:column;align-items:center;gap:var(--space-2);padding:var(--space-3);background:var(--surface2);border:1px solid var(--border);border-radius:12px;}
.bk-qr{width:220px;max-width:100%;background:#fff;padding:var(--space-10px);border-radius:var(--r-sm);box-shadow:var(--sh-sm);}
.bk-qr svg{display:block;width:100%;height:auto;}
.bk-qr-hint{font-size:11px;color:var(--tx3);text-align:center;line-height:1.4;}
.bk-qr-hint.bk-qr-warn{color:var(--orange);font-weight:600;}

/* SETTINGS VIEW */
.settings-view{display:none;padding:0 var(--space-4) var(--nav-clear);overflow-y:auto;height:100%;flex:1 1 auto;min-width:0;}
.settings-view.active{display:block;}
.setv-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);overflow:hidden;box-shadow:var(--sh-sm);margin-bottom:var(--space-4);}
.setv-section-title{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.5px;color:var(--tx3);padding:var(--space-3) var(--space-5) 0;}
.setv-row{display:flex;align-items:center;justify-content:space-between;padding:var(--space-14px) var(--space-5);border-bottom:1px solid var(--border);}
.setv-row:last-child{border-bottom:none;}
.setv-row-left{display:flex;flex-direction:column;gap:2px;}
.setv-row-label{font-size:14px;font-weight:500;color:var(--tx1);}
.setv-row-sub{font-size:11px;color:var(--tx3);}
/* ── « Ce déploiement et ses limites » — le CORPS, en modale ──
   La rangée qui l'ouvre n'a aucun style propre : c'est une .setv-row
   comme les autres, et c'est tout le point du changement.

   Ces règles habillent le contenu DANS la modale de Nouveautés, dont
   elles héritent le cadre, le défilement et le bouton. Le corps est du
   TEXTE : mesure de ligne d'un paragraphe (1,55) et non celle, serrée,
   des libellés de réglage.

   Aucune couleur en dur — le bloc « ce qui n'est jamais en jeu »
   s'appuie sur --green, la teinte de la progression, parce qu'il dit la
   même chose : ça va. */
/* ── La modale des limites prend la place disponible ──────────
   Elle PARTAGE son composant avec « Nouveautés » et le dialogue
   d'import : `.changelog-list{max-height:min(55vh,420px)}` sert aux
   trois, et Nouveautés en a besoin — son contenu grandit à chaque
   version. On ne touche donc pas au composant, on cible cette modale.

   MESURÉ AVANT : sur 1280×800, le corps affichait 420 px pour 472 px de
   contenu — il manquait 52 px alors que 201 px de viewport restaient
   libres autour du dialogue. Une barre de défilement pour cinq
   paragraphes qui tenaient presque.

   CE QU'ON CONTRAINT N'EST PAS LE CORPS MAIS LE DIALOGUE. Un plafond en
   `vh` sur le corps aurait forcé un compromis : assez haut pour que le
   desktop tienne (il faut > 59 % sur un écran de 800), et le dialogue
   dépassait alors l'écran en 320×568 — donc bouton Fermer hors champ,
   ce qui est pire qu'une barre de défilement. En bornant le dialogue et
   en laissant le corps prendre le reste (`flex:1`, `min-height:0`), la
   règle s'adapte seule : tout tient sur desktop, le mobile défile comme
   avant, et le bouton reste visible partout par construction.

   `dvh` et non `vh` : sur mobile, la barre d'URL fait varier la hauteur,
   et `vh` fige la valeur écran-plein — le bouton passerait sous la
   barre au premier défilement. ── */
.limits-overlay .import-dialog{max-height:calc(100dvh - 32px);}
.limits-overlay .changelog-list{max-height:none;flex:1 1 auto;min-height:0;}

.setv-limits-body p{font-size:12.5px;line-height:1.55;color:var(--tx3);margin:0 0 var(--space-3);}
.setv-limits-body p:last-child{margin-bottom:0;}
.setv-limits-body strong{color:var(--tx2);font-weight:600;}
.setv-limits-lead{color:var(--tx2)!important;}
.setv-limits-safe{border-left:2px solid var(--green);padding-left:var(--space-3);}
.setv-btn{padding:var(--space-2) var(--space-5);border-radius:var(--r-pill);border:1.5px solid var(--border);background:var(--surface2);font-family:var(--font-b);font-size:13px;font-weight:600;color:var(--tx2);cursor:pointer;transition:var(--ease);white-space:nowrap;}
.setv-btn:hover{background:var(--surface3);border-color:var(--red);color:var(--red);}
.setv-btn.danger{border-color:var(--red-m);color:var(--red);}
.setv-btn.danger:hover{background:var(--red-s);}

/* ── Stats : outils de collectionneur (onglets internes + listes) ── */
.sv-tools{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);
  padding:var(--space-3);display:flex;flex-direction:column;gap:var(--space-3);margin-bottom:var(--space-5);}
.sv-tools-tabs{display:flex;gap:var(--space-2);flex-wrap:wrap;}
.sv-tool-tab{display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 var(--space-3);
  border-radius:var(--r-pill);border:1px solid var(--border);background:var(--surface2);
  color:var(--tx2);font-family:var(--font-b);font-size:12.5px;font-weight:600;cursor:pointer;
  transition:var(--ease);}
.sv-tool-tab:hover{color:var(--tx1);border-color:var(--tx3);background:var(--surface3);}
.sv-tool-tab.active{background:var(--tx1);border-color:var(--tx1);color:var(--surface);}
.qbtn:focus-visible,.qadd-type:focus-visible{outline:2px solid var(--red);outline-offset:2px;}
.sv-tool-tab:focus-visible{outline:2px solid var(--red);outline-offset:2px;}
.sv-tool-n{font-variant-numeric:tabular-nums;opacity:0.75;}
.sv-tools-body{display:flex;flex-direction:column;gap:2px;max-height:420px;overflow-y:auto;}
.sv-tool-row{display:flex;align-items:center;gap:var(--space-2);padding:7px var(--space-2);
  border-radius:var(--r-md);font-size:13px;color:var(--tx1);}
.sv-tool-row:nth-child(even){background:var(--surface2);}
/* Les symboles de catégorie sont des SVG au trait : ils se dimensionnent
   par width/height, plus par font-size. */
.sv-tool-cat{flex:none;display:inline-flex;}
.sv-tool-cat .ic{width:14px;height:14px;}
.sv-feat-name .ic{width:15px;height:15px;vertical-align:-2px;margin-right:2px;}
.sv-row-ic .ic,.sv-cat .ic{width:16px;height:16px;}
/* Sur la tuile et dans la fiche, l'icône remplace un émoji de 40 px. */
.card-cat-ic{display:flex;align-items:center;justify-content:center;color:var(--tx2);}
.card-cat-ic .ic{width:40px;height:40px;}
.mo-cat-ic{display:flex;align-items:center;justify-content:center;width:100%;height:100%;color:var(--tx2);}
.mo-cat-ic .ic{width:64px;height:64px;}
.sv-tool-num{flex:none;font-variant-numeric:tabular-nums;color:var(--tx3);font-size:12px;min-width:38px;}
.sv-tool-name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.sv-tool-chip{flex:none;font-size:11px;font-weight:700;padding:2px 8px;border-radius:var(--r-pill);}
.sv-tool-wish{flex:none;font-size:13px;}
.sv-tool-types{flex:none;display:inline-flex;gap:6px;font-size:12px;color:var(--tx2);}
.sv-tool-type{white-space:nowrap;}
.sv-tool-subtitle{font-family:var(--font-d);font-weight:700;font-size:12.5px;color:var(--tx2);
  padding:var(--space-2) var(--space-2) 2px;display:flex;align-items:center;gap:6px;}
.sv-tool-empty{padding:var(--space-4);text-align:center;font-size:13px;color:var(--tx2);}

/* ── Vue Compte : un accent sémantique par zone (bordure gauche fine)
   — uniquement des tokens existants (cloud=bleu, sauvegarde=vert,
   avis=violet, danger=rouge). ── */
/* ── Composant partagé de saisie segmentée (otp-input.js) ──
   L'input réel est invisible au-dessus des cases (un seul input
   logique : clavier, collage, backspace, lecteur d'écran).
   Couche de tokens de composant : tout l'habillage passe par
   --seg-* , eux-mêmes alias des tokens sémantiques du thème.
   L'accent actif est le ROUGE de la marque ; l'erreur est ORANGE —
   volontairement distincte de l'accent pour que « sélectionné »
   et « faux » ne partagent jamais la même couleur. */
.cloud-otp-row{align-items:center;}
.otp-wrap{
  --seg-bg:var(--surface2);          /* fond de case */
  --seg-border:var(--border);        /* case vide */
  --seg-filled:var(--tx3);           /* case remplie */
  --seg-accent:var(--red);           /* case active (marque) */
  --seg-accent-glow:var(--red-glow); /* halo actif */
  --seg-ok:var(--green);             /* succès */
  --seg-ok-bg:var(--green-s);
  --seg-err:var(--orange);           /* erreur ≠ accent */
  --seg-err-glow:var(--orange-s);
  position:relative;display:inline-flex;align-items:center;max-width:100%;}
.otp-input{position:absolute;inset:0;width:100%;height:100%;opacity:0;font-size:16px;caret-color:transparent;z-index:2;cursor:text;}
.otp-boxes{display:flex;gap:6px;max-width:100%;}
/* les cases se compriment plutôt que de déborder : elles ne sont
   qu'un miroir visuel, la cible tactile est l'input plein-rang */
.otp-box{flex:0 1 36px;min-width:24px;height:44px;border:1.5px solid var(--seg-border);border-radius:10px;background:var(--seg-bg);
  display:flex;align-items:center;justify-content:center;position:relative;
  font-family:var(--font-d);font-size:18px;font-weight:700;color:var(--tx1);
  transition:border-color 0.15s ease,background 0.15s ease,box-shadow 0.15s ease;}
.otp-box.filled{border-color:var(--seg-filled);}
.otp-box.active{border-color:var(--seg-accent);box-shadow:0 0 0 3px var(--seg-accent-glow);animation:segPulse 1.8s ease-in-out infinite;}
@keyframes segPulse{50%{box-shadow:0 0 0 6px var(--seg-accent-glow);}}
/* caret visible : barre fine qui clignote dans la case active */
.otp-caret{display:none;width:2px;height:55%;border-radius:1px;background:var(--seg-accent);}
.otp-box.active:not(.filled) .otp-caret{display:block;animation:segCaret 1.1s steps(1) infinite;}
@keyframes segCaret{50%{opacity:0;}}
/* micro-pop du chiffre : 1.05 → 1 en ~120 ms */
.otp-box.pop{animation:segPop 0.12s ease-out;}
@keyframes segPop{40%{transform:scale(1.05);}}
/* erreur : secousse + contour orange (distinct de l'accent rouge) */
.otp-wrap.seg-error .otp-boxes{animation:segShake 0.3s ease;}
.otp-wrap.seg-error .otp-box{border-color:var(--seg-err);box-shadow:0 0 0 3px var(--seg-err-glow);}
@keyframes segShake{20%{transform:translateX(-5px);}45%{transform:translateX(4px);}70%{transform:translateX(-2px);}}
/* succès : cases vertes puis pastille au check dessiné + particules */
.otp-wrap.seg-success .otp-box{border-color:var(--seg-ok);background:var(--seg-ok-bg);}
.otp-badge{position:absolute;right:2px;top:-10px;width:26px;height:26px;border-radius:50%;
  border:2px solid var(--surface);
  background:var(--seg-ok);display:flex;align-items:center;justify-content:center;
  opacity:0;transform:scale(0.4);pointer-events:none;}
.otp-badge svg{width:16px;height:16px;stroke:var(--surface);}
.otp-badge-path{stroke-dasharray:24;stroke-dashoffset:24;}
.otp-wrap.seg-success .otp-badge{opacity:1;transform:scale(1);
  transition:opacity 0.18s ease,transform 0.25s cubic-bezier(0.34,1.56,0.64,1);}
.otp-wrap.seg-success .otp-badge-path{animation:segCheck 0.35s ease-out 0.15s forwards;}
@keyframes segCheck{to{stroke-dashoffset:0;}}
/* particules one-shot : 6 étincelles CSS pures autour de la pastille */
.otp-spark{position:absolute;left:50%;top:50%;width:4px;height:4px;border-radius:50%;background:var(--seg-ok);opacity:0;}
.otp-wrap.seg-success .otp-spark{animation:segSpark 0.55s ease-out 0.25s;}
.otp-wrap.seg-success .otp-spark:nth-child(2){--sx:18px;--sy:-14px;}
.otp-wrap.seg-success .otp-spark:nth-child(3){--sx:22px;--sy:4px;}
.otp-wrap.seg-success .otp-spark:nth-child(4){--sx:12px;--sy:18px;}
.otp-wrap.seg-success .otp-spark:nth-child(5){--sx:-14px;--sy:16px;}
.otp-wrap.seg-success .otp-spark:nth-child(6){--sx:-20px;--sy:-6px;}
.otp-wrap.seg-success .otp-spark:nth-child(7){--sx:-8px;--sy:-20px;}
@keyframes segSpark{
  0%{opacity:0.9;transform:translate(-2px,-2px) scale(1);}
  100%{opacity:0;transform:translate(calc(-2px + var(--sx,14px)),calc(-2px + var(--sy,-14px))) scale(0.4);}
}
@media(max-width:360px){.otp-box{flex-basis:30px;height:38px;font-size:16px;border-radius:8px;}.otp-boxes{gap:4px;}.otp-badge{right:0;top:-8px;width:22px;height:22px;}}
@media (prefers-reduced-motion: reduce){
  .otp-box.active,.otp-box.pop,.otp-wrap.seg-error .otp-boxes,
  .otp-caret,.otp-wrap.seg-success .otp-spark{animation:none;}
  .otp-wrap.seg-success .otp-badge{transition:none;transform:scale(1);}
  .otp-wrap.seg-success .otp-badge-path{animation:none;stroke-dashoffset:0;}
}

/* Réglages : mêmes accents sémantiques que la page Compte (tokens
   existants) — la classe est posée directement sur la section. */
.setv-section.set-appearance{border-left:3px solid var(--purple);}
.setv-section.set-security{border-left:3px solid var(--blue);}
.setv-section.set-about{border-left:3px solid var(--green);}
.setv-section.set-session{border-left:3px solid var(--red);}
/* États hover/focus des lignes et boutons — standard « Compte » */
.setv-row{transition:var(--ease-fast);}
.setv-row:hover{background:var(--surface2);}
.setv-btn:focus-visible{outline:2px solid var(--red);outline-offset:2px;}

.acc-accent > .setv-section{border-left:3px solid transparent;}
.acc-cloud > .setv-section{border-left-color:var(--blue);}
.acc-backup > .setv-section{border-left-color:var(--green);}
.acc-feedback > .setv-section{border-left-color:var(--purple);}
.acc-danger > .setv-section{border-left-color:var(--red);}
.danger-zone .setv-section-title{color:var(--red);}

/* Page Compte verrouillée (mode spectateur) */
.acc-locked{display:flex;flex-direction:column;align-items:center;text-align:center;
  gap:var(--space-4);padding:var(--space-10) var(--space-6);}
.acc-lock-icon{width:40px;height:40px;color:var(--tx3);}
.acc-locked-title{font-family:var(--font-d);font-weight:700;font-size:17px;color:var(--tx1);}
.acc-locked-msg{font-size:13.5px;line-height:1.65;color:var(--tx2);max-width:46ch;margin:0;}

/* Modale de suppression (confirmation forte) */
.danger-mo{position:fixed;inset:0;z-index:600;display:flex;align-items:center;justify-content:center;
  background:rgba(0,0,0,0.55);padding:var(--space-4);}
.danger-mo-box{width:100%;max-width:440px;background:var(--surface);border:1px solid var(--red-m);
  border-radius:var(--r-lg);padding:var(--space-5);display:flex;flex-direction:column;gap:var(--space-4);
  box-shadow:0 24px 64px rgba(0,0,0,0.4);}
.danger-mo-title{font-family:var(--font-d);font-weight:700;font-size:16px;color:var(--red);}
.danger-scopes{display:flex;flex-direction:column;gap:var(--space-2);}
.danger-scope{display:flex;align-items:flex-start;gap:var(--space-2);font-size:13px;color:var(--tx1);cursor:pointer;}
.danger-scope input{margin-top:2px;accent-color:var(--red);}
.danger-scope.off{opacity:0.45;cursor:not-allowed;}
.danger-warn{font-size:12.5px;line-height:1.55;color:var(--tx2);background:var(--red-s);
  border:1px solid var(--red-m);border-radius:var(--r-md);padding:var(--space-3);}
.danger-confirm{display:flex;flex-direction:column;gap:var(--space-1);}
.danger-word-hint{font-size:12px;font-weight:600;color:var(--tx2);}
#dangerWord{padding:var(--space-2) var(--space-3);border-radius:var(--r-md);border:1px solid var(--border);
  background:var(--surface2);color:var(--tx1);font-family:var(--font-b);font-size:14px;letter-spacing:1px;}
#dangerWord:focus-visible{outline:2px solid var(--red);outline-offset:1px;}
/* ══════════════════════════════════════════════════════════
   ZONE DANGER — la dimension SAISON, et le récapitulatif
   Deux questions indépendantes : QUELLE saison, puis QUELLE
   portée. La saison vient en premier — c'est la plus lourde de
   conséquences, et « cette saison — 2026 » nomme l'année, donc
   aucun doute sur ce qu'on regarde.
   ══════════════════════════════════════════════════════════ */
.danger-seasons{display:flex;gap:var(--space-2);margin-bottom:var(--space-3);}
.danger-season{flex:1;display:flex;align-items:center;gap:var(--space-2);
  padding:var(--space-10px) var(--space-3);border:1.5px solid var(--border);
  border-radius:12px;background:var(--surface2);cursor:pointer;font-size:13px;
  transition:var(--ease);}
.danger-season input{margin:0;flex:none;}
.danger-season.sel{border-color:var(--red);background:var(--red-s);
  color:var(--red-ink,var(--red));font-weight:700;}

.danger-recap{margin-top:var(--space-3);background:var(--surface2);
  border:1px solid var(--border);border-left:3px solid var(--red);
  border-radius:10px;padding:var(--space-3) var(--space-14px);}
.danger-recap-h{font-size:11px;text-transform:uppercase;letter-spacing:.06em;
  color:var(--tx3);margin-bottom:var(--space-2);}
.danger-recap ul{list-style:none;padding:0;margin:0;display:flex;
  flex-direction:column;gap:var(--space-1);}
.danger-recap li{font-size:13px;line-height:1.4;color:var(--tx1);}
.danger-recap li b{color:var(--red);font-weight:800;}
/* La ligne « Conservés » est la plus importante de l'écran : c'est elle
   qui évite la panique après une action destructive. Elle est donc en
   CORPS DE TEXTE (13 px, couleur de texte principale), séparée par un
   filet — pas reléguée en petite note grise sous le reste. */
.danger-keep{font-size:13px;line-height:1.4;color:var(--tx2);
  margin:var(--space-3) 0 0;padding-top:var(--space-10px);
  border-top:1px solid var(--border);}

.danger-mo-actions{display:flex;justify-content:flex-end;gap:var(--space-2);}
/* Confirmation in-app (remplace confirm()) — même boîte que la zone
   danger, sans la liste de portées. */
.confirm-box{max-width:400px;border-color:var(--border-s);}
.confirm-msg{font-size:14px;line-height:1.6;color:var(--tx1);}
.danger-mo-actions .setv-btn.danger:disabled{opacity:0.4;cursor:not-allowed;}
.setv-lang-sel{min-height:44px;padding:var(--space-2) var(--space-3);border-radius:var(--r-pill);border:1.5px solid var(--border);background:var(--surface2);font-family:var(--font-b);font-size:13px;color:var(--tx1);cursor:pointer;outline:none;transition:var(--ease);}
.setv-lang-sel:focus{border-color:var(--red);}
.setv-toggle{width:46px;height:26px;border-radius:var(--r-pill);border:none;background:var(--surface3);cursor:pointer;position:relative;transition:background 0.2s;flex-shrink:0;}
.setv-toggle::after{content:'';position:absolute;width:20px;height:20px;border-radius:50%;background:white;top:3px;left:3px;transition:transform 0.2s;box-shadow:0 1px 4px rgba(0,0,0,0.2);}
.setv-toggle.on{background:var(--red);}
.setv-toggle.on::after{transform:translateX(20px);}

/* MOBILE HEADER BREAKPOINT — nothing may overlap down to 320px:
   title + season pills hidden, logos shrunk. */
@media(max-width:640px){
  .header-inner{gap:var(--space-2);padding:0 var(--space-3);}
  .header-left{flex:0 0 auto;}
  .header-right{gap:var(--space-1);}
  /* Title pill yields first; season + total stay anchored right and
     can never wrap (two flex blocks, nowrap children). */
  .header .header-title{display:none;}
  .season-pill{padding:0 var(--space-2);}
}

/* RETIRED BADGE */
.retired-badge{display:inline-block;font-size:11px;font-weight:800;letter-spacing:0.5px;padding:1px var(--space-1);border-radius:4px;background:rgba(139,0,0,0.15);color:#8b0000;margin-left:var(--space-1);vertical-align:middle;text-transform:uppercase;font-family:var(--font-b);}


/* USER TITLE */
.user-title-wrap{display:flex;align-items:center;gap:var(--space-2);padding:var(--space-2) var(--space-14px);border-radius:12px;background:var(--surface2);border:1px solid var(--border);margin-bottom:var(--space-1);}
.user-title-icon{font-size:20px;}
.user-title-text{font-family:var(--font-d);font-weight:800;font-size:14px;letter-spacing:-0.3px;}
.user-title-sub{font-size:11px;color:var(--tx3);margin-left:auto;}
.user-title-next{font-size:11px;color:var(--tx3);margin-top:2px;}
.title-picker{margin-top:var(--space-2);max-height:200px;overflow-y:auto;}
.title-picker-grid{display:flex;flex-wrap:wrap;gap:var(--space-1);}
.title-pick{display:flex;align-items:center;gap:var(--space-1);padding:var(--space-1) var(--space-2);border-radius:8px;border:1px solid var(--border);background:var(--surface);font-size:11px;font-weight:600;cursor:pointer;transition:var(--ease);}
.title-pick:hover{background:var(--surface2);transform:scale(1.03);}
.title-pick.active{border-width:2px;}

/* ── Moved from inline styles ── */
#loginThemeSwitch{position:absolute;top:20px;right:20px;}
#app-wrapper{display:none;}
[data-boot="login"] #login-screen{display:flex;}
[data-boot="app"] #app-wrapper{display:flex;}
.user-title-next{cursor:pointer;}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;}

/* ══════════════════════════════════════════════════════════
   INTERACTIVE TUTORIAL — spotlight overlay + bubble
   The overlay itself doesn't capture clicks (the user must reach
   the real element); only the spotlight dims the rest of the page
   via a huge box-shadow, and the bubble is interactive.
   ══════════════════════════════════════════════════════════ */
.tut-overlay{position:fixed;inset:0;z-index:900;pointer-events:none;}
/* Le halo grise l'écran par une ombre géante. pointer-events:none est
   ESSENTIEL : sans lui le voile intercepte le défilement automatique
   qui amène la cible dans le cadre. */
.tut-spot{position:fixed;border-radius:var(--r-sm);pointer-events:none;
  box-shadow:0 0 0 9999px rgba(0,0,0,0.55),0 0 0 2px var(--red);
  transition:top 0.25s ease,left 0.25s ease,width 0.25s ease,height 0.25s ease;}
[data-theme="dark"] .tut-spot{box-shadow:0 0 0 9999px rgba(0,0,0,0.68),0 0 0 2px var(--red);}

.tut-bubble{position:fixed;pointer-events:auto;box-sizing:border-box;
  background:var(--surface);border:1px solid var(--border-s);border-radius:var(--r-md);
  box-shadow:var(--sh-lg);padding:var(--space-4);animation:tutIn 0.2s ease;}
.tut-bubble:focus{outline:none;}
.tut-bubble:focus-visible{outline:2px solid var(--red);outline-offset:3px;}
@keyframes tutIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}

/* Rail : un segment par chapitre, celui en cours se remplit. La
   position dans TOUT le parcours se lit d'un coup d'œil. */
.tut-rail{display:flex;gap:4px;margin-bottom:var(--space-3);}
.tut-rail i{flex:1;height:3px;border-radius:var(--r-pill);background:var(--surface3);overflow:hidden;}
.tut-rail i.done{background:var(--red);}
.tut-rail i.now span{display:block;height:100%;background:var(--red);border-radius:var(--r-pill);}

.tut-kicker{display:flex;align-items:center;gap:var(--space-2);margin-bottom:var(--space-2);}
.tut-chapter{font-family:var(--font-d);font-weight:700;font-size:11px;letter-spacing:0.12em;
  text-transform:uppercase;color:var(--red);}
.tut-count{font-family:var(--font-d);font-weight:600;font-size:11px;color:var(--tx3);
  font-variant-numeric:tabular-nums;margin-left:auto;}
.tut-title{font-family:var(--font-d);font-weight:800;font-size:17px;line-height:1.2;
  color:var(--tx1);margin:0 0 var(--space-1);}
.tut-text{font-size:13px;line-height:1.5;color:var(--tx2);margin:0;}

.tut-btns{display:flex;align-items:center;gap:var(--space-2);margin-top:var(--space-4);}
.tut-quit{background:none;border:none;color:var(--tx3);font:600 12px var(--font-b);cursor:pointer;
  padding:0 var(--space-6px);min-height:36px;border-radius:var(--r-pill);transition:var(--ease);}
.tut-quit:hover{color:var(--red);}
.tut-prev{background:none;border:1px solid var(--border);color:var(--tx2);font:600 12px var(--font-b);
  cursor:pointer;padding:0 var(--space-3);min-height:36px;border-radius:var(--r-pill);
  transition:var(--ease);margin-left:auto;}
.tut-prev:hover:not(:disabled){border-color:var(--tx3);color:var(--tx1);}
.tut-prev:disabled{opacity:0.35;cursor:default;}
.tut-skip{background:none;border:1px solid var(--border);color:var(--tx2);font:600 12px var(--font-b);
  cursor:pointer;padding:0 var(--space-3);min-height:36px;border-radius:var(--r-pill);transition:var(--ease);}
.tut-skip:hover{border-color:var(--tx3);color:var(--tx1);}
/* Le geste résiste depuis quelques secondes : la sortie de secours
   s'affirme, sans jamais avoir été absente. */
.tut-skip.urge{border-color:var(--red-m);color:var(--red);}
.tut-next{background:var(--red);border:none;color:var(--on-red,#fff);font:700 13px var(--font-b);
  border-radius:var(--r-pill);padding:0 var(--space-5);min-height:36px;cursor:pointer;
  box-shadow:var(--sh-sm);transition:var(--ease);}
.tut-next:hover{filter:brightness(1.08);}
.tut-do{margin-left:auto;font:700 12px var(--font-b);color:var(--red);white-space:nowrap;}
.tut-do::before{content:'↑ ';}
/* Quitter et Précédent sont des icônes à toutes les largeurs : à 320 px
   de bulle, les deux mots poussaient « Passer l'étape » sur deux lignes.
   Le nom accessible reste porté par aria-label. */
.tut-quit .tut-lg,.tut-prev .tut-lg{display:none;}
.tut-quit .tut-sm,.tut-prev .tut-sm{display:inline;font-size:16px;line-height:1;}
.tut-skip .tut-sm{display:none;}
.tut-btns button:focus-visible{outline:2px solid var(--red);outline-offset:2px;}

/* La flèche DÉSIGNE la cible : elle change de côté avec la bulle et
   se place à --tut-arrow, calculé sur le centre du halo. */
.tut-bubble::after{content:'';position:absolute;width:12px;height:12px;
  background:var(--surface);border:1px solid var(--border-s);transform:rotate(45deg);}
.tut-bubble[data-side="bottom"]::after{top:-7px;left:calc(var(--tut-arrow,50%) - 6px);border-bottom:none;border-right:none;}
.tut-bubble[data-side="top"]::after{bottom:-7px;left:calc(var(--tut-arrow,50%) - 6px);border-top:none;border-left:none;}
.tut-bubble[data-side="right"]::after{left:-7px;top:calc(var(--tut-arrow,50%) - 6px);border-top:none;border-right:none;}
.tut-bubble[data-side="left"]::after{right:-7px;top:calc(var(--tut-arrow,50%) - 6px);border-bottom:none;border-left:none;}
.tut-bubble[data-side="none"]::after{display:none;}

/* Cible hors cadre malgré le défilement : on dit OÙ regarder, on ne
   laisse jamais un écran grisé inerte. */
.tut-away{position:fixed;left:50%;transform:translateX(-50%);z-index:901;pointer-events:none;
  display:inline-flex;align-items:center;gap:6px;height:34px;padding:0 var(--space-4);
  border-radius:var(--r-pill);background:var(--red);color:var(--on-red,#fff);
  font:700 12px var(--font-b);box-shadow:var(--sh-md);}
.tut-away.down{bottom:calc(var(--nav-clear) + 8px);}
.tut-away.up{top:calc(56px + 12px);}

/* Mobile étroit : la bulle doit rester bien sous 40 % de la hauteur
   d'écran, sinon elle recouvre ce qu'elle explique. Une seule rangée
   d'actions (au lieu de deux), libellés courts, icônes pour les deux
   commandes secondaires — cibles toujours ≥ 36 px de haut. */
.tut-quit,.tut-prev{min-width:36px;padding:0 var(--space-3);}
@media (max-width:430px){
  .tut-bubble{padding:var(--space-14px);}
  .tut-title{font-size:16px;}
  .tut-skip .tut-lg{display:none;}
  .tut-skip .tut-sm{display:inline;}
  .tut-quit,.tut-prev{padding:0 var(--space-2);}
  .tut-skip{padding:0 var(--space-2);font-size:11px;}
  .tut-next{padding:0 var(--space-3);font-size:12px;}
  .tut-do{font-size:11px;}
}
@media (prefers-reduced-motion:reduce){.tut-spot{transition:none;}.tut-bubble{animation:none;}}

/* ══════════════════════════════════════════════════════════
   DIVINE — animated iridescent gradient for the top rarity.
   Applied via dedicated classes (the metadata color string
   stays as a plain fallback). One shared keyframe timeline;
   background-position only (no per-frame JS).
   ══════════════════════════════════════════════════════════ */
@keyframes divineShift{
  0%{background-position:0% 50%;}
  50%{background-position:100% 50%;}
  100%{background-position:0% 50%;}
}
.rar-divine-bg{
  background-image:linear-gradient(115deg,#8B5CF6,#3B82F6,#14B8A6,#FACC15,#F472B6,#8B5CF6)!important;
  background-size:300% 300%!important;
  color:#fff!important;
  animation:divineShift 8s ease-in-out infinite;
}
/* Rarity chips carry plain white text on every level, with NO shadow —
   deliberate design decision (2026-07-21), see rarityTextColor() in
   data.js for the rationale and the accepted contrast trade-off. */
.rar-divine-text{
  background-image:linear-gradient(115deg,#8B5CF6,#3B82F6,#14B8A6,#D99E00,#F472B6,#8B5CF6);
  background-size:300% 300%;
  -webkit-background-clip:text;background-clip:text;
  color:transparent!important;
  -webkit-text-fill-color:transparent;
  animation:divineShift 8s ease-in-out infinite;
}
/* Eternal (7e niveau, LE sommet) : noir & or animé + lueur — même
   mécanique que divine mais nettement au-dessus : fond sombre profond
   traversé d'un filon d'or, bord or, glow. Impossible à confondre avec
   le orange plat de legendary. Texte blanc (décision 2026-07-21). */
.rar-eternal-bg{
  background-image:linear-gradient(115deg,#1a1302,#7a5600,#FACC15,#FFF6C9,#FACC15,#7a5600,#1a1302)!important;
  background-size:300% 300%!important;
  color:#fff!important;
  border:1px solid #FACC15!important;
  box-shadow:0 0 10px rgba(250,204,21,0.55);
  animation:divineShift 6s ease-in-out infinite;
}
.rar-eternal-text{
  background-image:linear-gradient(115deg,#B45309,#FACC15,#FFF6C9,#FACC15,#B45309);
  background-size:300% 300%;
  -webkit-background-clip:text;background-clip:text;
  color:transparent!important;
  -webkit-text-fill-color:transparent;
  animation:divineShift 6s ease-in-out infinite;
}
/* Étoiles ✦ éternel — or lumineux, pulsation douce, dans la pastille
   (aucun chevauchement possible avec les éléments de la carte). */
.eternal-stars{
  color:#FACC15;
  text-shadow:0 0 3px #fff,0 0 7px rgba(250,204,21,0.9);
  letter-spacing:1px;
  animation:eternalStars 2.4s ease-in-out infinite;
}
@keyframes eternalStars{0%,100%{opacity:0.75;}50%{opacity:1;}}
/* Reduced motion: static iridescent gradient instead of the animation */
@media (prefers-reduced-motion:reduce){
  .rar-divine-bg,.rar-divine-text,.rar-eternal-bg,.rar-eternal-text{animation:none;background-position:50% 50%;}
  .eternal-stars{animation:none;opacity:1;}
}

/* ── Rarity chips redesign: full-color surfaces (divine keeps its
     animated gradient — same geometry, so it stays the natural top) ── */
/* Stats highlight: rarity as a solid chip */
.sv-feat-sub.sv-feat-chip{display:inline-flex;align-items:center;gap:var(--space-1);padding:3px var(--space-10px);border-radius:var(--r-pill);font-weight:700;margin-top:var(--space-6px);}

/* ── Font theme picker (Settings) ── */
.font-picker{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,150px),1fr));gap:var(--space-2);width:100%;}
.font-opt{display:flex;flex-direction:column;gap:2px;align-items:flex-start;padding:var(--space-10px) var(--space-3);border:1.5px solid var(--border);border-radius:var(--r-sm);background:var(--surface2);cursor:pointer;transition:var(--ease);text-align:left;}
.font-opt:hover{border-color:var(--tx3);}
.font-opt.active{border-color:var(--red);box-shadow:0 0 0 1px var(--red);}
.font-opt-name{font-size:15px;font-weight:700;color:var(--tx1);}
.font-opt-sample{font-size:11px;color:var(--tx2);}


/* ══════════════════════════════════════════════════════════
   CLAUDE DESIGN REFINEMENTS — PHASE 1 (01·02·05·06)
   Additive layer: nothing here replaces the foil/wild/nitro/divine
   treatments — it composes on top of them.
   ══════════════════════════════════════════════════════════ */

/* ── 01 · motorsport card hover ──
   Rest state untouched; on hover: soft red glow under the card, red
   accent border, a light sweep across the visual, and a micro-zoom of
   the driver number. The sheen lives on .card-visual::before (free
   slot — wild hides it, foils use ::after). */
.card .card-visual::before{
  content:'';position:absolute;inset:0;pointer-events:none;z-index:3;
  background:linear-gradient(105deg,transparent 42%,rgba(255,255,255,0.16) 50%,transparent 58%);
  transform:translateX(-160%);
  transition:transform 0.55s ease;
}
.card:hover .card-visual::before{transform:translateX(160%);}
.card .dn-num{transition:transform 0.25s ease;}
.card:hover .dn-num{transform:scale(1.06);}
.card:hover{
  transform:translateY(-3px);
  border-color:var(--red);
  box-shadow:var(--sh-lg), 0 12px 28px -6px var(--red-glow);
}
/* foils keep their own borders/halos on hover (gold, mauve, wild) */
.card[class*="tv-"]:hover{border-color:inherit;}
.card.tv-promo-blue:hover,.card.tv-promo-green:hover,.card.tv-promo-red:hover,.card.tv-promo-yellow:hover{border-color:rgba(255,205,90,0.85);}
.card.tv-nitro_foil:hover{border-color:rgba(200,140,255,0.85);}

/* ── 02 · coherent interactive states ──
   Keyboard focus NEVER disappears: a red ring via :focus-visible
   everywhere (a11y — outranks the scattered outline:none), the
   browser-blue outline never shows. Hover: slight darken + soft red
   halo. Press: gentle push-down. */
*:focus-visible{outline:2px solid var(--red) !important;outline-offset:2px;}
*:focus:not(:focus-visible){outline:none;}
/* Text-entry controls match :focus-visible on ANY focus (spec), so the
   ring would sit on them permanently while they hold focus. They get
   the app's discreet field treatment instead: thin red border + soft
   halo — never the outer ring. */
input:focus-visible,textarea:focus-visible,select:focus-visible{
  outline:none !important;
  border-color:var(--red);
  box-shadow:0 0 0 3px var(--red-glow);
}
.setv-btn:hover,.pin-key:hover,.import-dialog-btn:hover,
.setv-lang-sel:hover,.schip:hover,
.qbtn:hover,.lang-opt:hover,.font-opt:hover,.modal-close:hover,
.install-banner-btn:hover,.setv-toggle:hover{
  box-shadow:0 0 0 3px var(--red-glow);
  filter:brightness(0.97);
}
button:not(:disabled, .bn-tab):active,
.schip:active,.qbtn:active,
.pin-key:active,.setv-btn:active,.lang-opt:active,.font-opt:active{
  transform:scale(0.955);
}

/* ── 05 · dark-theme card depth (dark only, plain cards only — the
   foil washes carry their own treatments and stay untouched) ── */
[data-theme="dark"] .card:not([class*="tv-"]){
  border-color:rgba(255,255,255,0.13);
  box-shadow:inset 0 1px 0 rgba(255,255,255,0.07), var(--sh-sm);
}
[data-theme="dark"] .card:not([class*="tv-"]) .card-visual{
  box-shadow:inset 0 -14px 18px -14px rgba(0,0,0,0.5);
}
[data-theme="dark"] .card:not([class*="tv-"]):hover{
  box-shadow:inset 0 1px 0 rgba(255,255,255,0.07), var(--sh-lg), 0 12px 28px -6px var(--red-glow);
}

/* ── 06 · team-name legibility + team color dot ── */
.card-team{font-weight:600;letter-spacing:0.1px;}
.team-dot{
  display:inline-block;width:7px;height:7px;border-radius:50%;
  margin-right:var(--space-1);vertical-align:0.5px;flex-shrink:0;
  box-shadow:0 0 0 1px rgba(0,0,0,0.12);
}

@media (prefers-reduced-motion:reduce){
  .card .card-visual::before{transition:none;display:none;}
  .card .dn-num{transition:none;}
  .card:hover .dn-num{transform:none;}
  button:not(:disabled):active,.schip:active,.bn-tab:active,
  .qbtn:active,.pin-key:active,.setv-btn:active,.lang-opt:active,.font-opt:active{transform:none;}
}


/* ══════════════════════════════════════════════════════════
   BOOT SKELETONS — the grid is the entry view, and initApp()
   waits on four JSON fetches before it can render anything, so
   the shell used to sit empty. Skeleton cards keep the layout
   honest (same geometry as a real card) instead of a spinner.
   Only shown past ~150ms — see SKELETON_DELAY in render.js —
   so a fast load never flashes them.
   ══════════════════════════════════════════════════════════ */
.sk-card{
  background:var(--surface);border:1.5px solid var(--border);
  border-radius:var(--r-lg);overflow:hidden;box-shadow:var(--sh-sm);
  display:flex;flex-direction:column;pointer-events:none;
}
/* Le squelette reprend la MÊME hauteur fixe que la tuile. Tant que les
   tuiles avaient 13 hauteurs, il ne pouvait coller qu'à la médiane et
   se trompait de ±5 px aux extrêmes — c'était structurel et le README
   le disait. Avec une hauteur unique, il est EXACT. */
.sk-card{height:var(--card-h);}
.sk-visual{height:120px;}   /* = .card-visual, mesuré, pas approché */
.sk-body{padding:var(--space-3);display:flex;flex-direction:column;gap:var(--space-2);flex:1 1 auto;min-height:0;}
.sk-chip{margin-top:auto;}   /* comme .card-rarity-row : collé en bas */
.sk-line{height:10px;border-radius:var(--r-sm);}
.sk-line.w40{width:40%;} .sk-line.w70{width:70%;} .sk-line.w55{width:55%;}
.sk-chip{height:26px;width:96px;border-radius:var(--r-sm);margin-top:var(--space-1);}
.sk-visual,.sk-line,.sk-chip{
  background:linear-gradient(90deg,var(--surface2) 25%,var(--surface3) 37%,var(--surface2) 63%);
  background-size:400% 100%;
  animation:skShimmer 1.4s ease-in-out infinite;
}
@keyframes skShimmer{0%{background-position:100% 50%;}100%{background-position:0 50%;}}
@media (prefers-reduced-motion:reduce){
  .sk-visual,.sk-line,.sk-chip{animation:none;background:var(--surface2);}
}

/* ══════════════════════════════════════════════════════════
   v2.3 PHASE B — système d'icônes SVG + livrées + casque C1
   Surcharges volontairement regroupées ICI (fin de cascade) :
   les règles historiques au-dessus restent intactes.
   ══════════════════════════════════════════════════════════ */

/* ── Icônes SVG (icons.js) : 1em, currentColor, alignées au texte ── */
.ic{display:inline-block;width:1em;height:1em;vertical-align:-0.125em;flex:none;}
/* La couronne inline reproduit la boîte de ligne de l'ancien émoji 👑
   (17 px sur la ligne card-num) : dimension figée, PAS 1em — le
   remplacement ne doit pas bouger la hauteur des tuiles champions. */
.ic-crown{color:var(--gold,#FACC15);width:17px;height:17px;vertical-align:-5px;}
/* L'or reste sur le CORPS de la carte, pas dans le coin : .ic-crown est
   posée sur une surface de thème (ligne « #004 · ♛ »), où la teinte est
   légitime. Les marqueurs de coin, eux, sont neutres — voir le bloc
   MARQUEURS DE COIN plus haut. */
.ic-seal .ic-hole{stroke:var(--surface,#fff);stroke-width:2.2;}
.ic-danger{color:var(--red);}
.ic-danger .ic-hole{stroke:#fff;stroke-width:2;}
.schip .ic{width:13px;height:13px;display:block;color:var(--tx3);}
.schip[data-s="wishlist"].on .ic{color:#EC4899;}
.schip[data-s="favorite"].on .ic{color:var(--red);}
.mo-st .ic{width:12px;height:12px;}
.mo-cell-icon .ic{width:16px;height:16px;display:block;}
.qadd-type .ic{width:16px;height:16px;display:block;}
.owned-tag .ic{width:12px;height:12px;}
.theme-icon .ic{width:16px;height:16px;display:block;}
.install-banner-icon .ic{width:18px;height:18px;display:block;}
.setv-section-title .ic,.sv-section-title .ic{margin-right:2px;}

/* ── Pastilles CSS des 4 couleurs de base (remplacent 🔵🟢🔴🟡) ── */
.cdot{display:inline-block;width:.85em;height:.85em;border-radius:50%;flex:none;vertical-align:-0.05em;
  box-shadow:inset 0 -1px 2px rgba(0,0,0,.28),inset 0 1px 1px rgba(255,255,255,.35);
  border:1px solid rgba(0,0,0,.15);}
.cdot-blue{background:#2E86FF;}
.cdot-green{background:#2ECC71;}
.cdot-red{background:#FF4D4D;}
.cdot-yellow{background:#FFD000;}
.mo-cell-icon .cdot,.qadd-type .cdot{width:13px;height:13px;}
.owned-tag .cdot{width:9px;height:9px;}

/* ── Sélecteur de langue : pastille ISO typographique (remplace les
   drapeaux). Les drapeaux de nationalité SUR les cartes sont du
   contenu et ne changent pas. ── */
.lang-iso{display:inline-flex;align-items:center;justify-content:center;min-width:32px;height:24px;
  padding:0 7px;border-radius:7px;background:var(--surface2);border:1px solid var(--border);
  font-family:var(--font-d);font-weight:800;font-size:11px;letter-spacing:.09em;color:var(--tx2);}
.lang-opt:hover .lang-iso{color:var(--red);border-color:var(--red);}
.lang-globe{color:var(--red);}
.lang-globe .ic{width:34px;height:34px;}

/* ── LIVRÉE (arbitrage P1, 1.33.0) : colonne de 7 px, c1/c2 — l'écurie
   signe le bord gauche, le FOND du visuel appartient à la rareté. ── */
.lvb{position:absolute;left:0;top:0;bottom:0;width:7px;z-index:1;
  background:linear-gradient(180deg,var(--c1) 0 58%,var(--c2) 58% 100%);}
.card-visual.not-owned .lvb,
.modal-visual.not-owned .lvb{filter:grayscale(.9);opacity:.5;}

/* FOND DU VISUEL = RARETÉ (halo sobre en coin, identique pour les 10
   écuries — --rarc est posé inline par buildCardEl/updateModalVisual).
   Règle unique : jamais l'écurie, jamais la variante. wild/nitro
   restent les deux exceptions spectaculaires assumées. */
.card-visual,.modal-visual{background:
  radial-gradient(120% 130% at 85% -20%, color-mix(in srgb,var(--rarc,#8E8E93) 26%,transparent) 0%, transparent 55%),
  linear-gradient(150deg,#191a1f,#101014);}
/* Non possédée : même surface neutre sombre, SANS halo de rareté —
   la carte fantôme ne réclame rien (casque grisé, numéro éteint).
   Remplace l'ancien aplat clair var(--not-owned), pensé pour l'époque
   des petits visuels émoji. */
.card-visual.not-owned,.modal-visual.not-owned{background:linear-gradient(150deg,#17181c,#101014);}
.card-visual.not-owned .dn-num,.modal-visual.not-owned .dn-num{opacity:.45;text-shadow:none;}
.card-visual.not-owned .team-emblem,.modal-visual.not-owned .team-emblem{opacity:.45;}

/* ── CASQUE C1 « Visière XL » — silhouette générique, couleurs d'écurie ── */
.helm{position:absolute;left:6px;bottom:2px;width:92px;height:92px;z-index:2;
  filter:drop-shadow(0 4px 10px rgba(0,0,0,.45));
  /* colorisation du <use> partagé : SATURÉE — la couleur d'écurie pure
     porte l'identité (le turquoise est turquoise, le rosso est rouge) */
  --helm-shell:var(--tc,#888);
  --helm-visor:color-mix(in srgb,#0b0d12 78%,var(--tc,#888));
  --helm-det:color-mix(in srgb,#fff 82%,var(--tc,#888));
  --helm-lip:color-mix(in srgb,var(--tc,#888) 45%,#000);}
.card-visual.not-owned .helm{filter:grayscale(1) opacity(.55);}
.modal-visual .helm{left:18px;bottom:4px;width:140px;height:140px;}
/* Tuiles étroites (2 colonnes à 320 px) : casque réduit, le numéro respire */
@media (max-width:420px){
  .card-visual .helm{width:72px;height:72px;left:2px;}
  .card-visual .driver-number{padding-right:10px;}
}

/* ── Pilote : numéro à droite, blanc, italique de vitesse, sur la livrée ── */
.driver-number{flex-direction:row;justify-content:flex-end;padding-right:14px;gap:0;}
.card-visual .driver-number.no-img .dn-num{font-size:44px;letter-spacing:-.04em;}
.modal-visual .driver-number.no-img .dn-num{font-size:92px;letter-spacing:-.04em;}
/* Fiche : l'italique de vitesse déborde de sa boîte et le ✕ occupe le
   coin — le numéro respire à gauche de la colonne du bouton. */
.modal-visual .driver-number{padding-right:52px;}
.driver-number.no-img .dn-num{position:relative;z-index:2;color:#fff;transform:skewX(-8deg);
  text-shadow:0 0 12px rgba(0,0,0,.7),0 2px 10px rgba(0,0,0,.85);}

/* ── Écurie : monogramme redessiné + filet + nom complet ── */
.team-emblem{position:relative;z-index:2;display:flex;flex-direction:column;align-items:center;gap:5px;width:100%;padding:0 8px;}
.team-mono{color:#fff;letter-spacing:.06em;transform:skewX(-7deg);
  text-shadow:0 2px 12px rgba(0,0,0,.75);line-height:1;}
.card-visual .team-mono{font-size:30px;}
.modal-visual .team-mono{font-size:52px;}
.team-rule{width:44px;height:2.5px;border-radius:2px;background:#fff;opacity:.9;}
.modal-visual .team-rule{width:70px;}
.team-name{font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  color:rgba(255,255,255,.82);text-align:center;line-height:1.25;max-width:94%;
  text-shadow:0 1px 6px rgba(0,0,0,.8);}
.modal-visual .team-name{font-size:13px;}

/* Sur la TUILE seulement : le nom d'écurie est borné à 2 lignes et
   l'emblème remonte, ce qui réserve le bas du visuel au bouton « + ».
   Avant, les noms longs (Mercedes-AMG Petronas Formula One Team,
   Aston Martin Aramco…) passaient DERRIÈRE le bouton : des glyphes
   masqués, sans rien qui signale la coupe — ça se lit comme un bug de
   rendu. Une ellipse, elle, dit « c'est tronqué ».
   Limite assumée : -webkit-line-clamp coupe à la ligne, pas au mot
   (« …FORMULA ON… »). Aucun réglage CSS ne déplace ce point ; le nom
   complet reste lisible dans la fiche, à 13 px.
   Le -18px n'est pas arrondi : à 320 px, avec -16, le bas du nom
   tombait 1 px DANS le bouton. 18 dégage la boîte de ligne entière et
   laisse encore 7 px de marge en haut du visuel. */
.card-visual .team-name{display:-webkit-box;-webkit-line-clamp:2;
  -webkit-box-orient:vertical;overflow:hidden;max-width:100%;}
.card-visual .team-emblem{transform:translateY(-18px);}

/* ══════════════════════════════════════════════════════════
   v2.3 PHASE C ① — ZONES TACTILES ≥ 44 px
   Le visuel ne bouge pas d'un pixel : la zone de hit est étendue
   par pseudo-élément transparent (il participe au hit-testing de
   son parent). Les extensions horizontales s'arrêtent à mi-chemin
   du voisin interactif pour ne jamais détourner un tap.
   ══════════════════════════════════════════════════════════ */
/* .qbtn et .modal-close sont DÉJÀ position:absolute — les
   passer en relative les sortait de leur coin (régression 1.32.0) ;
   leur ::after fonctionne tel quel sur un élément absolu. */
.schip,.qadd-type,.mqbtn,.sv-tool-tab,
.user-title-next,.season-pill,.logo,.setv-btn,.toast-action{position:relative;}
.schip::after,.qbtn::after,.qadd-type::after,.mqbtn::after,.modal-close::after,
.sv-tool-tab::after,.user-title-next::after,.season-pill::after,
.logo::after,.setv-btn::after,.toast-action::after,.setv-toggle::before{
  content:'';position:absolute;inset:-5px;}
/* ⭐/❤️ des tuiles (24 px) : 44×44 chacune — l'étoile s'étend vers la
   zone morte à sa gauche, le cœur vers le bord de carte à sa droite ;
   entre les deux, coupure à mi-écart (aucun chevauchement). */
.schip[data-s="wishlist"]::after{top:-10px;bottom:-10px;left:-17px;right:-3px;}
.schip[data-s="favorite"]::after{top:-10px;bottom:-10px;left:-3px;right:-17px;}
/* + de tuile (34 px) et partage badges (34 px) → 44 */
.qbtn::after{inset:-5px;}
/* variantes du popover + (30 px) : 44 en vertical, mi-écart en horizontal */
.qadd-type::after{top:-7px;bottom:-7px;left:-3px;right:-3px;}
/* − / + de la fiche (22 px) : 44 en vertical, extension vers le compteur
   (non interactif) — les voisins des cellules adjacentes restent hors
   d'atteinte grâce au padding des cellules */
.mqbtn::after{top:-12px;bottom:-12px;left:-12px;right:-12px;}
/* fermeture de fiche (29 px) → 45 */
.modal-close::after{inset:-8px;}
/* interrupteurs (46×26) : 44 en vertical (::after = curseur, donc ::before) */
.setv-toggle::before{top:-9px;bottom:-9px;left:0;right:0;}
/* onglets outils (32 px), pilule saison (28 px), boutons réglages (34 px),
   action de toast : 44 en vertical */
.sv-tool-tab::after{top:-6px;bottom:-6px;left:0;right:0;}
.season-pill::after{top:-8px;bottom:-8px;left:-2px;right:-2px;}
.setv-btn::after{top:-5px;bottom:-5px;left:-2px;right:-2px;}
.toast-action::after{top:-12px;bottom:-12px;left:-8px;right:-8px;}
/* lockup du header (lien, 26 px de haut) et « ▼ choisir mon titre » (12 px) */
.logo::after{top:-9px;bottom:-9px;left:-6px;right:-6px;}
.user-title-next::after{top:-16px;bottom:-16px;left:-8px;right:-8px;}
/* cases à cocher du contenu des sauvegardes : le label EST la cible —
   padding + marge négative = même rendu, hit de 44 px */
.import-set-row{padding:12px 0;margin:-12px 0;position:relative;z-index:1;}

/* ── PHASE C ② — transition d'entrée entre vues ──
   Même côté d'arrivée que le voyage de la pastille de navigation.
   Transform + opacity uniquement : aucune incidence de layout. */
@keyframes view-in-r{from{opacity:0;transform:translateX(14px);}to{opacity:1;transform:none;}}
@keyframes view-in-l{from{opacity:0;transform:translateX(-14px);}to{opacity:1;transform:none;}}
.view-in-r{animation:view-in-r 150ms ease-out;}
.view-in-l{animation:view-in-l 150ms ease-out;}
@media (prefers-reduced-motion:reduce){
  .view-in-r,.view-in-l{animation:none;}
}

/* Mode réduction des animations = mode performance de fait : TOUTES les
   animations de foil s'arrêtent. La bande reste POSÉE en travers de la
   tuile plutôt que sortie du cadre : à l'arrêt, un foil se distingue
   toujours d'un non-foil au premier regard. */
@media (prefers-reduced-motion:reduce){
  .card.tv-wild_foil,.card.tv-nitro_foil,
  .modal-visual.tv-wild_foil,.modal-visual.tv-nitro_foil{animation:none !important;}
  .card-visual[class*="tv-"]::after,.modal-visual[class*="tv-"]::after,
  .card.tv-nitro_foil::after,.modal-visual.tv-nitro_foil::after{
    animation:none !important;transform:translateX(60%) !important;}
}

/* Fiche à 320 px : libellés de types sur 2 lignes plutôt que tronqués */
.mo-cell-label{white-space:normal;line-height:1.15;display:-webkit-box;
  -webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}


/* ── COULEURS DE VARIANTE VISIBLES DÈS LA BASE (1.34.0) ──
   La couleur possédée lave le CORPS de la carte (dégradé haut → surface)
   + une arête supérieure franche. Le fond du visuel reste à la rareté
   (règle P1). Palette alignée sur les pastilles .cdot. */
.card.vt-blue{--vc:#2E86FF;}
.card.vt-green{--vc:#2ECC71;}
.card.vt-red{--vc:#FF4D4D;}
.card.vt-yellow{--vc:#FFD000;}
.card.vt-blue .card-body,.card.vt-green .card-body,
.card.vt-red .card-body,.card.vt-yellow .card-body{
  background:linear-gradient(168deg,color-mix(in srgb,var(--vc) 26%,var(--surface)) 0%,var(--surface) 72%);
  box-shadow:inset 0 3px 0 color-mix(in srgb,var(--vc) 85%,transparent);
}


/* ══ PHASE D — PASSE THÈME CLAIR (1.35.0) ══
   :root = clair (défaut), [data-theme=dark] = surcharge. Les encres
   colorées inline passent par --ink-mix : pleines en sombre, assombries
   à 74 % en clair pour tenir AA sur fonds clairs. */
:root{--ink-mix:74%;--red-ink:#C50026;--on-red:#FFFFFF;}
[data-theme="dark"]{--ink-mix:100%;--red-ink:var(--red);--on-red:#2A0509;}
/* pilule saison active : rouge d'encre plus profond en clair */
.season-pill.active{color:var(--red-ink);}
/* libellé actif de la nav : même encre */
.bn-label{color:var(--red-ink);}
/* échelle Parcours (Badges) : « fait » lisible en clair */
.bl-rung.done .bl-lbl{color:#1B7A3D;}
[data-theme="dark"] .bl-rung.done .bl-lbl{color:var(--green);}
.bt-chk{background:#1F8747;}
[data-theme="dark"] .bt-chk{background:var(--green);}
/* bouton danger : encre rouge profonde en clair */
.setv-btn.danger{color:var(--red-ink);}

/* ── BADGES v3 (1.36.0) : famille Écuries + ligne de difficulté ── */
.bf-ecuries{--fam:#6692FF;}
.bd-diff{display:flex;align-items:center;gap:8px;margin:6px 0 2px;}
.bd-diff-lb{font-size:11px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;padding:2px 8px;border-radius:99px;}
.dl-facile{background:var(--green-s);color:#1B7A3D;}
.dl-modere{background:#FFF3D6;color:#946800;}
.dl-difficile{background:var(--red-s);color:var(--red-ink,#C50026);}
.dl-extreme{background:#F3E8FF;color:#6D28D9;}
.dl-legendaire{background:#1a1816;color:var(--gold,#FACC15);}
[data-theme="dark"] .dl-facile{background:#14301c;color:#5adf87;}
[data-theme="dark"] .dl-modere{background:#33290a;color:#f7c948;}
[data-theme="dark"] .dl-difficile{background:#3a0f18;color:#ff7f95;}
[data-theme="dark"] .dl-extreme{background:#2a1745;color:#c4a6ff;}
/* ── .dl-legendaire en thème sombre : L'INVERSION ──
   Cette surcharge MANQUAIT — les quatre autres niveaux l'avaient, pas
   celui-ci. Conséquence mesurée : le sommet de l'échelle passait du
   PLUS distinct en clair (contraste 15,72 avec le fond de page, chip
   noir sur page crème) au MOINS distinct en sombre (1,09 — son
   #1a1816 est presque exactement le fond #100e0f). Une inversion, pas
   une simple fadeur : « modéré » y était plus visible que « légendaire ».
   Le principe est celui du thème clair, renversé : ce qui est sombre
   sur page claire devient clair sur page sombre. Contraste avec la
   page : 1,09 → 12,56. Texte/fond : 11,56, soit AAA dans les deux
   thèmes.
   L'or tire l'œil, et c'est sa fonction — un sommet d'échelle doit se
   voir, et ces badges sont rares par construction. */
[data-theme="dark"] .dl-legendaire{background:var(--gold);color:#1a1816;}
.bd-diff-pct{font-size:11px;color:var(--tx3);font-variant-numeric:tabular-nums;}

/* indicateur de difficulté sur chaque tuile (1.37.0) */
.bt-diff{font-size:11px;font-weight:800;letter-spacing:.03em;padding:1px 6px;border-radius:99px;margin-top:2px;}

/* sélecteur de titre v3 : liste complète triée par difficulté */
.title-picker{max-height:280px;}
.title-pick{display:flex;align-items:center;gap:6px;min-height:30px;}
.title-pick .tp-diff{margin-left:auto;font-size:11px;font-weight:800;padding:1px 6px;border-radius:99px;flex:none;}
.tp-locked{opacity:.45;filter:grayscale(.6);cursor:default;pointer-events:none;}

/* ══ PHASE E — hint d'ajout rapide (1.39.0) ══ */
/* bulle du hint : ancrée dans le visuel, pointe vers le + (bas-droit) */
.qa-hint{position:absolute;right:6px;bottom:50px;z-index:5;display:flex;align-items:center;gap:8px;
  max-width:210px;background:#141317;color:#fff;border:1px solid rgba(255,255,255,.16);
  border-radius:12px;padding:8px 10px;font-size:11px;line-height:1.4;font-weight:600;
  box-shadow:0 6px 20px rgba(0,0,0,.45);animation:qa-in .25s ease-out;}
.qa-hint::after{content:'';position:absolute;right:16px;bottom:-6px;width:10px;height:10px;
  background:#141317;border-right:1px solid rgba(255,255,255,.16);border-bottom:1px solid rgba(255,255,255,.16);
  transform:rotate(45deg);}
.qa-hint-x{border:none;background:transparent;color:rgba(255,255,255,.6);font-size:12px;cursor:pointer;
  padding:2px;flex:none;position:relative;}
.qa-hint-x::after{content:'';position:absolute;inset:-12px;}
@keyframes qa-in{from{opacity:0;transform:translateY(4px);}to{opacity:1;transform:none;}}
@media (prefers-reduced-motion:reduce){.qa-hint{animation:none;}}

/* ══ PHASE H — objectifs proches dans Stats (1.41.0) ══ */
.sv-goals{display:flex;flex-direction:column;gap:8px;}
.sv-goal{background:var(--surface);border:1px solid var(--border-s);border-radius:14px;padding:10px 14px;cursor:pointer;transition:var(--ease);}
.sv-goal:hover{border-color:var(--red-m);}
.sv-goal-row{display:flex;align-items:center;gap:14px;}
.sv-goal-txt{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px;}
.sv-goal-txt b{font:700 13px var(--font-d);}
.sv-goal-txt span{font-size:11px;color:var(--tx3);}
.sv-goal .sv-mini-bar{width:110px;flex:none;}
.sv-goal-miss{display:flex;flex-wrap:wrap;gap:5px;margin-top:9px;padding-top:9px;border-top:1px solid var(--border);}

/* ══ A11Y (audit 1.42.2) — la tuile et ses puces d'état sont devenues
   des cibles clavier : il leur faut un anneau de focus visible. Le
   liseré de la tuile est décalé vers l'extérieur pour ne pas se
   confondre avec le liseré « set complet ». ══ */
.card:focus-visible{outline:2px solid var(--red);outline-offset:4px;}
.schip:focus-visible{outline:2px solid var(--red);outline-offset:2px;}
/* Badge propre à une saison passée : une étiquette, pas un masquage —
   un badge gagné reste visible hors de sa saison (règle « une fois
   débloqué, toujours débloqué »), l'étiquette dit seulement pourquoi il
   ne peut plus être obtenu. */
.bd-season{font-size:11px;font-weight:700;color:var(--tx3);background:var(--surface2);
  border:1px solid var(--border);border-radius:var(--r-pill);padding:1px 8px;
  vertical-align:2px;font-variant-numeric:tabular-nums;}
