html, body {
    font-family: 'Stack Sans Text', 'Inter', 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

a, .btn-link {
    color: #006bb7;
}

.btn-primary {
    color: #fff;
    background-color: #1b6ec2;
    border-color: #1861ac;
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}

.content {
    padding-top: 1.1rem;
}

h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}

/* Scale canvas element to fill the 16:9 container div.
   The canvas intrinsic size stays at 1920×1080 (coordinate system),
   but visually it stretches to cover the container. */
#stream-canvas-container {
    position: relative; /* ancre l'overlay des curseurs distants */
}

#stream-canvas-container canvas {
    width: 100%;
    height: 100%;
    display: block;
}

/* Curseurs des autres éditeurs (présence collaborative). Positionnés en % de la base 1920x1080. */
.remote-cursors {
    position: absolute;
    inset: 0;
    overflow: hidden;
    pointer-events: none;
    z-index: 5;
}

.remote-cursor {
    position: absolute;
    display: flex;
    align-items: flex-start;
    gap: 3px;
    transform: translate(-2px, -2px);
    /* Lissage (lerp) : interpole linéairement entre deux positions réseau (~45 ms + gigue),
       pour un déplacement fluide plutôt que des sauts. Le 1er rendu ne transitionne pas. */
    transition: left 100ms linear, top 100ms linear;
}

.remote-cursor > svg {
    flex: 0 0 auto;
    filter: drop-shadow(0 1px 1px rgba(0, 0, 0, .5));
}

.remote-cursor-label {
    margin-top: 10px;
    font-size: 11px;
    font-weight: 600;
    line-height: 1;
    color: #fff;
    padding: 2px 6px;
    border-radius: 8px;
    white-space: nowrap;
    box-shadow: 0 1px 3px rgba(0, 0, 0, .4);
}

/* Curseurs de l'éditeur de canvas. La classe est posée sur le conteneur via Canvas.Cursor ;
   `cursor` étant hérité, le <canvas> enfant l'applique aussi. Sélecteur id+classe = spécificité
   suffisante pour primer sur les utilitaires éventuels (MudBlazor). */
#stream-canvas-container.cursor-default     { cursor: default; }
#stream-canvas-container.cursor-pointer     { cursor: pointer; }
#stream-canvas-container.cursor-move        { cursor: move; }
#stream-canvas-container.cursor-grab        { cursor: grab; }
#stream-canvas-container.cursor-grabbing    { cursor: grabbing; }
#stream-canvas-container.cursor-ns-resize   { cursor: ns-resize; }
#stream-canvas-container.cursor-ew-resize   { cursor: ew-resize; }
#stream-canvas-container.cursor-nwse-resize { cursor: nwse-resize; }
#stream-canvas-container.cursor-crosshair   { cursor: crosshair; }
#stream-canvas-container.cursor-nesw-resize { cursor: nesw-resize; }

/* Toggle group qui remplit la largeur, boutons répartis équitablement (classe posée au cas par cas). */
.oc-toggle-justify .mud-toggle-item { flex: 1 1 0; }

/* Animations d'entrée des alertes (partagées : vue OBS + aperçu du gestionnaire). */
.ab-anim-fade        { animation: abFade 450ms ease both; }
.ab-anim-slide-up    { animation: abSlideUp 500ms cubic-bezier(.2,.8,.2,1) both; }
.ab-anim-slide-down  { animation: abSlideDown 500ms cubic-bezier(.2,.8,.2,1) both; }
.ab-anim-slide-left  { animation: abSlideLeft 500ms cubic-bezier(.2,.8,.2,1) both; }
.ab-anim-slide-right { animation: abSlideRight 500ms cubic-bezier(.2,.8,.2,1) both; }
.ab-anim-pop         { animation: abPop 500ms cubic-bezier(.2,.8,.2,1.4) both; }
.ab-anim-bounce      { animation: abBounce 750ms both; }
.ab-anim-tada        { animation: abTada 850ms both; }

@keyframes abFade { from { opacity: 0; } to { opacity: 1; } }
@keyframes abSlideUp { from { opacity: 0; transform: translateY(40px); } to { opacity: 1; transform: translateY(0); } }
@keyframes abSlideDown { from { opacity: 0; transform: translateY(-40px); } to { opacity: 1; transform: translateY(0); } }
@keyframes abSlideLeft { from { opacity: 0; transform: translateX(60px); } to { opacity: 1; transform: translateX(0); } }
@keyframes abSlideRight { from { opacity: 0; transform: translateX(-60px); } to { opacity: 1; transform: translateX(0); } }
@keyframes abPop { 0% { opacity: 0; transform: scale(.5); } 60% { opacity: 1; transform: scale(1.08); } 100% { opacity: 1; transform: scale(1); } }
@keyframes abBounce { 0% { opacity: 0; transform: translateY(-60px); } 50% { opacity: 1; transform: translateY(12px); } 70% { transform: translateY(-6px); } 100% { transform: translateY(0); } }
@keyframes abTada { 0% { transform: scale(1); } 10%,20% { transform: scale(.9) rotate(-3deg); } 30%,50%,70%,90% { transform: scale(1.1) rotate(3deg); } 40%,60%,80% { transform: scale(1.1) rotate(-3deg); } 100% { transform: scale(1) rotate(0); } }

/* Animations de SORTIE des alertes (.ab-out-*). */
.ab-out-none        { opacity: 0; }
.ab-out-fade        { animation: abOutFade 350ms ease both; }
.ab-out-slide-up    { animation: abOutSlideUp 350ms ease both; }
.ab-out-slide-down  { animation: abOutSlideDown 350ms ease both; }
.ab-out-slide-left  { animation: abOutSlideLeft 350ms ease both; }
.ab-out-slide-right { animation: abOutSlideRight 350ms ease both; }
.ab-out-pop         { animation: abOutPop 350ms ease both; }

@keyframes abOutFade { to { opacity: 0; } }
@keyframes abOutSlideUp { to { opacity: 0; transform: translateY(-40px); } }
@keyframes abOutSlideDown { to { opacity: 0; transform: translateY(40px); } }
@keyframes abOutSlideLeft { to { opacity: 0; transform: translateX(-60px); } }
@keyframes abOutSlideRight { to { opacity: 0; transform: translateX(60px); } }
@keyframes abOutPop { to { opacity: 0; transform: scale(.5); } }

/* Hôte des animations in/out quand elles sont portées par le texte (span) : les transforms
   CSS ne s'appliquent pas aux inline non-remplacés. */
.ab-anim-host { display: inline-block; }

/* Révélation différée du texte d'alerte : maintient le texte invisible pendant le délai configuré
   (animation-delay inline), même pour les animations d'entrée dont la frame 0 est visible (tada). */
.ab-text-delay { display: inline-block; animation: abReveal 1ms linear both; }
@keyframes abReveal { from { opacity: 0; } to { opacity: 1; } }

/* Animations IDLE en boucle sur le texte (.ab-idle-*). Appliquées à un <span> inline-block. */
.ab-idle-pulse   { display: inline-block; animation: abIdlePulse 1.2s ease-in-out infinite; }
.ab-idle-bounce  { display: inline-block; animation: abIdleBounce 1s ease-in-out infinite; }
.ab-idle-shake   { display: inline-block; animation: abIdleShake .5s ease-in-out infinite; }
.ab-idle-wobble  { display: inline-block; animation: abIdleWobble 1.5s ease-in-out infinite; }
.ab-idle-tada    { display: inline-block; animation: abTada 1.8s ease infinite; }
.ab-idle-rainbow { display: inline-block; animation: abIdleRainbow 3s linear infinite; }

@keyframes abIdlePulse { 0%,100% { transform: scale(1); } 50% { transform: scale(1.08); } }
@keyframes abIdleBounce { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-10px); } }
@keyframes abIdleShake { 0%,100% { transform: translateX(0); } 25% { transform: translateX(-4px); } 75% { transform: translateX(4px); } }
@keyframes abIdleWobble { 0%,100% { transform: rotate(0deg); } 25% { transform: rotate(-3deg); } 75% { transform: rotate(3deg); } }
@keyframes abIdleRainbow { 0% { filter: hue-rotate(0deg); } 100% { filter: hue-rotate(360deg); } }

/* Bouton de profil (bas de la sidebar) : occupe toute la largeur de la sidebar.
   On force l'enveloppe d'activateur du MudMenu (et le bouton) en bloc pleine largeur,
   puis le contenu (avatar / nom / chevron) s'aligne à gauche dans un label plein. */
.profile-menu,
.profile-menu > * {
    display: block;
    width: 100%;
}

.mud-menu-profile {
    justify-content: flex-start;
    border-radius: 0;   /* boutons de la navbar (profil, sélecteur de scope) carrés, comme les liens */
}

/* Pages « à soi » (paramètres, mon profil viewer) : le bouton profil porte le fond
   actif — même langage que l'item de nav actif (warm + encre). */
.sb-profile-active {
    background: var(--sb-grad-warm) !important;
}
.sb-profile-active, .sb-profile-active * {
    color: var(--sb-ink) !important;
}

.mud-menu-profile .mud-button-label {
    width: 100%;
}

/* Item de thème / langue sélectionné dans le menu profil : même langage que l'item
   de nav actif — fond jaune → pêche, texte encre. (La coche a été retirée car,
   imbriquée dans un MudMenuItem, elle décalait l'alignement.) */
.mud-menu-toggle-selected {
    background: var(--sb-grad-warm) !important;
}
.mud-menu-toggle-selected,
.mud-menu-toggle-selected * {
    color: var(--sb-ink) !important;
}

/* ── Drawer : repli/extension fluide ─────────────────────────────────────────
   Largeur du drawer et marge du contenu sur la même courbe décélérée (surcharge
   des transitions MudBlazor, d'où !important — le bloc no-anim CI-DESSOUS reprend
   la main au chargement, ne pas le déplacer au-dessus). Les libellés s'estompent
   vite au repli et reviennent en glissant une fois la place faite (délai) :
   plus de rognage sec. */
.mud-drawer-mini,
.mud-main-content {
    transition-duration: .3s !important;
    transition-timing-function: cubic-bezier(.2, .8, .2, 1) !important;
}
.mud-drawer-mini .mud-nav-link {
    transition: padding .3s cubic-bezier(.2, .8, .2, 1);
}
.mud-drawer-mini .mud-nav-link-text {
    white-space: nowrap;
    opacity: 1;
    transition: opacity .18s ease .1s, transform .25s cubic-bezier(.2, .8, .2, 1) .1s;
}
.mud-drawer-mini:not(.mud-drawer--open) .mud-nav-link-text {
    opacity: 0;
    transform: translateX(-8px);
    transition: opacity .1s ease, transform .12s ease;
}
.sb-brand-text {
    max-height: 40px;
    overflow: hidden;
    transition: opacity .18s ease .1s, max-height .3s cubic-bezier(.2, .8, .2, 1), color .45s ease;
}
/* Le titre suit la couleur de la mascotte (l'état est posé sur .sb-brand par le NavMenu). */
.sb-brand--streamer .sb-brand-text { color: #0f95ba !important; }
.sb-brand--modo .sb-brand-text     { color: #03dba8 !important; }
.sb-brand--viewer .sb-brand-text   { color: #ffad33 !important; }
.mud-drawer-mini:not(.mud-drawer--open) .sb-brand-text {
    opacity: 0;
    max-height: 0;
    transition: opacity .1s ease, max-height .3s cubic-bezier(.2, .8, .2, 1);
}
.sb-profile-name,
.sb-profile-chevron {
    transition: opacity .18s ease .12s, width .2s ease, min-width .2s ease;
}
.mud-drawer-mini:not(.mud-drawer--open) .sb-profile-name,
.mud-drawer-mini:not(.mud-drawer--open) .sb-profile-chevron {
    opacity: 0;
    transition: opacity .08s ease, width .2s ease, min-width .2s ease;
}
.mud-drawer-mini:not(.mud-drawer--open) .sb-profile-chevron {
    width: 0;
    min-width: 0;
}
/* Drawer replié : l'avatar (sélecteur de scope, bouton profil) se centre — le libellé
   fondu gardait sa marge ml-3 et son flex-grow, poussant l'avatar contre le bord.
   Le vrai conteneur flex des enfants est le .mud-button-label (span interne pleine
   largeur du MudButton), pas le bouton : c'est lui qu'il faut centrer. */
.mud-drawer-mini:not(.mud-drawer--open) .mud-menu-profile,
.mud-drawer-mini:not(.mud-drawer--open) .mud-menu-profile .mud-button-label {
    justify-content: center;
}
.mud-drawer-mini:not(.mud-drawer--open) .mud-menu-profile {
    min-width: 0;             /* .mud-button impose 64px : le bouton débordait du rail de 56 */
    padding-left: 0;
    padding-right: 0;
}
.mud-drawer-mini:not(.mud-drawer--open) .sb-profile-name {
    margin-left: 0 !important;
    flex: 0 0 0 !important;   /* grow seul laissait la largeur intrinsèque du texte (basis auto) */
    width: 0;
}

/* Désactive les transitions/animations pendant le chargement du layout (classe posée par
   MainLayout tant que !IsLoaded). Évite que le drawer / la sidebar secondaire « glissent »
   à l'arrivée sur une page (notamment Paramètres, rechargée en pleine page). */
.no-anim,
.no-anim * {
    transition: none !important;
    animation: none !important;
}

/* Sidebar (drawer mini) : jamais de scrollbar HORIZONTALE. Le conteneur du drawer et le MudNavMenu
   ont overflow-y: auto ; d'après la spec CSS, l'axe « visible » est alors calculé en « auto », donc
   un contenu à peine trop large (mode replié) affichait une scrollbar horizontale parasite. */
.mud-drawer-mini .mud-drawer-content,
.mud-drawer-mini .mud-navmenu {
    overflow-x: hidden;
}

/* ── Colonne de nav scrollable (brand + scope + menu) ; burger et profil FIXES ──
   Le burger est posé en absolu au-dessus de la zone ; le masque en dégradé estompe
   le contenu qui remonte sous lui — les flèches des groupes (à droite, comme lui)
   disparaissent en douceur avant de l'atteindre, aucun chevauchement brut. */
.sb-nav-burger {
    position: absolute;
    top: 10px;
    right: 12px;
    z-index: 3;
    padding: 0;
}
.sb-nav-scroll {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    overflow-y: auto;
    overflow-x: hidden;
    padding-top: 52px;             /* la place du burger au repos */
    mask-image: linear-gradient(180deg, transparent 8px, #000 48px);
    -webkit-mask-image: linear-gradient(180deg, transparent 8px, #000 48px);
}
/* Sans ça, le MudNavMenu (overflow-x hidden ⇒ taille minimale flex = 0) se COMPRIMAIT
   à l'espace restant et scrollait lui-même — le scroll ne couvrait pas la colonne. */
.sb-nav-scroll > * {
    flex-shrink: 0;
}
/* MudBlazor pose overscroll-behavior-y: contain sur .mud-navmenu, et notre overflow-x: hidden
   ci-dessus en fait un conteneur de défilement (spec : l'autre axe passe de visible à auto).
   Un menu qui n'a désormais RIEN à faire défiler AVALAIT donc la molette au lieu de la passer
   à .sb-nav-scroll : seul le glissement de la barre fonctionnait. Mesuré en page de test. */
.sb-nav-scroll .mud-navmenu {
    overscroll-behavior-y: auto;
}

/* ── Rail replié (56 px) : rien ne doit se décaler quand le contenu déborde ──
   Une scrollbar de 12 px y prend un cinquième de la largeur : icônes, logo et avatar se
   recentraient sur les 44 px restants, soit 6 px trop à gauche (mesuré). Elle est donc
   masquée dans ce seul état — le défilement reste possible (molette, tactile, clavier) et
   le masque en dégradé du haut signale déjà que le contenu continue. Le rail déplié, lui,
   garde sa barre : sur 250 px elle ne décale rien. */
.mud-drawer-mini:not(.mud-drawer--open) .sb-nav-scroll {
    scrollbar-width: none;
}
.mud-drawer-mini:not(.mud-drawer--open) .sb-nav-scroll::-webkit-scrollbar {
    width: 0;
    height: 0;
}
/* Le burger restait calé à droite (position du rail déplié) : 4 px hors du centre, avec ou
   sans scrollbar. Replié, il se centre comme les icônes qu'il surmonte. */
.mud-drawer-mini:not(.mud-drawer--open) .sb-nav-burger {
    right: 50%;
    transform: translateX(50%);
}

/* Champ de couleur de l'inspecteur : la barre (activateur du MudMenu) occupe toute la largeur,
   posée sous le libellé (le min-width sur la pastille évite qu'elle disparaisse si besoin). */
.oc-color-menu,
.oc-color-menu > * {
    display: block;
    width: 100%;
}

/* Barres de défilement : pilule flottante au dégradé signature (rose → pêche → jaune) sur
   piste invisible, sans flèches ; la pilule s'épaissit au survol. ATTENTION : ne PAS poser
   scrollbar-width/color globalement — dès qu'ils sont définis, Chrome ignore les
   ::-webkit-scrollbar riches et retombe sur des barres basiques grises. Le standard est
   réservé à Firefox via @supports. */
::-webkit-scrollbar { width: 12px; height: 12px; background: transparent; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-button { display: none; width: 0; height: 0; }
::-webkit-scrollbar-corner { background: transparent; }
::-webkit-scrollbar-thumb {
    background-image: linear-gradient(180deg, #f2527e, #f69874 55%, #ffd56f);
    background-clip: padding-box;                 /* le dégradé ne peint que l'intérieur… */
    border: 3px solid transparent;                /* …la bordure transparente fait « flotter » la pilule */
    border-radius: 999px;
    min-height: 40px;
}
::-webkit-scrollbar-thumb:horizontal {
    background-image: linear-gradient(90deg, #f2527e, #f69874 55%, #ffd56f);
    min-height: 0;
    min-width: 40px;
}
::-webkit-scrollbar-thumb:hover {
    background-image: linear-gradient(180deg, #f2527e, #ffad33);   /* plus chaud… */
    border-width: 2px;                                             /* …et plus épais sous la souris */
}
::-webkit-scrollbar-thumb:horizontal:hover {
    background-image: linear-gradient(90deg, #f2527e, #ffad33);
}

/* Firefox : pas de dégradé possible — pêche uni. */
@supports not selector(::-webkit-scrollbar) {
    * {
        scrollbar-width: thin;
        scrollbar-color: #f69874 transparent;
    }
}

/* Grille des tuiles métriques (page d'analyse, 12 tuiles) : 6 par rangée sur desktop,
   4 en tablette, 2 en mobile.
   minmax(0, …) et non « 1fr » seul : une piste 1fr a pour minimum la taille de son CONTENU,
   donc une tuile au libellé long (« Récompenses », « Bans/Unbans ») élargissait sa colonne
   et la rangée débordait de la page — les colonnes n'étaient même plus égales. Un minimum
   de 0 les rend vraiment égales ; le libellé, lui, se tronque (il a déjà son ellipsis). */
.sb-tiles {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: 12px;
}
@media (max-width: 1100px) { .sb-tiles { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (max-width: 700px)  { .sb-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* Rangée héros (Abonnements + Follows) et rangée « public » (4 tuiles moyennes). */
.sb-tiles-hero {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 12px;
}
/* Même minimum de 0 que .sb-tiles : sans lui les colonnes devenaient inégales dès qu'un
   libellé s'allongeait, et la rangée finissait par déborder sur les zones étroites. */
.sb-tiles-4 {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 12px;
}
@media (max-width: 700px) { .sb-tiles-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }


/* ═══ DA StreamBlades (maquette Figma « UI Moodboard ») ══════════════════════════
   6 accents pastel déclinés en dégradés ; texte TOUJOURS encre (#1e2128) sur dégradé,
   quel que soit le thème. Les couleurs plates vivent dans le thème MudBlazor
   (ThemeService) ; ici, uniquement ce que MudBlazor ne sait pas faire : les dégradés. */
:root {
    --sb-ink: rgba(30, 33, 40, .9);
    --sb-ink-soft: rgba(30, 33, 40, .55);
    --sb-grad-rose:   linear-gradient(99deg,  #f2527e, #f69874);  /* rose → pêche   */
    --sb-grad-sunset: linear-gradient(107deg, #ffd56f, #03dba8);  /* jaune → menthe */
    --sb-grad-mint:   linear-gradient(99deg,  #03dba8, #0f95ba);  /* menthe → bleu  */
    --sb-grad-warm:   linear-gradient(120deg, #ffd56f, #f69874);  /* jaune → pêche  */
    --sb-clickable:   #FEF8F6;     /* fond des surfaces CLIQUABLES (tuiles, moments, clips) */
    --sb-tile:        #FFFFFF;     /* fond des tuiles NON cliquables (conteneurs, sections) */
}
/* Posée par MainLayout sur le MudLayout quand le thème sombre est actif. */
.sb-dark {
    --sb-clickable: #2E3343;
    --sb-tile:      #232733;
}
/* Les popovers/tooltips MudBlazor rendent HORS du MudLayout (provider en fin de body) :
   sans ceci ils résoudraient la valeur claire de :root même en thème sombre. */
body:has(.sb-dark) {
    --sb-clickable: #2E3343;
    --sb-tile:      #232733;
}

/* Champs « outlined » MudBlazor : PAS de surcharge du focus. La bordure 2px native respecte
   l'encoche du libellé (un trou dans la bordure, via legend) ; un anneau box-shadow, lui,
   ignorait ce trou et BARRAIT le libellé. Les champs maison (police, couleur) gardent leur
   anneau : leurs libellés ont un fond opaque. Même épaisseur visuelle des deux côtés. */

/* Compteur discret dans les titres de pages de sélection : « Génériques (3) ». */
.sb-title-count {
    margin-left: 6px;
    color: var(--mud-palette-text-secondary);
    font-size: .65em;
    font-weight: 400;
}

/* Pilule d'action à plafond atteint : éteinte, sans survol ni relief. */
.sb-act:disabled {
    opacity: .45;
    cursor: default;
    transform: none !important;
    box-shadow: none !important;
}

/* Cartes (MudPaper Outlined) : ombre douce de la maquette au lieu du contour,
   identique en light et dark (le contraste surface/fond fait le reste). */
.mud-paper-outlined {
    border: none !important;
    box-shadow: 0 0 3px rgba(0, 0, 0, .08), 0 12px 10px rgba(0, 0, 0, .08) !important;
}

/* Aération : padding interne des cartes relevé (pa-3/pa-4 sont posés sur les MudPaper
   de tout le site — un seul point de contrôle ici plutôt que 50 edits). */
.mud-paper-outlined.pa-3 { padding: 20px !important; }
.mud-paper-outlined.pa-4 { padding: 24px !important; }

/* Surfaces en dégradé (tuile phare, cartes mises en avant). */
.sb-grad-rose   { background: var(--sb-grad-rose) !important; }
.sb-grad-sunset { background: var(--sb-grad-sunset) !important; }
.sb-grad-mint   { background: var(--sb-grad-mint) !important; }
.sb-grad-warm   { background: var(--sb-grad-warm) !important; }

.sb-grad-rose, .sb-grad-sunset, .sb-grad-mint, .sb-grad-warm,
.sb-grad-rose .mud-typography, .sb-grad-sunset .mud-typography,
.sb-grad-mint .mud-typography, .sb-grad-warm .mud-typography,
.sb-grad-rose .mud-icon-root, .sb-grad-sunset .mud-icon-root,
.sb-grad-mint .mud-icon-root, .sb-grad-warm .mud-icon-root {
    color: var(--sb-ink) !important;
}

.sb-grad-rose .mud-text-secondary, .sb-grad-sunset .mud-text-secondary,
.sb-grad-mint .mud-text-secondary, .sb-grad-warm .mud-text-secondary {
    color: var(--sb-ink-soft) !important;
}

/* Code couleur par métrique (mapping des tuiles de la maquette Figma), identique light
   et dark : rose = abonnements/score, pêche = messages/gifts, doré = chatteurs/tops/points,
   jaune = raids, vert = follows, bleu = bits/emotes. Posé sur icônes et textes. */
.sb-rose   { color: #f2527e !important; }
.sb-peach  { color: #f69874 !important; }
.sb-gold   { color: #ffad33 !important; }
.sb-yellow { color: #ffd56f !important; }
.sb-mint   { color: #03dba8 !important; }
.sb-blue   { color: #0f95ba !important; }
.sb-red    { color: var(--mud-palette-error) !important; }

/* Chip outline doré (« Les plus fidèles » de la maquette). */
.sb-chip-gold { color: #ffad33 !important; border-color: #ffad33 !important; }

/* Score : barre de composition empilée (la part de chaque métrique dans le total).
   Gap de 2 px entre segments (séparation lisible sans dépendre de la couleur),
   piste = fond de page. Pas d'overflow:hidden : les segments gonflent au survol. */
.sb-stack {
    display: flex;
    gap: 2px;
    height: 14px;
    border-radius: 8px;
    background: var(--mud-palette-background);
    transform-origin: left center;
    animation: sbGrow .5s cubic-bezier(.2, .8, .2, 1);
}
@keyframes sbGrow { from { transform: scaleX(0); } to { transform: scaleX(1); } }

.sb-seg {
    min-width: 4px;
    height: 100%;
    border-radius: 4px;
    cursor: pointer;
    transition: transform .18s cubic-bezier(.34, 1.56, .64, 1), filter .18s ease, opacity .18s ease;
}

/* Survol « focus » : le segment visé gonfle (petit rebond) et s'illumine, les autres s'estompent. */
.sb-stack:hover .sb-seg { opacity: .45; }
.sb-stack .sb-seg:hover {
    opacity: 1;
    transform: scaleY(1.5);
    filter: brightness(1.08) saturate(1.12);
    box-shadow: 0 2px 10px rgba(0, 0, 0, .35);
}

/* Tuiles de métriques (analyse) : neutres par défaut, le dégradé signature + texte encre est
   l'état de SURVOL ; cliquables (le clic ouvre le détail de la métrique). */
/* Un dégradé CSS ne s'anime pas en transition : le fond chaud vit dans un ::before qui FOND
   en opacité au survol ; le contenu passe au-dessus (z-index) et ses couleurs fondent aussi. */
.sb-tile { cursor: pointer; position: relative; transition: transform .15s ease; background: var(--sb-clickable) !important; }
.sb-tile::before {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--sb-grad-rose);
    border-radius: inherit;
    opacity: 0;
    transition: opacity .25s ease;
    pointer-events: none;
}
.sb-tile > * { position: relative; z-index: 1; }
.sb-tile .mud-typography, .sb-tile .mud-icon-root { transition: color .2s ease; }
.sb-tile:hover { transform: translateY(-2px); }
.sb-tile:hover::before { opacity: 1; }
/* Tuile actionnable au clavier : focus visible aux couleurs du thème. */
.sb-tile:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 2px;
}
/* Badge de tendance des tuiles : un cran plus lisible que la caption standard. Masqué sur
   mobile — les tuiles en 2 colonnes sont trop étroites (débordement ou ligne parasite qui
   désaligne les tuiles), et son tooltip (survol) n'existe pas au tactile. */
.sb-trend {
    font-size: .8rem !important;
    font-weight: 700 !important;
}
@media (max-width: 700px) {
    .sb-trend { display: none !important; }
}

/* Tuiles héros (Abonnements / Follows) : chacune dans SA couleur, en dégradé plein qui
   claque — rose pour les abonnements, menthe → bleu pour les follows. Sur dégradé,
   tout passe encre (règle DA), tendance comprise (le vert/rose du badge se noierait).
   Placées APRÈS .sb-tile pour gagner la cascade. */
.sb-hero-subs    { background: linear-gradient(120deg, #f2527e, #ff85ab) !important; }
.sb-hero-follows { background: var(--sb-grad-mint) !important; }
/* Héros du profil : messages en pêche (la couleur des messages partout sur le site). */
.sb-hero-messages { background: linear-gradient(120deg, #f69874, #ffbe98) !important; }
.sb-tile-hero .mud-typography, .sb-tile-hero .mud-icon-root {
    color: var(--sb-ink) !important;
}
.sb-tile-hero .mud-text-secondary {
    color: var(--sb-ink-soft) !important;
}
/* La VALEUR (le h3) : encre au repos comme le reste, BLANCHE au survol — le même
   comportement que les autres tuiles. Spécificité supérieure au hover générique
   (qui repasserait tout en encre). Scopé aux héros CLIQUABLES (.sb-tile) : ceux
   du profil sont de purs affichages et ne doivent suggérer aucun clic. */
.sb-tile.sb-tile-hero:hover .mud-typography.mud-typography-h3 {
    color: #fff !important;
}

/* ⓘ de définition d'une tuile : posé dans l'angle, discret en permanence. */
.sb-tile .sb-tile-info {
    position: absolute;
    top: 6px;
    right: 6px;
    font-size: 1rem;
    opacity: .5;
    transition: opacity .15s ease;
}
.sb-tile:hover .sb-tile-info { opacity: .9; }

/* Lignes des moments forts : toute la ligne mène à la VOD. Hover chaud en fondu (calque),
   textes en encre, et la flèche file vers la droite. */
.sb-moment {
    position: relative;
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: 10px 12px;
    border-radius: 10px;
    text-decoration: none;
    color: inherit;
    cursor: pointer;
    transition: transform .15s ease;
    background: var(--sb-clickable);
}

/* Les moments en GRILLE : des cartes compactes, plusieurs par ligne dès que la place le permet. */
.sb-moments {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: .5rem;
}

/* ── Page de sélection des streams : cartes .sb-moment (même hover dégradé + flèche
   « Analyser ») en deux dispositions — liste (une colonne) ou grille (avec miniature). ── */
.sb-streams-list {
    display: flex;
    flex-direction: column;
    gap: .5rem;
}
.sb-streams-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
    gap: 1rem;
}
.sb-stream-item { min-width: 0; }
.sb-stream-main {
    flex: 1 1 auto;
    min-width: 0;
}
.sb-stream-title {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.sb-stream-stats {
    display: flex;
    align-items: center;
    gap: .9rem;
    flex: 0 0 auto;
}
.sb-stream-stat {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    font-size: .85rem;
    white-space: nowrap;
}
/* En liste : chaque stat a une largeur minimale par colonne (icône calée à gauche),
   pour que abonnements / follows / messages / rang s'alignent d'une carte à l'autre. */
.sb-streams-list .sb-stream-stat:nth-child(1) { min-width: 68px; }
.sb-streams-list .sb-stream-stat:nth-child(2) { min-width: 68px; }
.sb-streams-list .sb-stream-stat:nth-child(3) { min-width: 92px; }   /* messages : les plus longs */
.sb-streams-list .sb-stream-stat:nth-child(4) { min-width: 52px; }
/* Liste sur mobile : la rangée unique ne tient plus — titre/date gardent la ligne de
   la coche, les stats passent dessous en pleine largeur ; le CTA « Analyser » et la
   flèche (états de survol) n'ont pas de sens au tactile et rendent leur place. */
@media (max-width: 700px) {
    .sb-stream-item .sb-moment-cta,
    .sb-stream-item .sb-moment-arrow { display: none; }
    .sb-streams-list .sb-stream-item { flex-wrap: wrap; }
    /* Base 0 : le bloc titre/date ne wrappe JAMAIS (un item flex ne se comprime pas
       pour éviter le wrap, il faut une base nulle) — il reste sur la ligne de la
       coche et se tronque au besoin. */
    .sb-streams-list .sb-stream-main { flex: 1 1 0; }
    /* Les stats passent sous le titre et s'étalent d'un bord à l'autre. Elles gardent
       leurs LARGEURS DE COLONNE (déclarées plus haut) : ces largeurs étant les mêmes sur
       toutes les cartes, et la carte ayant toujours la même largeur, space-between place
       les icônes exactement au même endroit d'un stream à l'autre — sans laisser le vide
       en fin de rangée que produisaient des colonnes égales alignées à gauche. */
    .sb-streams-list .sb-stream-stats {
        flex: 1 1 100%;
        flex-wrap: nowrap;
        justify-content: space-between;
        gap: .5rem;
        padding: 0 .5rem;      /* la rangée ne colle plus aux bords de la carte */
    }
    .sb-streams-list .sb-stream-stat { flex: 0 0 auto; }
}

/* ── Page de sélection des génériques : mêmes cartes .sb-moment que les streams, en deux
      dispositions. La grille n'a pas de miniature : ses zones lui sont propres. ── */
.sb-credit-list { display: flex; flex-direction: column; gap: .5rem; }
.sb-credit-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
    gap: 1rem;
}
.sb-credit-item { min-width: 0; }
/* Pastille de la couleur principale : l'identité du générique d'un coup d'œil. Le liseré la
   détache du fond quand la couleur s'en approche (générique blanc sur carte claire). */
.sb-credit-swatch {
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    border: 2px solid rgba(128, 128, 128, .35);
}
/* ── Menu trois-points des cartes (génériques ET blades) — un seul style. ──
   Pastille ENCRE translucide, lisible posée sur un aperçu comme sur le fond de carte ; au
   survol, la signature de la DA : dégradé chaud, glyphe encre. En grille, il se pose sur
   l'aperçu (coin haut droit) ; en liste, il ferme la rangée, en flux. */
.sb-card-menu { display: inline-flex; }
.sb-card-menu .mud-icon-button {
    padding: 4px;
    border-radius: 8px;                 /* le carré arrondi de la DA, pas le rond MudBlazor */
    background: #1E2128;                /* l'encre de la palette, PLEINE — ni voile ni bordure */
    color: #fff;
    transition: background-color .15s ease, color .15s ease;
}
/* Même glyphe partout : blades et génériques déclarent des tailles MudBlazor différentes. */
.sb-card-menu .mud-icon-button .mud-icon-root { font-size: 1.35rem; }
.sb-card-menu .mud-icon-button:hover {
    background: var(--sb-grad-warm);
}
/* Le bouton est SOUSTRAIT au survol de la carte : .sb-moment:hover repeint toutes les icônes
   en encre (!important), ce qui rendait le glyphe invisible sur la pastille encre. Blanc tant
   que la pastille est sombre, encre quand SON survol pose le dégradé — carte survolée ou non.
   (Même parade que la pastille « Fusionner » des streams.) */
.sb-card-menu .mud-icon-root,
.sb-moment:hover .sb-card-menu .mud-icon-root { color: #fff !important; }
.sb-card-menu .mud-icon-button:hover .mud-icon-root,
.sb-moment:hover .sb-card-menu .mud-icon-button:hover .mud-icon-root { color: var(--sb-ink) !important; }
.sb-blade-grid .sb-card-menu,
.sb-credit-grid .sb-card-menu {
    position: absolute;      /* sur l'aperçu — les cartes-grille sont position:relative */
    top: 14px;
    right: 14px;
    z-index: 2;              /* au-dessus du calque de survol (enfants de .sb-moment : z 1) */
}
/* Révélé au survol de la carte ou au focus clavier (menu ouvert compris), façon coche des
   streams. L'opacité préserve l'espace en liste. Au tactile, toujours visible. */
@media (hover: hover) {
    .sb-credit-item .sb-card-menu,
    .sb-blade-item .sb-card-menu { opacity: 0; transition: opacity .15s ease; }
    .sb-credit-item:hover .sb-card-menu,
    .sb-credit-item:focus-within .sb-card-menu,
    .sb-blade-item:hover .sb-card-menu,
    .sb-blade-item:focus-within .sb-card-menu { opacity: 1; }
}
/* En grille : aperçu au-dessus (le menu trois-points se pose dessus, comme les blades), nom,
   repères, le CTA « Éditer » et la flèche ferment la carte. (En liste, le flex par défaut de
   .sb-moment aligne tout en rangée.) */
.sb-credit-grid .sb-credit-item {
    position: relative;                 /* ancre du menu posé sur l'aperçu */
    overflow: hidden;                   /* rogne l'aperçu débordant au rayon de la carte */
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    grid-template-areas:
        "thumb  thumb thumb  thumb"
        "swatch main  main   main"
        "stats  stats cta    arrow";
    /* Rangées EXPLICITES. Sans elles, les trois pistes sont « auto » et align-content vaut
       stretch en grille : dès qu'une carte est étirée à la hauteur de sa voisine, chaque piste
       grandit et align-items:center recentre la vignette dans la sienne — d'où un espace au
       -dessus de l'aperçu. Ici la vignette garde sa hauteur naturelle (auto, donc ancrée en
       haut) et c'est la rangée du titre qui absorbe le surplus. */
    grid-template-rows: auto 1fr auto;
    row-gap: .65rem;
    align-items: center;
}
/* Miniature : l'overlay réel dans une iframe, transparente aux clics (la carte mène à
   l'éditeur). Fond sombre fixe : l'overlay est transparent et les génériques sont typiquement
   en texte clair — sur le fond de carte du thème clair, ils seraient invisibles.
   BORD À BORD comme les cartes de streams : les marges négatives annulent le padding de la
   carte (10px 12px), les coins hauts reprennent son rayon. */
/* Déborde d'un pixel de plus que le padding annulé : au survol, le calque dégradé de la carte
   s'allume SOUS la vignette, et l'arrondi sous-pixel laissait sinon poindre un liseré sur ses
   bords. La carte, overflow:hidden, rogne ce débord à son propre rayon. */
.sb-credit-thumb {
    grid-area: thumb;
    width: calc(100% + 26px);
    margin: -11px -13px 0;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    /* Le même damier de transparence que les vignettes de blades. */
    background: repeating-conic-gradient(#232832 0 25%, #191d26 0 50%) 0 0 / 16px 16px;
}
.sb-credit-thumb iframe {
    display: block;
    width: 100%;
    height: 100%;
    border: 0;
    pointer-events: none;
}
.sb-credit-grid .sb-credit-swatch  { grid-area: swatch; }
.sb-credit-grid .sb-stream-main    { grid-area: main; }
.sb-credit-grid .sb-stream-stats   { grid-area: stats; justify-self: start; flex-wrap: wrap; gap: .7rem; }

.sb-credit-grid .sb-moment-cta     { grid-area: cta; justify-self: end; }
.sb-credit-grid .sb-moment-arrow   { grid-area: arrow; }
/* En liste : largeurs minimales par colonne de repère (icône calée à gauche), pour que
   catégories / animation / durée / période s'alignent d'une carte à l'autre. */
.sb-credit-list .sb-stream-stat:nth-child(1) { min-width: 44px; }
.sb-credit-list .sb-stream-stat:nth-child(2) { min-width: 108px; }   /* « Défilement », le plus long */
.sb-credit-list .sb-stream-stat:nth-child(3) { min-width: 62px; }
.sb-credit-list .sb-stream-stat:nth-child(4) { min-width: 66px; }
/* En étroit : le CTA et la flèche (états de survol) rendent leur place, les repères passent
   sous le titre en pleine largeur. */
@media (max-width: 700px) {
    .sb-credit-item .sb-moment-cta,
    .sb-credit-item .sb-moment-arrow { display: none; }
    .sb-credit-list .sb-credit-item { flex-wrap: wrap; }
    .sb-credit-list .sb-stream-main { flex: 1 1 0; }
    .sb-credit-list .sb-stream-stats {
        flex: 1 1 100%;
        flex-wrap: wrap;
        gap: .6rem;
        padding: 0 .25rem;
    }
}

/* ── Page de sélection des blades : mêmes cartes .sb-moment, l'aperçu étant le RENDU RÉEL du
      canvas (BladeCanvas non interactif). ── */
.sb-blade-list { display: flex; flex-direction: column; gap: .5rem; }
.sb-blade-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
    gap: 1rem;
}
.sb-blade-item { min-width: 0; }
/* En grille : aperçu au-dessus, nom et repères en dessous, le CTA « Éditer » et la flèche
   ferment la carte. (En liste, le flex par défaut de .sb-moment aligne tout en rangée.) */
.sb-blade-grid .sb-blade-item {
    position: relative;                 /* ancre du menu contextuel posé sur l'aperçu */
    overflow: hidden;                   /* rogne l'aperçu débordant au rayon de la carte */
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto;
    grid-template-areas:
        "thumb thumb thumb"
        "main  cta   arrow";
    row-gap: .65rem;
    align-items: center;
}
.sb-blade-grid .sb-blade-thumb    { grid-area: thumb; }
.sb-blade-grid .sb-stream-main    { grid-area: main; }
.sb-blade-grid .sb-moment-cta     { grid-area: cta; justify-self: end; }
.sb-blade-grid .sb-moment-arrow   { grid-area: arrow; }
/* Aperçu : fond sombre fixe — le canvas d'un blade est souvent transparent, ses éléments
   pensés pour se poser sur du gameplay. BORD À BORD comme les cartes de streams : les marges
   négatives annulent le padding de la carte, les coins hauts reprennent son rayon. */
/* Même débord d'un pixel que la vignette des génériques (liseré du calque de survol). */
.sb-blade-thumb {
    width: calc(100% + 26px);
    margin: -11px -13px 0;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    /* Damier de transparence (celui de l'aperçu de l'éditeur, en tuiles de vignette) :
       l'overlay est transparent dans OBS, le damier dit ce qui est vide. */
    background: repeating-conic-gradient(#232832 0 25%, #191d26 0 50%) 0 0 / 16px 16px;
}
/* En étroit : le CTA et la flèche (états de survol) rendent leur place. */
@media (max-width: 700px) {
    .sb-blade-item .sb-moment-cta,
    .sb-blade-item .sb-moment-arrow { display: none; }
}

/* Toolbar : la même carte-outils que l'éditeur de blade (MudPaper + MudToolBar compacte),
   collée en haut de la zone de scroll. Le fond de page du wrapper ne se voit que derrière
   les coins arrondis et dans le padding-top — l'air au-dessus de la carte collée, sans
   bande où la liste défilerait. Hauteur sticky totale : 8 + 52 = 60px (le top des mois). */
.sb-streams-toolbar {
    position: sticky;
    top: 0;
    z-index: 5;
    background: var(--mud-palette-background);
    padding-top: var(--sb-toolbar-pad);
}
/* Tous les contrôles de la barre à la MÊME hauteur : chacun suit sinon ses propres
   paddings MudBlazor (bouton-icône, bascule, bouton texte), et la rangée part en escalier.
   La bascule grille/liste sert de référence. */
.sb-streams-toolbar .sb-act,
.sb-streams-toolbar .mud-toggle-group {
    height: 38px;
    box-sizing: border-box;   /* la hauteur inclut bordure et padding des deux côtés */
}
.sb-streams-toolbar .sb-act { padding: 0 14px; }

/* Hauteur FIGÉE (remplace les 48-64px responsives de MudBlazor) : c'est le `top` de
   l'en-tête de mois sticky qui se cale dessous. UNE SEULE source de vérité — les deux
   règles dérivent des variables, plus de valeurs jumelles à garder synchronisées. */
:root {
    --sb-toolbar-pad: 8px;    /* l'air au-dessus de la carte collée */
    --sb-toolbar-h: 60px;     /* hauteur sticky TOTALE = padding + barre */
}
.sb-streams-toolbar .mud-toolbar {
    height: auto;
    min-height: calc(var(--sb-toolbar-h) - var(--sb-toolbar-pad));
}
/* Fusionner / Supprimer : deux boutons OUTLINE (radius 8, comme les cartes/menus de la
   DA) qui se REMPLISSENT au survol — Fusionner en pêche → dégradé chaud signature texte
   encre, Supprimer en rouge → fond rouge texte blanc. Hover soulevé + ombre colorée ;
   verrouillé = estompé, sans pointeur (les events passent au span porteur du tooltip). */
.sb-act {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    cursor: pointer;
    background: transparent;
    border: 1.5px solid transparent;
    border-radius: 8px;
    padding: 5.5px 14.5px;
    font-family: inherit;
    font-size: .85rem;
    font-weight: 700;
    transition: transform .15s ease, box-shadow .15s ease, background-color .15s ease, color .15s ease, border-color .15s ease;
}
.sb-act .mud-icon-root { font-size: 1.15rem; }
.sb-act:disabled { opacity: .4; cursor: default; pointer-events: none; box-shadow: none; }
/* Au repos, le contour ET le texte reprennent le dégradé chaud (le hover, lui, remplit le
   fond). Contour dégradé à ÉPAISSEUR UNIFORME : la bordure réelle (1.5px de .sb-act) est peinte
   par le dégradé (background-clip:border-box) et le centre masqué par une couche padding-box de
   la couleur de surface. Plus fiable que le masque sur ::before, qui rendait les coins et
   l'épaisseur irréguliers en sub-pixel. (Le centre suit la surface : identique au repos en
   thème sombre, écart clair/crème imperceptible en thème clair.) */
.sb-act-merge,
.sb-month-recap {
    border-color: transparent;
    background:
        linear-gradient(var(--mud-palette-surface), var(--mud-palette-surface)) padding-box,
        var(--sb-grad-warm) border-box;
    color: #ffd56f;            /* teinte l'icône SVG (non clippable) sur le JAUNE du dégradé —
                                  elle est à gauche, là où le dégradé du texte démarre en jaune */
}
/* Texte en dégradé (clip sur le glyphe). Cible le libellé, jamais l'icône SVG. */
.sb-act-merge span,
.sb-month-recap span {
    background: var(--sb-grad-warm);
    -webkit-background-clip: text;
            background-clip: text;
    color: transparent;
}
.sb-act-merge:hover,
.sb-month-recap:hover {
    background: var(--sb-grad-warm);
    color: var(--sb-ink);
    transform: translateY(-1px);
    /* Ombre volontairement DISCRÈTE : elle marque le soulèvement sans halo appuyé. */
    box-shadow: 0 2px 6px rgba(246, 152, 116, .25);
}
/* Fond plein au survol : la couche padding-box est écrasée (dégradé edge-to-edge, clip
   border-box par défaut), et le texte repasse en encre. */
.sb-act-merge:hover span,
.sb-month-recap:hover span {
    background: none;
    color: var(--sb-ink);
    -webkit-background-clip: initial;
            background-clip: initial;
}
.sb-act-delete {
    border-color: var(--mud-palette-error);
    color: var(--mud-palette-error);
}
.sb-act-delete:hover {
    background: var(--mud-palette-error);
    color: #fff;
    transform: translateY(-1px);
    box-shadow: 0 4px 14px rgba(242, 82, 110, .4);
}
/* En étroit : le label s'efface, le bouton se resserre autour de l'icône. */
@media (max-width: 700px) {
    .sb-streams-toolbar .sb-act-label { display: none; }
    /* Sans libellé, le bouton devient CARRÉ (la hauteur commune de la barre) : une icône
       centrée dans un carré, plutôt qu'un rectangle qui garderait la largeur du texte. */
    .sb-streams-toolbar .sb-act {
        width: 38px;
        padding: 0;
        justify-content: center;
    }
}
/* Rafraîchissement en cours : seul le glyphe du bouton tourne. */
.sb-spin svg { animation: sbSpin .8s linear infinite; }
@keyframes sbSpin { to { transform: rotate(360deg); } }

/* La coche façon Outlook : invisible au repos, révélée au survol de la carte, au focus
   clavier, ou dès qu'une sélection est en cours. L'opacité préserve l'espace (liste).
   Au tactile (pas de survol possible), toujours visible. */
.sb-stream-check { margin: -8px -4px; }   /* la checkbox MudBlazor a un padding généreux */
.sb-stream-check-zone { display: inline-flex; }
@media (hover: hover) {
    .sb-stream-item .sb-stream-check-zone { opacity: 0; transition: opacity .15s ease; }
    .sb-stream-item:hover .sb-stream-check-zone,
    .sb-stream-item:focus-within .sb-stream-check-zone,
    .sb-streams-selecting .sb-stream-check-zone { opacity: 1; }
}

/* ── Fusion suggérée : des cartes consécutives qui semblent être un même stream coupé
   (reconnexion Twitch, bot interrompu). Elles sont réunies derrière un RAIL pêche — la
   couleur du bouton Fusionner — qui court sur toute la hauteur du bloc. Le rail est
   l'affordance : il porte les raisons (tooltip), s'épaissit au survol pour révéler
   « Fusionner », et allume alors tout le bloc. La fusion reste une décision : le clic
   ouvre le même modal de confirmation que la barre d'outils. */
/* Le rail et la pile de cartes sont CÔTE À CÔTE : en s'ouvrant, le rail pousse les cartes
   vers la droite au lieu de passer par-dessus. */
.sb-merge-group {
    display: flex;
    align-items: stretch;
    gap: 6px;
}
/* Même rythme vertical que la liste : un bloc de fusion ne se distingue pas par son
   espacement, seulement par le rail qui le longe. */
.sb-merge-cards {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: .5rem;
}
/* Bord gauche PLAT face au rail : aucun arrondi du côté de la jonction. */
.sb-merge-cards > .sb-stream-item {
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
}

.sb-merge-rail {
    flex: 0 0 auto;
    width: 8px;
    padding: 0;
    border: none;
    border-radius: 8px 0 0 8px;   /* arrondi côté extérieur ; plat face aux cartes */
    background: #f69874;
    color: var(--sb-ink);
    cursor: pointer;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: flex-start;
    transition: width .18s ease, box-shadow .18s ease;
}
.sb-merge-rail:hover,
.sb-merge-rail:focus-visible {
    width: 132px;                 /* le libellé tient sur deux lignes : le rail reste étroit */
    box-shadow: 0 4px 14px rgba(246, 152, 116, .5);
}
/* Le libellé n'existe qu'une fois le rail ouvert (il est masqué par l'overflow au repos,
   l'opacité évite qu'il ne « clignote » pendant l'élargissement). */
.sb-merge-rail-label {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    padding: 0 12px;
    width: 132px;                 /* la largeur ouverte : le texte s'y répartit sur deux lignes */
    flex: 0 0 auto;
    text-align: left;
    font-family: inherit;
    font-size: .85rem;
    font-weight: 700;
    line-height: 1.25;
    opacity: 0;
    transition: opacity .15s ease;
}
.sb-merge-rail-label .mud-icon-root { flex-shrink: 0; }
.sb-merge-rail:hover .sb-merge-rail-label,
.sb-merge-rail:focus-visible .sb-merge-rail-label { opacity: 1; }

/* ── État vide (aucun stream) : la mascotte de l'app sur le dégradé signature, un titre
   et une phrase qui explique quoi attendre. Pas d'alerte : rien n'est en erreur. ── */
.sb-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    /* Centré aussi VERTICALEMENT : le bloc occupe la hauteur restante sous la barre
       d'outils plutôt que de se tasser en haut d'une page vide. */
    justify-content: center;
    /* Occupe la hauteur restante sous l'en-tête et la barre d'outils. La chaîne de parents
       (.mud-layout n'est pas un conteneur flex) ne propage aucune hauteur : impossible de
       s'appuyer sur un pourcentage, d'où le calcul en viewport. La déduction est VOLONTAIREMENT
       généreuse — titre, barre, espacements et marges de page — pour ne jamais provoquer de
       barre de défilement ; le bloc reste centré à quelques pixels près.
       En dvh : sur mobile, la barre d'adresse rétractable fausse 100vh. */
    min-height: calc(100dvh - 230px);
    text-align: center;
    gap: .5rem;
    padding: 24px;
}
/* Variante EN LIGNE : le même bloc à l'intérieur d'un panneau (onglet, carte), où la
   hauteur pleine page n'aurait aucun sens — il se contente de sa taille naturelle. */
.sb-empty-inline {
    min-height: 0;
    padding: 28px 16px;
}
.sb-empty-inline .sb-empty-art { width: 72px; height: 72px; border-radius: 18px; }

.sb-empty-art {
    width: 96px;
    height: 96px;
    margin-bottom: .6rem;
    border-radius: 24px;
    background: #0f95ba;            /* le bleu « streamer » de la mascotte */
    display: flex;
    align-items: center;
    justify-content: center;
}
.sb-empty-art svg { width: 80%; height: auto; }
.sb-empty-text { max-width: 460px; }

/* ── Dialogs qui listent des streams (fusion, suppression) : une ligne = un stream, avec
   sa période et ses repères. Ces règles vivent ici — et non dans un <style> de composant —
   pour être disponibles quel que soit le dialog ouvert en premier. */
.sb-dlg-list { display: flex; flex-direction: column; gap: 4px; }
.sb-dlg-row {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: 8px 12px;
    border-radius: 8px;
    background: var(--sb-clickable, var(--mud-palette-surface));
}
.sb-dlg-row-main { flex: 1 1 auto; min-width: 0; }
/* Le RÉSULTAT d'une fusion : cadré de pêche, c'est lui qu'on vérifie avant de confirmer. */
.sb-dlg-result { border: 1px solid #f69874; }
/* Ce qui va être DÉTRUIT : cadré de rouge, et les chiffres barrés (voir ci-dessous). */
.sb-dlg-doomed { border: 1px solid var(--mud-palette-error); }
.sb-dlg-doomed .sb-dlg-stats { text-decoration: line-through; opacity: .75; }
/* Une seule ligne de repères, dans les couleurs de la liste des streams. */
.sb-dlg-stats {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: .2rem .9rem;
    font-size: .8rem;
    color: var(--mud-palette-text-secondary);
}
.sb-dlg-stats > span { display: inline-flex; align-items: center; gap: .25rem; }
.sb-dlg-arrow {
    display: flex;
    justify-content: center;
    color: var(--mud-palette-text-secondary);
    padding: 6px 0 2px;
}

/* ── Fusion suggérée en vue GRILLE ────────────────────────────────────────────
   Les cartes reliées n'y sont pas empilées (elles peuvent être côte à côte, à cheval
   sur deux rangées, voire séparées par un retour à la ligne) : pas de rail, donc, mais
   un liseré pêche commun. Le survol de l'une allume tout le groupe — cette règle-là est
   générée par la page, le nombre de groupes étant dynamique. */
.sb-streams-grid .sb-stream-grouped {
    /* Un liseré EN BAS, pas un cadre. Au repos il est en demi-teinte ; le survol du groupe
       le porte à pleine couleur — sans jamais changer d'épaisseur, pour que rien ne bouge. */
    box-shadow: inset 0 -3px 0 rgba(246, 152, 116, .5);
    transition: box-shadow .15s ease;
}
/* Ligne « date · durée [badge live] [pastille de fusion] » : un conteneur FLEX, pour que
   tout y soit centré verticalement — les rustines de vertical-align sur des éléments plus
   hauts que le texte finissaient toujours par tomber d'un pixel. */
.sb-stream-when {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: .25rem;
}
/* Pastille d'action : l'icône seule au repos, le libellé se déplie au survol. Elle
   fusionne le GROUPE entier, pas la seule carte qui la porte. */
.sb-merge-chip {
    display: inline-flex;
    align-items: center;
    gap: .25rem;
    padding: 2px 8px;
    line-height: 1;
    border: 1px solid #f69874;
    border-radius: 999px;
    background: transparent;
    color: #f69874;
    font-family: inherit;
    font-size: .72rem;
    font-weight: 700;
    cursor: pointer;
    flex-shrink: 0;
    transition: background-color .15s ease, color .15s ease;
}
.sb-merge-chip .mud-icon-root { font-size: .9rem; }
.sb-merge-chip:hover { background: #f69874; color: var(--sb-ink); }
.sb-merge-chip-label { display: none; }
.sb-merge-chip:hover .sb-merge-chip-label { display: inline; }
/* Carte survolée : son fond passe au dégradé chaud, où un contour pêche se noierait.
   La pastille bascule donc en ENCRE — et s'y remplit à son tour quand on la vise. */
.sb-moment:hover .sb-merge-chip {
    border-color: var(--sb-ink);
    color: var(--sb-ink);
}
.sb-moment:hover .sb-merge-chip:hover {
    background: var(--sb-ink);
    color: #fff;
}
/* L'icône suit : sans cela, la règle d'encre du survol de carte la garderait sombre
   sur le fond sombre de la pastille remplie. */
.sb-moment:hover .sb-merge-chip:hover .mud-icon-root { color: #fff !important; }

/* Sélectionnée : le même langage que la sélection du site (fond warm léger). */
.sb-stream-selected {
    background: linear-gradient(120deg, rgba(255, 213, 111, .18), rgba(246, 152, 116, .18)),
                var(--sb-clickable);
}

/* Badge « EN DIRECT » (remplace « en cours ») : pastille rouge + point qui clignote. */
.sb-live-badge {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    padding: 1px 8px;
    border-radius: 999px;
    background: #ff3b30;
    color: #fff;
    font-weight: 700;
    font-size: .72rem;
    letter-spacing: .04em;
    vertical-align: middle;
}
/* Point du badge (nom dédié : .sb-live-dot existe déjà pour le point « en direct » de la
   navbar — position:absolute + rose — et l'écraserait, le faisant flotter hors du badge). */
.sb-live-badge-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: #fff;
    animation: sbLiveDot 1.1s ease-in-out infinite;
}
@keyframes sbLiveDot {
    0%, 100% { opacity: 1;  transform: scale(1); }
    50%      { opacity: .35; transform: scale(.65); }
}
/* Respect de « prefers-reduced-motion » : on coupe le clignotement du point du badge. */
@media (prefers-reduced-motion: reduce) {
    .sb-live-badge-dot { animation: none; }
}

/* En grille : la carte devient une grille CSS — miniature 16:9 bord à bord, puis
   titre/date pleine largeur, puis [stats … « Analyser » + flèche]. La coche vit SUR
   la miniature, posée sur un socle opaque du thème + ombre pour rester lisible
   quelle que soit l'image. */
.sb-streams-grid .sb-stream-item {
    display: grid;
    grid-template-columns: 1fr auto auto;
    grid-template-areas:
        "thumb thumb thumb"
        "main  main  main"
        "stats cta   arrow";
    /* Rangées explicites, comme les cartes de génériques : en « auto », align-content vaut
       stretch et une carte étirée à la hauteur de sa voisine ferait grandir chaque piste —
       align-items:center décollerait alors la vignette du haut. */
    grid-template-rows: auto 1fr auto;
    align-items: center;
    gap: .75rem .5rem;
}
.sb-streams-grid .sb-stream-main       { grid-area: main; }
.sb-streams-grid .sb-stream-stats      { grid-area: stats; justify-self: start; }
.sb-streams-grid .sb-moment-cta        { grid-area: cta; }
.sb-streams-grid .sb-moment-arrow      { grid-area: arrow; }
.sb-streams-grid .sb-stream-check-zone {
    position: absolute;
    top: 8px;
    left: 10px;
    z-index: 2;
    padding: 2px;
    border-radius: 8px;
    background: var(--sb-clickable);
    box-shadow: 0 2px 8px rgba(0, 0, 0, .35);
}
/* Coche compacte sur la miniature : padding du bouton interne réduit, icône un cran
   plus petite, marges négatives du mode liste neutralisées. */
.sb-streams-grid .sb-stream-check { margin: 0; }
.sb-streams-grid .sb-stream-check-zone .mud-button-root { padding: 3px; }
.sb-streams-grid .sb-stream-check-zone .mud-icon-root { font-size: 1.5rem; }   /* = coche Medium de la toolbar */
/* Le hover-dégradé de la carte ne recolore PAS la coche : elle vit sur son propre
   socle et garde ses couleurs d'état (grise / primaire cochée). */
.sb-streams-grid .sb-stream-item:hover .sb-stream-check-zone .mud-icon-root { color: inherit !important; }
.sb-stream-thumb {
    grid-area: thumb;
    width: calc(100% + 24px);
    margin: -10px -12px 0;            /* annule le padding de la carte : bord à bord */
    aspect-ratio: 16 / 9;
    border-radius: 10px 10px 0 0;
    overflow: hidden;
    background: var(--sb-grad-warm);  /* placeholder : la mascotte sur le dégradé signature */
    display: flex;
    align-items: center;
    justify-content: center;
}
/* Miniature absente (clip sans vignette, VOD encore en traitement) : la mascotte sur le bleu
   « streamer », comme les états vides — au ratio 16:9 des cartes de médias. La mascotte est
   SORTIE du flux (absolue) : dans le flux, un <svg> sans dimensions prend toute la largeur et
   sa hauteur minimale (ratio 1:1) BAT l'aspect-ratio du conteneur — c'était le carré géant.
   border-radius hérité + overflow : le bloc épouse les coins de la carte au lieu d'en déborder. */
.sb-media-ph {
    aspect-ratio: 16 / 9;
    background: #0f95ba;
    position: relative;
    overflow: hidden;
    border-radius: inherit;
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
}
.sb-media-ph svg { position: absolute; inset: 0; margin: auto; height: 58%; width: auto; }

.sb-stream-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.sb-stream-thumb svg {
    height: 58%;
    width: auto;
}

/* En-tête de mois : repliable (chevron qui pivote) + pilule « Récap » au dégradé
   signature, texte encre — le clic sur la pilule ne replie pas le mois.
   STICKY (mobile compris) : le mois consulté reste collé sous la toolbar — même
   hauteur sticky que celle-ci, via --sb-toolbar-h — et se fait pousser par le mois
   suivant (le sticky est borné par son groupe). Le fond couvre padding compris :
   les cartes défilent dessous, et le padding-top évite l'effet « collé ». */
.sb-month-head {
    display: flex;
    align-items: center;
    gap: .5rem;
    cursor: pointer;
    user-select: none;
    position: sticky;
    top: var(--sb-toolbar-h);
    z-index: 4;                    /* sous la toolbar (5) */
    background: var(--mud-palette-background);
    padding: .75rem 0 .5rem;       /* remplace le margin-bottom : le fond couvre l'espace */
}
.sb-month-chev { transition: transform .2s ease; }
.sb-month-chev-closed { transform: rotate(-90deg); }
/* Le bouton « Récap » porte la variante CHAUDE des actions (voir .sb-act-merge, dont il
   partage les sélecteurs) : il ne lui reste que son alignement à droite de l'en-tête. */
.sb-month-recap { margin-left: auto; }

/* Corps de mois : le repli s'anime via grid-template-rows (1fr ↔ 0fr), sans mesure JS.
   Une fois replié, visibility (retardée de la durée de l'animation) sort les cartes du
   parcours clavier. Le padding/margin ±2px évite que l'overflow ne rogne le léger
   translateY(-1px) des cartes au survol. */
.sb-month-body {
    display: grid;
    grid-template-rows: 1fr;
    transition: grid-template-rows .3s cubic-bezier(.2, .8, .2, 1);
}
.sb-month-body-closed { grid-template-rows: 0fr; }
.sb-month-body-inner {
    overflow: hidden;
    min-height: 0;
    padding: 2px;
    margin: -2px;
}
.sb-month-body-closed .sb-month-body-inner {
    visibility: hidden;
    transition: visibility 0s .3s;
}
.sb-moment::before {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--sb-grad-rose);
    border-radius: inherit;
    opacity: 0;
    transition: opacity .25s ease;
    pointer-events: none;
}
.sb-moment > * { position: relative; z-index: 1; }
.sb-moment .mud-typography, .sb-moment .mud-icon-root { transition: color .2s ease; }
/* .sb-moment-hot : le même état que :hover, déclenché par le survol du PIN correspondant
   sur la courbe (liaison pin ↔ ligne). */
.sb-moment:hover, .sb-moment.sb-moment-hot { transform: translateY(-1px); }
.sb-moment:hover::before, .sb-moment.sb-moment-hot::before { opacity: 1; }
.sb-moment:hover .mud-typography, .sb-moment:hover .mud-icon-root,
.sb-moment.sb-moment-hot .mud-typography, .sb-moment.sb-moment-hot .mud-icon-root { color: var(--sb-ink) !important; }
.sb-moment:hover .mud-text-secondary, .sb-moment.sb-moment-hot .mud-text-secondary { color: var(--sb-ink-soft) !important; }
/* « VOD » et la flèche : invisibles au repos, apparition en fondu-glissé (depuis la gauche)
   au survol de la ligne OU du pin lié. L'opacité préserve l'espace : aucun décalage de layout. */
.sb-moment .sb-moment-cta, .sb-moment .sb-moment-arrow {
    opacity: 0;
    transform: translateX(-8px);
    transition: opacity .22s ease, transform .25s cubic-bezier(.2, .8, .2, 1), color .2s ease;
}
.sb-moment:hover .sb-moment-cta, .sb-moment:hover .sb-moment-arrow,
.sb-moment.sb-moment-hot .sb-moment-cta, .sb-moment.sb-moment-hot .sb-moment-arrow {
    opacity: 1;
    transform: translateX(0);
}

/* Entête de l'analyse : un titre de stream long ne doit JAMAIS pousser la sous-ligne hors
   de la page. Un item flex refuse par défaut de passer sous la largeur de son contenu
   (min-width: auto) — d'où min-width: 0 sur toute la chaîne. Le titre est alors le seul à
   absorber la réduction (base 340px, rétrécissable) ; les repères courts (date, durée,
   total d'événements, statut) gardent leur largeur et ne se coupent pas en deux lignes,
   ce qui préserve la hauteur figée de 32 px. */
.sb-analysis-head,
.sb-analysis-head > .sb-analysis-titles,
.sb-analysis-subline {
    min-width: 0;
}
.sb-analysis-head > .mud-button-root {
    flex-shrink: 0;
}
.sb-analysis-subline {
    height: 32px;   /* figée (= chip Small + marges) : l'entête ne bouge pas entre squelette et données */
}
.sb-analysis-subline > * {
    flex-shrink: 0;
    white-space: nowrap;
}
.sb-analysis-subline > .sb-analysis-title {
    flex: 0 1 340px;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* Sous 700 px (le seuil mobile du site) la ligne ne peut plus tout porter, même titre réduit
   au minimum : il prend alors sa propre ligne et les repères passent dessous — le séparateur
   « · », qui resterait orphelin en fin de ligne, disparaît. */
@media (max-width: 700px) {
    .sb-analysis-subline {
        flex-wrap: wrap;
        height: auto;
        min-height: 32px;
    }
    .sb-analysis-subline > .sb-analysis-title {
        flex-basis: 100%;
    }
    .sb-analysis-subline > .sb-analysis-sep {
        display: none;
    }
}

/* Quand le pager ne tient plus à côté du titre, il passe à la ligne : il y prend alors TOUTE
   la largeur, chevrons rejetés aux deux extrémités. Le seuil porte sur la largeur de l'entête
   (@container) et non du viewport, car le drawer replié ou déplié déplace le point de bascule
   de 250 px. Mesuré : le pager passe naturellement à la ligne sous ~920 px d'entête. */
.sb-analysis-topline {
    container-type: inline-size;
}
@container (max-width: 930px) {
    .sb-analysis-topline .sb-pager {
        width: 100%;                      /* force aussi le passage à la ligne : 100 % ne tient jamais à côté du titre */
        justify-content: space-between;
    }
}

/* Pager de l'entête d'analyse : pilule segmentée, chevrons au dégradé chaud au survol. */
.sb-pager {
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 999px;
    padding: 2px;
    background: var(--mud-palette-surface);
}
.sb-pager .mud-icon-button { padding: 6px; }
.sb-pager .mud-icon-button:not([disabled]):hover {
    background: var(--sb-grad-rose);
    color: var(--sb-ink);
}

/* Podium MVP (page d'analyse) : marches au dégradé DA, rang en encre (règle : texte encre
   sur dégradé). Or, argent puis bronze-pêche. Toute la colonne est un lien vers le profil :
   au survol elle se soulève, la marche s'illumine et l'avatar grossit légèrement. */
.sb-podium-row {
    display: flex;
    align-items: flex-end;
    justify-content: center;
    gap: 1rem;
}
.sb-podium-col {
    display: flex;
    flex-direction: column;
    align-items: center;
    flex: 1 1 0;
    min-width: 110px;
    max-width: 200px;
    text-decoration: none;
    color: inherit;
    cursor: pointer;
    transition: transform .18s ease;
}
/* Mobile : les 3 colonnes (3×110px + gaps) débordaient de la carte — elles se partagent
   simplement la largeur disponible (les pseudos wrappent déjà). */
@media (max-width: 480px) {
    .sb-podium-row { gap: .5rem; }
    .sb-podium-col { min-width: 0; }
}
.sb-podium-col:hover { transform: translateY(-5px); }
.sb-podium-col:hover .sb-podium { filter: brightness(1.12); }
.sb-podium-col:hover .sb-podium-avatar { transform: scale(1.07); }

.sb-podium {
    width: 100%;
    border-radius: 10px 10px 0 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 800;
    font-size: 1.5rem;
    color: var(--sb-ink);
    box-shadow: inset 0 2px 0 rgba(255, 255, 255, .35);   /* biseau lumineux en haut de marche */
    transition: filter .18s ease;
}
.sb-podium-1 { background: linear-gradient(180deg, #ffd56f, #ffad33); }
.sb-podium-2 { background: linear-gradient(180deg, #e8e6ef, #b4b1c2); }
.sb-podium-3 { background: linear-gradient(180deg, #f69874, #d2691e); }

/* Anneau de l'avatar assorti à la marche. */
.sb-podium-avatar   { border: 3px solid transparent; transition: transform .18s ease; }
.sb-podium-avatar-1 { border-color: #ffad33; }
.sb-podium-avatar-2 { border-color: #b4b1c2; }
.sb-podium-avatar-3 { border-color: #d2691e; }

/* Barres du récap mensuel : une par stream, cliquables vers l'analyse. */
.sb-bar {
    position: absolute;
    bottom: 0;
    display: block;
    background: linear-gradient(180deg, #ffd56f, #f69874);
    border-radius: 4px 4px 0 0;
    min-height: 3px;
    transition: filter .12s ease;
}
.sb-bar:hover { filter: brightness(1.18); }
.sb-tile:hover .mud-typography, .sb-tile:hover .mud-icon-root { color: var(--sb-ink) !important; }
.sb-tile:hover .mud-text-secondary { color: var(--sb-ink-soft) !important; }
/* …sauf la VALEUR, en blanc sur le dégradé du survol (comme sur les héros) :
   h5 des tuiles publiques, subtitle1 des compactes. Icônes, libellés et badge
   de tendance restent encre. */
.sb-tile:hover .mud-typography.mud-typography-h5,
.sb-tile:hover .mud-typography.mud-typography-subtitle1 { color: #fff !important; }

/* Moments forts — repères numérotés sur la courbe : encre sur accent (DA), rebond au survol. */
.sb-pin {
    position: absolute;
    top: -8px;
    transform: translateX(-50%);
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    color: rgba(30, 33, 40, .9);
    font-size: .72rem;
    font-weight: 700;
    text-decoration: none;
    cursor: pointer;
    box-shadow: 0 1px 4px rgba(0, 0, 0, .35);
    transition: transform .18s cubic-bezier(.34, 1.56, .64, 1), box-shadow .18s ease;
}
/* .sb-pin-hot : même état que :hover, déclenché par le survol de la LIGNE du moment.
   Le rebond est amplifié et un anneau lumineux met le pin en valeur. */
.sb-pin:hover, .sb-pin.sb-pin-hot {
    transform: translateX(-50%) scale(1.5);
    box-shadow: 0 0 0 4px rgba(255, 255, 255, .18), 0 6px 18px rgba(0, 0, 0, .5);
    z-index: 3;
}

/* Courbe d'activité — exploration à la minute : la colonne survolée s'éclaire d'un halo teinté
   du jeu en cours (le fill porte déjà la couleur, seule l'opacité s'anime), et le crosshair
   adjacent (frère suivant dans le SVG) se révèle jusqu'à la courbe. */
.sb-mmcol { opacity: 0; cursor: pointer; transition: opacity .15s ease; }
.sb-mmcol:hover { opacity: .12; }
.sb-mline { opacity: 0; transition: opacity .12s ease; pointer-events: none; }
.sb-mmcol:hover + .sb-mline { opacity: .9; }


/* Tooltip suiveur (js/sb-tooltip.js) : carte de la DA — surface, radius 8 px, ombre douce. */
.sb-tip {
    position: fixed;
    z-index: 3000;
    pointer-events: none;
    background: var(--sb-clickable);   /* même fond que les tuiles : plus clair que la surface */
    color: var(--mud-palette-text-primary);
    border-radius: 8px;
    padding: 10px 14px;
    box-shadow: 0 0 3px rgba(0, 0, 0, .12), 0 12px 18px rgba(0, 0, 0, .22);
    font-size: .85rem;
    line-height: 1.45;
    /* width: max-content est INDISPENSABLE. Sans lui, la largeur d'un élément position:fixed
       se calcule dans l'espace « fenêtre moins left » : le tooltip encore posé près du bord
       droit se comprimait en colonne étroite AVANT d'être mesuré (98×575 au lieu de 280×181),
       et le placement calculé sur cette mesure fantôme était faux. */
    width: max-content;
    max-width: 280px;
    opacity: 0;
    transform: translateY(4px) scale(.97);
    transition: opacity .12s ease, transform .15s cubic-bezier(.2, .8, .2, 1);
}
.sb-tip-on { opacity: 1; transform: none; }
.sb-tip-head { display: flex; align-items: center; gap: .45rem; }
.sb-tip-value { margin-top: 3px; white-space: pre-line; }   /* \n = listes point par point */
.sb-tip-detail { margin-top: 2px; opacity: .7; }

/* Fonds par accent : segments de la barre de composition + pastilles de légende. */
.sb-bg-rose      { background: #f2527e; }
.sb-bg-peach     { background: #f69874; }
.sb-bg-gold      { background: #ffad33; }
.sb-bg-yellow    { background: #ffd56f; }
.sb-bg-mint      { background: #03dba8; }
.sb-bg-blue      { background: #0f95ba; }
.sb-bg-rosepeche { background: var(--sb-grad-rose); }
.sb-bg-violet    { background: #a06bd4; }   /* abos offerts : distinct des roses voisins */
.sb-bg-neutral   { background: var(--mud-palette-text-disabled); }

/* Pastille de légende (relie l'entrée de légende à son segment). */
.sb-dot { width: 10px; height: 10px; border-radius: 3px; display: inline-block; flex: 0 0 auto; }

/* Chip en dégradé (rang du stream…) : jaune → menthe, texte encre. */
.sb-chip-grad {
    background: var(--sb-grad-sunset) !important;
}
.sb-chip-grad, .sb-chip-grad .mud-chip-content, .sb-chip-grad .mud-icon-root {
    color: var(--sb-ink) !important;
}
/* Variante menthe → bleu : le rang « 30 derniers jours », distinct du rang global. */
.sb-chip-grad-mint {
    background: var(--sb-grad-mint) !important;
}
.sb-chip-grad-mint, .sb-chip-grad-mint .mud-chip-content, .sb-chip-grad-mint .mud-icon-root {
    color: var(--sb-ink) !important;
}

/* Item de navigation actif : texte encre ; le FOND, lui, est peint par la pilule
   .sb-nav-pill (nav-pill.js) qui GLISSE d'un lien à l'autre au lieu de se téléporter.
   Le survol garde le gris discret MudBlazor par défaut. */
.mud-nav-link.active {
    background: transparent !important;
}
.mud-nav-link.active, .mud-nav-link.active .mud-nav-link-text,
.mud-nav-link.active .mud-icon-root {
    color: var(--sb-ink) !important;
}

/* Le lien reste le référent de positionnement du point « en direct » (.sb-nav-live::after). */
.mud-nav-link {
    position: relative;
}

/* Pilule de page active : unique, absolue dans le menu (elle défile avec), dégradé
   jaune → pêche, courbe avec un soupçon de rebond. Peinte SOUS les liens (ordre DOM). */
.mud-drawer .mud-navmenu {
    position: relative;
}
.sb-nav-pill {
    position: absolute;
    top: 0;
    left: 0;
    width: 0;
    height: 0;
    background: var(--sb-grad-warm);
    opacity: 0;
    pointer-events: none;
    transition: top .3s cubic-bezier(.2, .8, .2, 1), left .3s cubic-bezier(.2, .8, .2, 1),
                width .3s cubic-bezier(.2, .8, .2, 1), height .3s cubic-bezier(.2, .8, .2, 1),
                opacity .15s ease;
}

/* ── Navigation mobile : barre du bas (<700px) ───────────────────────────────
   Le drawer latéral disparaît, remplacé par 4 onglets (icône + libellé) au bord
   bas : Dashboard, Streams (badge live), Plus (feuille des autres pages), Profil
   (feuille thème/langue/paramètres). La pilule warm y glisse horizontalement. */
.sb-bottom-bar {
    display: none;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1090;                 /* sous les feuilles (drawer 1100) et leur voile */
    background: var(--mud-palette-background);
    border-top: 1px solid var(--mud-palette-divider);
    padding-bottom: env(safe-area-inset-bottom);
}
.sb-bb-item {
    position: relative;
    z-index: 1;                    /* au-dessus de la pilule */
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    height: 60px;
    min-width: 0;
    background: none;
    border: none;
    cursor: pointer;
    text-decoration: none;
    color: var(--mud-palette-text-secondary);
    font-family: inherit;
}
.sb-bb-label {
    font-size: .68rem;
    line-height: 1;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.sb-bb-ico {
    position: relative;
    display: inline-flex;
}
.sb-bb-ico .mud-icon-root {
    font-size: 1.35rem;
}
.sb-bb-ico .sb-live-dot {
    left: auto;
    right: -5px;
    top: -3px;
}
.sb-bb-item.sb-bb-active, .sb-bb-item.sb-bb-active * {
    color: var(--sb-ink) !important;
}
/* Hors actif : gris verrouillé (le focus après un tap teintait le libellé). */
.sb-bb-item:not(.sb-bb-active), .sb-bb-item:not(.sb-bb-active) * {
    color: var(--mud-palette-text-secondary) !important;
}
.sb-bottom-bar .sb-nav-pill {
    border-radius: 12px;
}

/* Feuilles montantes (Plus / Profil) : des div MAISON, pas des MudDrawer — fermée,
   translateY(105%) la sort de l'écran de SA PROPRE hauteur, sans aucune mesure JS
   (le positionnement du MudDrawer bottom dépendait d'une mesure interne fragile).
   Coins arrondis, trait de délimitation comme la barre, safe-area, glissement doux.
   L'actif y garde un fond warm statique (pas de pilule dans une feuille éphémère). */
.sb-sheet {
    display: none;                 /* n'existe qu'en mobile (media query plus bas) */
    position: fixed;
    left: 0;
    right: 0;                      /* suit le viewport — sans scrollbar : le scroll est verrouillé feuille ouverte */
    bottom: 0;
    z-index: 1100;                 /* au-dessus de la barre (1090) et du voile (1099) */
    background: var(--mud-palette-surface);
    transform: translateY(105%);
    /* !important ×2 : MudBlazor stylise les DIV SIBLINGS du drawer mini
       (.mud-drawer-mini ~ div:not(.mud-main-content)) — feuilles et voile en sont :
       margin-left 56/250px (feuille décalée de la largeur du rail) et
       transition margin 225ms (qui écrasait celle du transform → aucune animation). */
    margin: 0 !important;
    transition: transform .3s cubic-bezier(.2, .8, .2, 1) !important;
    max-height: 85vh;
    overflow-y: auto;
    border-radius: 16px 16px 0 0;
    border-top: 1px solid var(--mud-palette-divider);   /* même délimitation que la barre */
    padding: 8px 0 calc(12px + env(safe-area-inset-bottom));
    pointer-events: none;
}
.sb-sheet-open {
    transform: translateY(0);
    /* Armement différé : le bouton de la barre qui ouvre la feuille et « Se déconnecter »
       partagent la même zone d'écran — un double-tap se déconnectait. Les taps pendant
       le glissement d'entrée (.3s) sont ignorés, la feuille ne devient cliquable qu'après. */
    animation: sbSheetArm .01s linear .4s both;
}
@keyframes sbSheetArm {
    to { pointer-events: auto; }
}

/* Feuille ouverte = contexte modal : le scroll de la page est verrouillé — la scrollbar
   disparaît le temps de la consultation, la feuille et le voile occupent donc toute la
   largeur réelle (une feuille qui passait sous la scrollbar, ou s'arrêtait avant, selon
   les pages). Comportement standard d'un modal. */
body:has(.sb-sheet-open) {
    overflow: hidden;
}
.sb-sheet-overlay {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 1099;
    background: rgba(0, 0, 0, .5);
    opacity: 0;
    pointer-events: none;
    margin: 0 !important;          /* même neutralisation que .sb-sheet */
    transition: opacity .25s ease !important;
}
.sb-sheet-overlay-on {
    opacity: 1;
    pointer-events: auto;
}
.sb-sheet-title {
    display: block;
    padding: 8px 16px 2px;
    color: var(--mud-palette-text-secondary);
}
.sb-sheet .mud-nav-link.active {
    background: var(--sb-grad-warm) !important;
}
/* Toggles thème/langue de la feuille profil : neutres au repos, sélection warm encre. */
.sb-sheet .mud-toggle-item {
    color: var(--mud-palette-text-secondary);
}
.sb-sheet .sb-toggle-selected {
    background: var(--mud-palette-primary) !important;   /* couleur pleine du thème, pas de dégradé */
}
.sb-sheet .sb-toggle-selected, .sb-sheet .sb-toggle-selected * {
    color: var(--mud-palette-primary-text) !important;
}

/* Ligne-bouton de feuille (déconnexion, choix de chaîne) : bouton nu au gabarit exact
   des MudNavLink voisins (padding 4px 16px, icône 24px, texte à 12px — et alignement
   gauche explicite : les <button> sont centrés par défaut par le navigateur). */
.sb-sheet-btn {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    text-align: left;
    gap: 12px;
    width: 100%;
    padding: 4px 16px;
    background: none;
    border: none;
    cursor: pointer;
    font-family: inherit;
    font-size: .875rem;
    color: var(--mud-palette-text-primary);
}

/* ── Secours d'avatar, commun à tout le site ─────────────────────────────────
   Fond en couleur principale frappé de la marque. Posé en ARRIÈRE-PLAN du conteneur :
   il se voit tant que la photo n'est pas peinte, disparaît sous elle une fois chargée,
   et reste si elle échoue — sans le moindre code de gestion d'erreur. */
.sb-avatar-ph {
    background-color: var(--mud-palette-primary);
    background-image: url("images/avatar-placeholder.png");
    background-repeat: no-repeat;
    background-position: center;
    background-size: 64%;
}
/* Beaucoup d'avatars Twitch sont des PNG à FOND TRANSPARENT : sans plaque opaque sous la
   photo, le secours (et le dégradé de la tuile active) transparaissent au travers, et deux
   images se mélangent. La plaque suit le thème — la photo se lit dans les deux modes.
   Le secours reste intact : sans URL, aucun <img> n'est rendu et le fond du conteneur se voit. */
.sb-avatar-ph img {
    background-color: var(--mud-palette-surface);
}

/* ── Sélecteur de chaîne de travail (scope) ──────────────────────────────────
   Pastille-initiale de la chaîne (dégradé warm, encre) ; la chaîne active est
   marquée en warm dans les listes (popover desktop et feuille mobile). */
.sb-scope-avatar {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    overflow: hidden;                  /* les avatars Twitch remplissent le rond */
    /* Pas de fond ici : .sb-avatar-ph le fournit (marque sur teinte de palette), comme partout
       ailleurs sur le site. */
    color: var(--sb-ink);
    font-weight: 600;
    font-size: .95rem;
}
.sb-scope-avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.sb-scope-avatar-sm {
    width: 30px;
    height: 30px;
    font-size: .8rem;
}
.sb-scope-active {
    background: var(--sb-grad-warm) !important;
}
.sb-scope-active, .sb-scope-active * {
    color: var(--sb-ink) !important;
}
/* Popover du sélecteur : la largeur de la navbar (RelativeWidth côté composant ;
   min-width pour le drawer replié), borné en hauteur — un admin du site voit des
   centaines de chaînes (recherche + Take). left:0 : le sélecteur vit dans le drawer,
   collé au bord gauche de l'écran — on écrase le left calculé du popover (style
   inline, battu par un !important de feuille) qui laissait une marge. */
.sb-scope-pop {
    left: 0 !important;
    min-width: 250px;
    max-height: 420px;
    overflow-y: auto;
}
.sb-scope-pop .mud-menu-item {
    padding: 7px 12px;
}
.sb-scope-search {
    padding: 8px 12px;            /* la liste n'a plus de padding : le champ garde le sien */
}

/* Sélecteur de chaîne de travail : une CARTE qui ressort par son fond (surfaces
   cliquables), sans contour — l'état « autre chaîne » est déjà porté par la
   mascotte et la couleur du titre. */
.sb-scope-btn {
    background: var(--sb-clickable) !important;
    border-radius: 10px !important;
    margin: 2px 8px 6px;
    width: calc(100% - 16px) !important;
}
/* Drawer replié : plus de tuile du tout — l'avatar nu, centré. */
.mud-drawer-mini:not(.mud-drawer--open) .sb-scope-btn {
    background: transparent !important;
    margin: 2px 0 6px;
    width: 100% !important;
    min-width: 0 !important;
    padding: 4px 0 !important;
    justify-content: center !important;
}
.mud-drawer-mini:not(.mud-drawer--open) .sb-scope-btn .mud-button-label {
    justify-content: center;
}

@media (max-width: 699px) {
    .mud-drawer-mini {
        display: none;
    }
    .mud-main-content {
        margin-left: 0 !important;
        padding-bottom: calc(64px + env(safe-area-inset-bottom));
    }
    .sb-bottom-bar {
        display: flex;
    }
    .sb-sheet {
        display: block;
    }
    .sb-sheet-overlay {
        display: block;
    }
}

/* Fonctionnalité désactivée pour les utilisateurs mais visible de l'admin : section estompée. */
.sb-feature-off {
    opacity: .55;
}

/* ── Mascotte de la navbar : 3 états (streamer / modo / viewer) ──────────────
   Un seul SVG superposé (NavMascot.razor), animations SANS opacité (rendait mal) :
   tout est géométrique — les yeux s'ouvrent/se ferment (scaleY), l'antenne se
   rétracte dans le coin noir (scale vers (12,14)), le corps à lobe est rogné par
   clip-path au-dessus du rect simple TOUJOURS visible dessous (identiques pixel
   pour pixel hors lobe : le wipe ne se voit pas), l'épée est dégainée du coin noir
   avec un slash (keyframes, pivot sur la garde). Origines en COORDONNÉES ABSOLUES
   du viewBox 32×32 (pas de fill-box : résolution non déterministe). L'ombre change
   de couleur en fondu de flood-color. */
/* Dimensionnement : icône contenue (le titre porte l'identité) ; en drawer replié,
   plus petite encore et l'en-tête se resserre (gap et padding du lien annulés —
   !important bat leur style inline). */
.sb-mascot {
    display: block;
    width: 100%;
    max-width: 96px;
    max-height: 96px;
    aspect-ratio: 1 / 1;
    transition: max-width .3s cubic-bezier(.2, .8, .2, 1), max-height .3s cubic-bezier(.2, .8, .2, 1);
}
.mud-drawer-mini:not(.mud-drawer--open) .sb-mascot {
    max-width: 40px;
    max-height: 40px;
}
.mud-drawer-mini:not(.mud-drawer--open) .sb-brand {
    gap: 0 !important;
    padding-bottom: 4px !important;
}
.sb-mascot .sb-m {
    transition: transform .4s cubic-bezier(.2, .8, .2, 1);
}
.sb-mascot .sb-mascot-flood {
    flood-color: #0f95ba;                   /* streamer : bleu */
    transition: flood-color .45s ease;
}
.sb-mascot--modo .sb-mascot-flood   { flood-color: #03dba8; }   /* menthe */
.sb-mascot--viewer .sb-mascot-flood { flood-color: #ffad33; }   /* doré  */

/* Corps : le rect simple (plain) reste TOUJOURS visible dessous ; le corps à lobe,
   au-dessus, est identique hors lobe — en viewer, le clip rogne le lobe (au-dessus
   de y≈7.5 et à gauche de x≈5.5, les bords du rect), le reste du wipe est invisible. */
.sb-mascot .sb-m-body-arm {
    clip-path: inset(-20% -20% -20% -20%);   /* englobe l'ombre portée */
    transition: clip-path .4s cubic-bezier(.2, .8, .2, 1);
}
.sb-mascot--viewer .sb-m-body-arm {
    clip-path: inset(24% 0 0 18%);
}

/* Antenne (bras haut-gauche) : se rétracte dans le coin noir — scale pur vers
   (12,14). La main simple (plaincorner) est TOUJOURS affichée dessous (noir sur
   noir sous le bras) : la rétraction la révèle telle quelle.
   ⚠ Interpolation : garder la MÊME liste de fonctions des deux côtés d'une
   transition (scale→scale, translate+scaleY→translate+scaleY). Des listes
   différentes passent par les matrices, et scale(0)/scaleY(0) sont singulières →
   le navigateur bascule en changement DISCRET (instantané). */
.sb-mascot .sb-m-armcorner {
    transform: scale(1);
    transform-origin: 5.5px 7.5px;   /* le nœud rond de l'antenne (haut-gauche) : le bras s'y aspire */
}
.sb-mascot--viewer .sb-m-armcorner { transform: scale(0); }

/* Yeux : ils S'OUVRENT et se FERMENT (scaleY autour de leur axe), fermés en modo.
   Au repos, ils suivent paresseusement la souris (variables posées par
   js/mascot-eyes.js — la transition .4s fait le lissage). */
.sb-mascot .sb-m-eyes {
    transform: translate(var(--sb-eye-x, 0px), var(--sb-eye-y, 0px)) scaleY(1);
    transform-origin: 19px 18.5px;
}
.sb-mascot--modo .sb-m-eyes {
    transform: translate(0px, 0px) scaleY(0);
}

/* Épée : sort du coin NOIR (12,14) en grandissant, sans rotation ; rangée en
   rétrécissant vers le même point. */
.sb-mascot .sb-m-sword {
    transform: scale(0);
    transform-origin: 12px 14px;
}
.sb-mascot--modo .sb-m-sword {
    transform: scale(1);
}

/* Point « en direct » de la BARRE MOBILE (le drawer utilise le meme .sb-live-dot) :
   rose DA posé sur le coin de l'icône, halo qui pulse. Le conteneur .sb-bb-ico le recale. */
.sb-live-dot {
    position: absolute;
    left: 34px;
    top: 10px;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: #f2527e;
    animation: sb-live-pulse 2s ease-out infinite;
    z-index: 2;
}
@keyframes sb-live-pulse {
    0%   { box-shadow: 0 0 0 0 rgba(242, 82, 126, .55); }
    70%  { box-shadow: 0 0 0 7px rgba(242, 82, 126, 0); }
    100% { box-shadow: 0 0 0 0 rgba(242, 82, 126, 0); }
}
/* Drawer REPLIÉ : c'est la règle d'opacité ci-dessus (.mud-drawer-mini:not(--open)
   .mud-nav-link-text) qui efface les libellés — celle de MudBlazor, en enfants DIRECTS, ne
   s'applique pas, le menu étant enveloppé dans .sb-nav-scroll. Or l'opacité vaut pour tout le
   sous-arbre : le point « en direct », logé dans le libellé, s'effaçait avec lui.
   Le conteneur qui PORTE le point (:has) reste donc opaque, mais réduit à néant : largeur nulle
   et texte invisible. Seul le point subsiste, positionné en absolu sur le lien.
   Ciblé par son CONTENU : aucune classe à propager depuis Blazor. */
.mud-drawer-mini:not(.mud-drawer--open) .mud-nav-link-text:has(.sb-live-dot) {
    display: block;      /* ceinture : si le display:none de MudBlazor venait à s'appliquer */
    opacity: 1;
    transform: none;
    width: 0;
    font-size: 0;
}

/* Point « en direct » sur un avatar du sélecteur de chaîne : posé sur le coin bas-droit, cerné
   d'un anneau de la couleur du fond pour rester lisible sur n'importe quelle photo de profil.
   Il est SŒUR de l'avatar (celui-ci a overflow:hidden, il l'aurait rogné). */
.sb-scope-ava {
    position: relative;
    display: inline-flex;
    flex: 0 0 auto;
}
.sb-scope-ava .sb-scope-live {
    position: absolute;
    right: -1px;
    bottom: -1px;
    width: 11px;
    height: 11px;
    border-radius: 50%;
    background: #f2527e;
    box-shadow: 0 0 0 2px var(--mud-palette-surface);
    animation: sb-live-pulse 2s ease-out infinite;
}
/* Dans la liste déroulante, les pastilles sont plus petites : le point suit. */
.sb-scope-ava .sb-scope-avatar-sm + .sb-scope-live { width: 9px; height: 9px; }

/* Bord droit du drawer LATÉRAL : un fin trait gris — la couleur des séparateurs, qui
   suit le thème — délimite la nav du contenu (à la place d'une ombre). Scopé au
   variant mini : les feuilles montantes mobiles sont aussi des MudDrawer. */
.mud-drawer-mini {
    border-right: 1px solid var(--mud-palette-divider);
}

/* ── Menu profil (popover) : même langage que la nav ─────────────────────────
   Popover arrondi à ombre douce ; au survol, chaque item (item simple, sous-menu
   Thème/Langue, choix de thème/langue, déconnexion) prend le dégradé jaune → pêche
   de l'item actif, en fondu avec texte encre. La déconnexion, elle, vire au rose.
   Compact : MudBlazor 8 gonfle chaque item (padding 8px + margin 4px du texte
   + icône à 12px) — tout est resserré ici via les classes .mud-menu-* réelles. */
.sb-menu-pop {
    border-radius: 8px !important;
    overflow: hidden;             /* items bord à bord coupés net dans les coins */
    box-shadow: 0 0 3px rgba(0, 0, 0, .12), 0 12px 24px rgba(0, 0, 0, .18) !important;
}
.sb-menu-pop .mud-menu-list {
    padding: 0 !important;        /* façon menu contextuel : aucune marge interne */
    min-width: 180px;
}
.sb-menu-pop .mud-menu-item {
    padding: 5px 10px;
}
/* Les entrées Thème/Langue sont des MudButton (petit-fils via .mud-menu-activator,
   en display: contents) — même gabarit que les items. */
.sb-menu-pop .mud-menu-activator .mud-button-root {
    padding: 5px 10px;
    min-height: 0;
    width: 100%;
    justify-content: flex-start;
}
.sb-menu-pop .mud-menu-item .mud-menu-item-text {
    margin: 2px 0;                /* 4px 0 par défaut : la moitié de l'air vertical */
}
.sb-menu-pop .mud-menu-item .mud-menu-item-icon {
    margin-inline-end: 8px;
}
.sb-menu-pop .mud-menu-activator .mud-button-icon-start {
    margin-inline-end: 8px;
}
.sb-menu-pop .mud-menu-list > .mud-divider {
    margin: 2px 0;
}
.sb-menu-pop .mud-icon-root {
    font-size: 1.15rem;
}
.sb-menu-pop .mud-menu-item,
.sb-menu-pop .mud-toggle-item,
.sb-menu-pop .mud-menu-activator .mud-button-root {
    border-radius: 0;             /* items carrés, pleine largeur (menu contextuel) */
    position: relative;
    overflow: hidden;
}
.sb-menu-pop .mud-menu-item::before,
.sb-menu-pop .mud-toggle-item::before,
.sb-menu-pop .mud-menu-activator .mud-button-root::before {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--sb-grad-warm);
    opacity: 0;
    transition: opacity .25s ease;
    pointer-events: none;
}
.sb-menu-pop .mud-menu-item:hover:not(.sb-scope-active)::before,
.sb-menu-pop .mud-toggle-item:hover:not(.mud-menu-toggle-selected)::before,
.sb-menu-pop .mud-menu-activator .mud-button-root:hover::before {
    opacity: 1;
}
.sb-menu-pop .mud-menu-item > *,
.sb-menu-pop .mud-toggle-item > *,
.sb-menu-pop .mud-menu-activator .mud-button-root > * {
    position: relative;
    z-index: 1;
}
/* :not(sélectionné/actif) : un élément déjà actif (choix de thème/langue, chaîne du
   scope) garde son fond warm tel quel au survol — sans ça, son fond passait
   transparent puis re-fondait via ::before (flash sombre). */
.sb-menu-pop .mud-menu-item:hover:not(.sb-scope-active),
.sb-menu-pop .mud-toggle-item:hover:not(.mud-menu-toggle-selected),
.sb-menu-pop .mud-menu-activator .mud-button-root:hover {
    background: transparent !important;   /* remplace le gris MudBlazor, le ::before fait le fond */
}
.sb-menu-pop .mud-menu-item:hover, .sb-menu-pop .mud-menu-item:hover *,
.sb-menu-pop .mud-toggle-item:hover, .sb-menu-pop .mud-toggle-item:hover *,
.sb-menu-pop .mud-menu-activator .mud-button-root:hover, .sb-menu-pop .mud-menu-activator .mud-button-root:hover * {
    color: var(--sb-ink) !important;
}
/* Déconnexion : dégradé rose → pêche au survol (action de sortie), icône encre pour rester lisible. */
.sb-menu-pop form .mud-menu-item::before {
    background: var(--sb-grad-rose);
}
.sb-menu-pop form .mud-button-root:hover {
    background: transparent !important;
}

/* ── Console SPARQL (/admin/sparql) ──────────────────────────────────────────
   Éditeur monospace plein cadre (focus liseré doré), résultats en table mono
   qui défile horizontalement, réponses brutes (Turtle, erreurs) en <pre>. */
.sb-sparql-editor {
    width: 100%;
    min-height: 200px;
    resize: vertical;
    padding: 12px 14px;
    border-radius: 10px;
    border: 1px solid var(--mud-palette-lines-inputs);
    background: var(--mud-palette-background);
    color: var(--mud-palette-text-primary);
    font-family: Consolas, "Cascadia Mono", ui-monospace, monospace;
    font-size: .85rem;
    line-height: 1.5;
    tab-size: 4;
    outline: none;
    transition: border-color .2s ease, box-shadow .2s ease;
}
.sb-sparql-editor:focus {
    border-color: #ffad33;
    box-shadow: 0 0 0 3px rgba(255, 173, 51, .25);
}
.sb-sparql-prefix {
    font-family: Consolas, "Cascadia Mono", ui-monospace, monospace;
    margin-left: 6px;
    cursor: help;
}
.sb-sparql-raw {
    margin: 0;
    max-height: 60vh;
    overflow: auto;
    font-family: Consolas, "Cascadia Mono", ui-monospace, monospace;
    font-size: .8rem;
    white-space: pre-wrap;
    word-break: break-word;
}
.sb-sparql-results .mud-table-container {
    overflow-x: auto;
}
.sb-sparql-results td {
    font-family: Consolas, "Cascadia Mono", ui-monospace, monospace;
    font-size: .8rem;
    white-space: nowrap;
    max-width: 480px;
    overflow: hidden;
    text-overflow: ellipsis;
}
.sb-sparql-filter {
    max-width: 260px;
}
/* Cellules de résultats : URI bleu DA copiable au clic, métadonnées (datatype,
   langue, nœud anonyme) estompées. */
.sb-cell-uri {
    color: var(--mud-palette-info);
    cursor: pointer;
}
.sb-cell-uri:hover {
    text-decoration: underline;
}
.sb-cell-meta {
    opacity: .55;
    font-size: .7rem;
    margin-left: 3px;
}