/* ==========================================================================
   Theme screenactu — surcouche claire/sombre pour SonataAdminBundle (AdminLTE)
   ========================================================================== */

:root {
    --sa-bg: #f4f5f9;
    --sa-surface: #ffffff;
    --sa-surface-muted: #f8f9fc;
    --sa-border: #e4e7f0;
    --sa-border-strong: #d3d8e6;
    --sa-text: #1f2430;
    --sa-text-muted: #6b7280;
    --sa-shadow: 0 1px 3px rgba(23, 26, 43, 0.06), 0 1px 2px rgba(23, 26, 43, 0.04);
    --sa-shadow-hover: 0 4px 14px rgba(23, 26, 43, 0.08);

    /* Décliné de l'accent : l'indigo d'origine était la seule autre teinte d'interface, et
       il jurait avec l'orange une fois les bleus d'AdminLTE basculés (adminlte-accent.css).
       Assombri par rapport à --sa-accent pour rester lisible sur fond clair (4.6:1). */
    --sa-primary: #c25708;
    --sa-primary-dark: #9c4508;
    --sa-primary-soft: #fff1e5;
    --sa-accent: #fb6c01;
    --sa-gradient: linear-gradient(135deg, var(--sa-accent) 0%, #ffb877 100%);

    /* Encre de l'en-tête : sombre sur le dégradé orange, lisible aux deux
       extrémités du dégradé (le blanc y tombe sous 3:1 côté clair). */
    --sa-header-ink: #2b1200;
    --sa-header-ink-muted: rgba(43, 18, 0, 0.66);
    --sa-header-hover-bg: rgba(0, 0, 0, 0.1);
    --sa-header-border: rgba(0, 0, 0, 0.14);

    --sa-sidebar-bg: #171a2b;
    --sa-sidebar-bg-alt: #1f2340;
    --sa-sidebar-text: #c7cbe0;
    --sa-sidebar-text-active: #ffffff;
    --sa-sidebar-active-bg: linear-gradient(90deg, rgba(251, 108, 1, 0.35) 0%, rgba(251, 108, 1, 0.08) 100%);
    --sa-sidebar-border: rgba(255, 255, 255, 0.07);

    --sa-success: #16a34a;
    --sa-danger: #dc2626;
    --sa-warning: #d97706;
    /* Parution programmée. Seul des statuts à porter une valeur par thème : mesuré sur
       le fond du badge (le ton lui-même à 14 % sur la surface), #1d4ed8 donne 5.4:1 en
       clair mais 2.3:1 en sombre — sous le plancher du petit texte. Le pendant sombre
       #60a5fa y remonte à 5.2:1. Ne pas fondre les deux en une seule valeur. */
    --sa-scheduled: #1d4ed8;

    /* Échelle secondaire. Le corps à 19px est taillé pour la lecture — un texte de
       contenu, un champ de formulaire. La navigation et le texte d'appoint n'en
       relèvent pas : à la même taille, une entrée de menu pèse autant qu'un
       paragraphe et l'écran perd sa hiérarchie. */
    --sa-font-ui: 16px;

    color-scheme: light;
}

html[data-theme="dark"] {
    --sa-bg: #12141f;
    --sa-surface: #1a1d2c;
    --sa-surface-muted: #20233480;
    --sa-border: #2b2f45;
    --sa-border-strong: #383d59;
    --sa-text: #e5e7f0;
    --sa-text-muted: #9296b0;
    --sa-shadow: 0 1px 3px rgba(0, 0, 0, 0.35), 0 1px 2px rgba(0, 0, 0, 0.25);
    --sa-shadow-hover: 0 6px 20px rgba(0, 0, 0, 0.4);

    --sa-primary: #ff9550;
    --sa-primary-dark: #fb6c01;
    --sa-primary-soft: #33210f;
    --sa-accent: #fb6c01;
    --sa-gradient: linear-gradient(135deg, var(--sa-accent) 0%, #ffd9ac 100%);

    --sa-sidebar-bg: #0d0f1a;
    --sa-sidebar-bg-alt: #151831;
    --sa-sidebar-text: #9fa3bd;
    --sa-sidebar-text-active: #ffffff;
    --sa-sidebar-border: rgba(255, 255, 255, 0.06);
    --sa-sidebar-active-bg: linear-gradient(90deg, rgba(251, 108, 1, 0.35) 0%, rgba(251, 108, 1, 0.08) 100%);

    --sa-scheduled: #60a5fa;

    color-scheme: dark;
}

* {
    transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

/* 19px comme le front, contre les 14px de Bootstrap 3. La règle d'origine porte
   sur `body` seul (spécificité 0,0,1) : la classe suffit à passer devant. */
body.sonata-bc {
    background-color: var(--sa-bg);
    color: var(--sa-text);
    font-size: 19px;
}

/* Bootstrap 3 redit 14px en dur sur les composants qui ne peuvent pas hériter
   d'un `body` qu'il ne connaît pas ; sans ces `inherit`, ils resteraient seuls
   à l'ancienne échelle. Les hauteurs fixes (34px, calées sur 14px + 2×6px de
   padding) passent en `auto`, sinon le texte agrandi déborde du cadre. */
body.sonata-bc .h5,
body.sonata-bc h5,
body.sonata-bc output,
body.sonata-bc .input-group-addon,
body.sonata-bc .popover,
body.sonata-bc .popover-title,
body.sonata-bc table.sonata-ba-list,
body.sonata-bc .select2-container--bootstrap .select2-selection,
body.sonata-bc .select2-container--bootstrap .select2-search--dropdown .select2-search__field {
    font-size: inherit;
}

body.sonata-bc .select2-container--bootstrap .select2-selection--single {
    height: auto;
}

/* Chrome de l'application : navigation, commandes, texte d'appoint. Ce qui sert à
   se déplacer ou à commenter descend d'un cran ; ce qui se lit reste à 19px. */
body.sonata-bc .sidebar-menu > li > a,
body.sonata-bc .sidebar-menu .treeview-menu > li > a,
body.sonata-bc .navbar-nav > li > a,
body.sonata-bc .dropdown-menu,
body.sonata-bc .breadcrumb,
body.sonata-bc .pagination > li > a,
body.sonata-bc .pagination > li > span,
body.sonata-bc .nav-tabs > li > a,
body.sonata-bc .btn,
body.sonata-bc .help-block,
body.sonata-bc .sonata-ba-list-field-header,
body.sonata-bc small,
body.sonata-bc .small {
    font-size: var(--sa-font-ui);
}

/* Les boutons compacts d'AdminLTE (actions de ligne, barre d'outils) gardaient leur
   propre échelle relative : un cran sous le chrome, pas sous le corps de texte. */
body.sonata-bc .btn-xs,
body.sonata-bc .btn-sm {
    font-size: calc(var(--sa-font-ui) - 2px);
}

/* ---------- En-tête ---------- */

.skin-black .main-header {
    background: var(--sa-gradient);
    box-shadow: var(--sa-shadow);
}

.skin-black .main-header .logo {
    background: var(--sa-sidebar-bg);
    color: #ffffff;
    font-weight: 600;
    letter-spacing: 0.02em;
    border-right: 1px solid rgba(255, 255, 255, 0.12);
    padding: 0;
}

.skin-black .main-header .logo:hover {
    background: var(--sa-sidebar-bg-alt);
}

/* Hauteur imposée, largeur déduite du rapport du SVG. `width: 100%` étirerait
   l'image sur les 230px de la boîte AdminLTE, soit un rapport de 4,6 alors que
   le tracé vaut 4,33 : la hauteur rendue passe à 53px dans une barre de 50px,
   et `overflow: hidden` ampute la ligne de base (« JV · CINÉ · ET PLUS SI
   AFFINITÉS »). Le tracé touchant les quatre bords de son viewBox, la réserve
   verticale vient d'un padding. */
.skin-black .main-header .logo img {
    display: block;
    margin: 0 auto;
    padding: 6px 0;
    height: 100%;
    max-height: 100%;
    width: auto;
    max-width: 100%;
    object-fit: contain;
}

.skin-black .main-header .navbar {
    background: transparent;
}

/* Pas d'agrandissement ici : le glyphe hérite de line-height 1.428 et le
   padding d'AdminLTE fait 2×15px, donc au-delà de 14px il sort des 50px
   de la barre. */
.skin-black .main-header .navbar .sidebar-toggle {
    color: var(--sa-header-ink);
    font-size: 14px;
}

.skin-black .main-header .navbar .sidebar-toggle:hover {
    color: var(--sa-header-ink);
    background: var(--sa-header-hover-bg);
}

/* Le séparateur d'AdminLTE est un trait blanc, invisible sur l'orange. */
.skin-black .main-header .navbar > .sidebar-toggle {
    border-right-color: var(--sa-header-border);
}

.skin-black .main-header .navbar .navbar-custom-menu .nav > li > a {
    color: var(--sa-header-ink);
    font-size: 17px;
}

.skin-black .main-header .navbar .navbar-custom-menu .nav > li > a:hover,
.skin-black .main-header .navbar .navbar-custom-menu .nav > li > a:focus,
.skin-black .main-header .navbar .navbar-custom-menu .nav > li > a:active,
.skin-black .main-header .navbar .navbar-custom-menu .nav .open > a {
    color: var(--sa-header-ink);
    background: var(--sa-header-hover-bg);
}

/* AdminLTE ne cale verticalement que les <a>/<span> du fil d'Ariane ; un
   segment en texte brut retombe donc en haut de la barre. On centre le
   conteneur pour que le calage ne dépende plus du contenu des <li>. */
.skin-black .main-header .breadcrumb {
    background: transparent;
    margin: 0;
    padding: 0;
    font-size: 16px;
    display: flex;
    align-items: center;
    min-height: 50px;
}

.skin-black .main-header .breadcrumb > li {
    color: var(--sa-header-ink);
    font-weight: 600;
    letter-spacing: -0.01em;
}

.skin-black .main-header .breadcrumb > li + li::before {
    color: var(--sa-header-ink-muted);
}

/* Les segments cliquables sont des ancêtres : moins appuyés que la page courante. */
.skin-black .main-header .navbar .breadcrumb a {
    color: var(--sa-header-ink-muted);
    font-weight: 500;
}

.skin-black .main-header .navbar .breadcrumb a:hover {
    color: var(--sa-header-ink);
    background: transparent;
}

/* Commandes rondes de l'en-tête : bascule clair/sombre, aperçu du site.

   Le second sélecteur reprend la chaîne d'ancêtres d'AdminLTE parce que la commande est
   un vrai lien : sans cette longueur, `.navbar-custom-menu .nav > li > a` lui impose son
   gabarit de lien de navigation (remplissage vertical, couleur) et le rond s'étire. La
   bascule de thème est un <button>, elle échappe d'elle-même à cette règle. */

.theme-toggle-btn,
.skin-black .main-header .navbar .navbar-custom-menu .nav > li > a.sa-header-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    padding: 0;
    margin: 8px 6px;
    border: none;
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.07);
    color: var(--sa-header-ink);
    font-size: 16px;
    cursor: pointer;
}

.theme-toggle-btn:hover,
.skin-black .main-header .navbar .navbar-custom-menu .nav > li > a.sa-header-btn:hover,
.skin-black .main-header .navbar .navbar-custom-menu .nav > li > a.sa-header-btn:focus {
    background: var(--sa-header-hover-bg);
    color: var(--sa-header-ink);
}

.theme-toggle-btn .theme-toggle-icon {
    font-size: 16px;
}

/* Changement de langue rapide de l'en-tête. Même forme que le sélecteur d'édition des
   listes — un segment en relief dans une gouttière arrondie —, à ceci près qu'il tient dans
   une barre de 50px et n'affiche donc que le raccourci de chaque langue.

   La largeur plancher aligne les segments d'une langue à l'autre : « FR » fait deux lettres
   latines, « 中 » un idéogramme pleine chasse. */

.sa-locale-switch {
    display: inline-flex;
    gap: 2px;
    margin: 8px 6px;
    padding: 3px;
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.07);
}

.sa-locale-switch__item {
    min-width: 30px;
    padding: 3px 8px;
    border: none;
    border-radius: 999px;
    background: transparent;
    color: var(--sa-header-ink-muted);
    font-size: 13px;
    font-weight: 600;
    line-height: 1.5;
    cursor: pointer;
    transition: background-color 120ms ease, color 120ms ease;
}

.sa-locale-switch__item:hover,
.sa-locale-switch__item:focus-visible {
    color: var(--sa-header-ink);
    background: var(--sa-header-hover-bg);
}

.sa-locale-switch__item--current,
.sa-locale-switch__item--current:hover {
    color: var(--sa-header-ink);
    background: var(--sa-header-hover-bg);
    box-shadow: 0 1px 2px rgb(0 0 0 / 12%);
}

/* Le mouvement est un confort, jamais le porteur d'une information : sans lui, la langue
   active reste celle qui est en relief. */
@media (prefers-reduced-motion: reduce) {
    .sa-locale-switch__item {
        transition: none;
    }
}

/* ---------- Sidebar ---------- */

.skin-black .main-sidebar,
.skin-black .main-sidebar .sidebar {
    background: var(--sa-sidebar-bg);
}

.sidebar-form {
    padding: 10px 12px;
}

/* La recherche appartient au chrome, pas au corps de texte : elle suit l'échelle
   de la navigation. Sans cela, elle héritait des 19px du body et dépassait en
   hauteur son propre bouton, resté à l'échelle des boutons. */
.sidebar-form .form-control {
    background: var(--sa-sidebar-bg-alt);
    border: 1px solid var(--sa-sidebar-border);
    color: var(--sa-sidebar-text-active);
    font-size: var(--sa-font-ui);
}

.sidebar-form .input-group-btn > .btn {
    min-height: 40px;
}

.sidebar-menu > li {
    border-bottom: 1px solid transparent;
}

.sidebar-menu > li.header {
    color: var(--sa-text-muted);
    background: transparent;
    text-transform: uppercase;
    font-size: 11px;
    letter-spacing: 0.06em;
    padding: 14px 16px 6px;
}

.sidebar-menu > li > a {
    color: var(--sa-sidebar-text);
    border-left: 3px solid transparent;
    padding: 11px 16px;
}

.sidebar-menu > li > a:hover {
    color: var(--sa-sidebar-text-active);
    background: rgba(255, 255, 255, 0.05);
}

.skin-black .sidebar-menu > li.active > a,
.skin-black .sidebar-menu > li.active > a:hover {
    color: var(--sa-sidebar-text-active);
    background: var(--sa-sidebar-active-bg);
    border-left-color: var(--sa-accent);
}

.sidebar-menu .treeview-menu > li > a {
    color: var(--sa-sidebar-text);
    padding: 8px 16px 8px 36px;
}

.skin-black .sidebar-menu .treeview-menu > li.active > a,
.skin-black .sidebar-menu .treeview-menu > li > a:hover {
    color: var(--sa-sidebar-text-active);
    background: rgba(255, 255, 255, 0.06);
}

/* ---------- Contenu ---------- */

.content-wrapper {
    background: var(--sa-bg);
}

.navbar-default {
    background: var(--sa-surface);
    border: 1px solid var(--sa-border);
    border-radius: 8px;
    box-shadow: var(--sa-shadow);
    margin: 0 0 16px;
}

/* ---------- Cartes (box) ---------- */

.box {
    background: var(--sa-surface);
    border: 1px solid var(--sa-border);
    border-radius: 10px;
    box-shadow: var(--sa-shadow);
}

.box:hover {
    box-shadow: var(--sa-shadow-hover);
}

.box.box-primary,
.box.box-solid.box-primary {
    border-top: none;
}

.box-header {
    background: var(--sa-surface-muted);
    border-bottom: 1px solid var(--sa-border);
    border-radius: 10px 10px 0 0;
    padding: 14px 18px;
}

/* Les 18px d'AdminLTE passaient sous le corps de texte à 19px : un titre de
   boîte redevient un titre. Idem pour le titre de page, calé un cran au-dessus. */
.box-header .box-title {
    color: var(--sa-text);
    font-weight: 600;
    font-size: 22px;
}

.content-header > h1 {
    font-size: 28px;
}

.box-body {
    padding: 18px;
    color: var(--sa-text);
}

.box-footer {
    background: var(--sa-surface-muted);
    border-top: 1px solid var(--sa-border);
    padding: 14px 18px;
}

/* ---------- Tableaux ---------- */

.table {
    color: var(--sa-text);
}

.table > thead > tr > th {
    color: var(--sa-text-muted);
    border-bottom: 1px solid var(--sa-border-strong);
    font-weight: 600;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.table > tbody > tr > td {
    border-top: 1px solid var(--sa-border);
}

.table-hover > tbody > tr:hover > td {
    background: var(--sa-primary-soft);
}

.table-striped > tbody > tr:nth-of-type(odd) {
    background: var(--sa-surface-muted);
}

/* Colonne d'actions : `width: 1%` la réduit au contenu (le tableau répartit le reste
   entre les autres colonnes) et `nowrap` garde les icônes sur une seule ligne — sans
   ça la colonne se fait rogner jusqu'à ce que les boutons passent à la ligne, et la
   hauteur de toutes les lignes double. */
.table > thead > tr > th.sonata-ba-list-field-header-actions,
.table > tbody > tr > td.sonata-ba-list-field-actions {
    width: 1%;
    white-space: nowrap;
    text-align: right;
}

/* Les boutons d'un `.btn-group` Bootstrap 3 sont flottants : `white-space` ne les
   retient pas, ils repassent à la ligne dès que le conteneur se rétrécit. En contexte
   flex les flottements n'ont plus d'effet, et `flex-wrap` vaut `nowrap` par défaut. */
.table > tbody > tr > td.sonata-ba-list-field-actions .btn-group {
    display: inline-flex;
    /* Pas de `gap` : les boutons restent jointifs comme le veut un groupe Bootstrap,
       dont la règle `.btn + .btn { margin-left: -1px }` superpose les bordures. */
}

/* ---------- Boutons ---------- */

.btn {
    border-radius: 6px;
    font-weight: 500;
    font-size: inherit;
}

.btn-primary,
.btn-primary.active {
    background: var(--sa-gradient);
    border-color: transparent;
    color: #ffffff;
}

.btn-primary:hover,
.btn-primary:focus {
    filter: brightness(1.06);
    box-shadow: var(--sa-shadow-hover);
}

.btn-default {
    background: var(--sa-surface);
    border: 1px solid var(--sa-border-strong);
    color: var(--sa-text);
}

.btn-default:hover {
    background: var(--sa-surface-muted);
    border-color: var(--sa-primary);
    color: var(--sa-primary);
}

.btn-danger {
    background: var(--sa-danger);
    border-color: var(--sa-danger);
}

.btn-flat {
    border-radius: 6px;
}

/* Une action de l'en-tête d'un écran d'édition est tantôt un <button> — soumettre le
   formulaire —, tantôt un <a> — sortir vers une autre page. Bootstrap habille le second
   en lien de navigation (15px de padding, couleur du chrome) par une règle plus
   spécifique que `.btn` : côte à côte, le lien dépassait son voisin d'une tête. On lui
   rend la métrique et les couleurs d'un bouton. */
.navbar-default .navbar-nav > li > a.btn {
    padding: 6px 12px;
    line-height: 1.42857143;
    color: var(--sa-text);
}

.navbar-default .navbar-nav > li > a.btn-default:hover,
.navbar-default .navbar-nav > li > a.btn-default:focus {
    background-color: var(--sa-surface-muted);
    border-color: var(--sa-primary);
    color: var(--sa-primary);
}

/* Les <li> de la barre se touchent : deux actions voisines ont besoin d'un espace. */
.navbar-default .navbar-nav > li + li > .btn {
    margin-left: 8px;
}

/* ---------- Formulaires ---------- */

.form-control {
    background: var(--sa-surface);
    border: 1px solid var(--sa-border-strong);
    color: var(--sa-text);
    border-radius: 6px;
    font-size: inherit;
    /* Cf. l'échelle de texte plus haut : les 34px de Bootstrap sont taillés pour
       du 14px. `min-height` conserve l'alignement des champs courts. */
    height: auto;
    min-height: 40px;
}

.form-control:focus {
    border-color: var(--sa-primary);
    box-shadow: 0 0 0 3px var(--sa-primary-soft);
}

label {
    color: var(--sa-text);
    font-weight: 600;
}

.help-block {
    color: var(--sa-text-muted);
}

/* ---------- Onglets (CRUD edit/show) ---------- */

.nav-tabs-custom {
    background: var(--sa-surface);
    box-shadow: var(--sa-shadow);
}

.nav-tabs-custom > .nav-tabs {
    border-bottom-color: var(--sa-border);
}

.nav-tabs-custom > .nav-tabs > li > a {
    color: var(--sa-text-muted);
}

.nav-tabs-custom > .nav-tabs > li > a:hover {
    color: var(--sa-text);
}

.nav-tabs-custom > .nav-tabs > li.active {
    border-top-color: var(--sa-primary);
}

.nav-tabs-custom > .nav-tabs > li.active:hover > a,
.nav-tabs-custom > .nav-tabs > li.active > a {
    background: var(--sa-surface);
    color: var(--sa-text);
}

.nav-tabs-custom > .nav-tabs > li.active > a {
    border-left-color: var(--sa-border);
    border-right-color: var(--sa-border);
}

.tab-content {
    background: var(--sa-surface);
    color: var(--sa-text);
}

/* ---------- Barre d'actions (CRUD) ---------- */

.sonata-ba-form-actions.well {
    background: var(--sa-surface);
    border: 1px solid var(--sa-border);
    box-shadow: var(--sa-shadow);
    color: var(--sa-text);
}

.sonata-ba-form-actions.well.stuck {
    box-shadow: var(--sa-shadow-hover);
}

/* ---------- Titre inline dans la barre de navigation (CRUD edit) ---------- */

.navbar-default:has(.sonata-ba-navbar-title-header) {
    display: flex;
    align-items: center;
    min-height: 50px;
}

.navbar-default:has(.sonata-ba-navbar-title-header) .container-fluid {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    width: 100%;
}

.navbar-default:has(.sonata-ba-navbar-title-header) .navbar-collapse {
    flex: 0 0 auto;
    margin-left: auto;
}

.sonata-ba-navbar-title-header {
    display: flex;
    align-items: center;
    flex: 1 1 auto;
    float: none;
    min-height: 50px;
    padding: 0 15px;
    min-width: 0;
}

.sonata-ba-navbar-title-group {
    margin: 0;
    flex: 1 1 auto;
    min-width: 0;
}

.sonata-ba-navbar-title-input {
    background: transparent;
    border: 1px solid transparent;
    border-radius: 6px;
    color: var(--sa-text);
    font-size: 18px;
    font-weight: 700;
    line-height: 20px;
    letter-spacing: -0.01em;
    padding: 6px 10px;
    width: 100%;
    box-shadow: none;
}

.sonata-ba-navbar-title-input:hover {
    border-color: var(--sa-border-strong);
}

.sonata-ba-navbar-title-input:focus {
    background: var(--sa-surface);
    border-color: var(--sa-primary);
    box-shadow: 0 0 0 3px var(--sa-primary-soft);
}

.sonata-ba-navbar-title-input::placeholder {
    color: var(--sa-text-muted);
    font-weight: 400;
}

/* ---------- Menus déroulants ---------- */

.dropdown-menu {
    background: var(--sa-surface);
    border: 1px solid var(--sa-border);
    box-shadow: var(--sa-shadow-hover);
    border-radius: 8px;
}

.dropdown-menu > li > a {
    color: var(--sa-text);
}

.dropdown-menu > li > a:hover {
    background: var(--sa-primary-soft);
    color: var(--sa-primary);
}

/* ---------- Pastilles / labels ---------- */

.label-success,
.badge-success {
    background: var(--sa-success);
}

.label-danger,
.badge-danger {
    background: var(--sa-danger);
}

.label-warning,
.badge-warning {
    background: var(--sa-warning);
}

/* ---------- Pagination ---------- */

.pagination > li > a,
.pagination > li > span {
    background: var(--sa-surface);
    border: 1px solid var(--sa-border);
    color: var(--sa-text);
}

.pagination > .active > a,
.pagination > .active > span {
    background: var(--sa-gradient);
    border-color: transparent;
}

/* ---------- Dropzone (upload médias) ---------- */

.dropzone {
    display: block;
    padding: 2rem;
    border: 2px dashed var(--sa-border-strong);
    border-radius: 8px;
    text-align: center;
    cursor: pointer;
    background: var(--sa-surface-muted);
    color: var(--sa-text-muted);
}

.dropzone--active {
    border-color: var(--sa-primary);
    background: var(--sa-primary-soft);
    color: var(--sa-primary);
}

/* ---------- Dépôt groupé de la popup de choix d'un visuel (issues #326 et #332) ---------- */

.sa-media-drop {
    margin-bottom: 12px;
}

/* Même construction que celle de la modale : un <label> qui porte le dépôt, et le
   champ de fichier invisible mais tabulable en dessous. */
.sa-media-drop__zone {
    position: relative;
    display: block;
    margin: 0;
    padding: 1.25rem;
    border: 2px dashed var(--sa-border-strong);
    border-radius: 8px;
    text-align: center;
    cursor: pointer;
    background: var(--sa-surface-muted);
    color: var(--sa-text-muted);
}

.sa-media-drop__zone.is-active {
    border-color: var(--sa-primary);
    background: var(--sa-primary-soft);
    color: var(--sa-primary);
}

.sa-media-drop__zone.is-busy {
    cursor: progress;
    opacity: .7;
}

.sa-media-drop__input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: inherit;
}

.sa-media-drop__zone:has(.sa-media-drop__input:focus-visible) {
    outline: 2px solid var(--sa-primary);
    outline-offset: 2px;
}

.sa-media-drop__status {
    margin: 6px 0 0;
    min-height: 1.2em;
    font-size: 12.5px;
    color: var(--sa-text-muted);
}

/* ---------- Panneau d'analyse SEO ---------- */

.seo-panel {
    position: sticky;
    top: 16px;
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.seo-panel h4 {
    margin: 0 0 8px;
    font-size: 12px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--sa-text-muted);
}

.seo-panel__scores {
    display: flex;
    gap: 12px;
}

.seo-score-badge {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    padding: 12px;
    border-radius: 8px;
    background: var(--sa-surface-muted);
    border: 1px solid var(--sa-border);
}

.seo-score-badge__label {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--sa-text-muted);
    font-weight: 600;
}

.seo-score-badge__value {
    font-size: 22px;
    font-weight: 700;
    color: var(--sa-text);
}

.seo-score-badge--good {
    background: color-mix(in srgb, var(--sa-success) 12%, var(--sa-surface));
    border-color: var(--sa-success);
}

.seo-score-badge--good .seo-score-badge__value {
    color: var(--sa-success);
}

.seo-score-badge--ok {
    background: color-mix(in srgb, var(--sa-warning) 12%, var(--sa-surface));
    border-color: var(--sa-warning);
}

.seo-score-badge--ok .seo-score-badge__value {
    color: var(--sa-warning);
}

.seo-score-badge--bad {
    background: color-mix(in srgb, var(--sa-danger) 12%, var(--sa-surface));
    border-color: var(--sa-danger);
}

.seo-score-badge--bad .seo-score-badge__value {
    color: var(--sa-danger);
}

.seo-score-badge--list {
    display: inline-flex;
    min-width: 32px;
    padding: 2px 8px;
    border-radius: 999px;
    font-weight: 700;
    font-size: 12px;
    justify-content: center;
}

.seo-score-badge--na {
    color: var(--sa-text-muted);
}

/* Pastille de lisibilité : même code couleur que le SEO, mais évidée pour
   qu'on distingue les deux scores d'un coup d'œil dans la colonne de liste. */
.seo-score-badge--outline {
    background: transparent;
}

.seo-score-cell {
    display: inline-flex;
    gap: 4px;
    align-items: center;
}

.seo-serp-preview {
    display: flex;
    gap: 12px;
    align-items: flex-start;
    padding: 12px;
    border-radius: 8px;
    border: 1px solid var(--sa-border);
    background: var(--sa-surface);
}

.seo-serp-preview__text {
    flex: 1;
    min-width: 0;
}

/* Vignette de l'aperçu mobile Google : carré ~92 px, à droite du texte. */
.seo-serp-preview__thumbnail {
    flex: 0 0 auto;
    width: 72px;
    height: 72px;
    object-fit: cover;
    border-radius: 8px;
    border: 1px solid var(--sa-border);
}

.seo-serp-preview__title {
    color: #1a0dab;
    font-size: 16px;
    line-height: 1.3;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.seo-serp-preview__url {
    color: var(--sa-success);
    font-size: 12px;
    margin: 2px 0;
}

.seo-serp-preview__description {
    color: var(--sa-text-muted);
    font-size: 13px;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.seo-assessment-list,
.seo-heading-outline {
    list-style: none;
    margin: 0 0 16px;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.seo-assessment {
    padding: 6px 10px;
    border-radius: 6px;
    font-size: 12.5px;
    border-left: 3px solid var(--sa-border-strong);
    background: var(--sa-surface-muted);
    color: var(--sa-text);
}

.seo-assessment--good {
    border-left-color: var(--sa-success);
}

.seo-assessment--ok {
    border-left-color: var(--sa-warning);
}

.seo-assessment--bad {
    border-left-color: var(--sa-danger);
}

.seo-assessment--na {
    border-left-color: var(--sa-border-strong);
    color: var(--sa-text-muted);
}

.seo-assessment__message {
    display: block;
}

.seo-assessment__hint {
    display: block;
    margin-top: 3px;
    font-size: 12px;
    line-height: 1.4;
    color: var(--sa-text-muted);
}

/* Replis des critères déjà validés et des règles non applicables : ils gonflent
   la liste sans rien apprendre au rédacteur tant qu'il ne les cherche pas. */
.seo-foldable {
    margin-bottom: 16px;
}

.seo-foldable > summary {
    cursor: pointer;
    padding: 4px 0;
    font-size: 12px;
    color: var(--sa-text-muted);
}

.seo-foldable > summary:hover {
    color: var(--sa-text);
}

.seo-foldable .seo-assessment-list {
    margin: 6px 0 0;
}

/* ---------- Conseils SEO greffés sous les champs ---------- */

.seo-inline {
    margin-top: 8px;
    padding: 8px 10px;
    border-radius: 6px;
    border: 1px solid var(--sa-border);
    border-left-width: 3px;
    background: var(--sa-surface-muted);
}

.seo-inline[hidden] {
    display: none;
}

.seo-inline__summary {
    margin: 0;
    font-size: 12px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--sa-text-muted);
}

.seo-inline .seo-assessment-list {
    margin: 6px 0 0;
}

.seo-inline .seo-assessment {
    background: var(--sa-surface);
}

.seo-inline--good {
    border-left-color: var(--sa-success);
}

.seo-inline--ok {
    border-left-color: var(--sa-warning);
}

.seo-inline--bad {
    border-left-color: var(--sa-danger);
}

.seo-inline--na {
    border-left-color: var(--sa-border-strong);
}

.seo-score-badge__progress {
    font-size: 11px;
    color: var(--sa-text-muted);
    text-align: center;
}

.seo-heading-outline__item {
    padding: 4px 8px;
    border-radius: 6px;
    font-size: 12.5px;
    color: var(--sa-text-muted);
}

.seo-heading-outline__item--level-3 {
    margin-left: 12px;
}

.seo-heading-outline__item--level-4 {
    margin-left: 24px;
}

.seo-heading-outline__item--issue {
    background: color-mix(in srgb, var(--sa-danger) 10%, transparent);
    color: var(--sa-danger);
}

/* ---------- Bloc image EditorJS (médiathèque interne) ---------- */

.image-library-tool__select {
    display: block;
    width: 100%;
    padding: 2rem;
    border: 2px dashed var(--sa-border-strong);
    border-radius: 8px;
    text-align: center;
    cursor: pointer;
    background: var(--sa-surface-muted);
    color: var(--sa-text-muted);
    font: inherit;
}

.image-library-tool__select:hover {
    border-color: var(--sa-primary);
    color: var(--sa-primary);
}

.image-library-tool__figure {
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/* La vignette d'un bloc image ou galerie est cliquable (assets/editorjs/media-edit-trigger.js) :
   bouton de retouche à elle seule dans la galerie, conteneur de deux boutons dans le bloc
   image — l'image remplace le visuel, la pastille ouvre sa retouche. Le même dessin dans
   les deux cas : rien à ajouter à la case, seulement le focus, la cible de clic et la
   pastille. */
.media-edit-trigger,
.media-edit-trigger__replace {
    position: relative;
    display: block;
    width: 100%;
    min-width: 0;
    padding: 0;
    border: none;
    background: none;
    line-height: 0;
    cursor: pointer;
}

/* Le bloc image affiche son visuel à sa taille, centré, là où la galerie étire les
   siens à la largeur de leur case : le bouton doit épouser l'image dans les deux cas,
   sinon la pastille se retrouve à flotter dans le vide à côté d'elle. */
.image-library-tool .media-edit-trigger {
    width: fit-content;
    max-width: 100%;
    align-self: center;
}

.media-edit-trigger:focus-visible,
.media-edit-trigger__replace:focus-visible,
.media-edit-trigger__badge:focus-visible {
    outline: 2px solid var(--sa-primary);
    outline-offset: 2px;
}

/* Un cadre cliquable sans marque ne se découvre pas. Même pastille que le bouton de
   retrait de la galerie — mêmes dimensions, même encre translucide, même retrait de
   4px depuis le bord de l'image — à l'autre coin de la même vignette : les deux se
   lisent comme une paire. */
.media-edit-trigger__badge {
    position: absolute;
    top: 4px;
    left: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: rgba(23, 26, 43, 0.65);
    color: #fff;
    font-size: 12px;
    line-height: 1;
    border: none;
    padding: 0;
}

/* Galerie : le clic vise la vignette entière, la pastille n'est qu'un repère — elle ne
   doit pas lui voler le coin qu'elle couvre. Bloc image, elle est un `<button>` : la
   vignette y remplace le visuel, et elle seule ouvre la retouche. */
span.media-edit-trigger__badge {
    pointer-events: none;
}

button.media-edit-trigger__badge {
    cursor: pointer;
}

.media-edit-trigger:hover span.media-edit-trigger__badge,
.media-edit-trigger:focus-visible span.media-edit-trigger__badge,
button.media-edit-trigger__badge:hover,
button.media-edit-trigger__badge:focus-visible {
    background: rgba(23, 26, 43, 0.85);
}

.image-library-tool__image {
    max-width: 100%;
    border-radius: 8px;
    border: 1px solid var(--sa-border);
}

/* Aperçu pas encore résolu (le bloc ne stocke qu'un id, cf. image-library-tool.js). */
.image-library-tool__image.is-pending {
    min-height: 80px;
    background: var(--sa-surface-muted);
}

.image-library-tool__caption {
    font-size: 13px;
    color: var(--sa-text-muted);
    padding: 6px 8px;
    border: 1px solid var(--sa-border);
    border-radius: 6px;
    background: var(--sa-surface-muted);
    min-height: 1.4em;
}

.image-library-tool__caption:empty::before {
    content: attr(data-placeholder);
    color: var(--sa-text-muted);
}

/* ---------- Modale de sélection d'image ---------- */

.image-library-picker__overlay {
    position: fixed;
    inset: 0;
    background: rgba(23, 26, 43, 0.5);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
}

.image-library-picker {
    display: flex;
    flex-direction: column;
    gap: 16px;
    width: min(720px, 92vw);
    max-height: 80vh;
    padding: 16px;
    border-radius: 10px;
    background: var(--sa-surface);
    box-shadow: var(--sa-shadow-hover);
}

.image-library-picker__header {
    display: flex;
    align-items: center;
    gap: 12px;
}

.image-library-picker__search {
    flex: 1;
    padding: 8px 10px;
    border-radius: 6px;
    border: 1px solid var(--sa-border-strong);
    background: var(--sa-surface-muted);
    color: var(--sa-text);
    font: inherit;
}

.image-library-picker__scope {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12.5px;
    color: var(--sa-text-muted);
    white-space: nowrap;
}

.image-library-picker__close {
    border: none;
    background: transparent;
    color: var(--sa-text-muted);
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
    padding: 4px 8px;
}

.image-library-picker__close:hover {
    color: var(--sa-text);
}

.image-library-picker__results {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 12px;
    overflow-y: auto;
    padding-right: 4px;
}

.image-library-picker__status {
    grid-column: 1 / -1;
    text-align: center;
    color: var(--sa-text-muted);
    font-size: 13px;
    margin: 24px 0;
}

.image-library-picker__item {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 8px;
    border-radius: 8px;
    border: 1px solid var(--sa-border);
    background: var(--sa-surface-muted);
    cursor: pointer;
    text-align: left;
    font: inherit;
}

.image-library-picker__item:hover {
    border-color: var(--sa-primary);
}

.image-library-picker__item img {
    width: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    border-radius: 4px;
}

.image-library-picker__item-name {
    font-size: 12.5px;
    color: var(--sa-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.image-library-picker__item-fiche {
    font-size: 11px;
    color: var(--sa-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* `hidden` seul ne suffit pas sur un `.btn` : la règle d'affichage de Bootstrap
   l'emporterait sur celle du navigateur. */
.media-picker-actions [hidden],
.image-library-picker [hidden] {
    display: none !important;
}

/* Portée du picker : fiches liées, et ajout d'une fiche sans quitter la modale. */
.image-library-picker__bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
}

.image-library-picker__chips {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}

.image-library-picker__chip {
    padding: 2px 8px;
    border-radius: 999px;
    background: var(--sa-surface-muted);
    border: 1px solid var(--sa-border);
    font-size: 12px;
    color: var(--sa-text);
}

.image-library-picker__chip-empty {
    font-size: 12px;
    color: var(--sa-text-muted);
}

.image-library-picker__link {
    position: relative;
}

.image-library-picker__fiche-search {
    padding: 6px 10px;
    border-radius: 6px;
    border: 1px solid var(--sa-border-strong);
    background: var(--sa-surface-muted);
    color: var(--sa-text);
    font: inherit;
    font-size: 12.5px;
}

.image-library-picker__suggestions {
    position: absolute;
    right: 0;
    z-index: 1;
    display: flex;
    flex-direction: column;
    min-width: 220px;
    max-height: 240px;
    overflow-y: auto;
    border: 1px solid var(--sa-border-strong);
    border-radius: 6px;
    background: var(--sa-surface);
}

.image-library-picker__suggestion {
    padding: 6px 10px;
    border: none;
    background: transparent;
    color: var(--sa-text);
    font: inherit;
    font-size: 12.5px;
    text-align: left;
    cursor: pointer;
}

.image-library-picker__suggestion:hover {
    background: var(--sa-surface-muted);
}

.image-library-picker__upload {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    font-size: 12.5px;
    color: var(--sa-text-muted);
}

/* La zone de dépôt est un <label> sur un vrai <input type="file"> : le champ reste
   atteignable au clavier et par les technologies d'assistance, il n'est que sorti du
   flux visuel. `opacity: 0` et non `display: none`, qui le retirerait de l'ordre de
   tabulation (issue #326). */
.image-library-picker__dropzone {
    position: relative;
    flex: 1 1 14rem;
    padding: 10px 14px;
    border: 2px dashed var(--sa-border-strong);
    border-radius: 6px;
    text-align: center;
    cursor: pointer;
    background: var(--sa-surface-muted);
}

.image-library-picker__dropzone.is-active {
    border-color: var(--sa-primary);
    background: var(--sa-primary-soft);
    color: var(--sa-primary);
}

.image-library-picker__dropzone.is-busy {
    cursor: progress;
    opacity: .7;
}

.image-library-picker__upload-file {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: inherit;
}

/* L'anneau de focus se pose sur la zone, le champ étant invisible : sans cela, la
   tabulation traverserait le composant sans que rien ne bouge à l'écran. */
.image-library-picker__upload-file:focus-visible + .image-library-picker__dropzone-hint,
.image-library-picker__dropzone:has(.image-library-picker__upload-file:focus-visible) {
    outline: 2px solid var(--sa-primary);
    outline-offset: 2px;
}

.image-library-picker__upload-status {
    flex: 1 1 100%;
    margin: 0;
    min-height: 1.2em;
}

/* Sélection multiple : bloc galerie (issue #73). Le mode par défaut, mono-sélection,
   n'affiche ni l'état retenu ni la barre de validation. */
/* Contour épais et accentué plutôt qu'une bordure de 1px : dans une grille de
   vignettes, l'état retenu doit se lire d'un coup d'œil sur toute la planche. */
.image-library-picker__item.is-selected {
    border-color: var(--sa-accent);
    outline: 3px solid var(--sa-accent);
    outline-offset: -3px;
}

/* Déjà dans la galerie : montré comme tel, et hors d'atteinte. */
.image-library-picker__item.is-excluded {
    opacity: .55;
    cursor: default;
}

.image-library-picker__item-already {
    font-size: 11px;
    font-weight: 600;
    color: var(--sa-accent);
}

.image-library-picker__confirm {
    display: flex;
    justify-content: flex-end;
}

.image-library-picker__confirm-submit {
    padding: 8px 14px;
    border-radius: 6px;
    border: 1px solid var(--sa-primary);
    background: var(--sa-primary);
    color: #fff;
    cursor: pointer;
    font: inherit;
    font-size: 12.5px;
}

.image-library-picker__confirm-submit:disabled {
    border-color: var(--sa-border);
    background: var(--sa-surface-muted);
    color: var(--sa-text-muted);
    cursor: default;
}

/* ---------- Bloc galerie EditorJS (issue #73) ---------- */

.gallery-tool__items {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: 12px;
}

.gallery-tool__empty {
    grid-column: 1 / -1;
    margin: 0;
    padding: 1.5rem;
    border: 2px dashed var(--sa-border-strong);
    border-radius: 8px;
    text-align: center;
    background: var(--sa-surface-muted);
    color: var(--sa-text-muted);
    font-size: 13px;
}

.gallery-tool__item {
    position: relative;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 8px;
    border-radius: 8px;
    border: 1px solid var(--sa-border);
    background: var(--sa-surface-muted);
    cursor: grab;
    /* La taille minimale automatique d'un élément de grille vaut son contenu, et
       celle d'une image sa largeur intrinsèque : sans ce 0, un visuel de 4000px
       élargit sa piste et fait déborder toute la grille, `width: 100%` ou pas. */
    min-width: 0;
}

.gallery-tool__item.is-dragging {
    opacity: 0.5;
}

.gallery-tool__item.is-drop-target {
    border-color: var(--sa-primary);
    box-shadow: inset 0 0 0 1px var(--sa-primary);
}

.gallery-tool__thumb {
    width: 100%;
    max-width: 100%;
    min-width: 0;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    border-radius: 4px;
}

/* Aperçu pas encore résolu (le bloc ne stocke que des ids, cf. gallery-tool.js) :
   la case garde sa place le temps de l'appel à la médiathèque. */
.gallery-tool__thumb.is-pending {
    background: var(--sa-border);
}

.gallery-tool__caption {
    width: 100%;
    padding: 5px 8px;
    border-radius: 6px;
    border: 1px solid var(--sa-border);
    background: var(--sa-surface);
    color: var(--sa-text);
    font: inherit;
    font-size: 12.5px;
}

.gallery-tool__remove {
    position: absolute;
    top: 12px;
    right: 12px;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    border: none;
    background: rgba(23, 26, 43, 0.65);
    color: #fff;
    line-height: 1;
    cursor: pointer;
    font: inherit;
    font-size: 16px;
}

.gallery-tool__actions {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-top: 12px;
}

.gallery-tool__add {
    padding: 6px 12px;
    border-radius: 6px;
    border: 1px solid var(--sa-border-strong);
    background: var(--sa-surface);
    color: var(--sa-text);
    cursor: pointer;
    font: inherit;
    font-size: 12.5px;
}

.gallery-tool__add:hover {
    border-color: var(--sa-primary);
    color: var(--sa-primary);
}

.gallery-tool__layout {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    color: var(--sa-text-muted);
}

.gallery-tool__layout-select {
    padding: 5px 8px;
    border-radius: 6px;
    border: 1px solid var(--sa-border-strong);
    background: var(--sa-surface);
    color: var(--sa-text);
    font: inherit;
    font-size: 12.5px;
}

/* ---------- Lien interne EditorJS (issue #21) ---------- */

.internal-link-tool__link {
    text-decoration: underline dotted;
    text-underline-offset: 2px;
    color: var(--sa-primary);
}

/* ---------- Bloc spoiler EditorJS ---------- */

.spoiler-block-tool {
    padding: 10px 12px;
    border: 1px dashed var(--sa-border-strong);
    border-radius: 8px;
    background: var(--sa-surface-muted);
}

.spoiler-block-tool__label {
    margin-bottom: 6px;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--sa-text-muted);
}

.spoiler-block-tool__content:empty::before {
    content: attr(data-placeholder);
    color: var(--sa-text-muted);
}

/* ---------- Modale de sélection de lien interne ---------- */

.internal-link-picker__overlay {
    position: fixed;
    inset: 0;
    background: rgba(23, 26, 43, 0.5);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
}

.internal-link-picker {
    display: flex;
    flex-direction: column;
    gap: 16px;
    width: min(560px, 92vw);
    max-height: 80vh;
    padding: 16px;
    border-radius: 10px;
    background: var(--sa-surface);
    box-shadow: var(--sa-shadow-hover);
}

.internal-link-picker__header {
    display: flex;
    align-items: center;
    gap: 12px;
}

.internal-link-picker__search {
    flex: 1;
    padding: 8px 10px;
    border-radius: 6px;
    border: 1px solid var(--sa-border-strong);
    background: var(--sa-surface-muted);
    color: var(--sa-text);
    font: inherit;
}

.internal-link-picker__close {
    border: none;
    background: transparent;
    color: var(--sa-text-muted);
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
    padding: 4px 8px;
}

.internal-link-picker__close:hover {
    color: var(--sa-text);
}

.internal-link-picker__results {
    display: flex;
    flex-direction: column;
    gap: 6px;
    overflow-y: auto;
    padding-right: 4px;
}

.internal-link-picker__status {
    text-align: center;
    color: var(--sa-text-muted);
    font-size: 13px;
    margin: 24px 0;
}

.internal-link-picker__item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border-radius: 8px;
    border: 1px solid var(--sa-border);
    background: var(--sa-surface-muted);
    cursor: pointer;
    text-align: left;
    font: inherit;
}

.internal-link-picker__item:hover {
    border-color: var(--sa-primary);
}

.internal-link-picker__badge {
    flex-shrink: 0;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 600;
    color: var(--sa-text-muted);
    background: var(--sa-surface);
    border: 1px solid var(--sa-border);
}

.internal-link-picker__item-title {
    flex: 1;
    font-size: 13px;
    color: var(--sa-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.internal-link-picker__item-subtitle {
    flex-shrink: 0;
    font-size: 11px;
    color: var(--sa-text-muted);
}

.list-title-cell__slug {
    display: block;
    font-size: 11px;
    color: var(--sa-text-muted);
    opacity: 0;
    visibility: hidden;
    transition: opacity .15s ease;
}

tr:hover .list-title-cell__slug {
    opacity: 1;
    visibility: visible;
}

.list-title-cell__title {
    font-weight: 600;
}

/* Le titre rendu faute de version dans la langue de l'écran : il reste lisible — c'est à
   quoi on reconnaît la ligne — mais ne doit pas se donner pour la traduction. */
.list-title-cell__title--source {
    font-weight: 400;
    font-style: italic;
    color: var(--sa-text-muted);
}

/* Préfixe de rubrique, à la place de l'ancienne colonne univers : même corps que le
   titre pour rester visible, en gras et en capitales. Les accents sont ceux des thèmes
   du front (front/assets/styles/_themes.scss) ; ils portent ici du texte, d'où le
   mélange avec l'encre courante : ces bleus/verts moyens passent sous le seuil de
   contraste sur la surface sombre s'ils sont utilisés purs. */
.list-title-cell__univers {
    margin-right: 4px;
    font-weight: 800;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: color-mix(in srgb, var(--sa-univers, var(--sa-primary)) 70%, var(--sa-text));
}

.list-title-cell__univers--cine {
    --sa-univers: #2f5d8a;
}

.list-title-cell__univers--game {
    --sa-univers: #fb6102;
}

.list-title-cell__univers--pop {
    --sa-univers: #b23a73;
}

.list-title-cell__univers--hardware {
    --sa-univers: #2e8b6f;
}

/* Le groupe des champs de test se masque via l'attribut `hidden` (cf.
   article_test_fields_controller.js). La règle est explicite parce que la classe de
   colonne Bootstrap portée par le même élément l'emporterait autrement sur le
   `display: none` de la feuille de style du navigateur, à spécificité égale. */
.js-article-test-fields[hidden] {
    display: none;
}

/* Catégorie éditoriale, à côté de la rubrique et seulement quand elle sort de
   l'ordinaire (un test). Pastille pleine et non préfixe entre crochets : elle se lit
   d'un balayage vertical de la liste, ce qui est tout son intérêt. */
.list-title-cell__category {
    margin-right: 4px;
    padding: 1px 6px;
    border-radius: 4px;
    font-size: 11px;
    font-weight: 800;
    letter-spacing: 0.04em;
    vertical-align: 1px;
    color: var(--sa-surface);
    background: var(--sa-primary);
}

/* Tags sous le titre : toujours visibles (contrairement au slug qu'ils remplacent) et
   assez discrets pour ne pas concurrencer le titre. */
.list-title-cell__tags {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-top: 4px;
}

.sa-tag-badge {
    padding: 1px 8px;
    border-radius: 999px;
    font-size: 11px;
    line-height: 1.6;
    color: var(--sa-text-muted);
    background: var(--sa-surface-muted);
    border: 1px solid var(--sa-border-strong);
}

/* ---------- Cellules de listing (publication, auteur) ---------- */

.sa-publication-cell {
    display: inline-flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
}

.sa-publication-cell__date {
    font-size: 12px;
    color: var(--sa-text-muted);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.sa-status-badge {
    display: inline-block;
    padding: 2px 10px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.02em;
    white-space: nowrap;
}

.sa-status-badge--published {
    color: var(--sa-success);
    background: color-mix(in srgb, var(--sa-success) 14%, var(--sa-surface));
    border: 1px solid color-mix(in srgb, var(--sa-success) 45%, transparent);
}

.sa-status-badge--draft {
    color: var(--sa-warning);
    background: color-mix(in srgb, var(--sa-warning) 14%, var(--sa-surface));
    border: 1px solid color-mix(in srgb, var(--sa-warning) 45%, transparent);
}

/* Publié mais daté de plus tard. La couleur n'est qu'un renfort : le badge dit
   « Programmé » et non « Publié », c'est le mot qui distingue les deux états. */
.sa-status-badge--scheduled {
    color: var(--sa-scheduled);
    background: color-mix(in srgb, var(--sa-scheduled) 14%, var(--sa-surface));
    border: 1px solid color-mix(in srgb, var(--sa-scheduled) 45%, transparent);
}

/* Créer dans une édition, depuis la barre de la liste (issue #189).

   Écrire est le geste que la rédaction répète toute la journée : ces raccourcis sont donc
   posés à côté des filtres et non dans le menu « Actions », où il aurait fallu un clic pour
   les faire apparaître. Ils ne remplacent pas le bouton « Ajouter » de Sonata, qui crée dans
   la langue de l'écran — celui-là reste à sa place, là où va la mémoire des doigts.

   Un liséré à gauche les rattache les uns aux autres et les détache des filtres : trois
   liens nus dans une barre en portant déjà se liraient comme un seul groupe de cinq. */
.sa-create-in-edition {
    margin-left: 6px;
    padding-left: 6px;
    border-left: 1px solid var(--sa-border);
}

.sa-create-in-edition > li > a {
    padding-left: 9px;
    padding-right: 9px;
    font-weight: 600;
}

/* La largeur plancher aligne les raccourcis entre eux : « FR » fait deux lettres latines,
   « 中 » un idéogramme pleine chasse, et sans elle les trois boutons dansent. */
.sa-create-in-edition > li > a > span {
    display: inline-block;
    min-width: 20px;
    text-align: center;
}

.sa-create-in-edition > li > a > i {
    margin-right: 3px;
}

/* Sélecteur d'édition en tête de liste : un contrôle segmenté, la langue ouverte posée sur
   une pastille en relief dans une gouttière creuse.

   Un segment relevé plutôt qu'un trait de couleur : l'état se lit au **relief** — fond
   propre, ombre courte, contraste du texte —, trois indices dont aucun n'est une teinte à
   comparer. C'est ce que demande un écran qui doit rester lisible sans distinguer les
   couleurs, et c'est plus sûr qu'un soulignement de 2 px.

   Les noms restent entiers ici, contrairement aux pastilles de la colonne d'état : un
   sélecteur de langue est le seul endroit qu'on lit sans comprendre la langue affichée, et
   « 中 » ne sortirait personne d'un back-office qu'il ne sait pas lire. */
.sa-edition-switcher {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 2px;
    /* Le `<nav>` est seul dans `list_header`, sans conteneur qui l'écarterait des bords :
       c'est sa propre marge qui le décolle du cadre et du tableau. Plus d'air en bas qu'en
       haut, pour qu'il se rattache à la liste qu'il commande plutôt qu'au bord au-dessus. */
    margin: 12px 12px 16px;
    padding: 3px;
    border: 1px solid var(--sa-border);
    border-radius: 999px;
    background: color-mix(in srgb, var(--sa-border) 35%, var(--sa-surface));
}

.sa-edition-switcher__item {
    padding: 5px 15px;
    border-radius: 999px;
    color: var(--sa-text-muted);
    font-size: 13px;
    font-weight: 600;
    line-height: 1.35;
    text-decoration: none;
    white-space: nowrap;
    transition: background-color 120ms ease, color 120ms ease;
}

.sa-edition-switcher__item:hover,
.sa-edition-switcher__item:focus-visible {
    color: var(--sa-text);
    background: color-mix(in srgb, var(--sa-surface) 60%, transparent);
    text-decoration: none;
}

.sa-edition-switcher__item--current,
.sa-edition-switcher__item--current:hover {
    color: var(--sa-primary);
    background: var(--sa-surface);
    box-shadow: 0 1px 2px rgb(0 0 0 / 12%);
}

/* Le mouvement est un confort, jamais le porteur d'une information : sans lui, le segment
   ouvert reste celui qui est en relief. */
@media (prefers-reduced-motion: reduce) {
    .sa-edition-switcher__item {
        transition: none;
    }
}

/* Une traduction pas encore écrite : ni verte ni orange, elle n'est pas un état de
   parution mais son absence. Le gris muet la range visuellement derrière les deux autres,
   ce qui est le bon ordre de lecture d'une colonne dont on scrute les trous. */
.sa-status-badge--missing {
    color: var(--sa-text-muted);
    background: transparent;
    border: 1px dashed color-mix(in srgb, var(--sa-text-muted) 45%, transparent);
}

/* Colonne d'état de traduction : les pastilles sont des liens vers l'écran de rédaction de
   leur langue, d'où le retrait du soulignement — la pastille est déjà l'affordance.

   `min-width` et centrage : les raccourcis n'ont pas la même largeur — « FR » fait deux
   lettres latines, « 中 » un idéogramme pleine chasse — et sans largeur plancher les
   pastilles se décalent d'une ligne à l'autre. Or c'est une colonne qu'on **balaie**
   verticalement à la recherche d'un trou : des pastilles qui ne s'alignent pas obligent à
   lire chaque rangée. */
.sa-translation-states {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 4px;
}

.sa-translation-states .sa-status-badge {
    min-width: 34px;
    padding: 2px 8px;
    text-align: center;
}

.sa-translation-states a.sa-status-badge {
    text-decoration: none;
}

/* Un lien reste un lien : au survol la pastille se marque, sinon rien ne dit qu'on peut
   cliquer pour aller écrire la langue qui manque. */
.sa-translation-states a.sa-status-badge:hover,
.sa-translation-states a.sa-status-badge:focus-visible {
    filter: brightness(0.96);
    text-decoration: none;
}

/* Colonne « Éditions » de la liste des articles : une rangée par langue, portant sa
   pastille d'état, ses deux scores et les commandes qui ne valent que pour elle.

   Les largeurs planchers ne sont pas décoratives. C'est une colonne qu'on **balaie**
   verticalement à la recherche d'un trou, et les contenus n'ont pas la même chasse
   d'une rangée à l'autre — « FR » fait deux lettres latines, « 中 » un idéogramme
   pleine chasse, et un score à deux chiffres est plus étroit qu'un tiret cadratin. Sans
   plancher, chaque rangée se décale et il faut lire au lieu de balayer.

   Une langue sans traduction n'a que sa pastille : le groupe de scores et celui des
   commandes ne sont pas rendus vides, ils ne sont pas rendus du tout. La rangée reste
   néanmoins alignée, les planchers étant portés par les cellules qui existent. */
.sa-editions {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.sa-editions__row {
    display: flex;
    align-items: center;
    gap: 8px;
}

.sa-editions__language .sa-status-badge {
    min-width: 34px;
    padding: 2px 8px;
    text-align: center;
}

/* Un mot-clé rangé dans une famille n'a pas d'éditions à suivre (issue #348) : la colonne
   porte une mention au lieu de trois rangées. Atténuée et sans pastille — ce n'est pas un
   état de traduction, c'est l'absence de la question ; lui donner l'apparence d'un état la
   ferait chercher parmi les autres.

   Le jeton et non un gris littéral : il porte déjà la valeur du thème sombre, et il est
   mesuré — #6b7280 sur la surface claire vaut 4,83:1, au-dessus du seuil de 4,5:1 d'un
   texte. Un gris choisi à l'œil pour « atténuer » tombe sous le seuil sans le dire. */
.sa-editions__untranslatable {
    color: var(--sa-text-muted);
    font-size: 0.85em;
    font-style: italic;
}

.sa-editions__language a.sa-status-badge {
    text-decoration: none;
}

/* Un lien reste un lien : au survol la pastille se marque, sinon rien ne dit qu'on peut
   cliquer pour aller écrire la langue qui manque. */
.sa-editions__language a.sa-status-badge:hover,
.sa-editions__language a.sa-status-badge:focus-visible {
    filter: brightness(0.96);
    text-decoration: none;
}

.sa-editions__scores {
    display: inline-flex;
    gap: 4px;
    min-width: 72px;
}

/* Les commandes à droite de la rangée, collées au bord de la colonne : elles se pointent
   à la souris, pas se lisent. `auto` plutôt qu'une largeur, pour qu'une rangée sans
   commande ne creuse pas un blanc au milieu. */
.sa-editions__actions {
    display: inline-flex;
    gap: 2px;
    margin-left: auto;
}

/* Trois rangées de boutons à la taille d'avant auraient triplé la hauteur de chaque
   ligne du tableau. Le bouton reste un `btn btn-sm` d'AdminLTE — mêmes couleurs, même
   focus —, resserré juste assez pour que trois rangées tiennent la hauteur qu'une seule
   prenait. */
.sa-editions__actions .btn {
    padding: 1px 6px;
    line-height: 1.4;
}

/* Demander la traduction d'un article, dans la barre d'actions de son écran (issue #236).

   Un `<li>` de la barre comme les autres, sauf qu'il en contient plusieurs : autant de
   boutons que d'éditions à proposer, chacun dans son propre `<form>` — une demande écrit en
   base, donc POST et jeton CSRF. Les formulaires ne portent donc aucune mise en forme, ils
   ne sont là que pour la méthode HTTP. */
.sa-translate-panel {
    float: left;
}

.sa-translate {
    display: inline-flex;
    gap: 4px;
}

.sa-translate__form {
    margin: 0;
}

/* La largeur plancher aligne les boutons entre eux et surtout d'un état à l'autre : « EN »
   fait deux lettres latines, « 中 » un idéogramme pleine chasse, et le sablier n'a pas la
   largeur du globe. Sans elle, la barre d'actions se réorganise à chaque sondage, sous le
   curseur de qui allait cliquer ailleurs. */
.sa-translate__button {
    min-width: 62px;
    text-align: center;
    white-space: nowrap;
}

.sa-translate__button > i {
    margin-right: 4px;
}

/* En attente, en cours : rien à cliquer. Le gris le dit avant le curseur. */
.sa-translate__button--queued,
.sa-translate__button--running {
    color: var(--sa-text-muted);
    cursor: default;
}

/* L'échec garde la couleur d'un avertissement, pas d'une erreur : rien n'est perdu, le
   bouton redemande. */
.sa-translate__button--failed {
    color: var(--sa-warning);
}

/* En retard sur sa source (issue #358) : même teinte, parce que c'est la même invitation —
   rien n'est cassé, un clic remet à niveau. L'icône et le nom accessible distinguent les deux. */
.sa-translate__button--outdated,
.sa-translation-job--outdated {
    color: var(--sa-warning);
}

.sa-translate__spinner {
    animation: sa-translate-spin 1.4s linear infinite;
}

@keyframes sa-translate-spin {
    to {
        transform: rotate(360deg);
    }
}

/* Le mouvement est un confort, jamais le porteur d'une information : l'état « en cours »
   est dit par l'infobulle, le nom accessible et la région vivante, pas par la rotation. */
@media (prefers-reduced-motion: reduce) {
    .sa-translate__spinner {
        animation: none;
    }
}

.sa-author-cell {
    display: inline-flex;
    align-items: center;
    white-space: nowrap;
}

.sa-author-cell__name {
    max-width: 140px;
    overflow: hidden;
    text-overflow: ellipsis;
}

.sa-author-cell__empty {
    color: var(--sa-text-muted);
}

/* ---------- Analytics (dashboard + colonnes de listing) ---------- */

/* Deux séries, une paire validée sur les six checks (bande de clarté, chroma, séparation
   daltonisme, plancher vision normale, contraste) contre les surfaces réelles claire
   #ffffff et sombre #1a1d2c. Ne pas changer ces valeurs sans revalider. */
:root {
    --sa-series-visitors: #df6a00;
    --sa-series-views: #4f46e5;
    --sa-chart-area: rgba(223, 106, 0, 0.16);
    --sa-chart-grid: var(--sa-border);
}

html[data-theme="dark"] {
    /* #4f46e5 tombe à 2.66:1 sur la surface sombre : on remonte d'un cran. */
    --sa-series-views: #6366f1;
    --sa-chart-area: rgba(223, 106, 0, 0.22);
}

.sa-analytics-empty {
    margin: 0;
    padding: 18px 0;
    color: var(--sa-text-muted);
    font-size: 13px;
}

/* Barre de période : deux flèches, un menu, la fenêtre réellement couverte. */
.sa-period {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px 14px;
}

.sa-period__nav {
    display: flex;
    gap: 6px;
}

.sa-period__step {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border: 1px solid var(--sa-border-strong);
    border-radius: 8px;
    background: var(--sa-surface-muted);
    color: var(--sa-text);
    font-size: 20px;
    line-height: 1;
    text-decoration: none;
}

.sa-period__step:hover {
    background: var(--sa-primary-soft);
    color: var(--sa-primary-dark);
    text-decoration: none;
}

/* Le focus ne doit pas ressembler au survol : un vrai anneau, en `outline` et pas en
   `box-shadow`, pour rester visible sous un thème à contrastes forcés. */
.sa-period__step:focus-visible {
    outline: 2px solid var(--sa-primary);
    outline-offset: 2px;
    color: var(--sa-primary-dark);
    text-decoration: none;
}

/* Pas de fenêtre après la fenêtre en cours : le repère reste, l'action disparaît. */
.sa-period__step--off {
    color: var(--sa-text-muted);
    opacity: 0.45;
}

.sa-period__form {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0;
}

.sa-period__select {
    width: auto;
    min-width: 210px;
}

/* Bootstrap 3 donne `display: inline-block` à `.btn`, ce qui l'emporte sur le
   `[hidden] { display: none }` de la feuille par défaut : sans cette règle, le bouton
   masqué par le contrôleur Stimulus resterait affiché. */
.sa-period__submit[hidden] {
    display: none;
}

/* La fenêtre réelle, pas le nom du préréglage : c'est ce que les flèches déplacent. */
.sa-period__range {
    margin: 0;
    color: var(--sa-text-muted);
    font-size: 13px;
    font-variant-numeric: tabular-nums;
}

/* Rangée de chiffres de tête : la valeur exacte se lit ici, pas sur le graphe. */
.sa-kpi-row {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin-bottom: 22px;
}

.sa-kpi {
    flex: 1 1 150px;
    padding: 14px 16px;
    border: 1px solid var(--sa-border);
    border-radius: 10px;
    background: var(--sa-surface-muted);
}

.sa-kpi__label {
    display: block;
    font-size: 13px;
    font-weight: 600;
    color: var(--sa-text);
}

.sa-kpi__value {
    display: block;
    margin: 6px 0 10px;
    font-size: 34px;
    font-weight: 700;
    line-height: 1.05;
    color: var(--sa-text);
    font-variant-numeric: tabular-nums;
}

.sa-chart {
    display: block;
    width: 100%;
    height: auto;
}

.sa-chart__grid {
    stroke: var(--sa-chart-grid);
    stroke-width: 1;
}

/* Graduations de l'axe y, calées à droite de la gouttière. */
.sa-chart__scale {
    fill: var(--sa-text-muted);
    font-size: 11px;
    text-anchor: end;
}

/* Heures sous l'axe x, centrées sur leur colonne. */
.sa-chart__tick {
    fill: var(--sa-text-muted);
    font-size: 11px;
    text-anchor: middle;
}

.sa-chart__bar--s0 {
    fill: var(--sa-series-visitors);
}

.sa-chart__bar--s1 {
    fill: var(--sa-series-views);
}

/* Bandes de survol : l'infobulle est le <title> natif du SVG, aucun JS embarqué. */
.sa-chart__hit {
    fill: transparent;
}

.sa-chart__hit:hover {
    fill: var(--sa-text);
    fill-opacity: 0.06;
}

.sa-legend {
    display: flex;
    gap: 18px;
    justify-content: center;
    margin-top: 10px;
    font-size: 12px;
    color: var(--sa-text-muted);
}

.sa-legend__item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.sa-legend__swatch {
    width: 9px;
    height: 9px;
    border-radius: 50%;
}

.sa-legend__swatch--s0 {
    background: var(--sa-series-visitors);
}

.sa-legend__swatch--s1 {
    background: var(--sa-series-views);
}

.sa-sparkline {
    display: inline-block;
    vertical-align: middle;
    overflow: visible;
}

.sa-sparkline__area {
    fill: var(--sa-chart-area);
}

.sa-sparkline__line {
    fill: none;
    stroke: var(--sa-series-visitors);
    stroke-width: 1.5;
    stroke-linejoin: round;
    stroke-linecap: round;
}

/* Variation : la flèche et le signe portent le sens, la couleur ne fait que le
   renforcer — jamais d'information par la couleur seule. */
.sa-trend {
    display: inline-block;
    font-size: 12px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Dans un KPI, la variation devient une pastille teintée ; en cellule de liste elle
   reste du texte nu, une pastille par ligne saturerait le tableau. */
.sa-kpi .sa-trend {
    padding: 3px 9px;
    border-radius: 6px;
}

.sa-trend--up {
    color: var(--sa-success);
}

.sa-kpi .sa-trend--up {
    background: color-mix(in srgb, var(--sa-success) 16%, transparent);
}

.sa-trend--down {
    color: var(--sa-danger);
}

.sa-kpi .sa-trend--down {
    background: color-mix(in srgb, var(--sa-danger) 16%, transparent);
}

.sa-trend--flat,
.sa-trend--none {
    color: var(--sa-text-muted);
}

.sa-kpi .sa-trend--flat,
.sa-kpi .sa-trend--none {
    background: var(--sa-surface-muted);
}

.sa-trend--new {
    color: var(--sa-primary);
}

.sa-kpi .sa-trend--new {
    background: color-mix(in srgb, var(--sa-primary) 16%, transparent);
}

.sa-top-pages {
    max-height: 520px;
    overflow-y: auto;
}

.sa-top-pages__table {
    margin-bottom: 0;
}

.sa-top-pages__table > thead > tr > th {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--sa-surface);
    border-bottom: 1px solid var(--sa-border);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--sa-text-muted);
}

.sa-top-pages__rank {
    width: 44px;
    color: var(--sa-text-muted);
    font-variant-numeric: tabular-nums;
}

.sa-top-pages__path {
    max-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sa-top-pages__num {
    width: 100px;
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.sa-top-pages__share {
    width: 170px;
    white-space: nowrap;
}

.sa-bar {
    /* Flex et non bloc : la barre de portée du dashboard push empile deux segments
       côte à côte (ceux qui ont coché l'univers, puis ceux qui suivent « Tout »). */
    display: inline-flex;
    width: 90px;
    height: 6px;
    margin-right: 8px;
    vertical-align: middle;
    border-radius: 3px;
    background: var(--sa-surface-muted);
    border: 1px solid var(--sa-border);
    overflow: hidden;
}

.sa-bar__fill {
    display: block;
    flex: 0 0 auto;
    height: 100%;
    background: var(--sa-series-visitors);
}

/* Second segment d'une barre empilée : présent, mais visiblement subordonné au premier
   — c'est le premier qui distingue une ligne de la suivante. */
.sa-bar__fill--soft {
    background: color-mix(in srgb, var(--sa-series-visitors) 38%, transparent);
}

.sa-bar__value {
    font-variant-numeric: tabular-nums;
}

.sa-trend-cell {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    white-space: nowrap;
}

.sa-trend-cell__views {
    font-variant-numeric: tabular-nums;
    color: var(--sa-text);
}

/* Encart d'audience, en tête du groupe « Informations » de l'écran d'édition.

   Il vit dans une colonne de quatre, au-dessus du reste des informations : Sonata rend un
   groupe par colonne, et les quatre groupes de cet écran pavent déjà exactement deux
   rangées de douze (cf. `App\Form\Type\AudiencePanelType`). Tout s'y mesure donc en
   hauteur  une ligne de plus ici repousse d'autant les champs qui suivent, d'où le total
   sur la ligne du titre et non sous la liste.

   Pas de carte dans la carte : un simple filet le sépare des champs, là où un second
   encadré aurait doublé les bordures et mangé la largeur.

   **Une seule cellule est élastique, la langue.** Les deux autres ont une largeur
   plancher, si bien que les chiffres et les courbes s'alignent d'une rangée à l'autre :
   c'est ce qui permet de comparer les éditions d'un coup d'œil plutôt que de lire ligne à
   ligne. Un nom de langue trop long est coupé par une ellipse ; il reste entier à
   l'oreille, l'élément portant son texte complet. */
.sa-audience {
    margin: 0 0 12px;
    padding-bottom: 12px;
    border-bottom: 1px solid var(--sa-border);
}

.sa-audience__title {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 2px 8px;
    margin: 0 0 8px;
    font-size: 12px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--sa-text-muted);
}

/* La fenêtre se lit à côté du titre et non dans chaque ligne : elle vaut pour tout
   l'encart, et se choisit ailleurs  sur le tableau de bord. */
.sa-audience__window {
    font-weight: 400;
    text-transform: none;
    letter-spacing: 0;
}

/* Poussé à droite de la ligne de titre, et replié dessous quand la colonne est trop
   étroite pour les trois. */
.sa-audience__total {
    margin-left: auto;
    font-weight: 700;
    text-transform: none;
    letter-spacing: 0;
    font-variant-numeric: tabular-nums;
    color: var(--sa-text);
}

.sa-audience__editions {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.sa-audience__edition {
    display: flex;
    align-items: center;
    gap: 8px;
}

.sa-audience__language {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 600;
    color: var(--sa-text);
}

.sa-audience__views {
    flex: 0 0 auto;
    min-width: 56px;
    text-align: right;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
    color: var(--sa-text);
}

/* Une édition qu'Umami ne connaît pas, ce qui n'est pas une édition à zéro vue : le gris
   dit « pas de données » là où un zéro dirait « personne n'est venu ». */
.sa-audience__views--none {
    color: var(--sa-text-muted);
}

.sa-audience__trend {
    flex: 0 0 auto;
    min-width: 92px;
    display: inline-flex;
    align-items: center;
    justify-content: flex-end;
    gap: 6px;
    white-space: nowrap;
}

/* Ligne d'appoint d'un chiffre de tête : une part, un dénominateur. Reste sous la
   valeur et ne doit jamais lui disputer l'oeil. */
.sa-kpi__note {
    display: block;
    font-size: 12px;
    color: var(--sa-text-muted);
    font-variant-numeric: tabular-nums;
}

/* Teinte d'alerte sur la valeur elle-même. Doublée par le libellé du KPI, jamais
   porteuse seule de l'information. */
.sa-kpi__value--warn {
    color: var(--sa-warning);
}

/* ==========================================================================
   Tableau de bord push
   ========================================================================== */

.sa-push-subtitle {
    margin: 26px 0 10px;
    font-size: 13px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--sa-text-muted);
}

.sa-push-audience {
    margin-bottom: 6px;
}

.sa-push-audience__reach {
    white-space: nowrap;
}

.sa-push-audience__reach > strong {
    display: inline-block;
    min-width: 3.5em;
    font-variant-numeric: tabular-nums;
}

.sa-push-audience__reach .sa-bar {
    margin-right: 0;
}

/* La ligne « Tout » n'est pas une catégorie de plus : elle explique les précédentes. */
.sa-push-audience__all > * {
    color: var(--sa-text-muted);
}

.sa-push-rate {
    width: 170px;
    white-space: nowrap;
}

/* ==========================================================================
   Éditeur de contenu (EditorJS)
   ========================================================================== */

/* Une URL de partage arrive avec sa traîne de paramètres de suivi : ni espace,
   ni tiret, ni slash exploitable — un seul « mot » de 300 caractères. Aucun
   point de coupure n'étant autorisé par défaut, le bloc déborde de l'éditeur et
   traverse la page.
   `anywhere` et non `break-word` : ce dernier n'agit qu'en dernier recours et,
   surtout, n'est pas pris en compte dans le calcul de la largeur minimale du
   conteneur — le mot élargit quand même la colonne avant d'être coupé. */
.editorjs-holder {
    overflow-wrap: anywhere;
}

/* Poignée de bloc portant l'icône de son type (assets/editorjs/block-badge.js). Les
   icônes viennent des `toolbox` des outils, dessinées de 16 à 24 px selon le paquet
   d'origine : sans normalisation, la poignée changerait de taille d'un bloc à l'autre.
   La règle ne vise que l'état porteur d'icône — les six points d'origine gardent la
   leur. */
.ce-toolbar__settings-btn.sa-block-badge svg {
    width: 20px;
    height: 20px;
}

/* Surligneur de l'outil inline « Surligner » (assets/editorjs/highlight-inline-tool.js).
   Repris du front (`.prose mark`, front/assets/styles/components/_article.scss) : sans
   cette règle le navigateur peindrait son jaune par défaut, et le rédacteur choisirait
   un surlignage sur un aperçu qui ne ressemble pas à la page publiée. */
/* ---------- Correcteur orthographique et grammatical (docs/PROOFREADING.md) ---------- */

/* Les fautes sont peintes par l'API CSS Custom Highlight, donc sans qu'aucun nœud ne soit
   ajouté dans un bloc : le HTML d'un bloc EditorJS *est* la donnée enregistrée, un <span>
   de soulignement finirait dans le JSON. Le nom du surlignage est littéral, ce qui oblige
   les deux éditeurs d'un même formulaire à partager ces deux registres
   (assets/editorjs/proofreader.js).

   **La vaguelette seule, sans fond.** C'est la convention de tout correcteur, et un lavis
   de couleur sous chaque faute rend un paragraphe illisible dès qu'il en compte trois. Le
   fond avait été posé en repli, `text-decoration` n'étant pas honoré dans un
   `::highlight()` par tous les moteurs ni depuis longtemps — vérifié inutile ici, les
   navigateurs du back-office le rendent. Le revers assumé : un moteur qui l'ignorerait
   n'afficherait rien du tout, `background-image` étant lui aussi ignoré dans un surlignage.

   Contrastes mesurés contre les surfaces réelles (#ffffff, #1a1d2c) : rouge 4,83:1 en
   clair et 6,04:1 en sombre, indigo 6,29:1 et 5,61:1. Tous au-dessus du plancher de 3:1
   des indicateurs non textuels. Ne pas changer ces valeurs sans les revalider. */
:root {
    --sa-proofread-spelling: #dc2626;
    --sa-proofread-grammar: #4f46e5;
}

html[data-theme="dark"] {
    --sa-proofread-spelling: #f87171;
    --sa-proofread-grammar: #818cf8;
}

::highlight(proofread-spelling) {
    text-decoration: underline wavy var(--sa-proofread-spelling);
}

::highlight(proofread-grammar) {
    text-decoration: underline wavy var(--sa-proofread-grammar);
}

/* Le menu de correction. Positionné en absolu sur le document — et non dans le bloc — pour
   ne pas dépendre de l'`overflow` des conteneurs d'EditorJS. */
.proofread-popover {
    position: absolute;
    z-index: 1050;
    max-width: 320px;
    padding: 10px;
    display: flex;
    flex-direction: column;
    gap: 4px;
    background: var(--sa-surface);
    border: 1px solid var(--sa-border-strong);
    border-radius: 6px;
    box-shadow: var(--sa-shadow-hover);
    color: var(--sa-text);
    font-size: 13px;
}

.proofread-popover__message {
    margin: 0 0 4px;
    color: var(--sa-text-muted);
}

.proofread-popover__none {
    margin: 0;
    font-style: italic;
    color: var(--sa-text-muted);
}

.proofread-popover__fix,
.proofread-popover__close {
    padding: 4px 8px;
    text-align: left;
    background: var(--sa-surface-muted);
    border: 1px solid var(--sa-border);
    border-radius: 4px;
    color: inherit;
}

.proofread-popover__fix {
    font-weight: 600;
}

.proofread-popover__close {
    margin-top: 4px;
    font-weight: 400;
    color: var(--sa-text-muted);
}

.proofread-popover__fix:hover,
.proofread-popover__close:hover {
    border-color: var(--sa-border-strong);
    background: var(--sa-primary-soft);
}

.proofread-popover__fix:focus-visible,
.proofread-popover__close:focus-visible {
    outline: 2px solid var(--sa-primary);
    outline-offset: 2px;
}

.editorjs-holder mark {
    background: none;
    color: inherit;
    padding: 0 5px;
    background-image: linear-gradient(transparent 52%, rgba(251, 97, 2, .45) 52%, rgba(251, 97, 2, .45) 134%, transparent 134%);
    -webkit-box-decoration-break: clone;
    box-decoration-break: clone;
}

/* ==========================================================================
   Vue d'ensemble SEO (issue #58)
   ========================================================================== */

/* Une distribution se lit d'abord comme une forme : la barre donne la proportion
   d'un coup d'œil, la légende porte les nombres exacts. La barre est donc
   `aria-hidden` dans le gabarit — elle ne dit rien que la légende ne dise. */
.sa-seo-dist {
    margin-bottom: 22px;
}

.sa-seo-dist__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 8px;
}

.sa-seo-dist__name {
    font-weight: 600;
    color: var(--sa-text);
}

.sa-seo-dist__total {
    font-size: var(--sa-font-ui);
    color: var(--sa-text-muted);
    font-variant-numeric: tabular-nums;
}

.sa-seo-dist__bar {
    display: flex;
    height: 12px;
    border-radius: 999px;
    overflow: hidden;
    background: var(--sa-surface-muted);
    border: 1px solid var(--sa-border);
}

.sa-seo-dist__seg {
    display: block;
    min-width: 0;
}

.sa-seo-dist__seg--good {
    background: var(--sa-success);
}

.sa-seo-dist__seg--ok {
    background: var(--sa-warning);
}

.sa-seo-dist__seg--bad {
    background: var(--sa-danger);
}

.sa-seo-dist__seg--unscored {
    background: var(--sa-border-strong);
}

.sa-seo-dist__legend {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 20px;
    margin: 10px 0 0;
    padding: 0;
    list-style: none;
    font-size: var(--sa-font-ui);
    color: var(--sa-text-muted);
}

.sa-seo-dist__legend li {
    display: flex;
    align-items: center;
    gap: 7px;
}

.sa-seo-dist__legend b {
    color: var(--sa-text);
    font-variant-numeric: tabular-nums;
}

.sa-seo-dot {
    width: 10px;
    height: 10px;
    border-radius: 3px;
    flex: 0 0 auto;
}

.sa-seo-dot--good {
    background: var(--sa-success);
}

.sa-seo-dot--ok {
    background: var(--sa-warning);
}

.sa-seo-dot--bad {
    background: var(--sa-danger);
}

.sa-seo-dot--unscored {
    background: var(--sa-border-strong);
}

/* Les manques : trois compteurs. Un compteur à zéro n'est pas une alerte, il ne
   doit donc pas en avoir la couleur — d'où la variante `--flagged`. */
.sa-seo-gaps {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin: 0 0 18px;
}

.sa-seo-gap {
    flex: 1 1 190px;
    padding: 14px 16px;
    border: 1px solid var(--sa-border);
    border-radius: 10px;
    background: var(--sa-surface-muted);
}

.sa-seo-gap--flagged {
    border-color: var(--sa-warning);
    background: var(--sa-primary-soft);
}

.sa-seo-gap__value {
    display: block;
    font-size: 32px;
    font-weight: 700;
    line-height: 1.05;
    color: var(--sa-text);
    font-variant-numeric: tabular-nums;
}

/* La carte signalée porte déjà sa teinte au filet et au fond ; le chiffre garde l'encre
   ordinaire. Mesuré, `--sa-warning` sur `--sa-primary-soft` tombe à 2,88:1 en clair —
   sous les 3:1 qu'un texte de cette taille exige — quand `--sa-text` tient 14:1. */
.sa-seo-gap--flagged .sa-seo-gap__value {
    color: var(--sa-text);
}

.sa-seo-gap__label {
    display: block;
    margin-top: 6px;
    font-size: var(--sa-font-ui);
    color: var(--sa-text-muted);
}

.sa-seo-section-title {
    margin: 26px 0 10px;
    font-size: 13px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--sa-text-muted);
}

/* Un tableau de chiffres se compare colonne par colonne : chasse fixe, sinon les
   unités ne s'alignent pas d'une ligne à l'autre. */
.sa-seo-table {
    font-size: var(--sa-font-ui);
}

.sa-seo-table td,
.sa-seo-table th {
    vertical-align: middle;
}

.sa-seo-table .sa-seo-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.sa-seo-table .sa-seo-num--zero {
    color: var(--sa-text-muted);
}

/* La graisse porte l'alerte, pas la teinte : `--sa-danger` sur la surface de carte tient
   4,83:1 en clair mais retombe à 3,46:1 en sombre, les deux tokens d'accent n'étant pas
   redéclinés par thème. Un chiffre de 16px en demande 4,5 des deux côtés. */
.sa-seo-table .sa-seo-num--bad {
    color: var(--sa-text);
    font-weight: 600;
}

/* Pastille de score : lue seule, sans le libellé de la colonne, elle doit porter
   son propre verdict — la couleur ne suffit pas (cf. CLAUDE.md « Accessibilité »),
   le nombre reste écrit. */
.sa-seo-score {
    display: inline-block;
    min-width: 44px;
    padding: 3px 8px;
    border-radius: 6px;
    font-size: var(--sa-font-ui);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    text-align: center;
    border: 1px solid transparent;
}

/* La teinte porte le registre, le chiffre garde l'encre ordinaire. Mesuré sur le fond
   teinté correspondant : `--sa-success` plafonne à 2,88:1 en clair, `--sa-warning` à
   2,80:1, `--sa-danger` à 3,21:1 en sombre — un texte de 16px en demande 4,5. Avec
   `--sa-text` les trois passent à plus de 11:1 dans les deux thèmes. Rien n'est perdu :
   la pastille affiche toujours sa note, la couleur ne dit rien que le chiffre ne dise. */
.sa-seo-score--good {
    background: rgba(22, 163, 74, 0.12);
    border-color: rgba(22, 163, 74, 0.35);
    color: var(--sa-text);
}

.sa-seo-score--ok {
    background: rgba(217, 119, 6, 0.12);
    border-color: rgba(217, 119, 6, 0.35);
    color: var(--sa-text);
}

.sa-seo-score--bad {
    background: rgba(220, 38, 38, 0.12);
    border-color: rgba(220, 38, 38, 0.35);
    color: var(--sa-text);
}

.sa-seo-score--none {
    color: var(--sa-text-muted);
    min-width: 0;
    border: 0;
    padding: 0;
}

/* Écart de score sur la fenêtre. Même parti que la pastille de score : le signe et le
   nombre portent le verdict, la teinte ne fait que le redire — un daltonien lit « +26 »
   aussi bien qu'un autre. D'où l'encre ordinaire sur fond teinté, et non l'inverse. */
.sa-seo-delta {
    display: inline-block;
    min-width: 44px;
    padding: 3px 8px;
    border-radius: 6px;
    font-size: var(--sa-font-ui);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    text-align: center;
    border: 1px solid transparent;
    color: var(--sa-text);
}

.sa-seo-delta--up {
    background: rgba(22, 163, 74, 0.12);
    border-color: rgba(22, 163, 74, 0.35);
}

.sa-seo-delta--down {
    background: rgba(220, 38, 38, 0.12);
    border-color: rgba(220, 38, 38, 0.35);
}

/* Zéro n'est ni bon ni mauvais : le contenu a été rouvert sans que son score bouge. Le
   cadre neutre le distingue du tiret, qui veut dire « rien à comparer ». */
.sa-seo-delta--flat {
    background: var(--sa-surface-muted);
    border-color: var(--sa-border-strong);
    color: var(--sa-text-muted);
}

.sa-seo-delta--none {
    color: var(--sa-text-muted);
    min-width: 0;
    border: 0;
    padding: 0;
}

.sa-seo-issue {
    display: inline-block;
    margin: 2px 4px 2px 0;
    padding: 2px 8px;
    border-radius: 999px;
    border: 1px solid var(--sa-border-strong);
    background: var(--sa-surface-muted);
    color: var(--sa-text-muted);
    font-size: calc(var(--sa-font-ui) - 2px);
    white-space: nowrap;
}

.sa-seo-type {
    color: var(--sa-text-muted);
    font-size: calc(var(--sa-font-ui) - 2px);
}

.sa-seo-keyphrase {
    font-weight: 600;
    color: var(--sa-text);
}

/* Le renvoi sous chaque bloc : une explication, pas une donnée. */
.sa-seo-note {
    margin: 12px 0 0;
    color: var(--sa-text-muted);
    font-size: var(--sa-font-ui);
    max-width: 78ch;
}

.sa-seo-empty {
    margin: 0;
    padding: 6px 0;
    color: var(--sa-text-muted);
}

/* Un constat jamais pris n'est pas un constat rassurant : l'état « jamais vérifié »
   se distingue du vide ordinaire. Marqué par un filet et un fond teinté, pas par la
   couleur du texte : l'ambre sur blanc plafonne à 3:1, sous les 4,5:1 exigés d'un
   texte — la couleur signale, le mot informe. */
.sa-seo-empty--warning {
    padding: 10px 12px;
    border-left: 3px solid var(--sa-warning);
    background: rgba(217, 119, 6, 0.08);
    color: var(--sa-text);
}

.sa-seo-scan {
    margin: 0 0 12px;
    color: var(--sa-text-muted);
    font-size: var(--sa-font-ui);
}

/* Le fond teinté porte le registre, le texte reste à la couleur ordinaire : mesuré,
   `--sa-warning` sur ce fond plafonne à 2,8:1 en clair et pire en sombre, quand un
   texte de 14px en demande 4,5. La teinte signale, le mot informe. */
.sa-seo-scan__flag {
    display: inline-block;
    margin-left: 6px;
    padding: 1px 8px;
    border-radius: 999px;
    border: 1px solid rgba(217, 119, 6, 0.35);
    background: rgba(217, 119, 6, 0.12);
    color: var(--sa-text);
    font-size: calc(var(--sa-font-ui) - 2px);
}

.sa-seo-anchor {
    display: block;
    color: var(--sa-text);
}

/* Une adresse morte est une donnée à lire, pas une cible à cliquer : elle reste en
   texte, et se casse n'importe où plutôt que d'élargir la table. */
.sa-seo-url {
    display: block;
    max-width: 46ch;
    padding: 0;
    background: none;
    color: var(--sa-text-muted);
    font-size: calc(var(--sa-font-ui) - 2px);
    overflow-wrap: anywhere;
}

.sa-seo-verdict {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 999px;
    border: 1px solid var(--sa-border-strong);
    background: var(--sa-surface-muted);
    color: var(--sa-text);
    font-size: calc(var(--sa-font-ui) - 2px);
    white-space: nowrap;
}

/* Interne contre externe, pas bon contre mauvais : les deux sont cassés, mais l'un se
   répare dans l'admin et l'autre dépend d'un tiers. La teinte double le mot du verdict,
   elle ne le remplace pas — d'où un texte qui garde `--sa-text` dans les deux cas. */
.sa-seo-verdict--internal {
    border-color: rgba(220, 38, 38, 0.35);
    background: rgba(220, 38, 38, 0.12);
}

.sa-seo-verdict--external {
    border-color: rgba(217, 119, 6, 0.35);
    background: rgba(217, 119, 6, 0.12);
}

.sa-seo-verdict__detail {
    display: block;
    margin-top: 2px;
    white-space: normal;
}

/* ==========================================================================
   Recadrage d'un visuel (médiathèque)
   ========================================================================== */

/* Enveloppe exactement l'aperçu : le cadre de sélection est positionné en
   pourcentages de cette boîte, ce qui le fait suivre le redimensionnement de
   l'image sans qu'aucun code n'écoute `resize`. */
.sa-crop-stage {
    position: relative;
    display: inline-block;
    max-width: 100%;
    overflow: hidden;
    line-height: 0;
    /* Un glissé au doigt doit tracer, pas faire défiler la page. */
    touch-action: none;
    cursor: crosshair;
    border: 1px solid var(--sa-border);
    border-radius: 6px;
}

.sa-crop-image {
    display: block;
    max-width: 100%;
    height: auto;
    /* Le tracé part souvent de l'image elle-même : sans ça, le navigateur
       démarre son propre glisser-déposer d'image. */
    -webkit-user-drag: none;
    user-select: none;
}

/* L'ombre démesurée assombrit tout ce qui est hors du cadre, que `overflow: hidden`
   sur l'étape se charge de rogner. Le cadre reçoit les évènements de pointeur, lui :
   c'est ce qui le rend déplaçable. Son ombre, purement visuelle, ne participe pas au
   test de survol — un clic hors du cadre atteint donc bien l'étape. */
.sa-crop-box {
    position: absolute;
    border: 2px dashed var(--sa-accent);
    box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.45);
    cursor: move;
}

/* Poignées de redimensionnement, centrées sur les coins et les milieux de côté.
   Fond clair cerné d'accent : elles doivent se voir sur n'importe quelle photo. */
.sa-crop-handle {
    position: absolute;
    width: 12px;
    height: 12px;
    background: var(--sa-surface);
    border: 2px solid var(--sa-accent);
    border-radius: 2px;
}

.sa-crop-handle--nw, .sa-crop-handle--n, .sa-crop-handle--ne { top: -7px; }
.sa-crop-handle--sw, .sa-crop-handle--s, .sa-crop-handle--se { bottom: -7px; }
.sa-crop-handle--nw, .sa-crop-handle--w, .sa-crop-handle--sw { left: -7px; }
.sa-crop-handle--ne, .sa-crop-handle--e, .sa-crop-handle--se { right: -7px; }
.sa-crop-handle--n, .sa-crop-handle--s { left: 50%; margin-left: -6px; }
.sa-crop-handle--w, .sa-crop-handle--e { top: 50%; margin-top: -6px; }

.sa-crop-handle--nw, .sa-crop-handle--se { cursor: nwse-resize; }
.sa-crop-handle--ne, .sa-crop-handle--sw { cursor: nesw-resize; }
.sa-crop-handle--n, .sa-crop-handle--s { cursor: ns-resize; }
.sa-crop-handle--w, .sa-crop-handle--e { cursor: ew-resize; }

.sa-crop-fields {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 0 1rem;
    margin-top: 1rem;
    max-width: 720px;
}

.sa-crop-note {
    margin-top: 0.25rem;
}

.sa-crop-alert {
    margin-top: 0.75rem;
}

.sa-crop-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 1rem;
}

/* ==========================================================================
   Point focal d'un visuel (médiathèque)
   ========================================================================== */

/* Les deux coordonnées vivent en variables sur le formulaire : le marqueur et les
   deux aperçus s'y accrochent, si bien qu'un clic n'a qu'une seule valeur à écrire
   pour que les trois suivent. */
.sa-focal-form {
    --sa-focal-x: 50%;
    --sa-focal-y: 50%;
}

/* Même boîte que l'étape de recadrage, à ceci près qu'on y pointe au lieu d'y
   tracer : le marqueur se positionne en pourcentages de l'aperçu, il suit donc son
   redimensionnement sans que rien n'écoute `resize`. */
.sa-focal-stage {
    position: relative;
    display: inline-block;
    max-width: 100%;
    line-height: 0;
    touch-action: none;
    cursor: crosshair;
    border: 1px solid var(--sa-border);
    border-radius: 6px;
    overflow: hidden;
}

.sa-focal-image {
    display: block;
    max-width: 100%;
    height: auto;
    -webkit-user-drag: none;
    user-select: none;
}

/* Anneau clair cerné de sombre : le marqueur doit rester lisible aussi bien sur un
   ciel blanc que sur une ombre. Sans évènement de pointeur, pour qu'un clic posé
   dessus atteigne l'étape et déplace le point au lieu d'être avalé. */
.sa-focal-marker {
    position: absolute;
    left: var(--sa-focal-x);
    top: var(--sa-focal-y);
    width: 22px;
    height: 22px;
    margin: -11px 0 0 -11px;
    border: 2px solid #fff;
    border-radius: 50%;
    box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.55), inset 0 0 0 2px rgba(0, 0, 0, 0.55);
    pointer-events: none;
}

.sa-focal-marker::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    width: 4px;
    height: 4px;
    margin: -2px 0 0 -2px;
    background: #fff;
    border-radius: 50%;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.55);
}

/* Deux ratios et pas un seul : c'est la démonstration que le réglage vaut pour tous
   les formats d'affichage, et le carré est celui qui coupe le plus. */
.sa-focal-previews {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin-top: 1rem;
}

.sa-focal-preview {
    flex: none;
    overflow: hidden;
    border: 1px solid var(--sa-border);
    border-radius: 6px;
}

.sa-focal-preview--wide {
    width: 240px;
    aspect-ratio: 16 / 9;
}

.sa-focal-preview--square {
    width: 135px;
    aspect-ratio: 1;
}

/* Le même `object-fit: cover` que le front, piloté par les mêmes pourcentages : ce
   que montre l'aperçu est ce que le site affichera. */
.sa-focal-preview__img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: var(--sa-focal-x) var(--sa-focal-y);
}

.sa-focal-fields {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 0 1rem;
    margin-top: 1rem;
    max-width: 480px;
}

.sa-focal-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 1rem;
}

/* --- Fenêtre modale ------------------------------------------------------- */

/* `<dialog>` natif : le fond assombri, le piège de focus et la fermeture par Échap
   viennent du navigateur, et la couche supérieure met la fenêtre hors de portée des
   `overflow` et des contextes d'empilement de ses ancêtres. */
.sa-media-dialog {
    width: min(900px, 92vw);
    max-height: 90vh;
    padding: 0;
    overflow: hidden;
    border: 1px solid var(--sa-border);
    border-radius: 8px;
    background: var(--sa-surface);
    color: var(--sa-text);
    box-shadow: var(--sa-shadow-hover);
}

.sa-media-dialog::backdrop {
    background: rgba(9, 11, 20, 0.6);
}

.sa-media-dialog__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.75rem 1rem;
    border-bottom: 1px solid var(--sa-border);
    background: var(--sa-surface-muted);
}

.sa-media-dialog__title {
    margin: 0;
    font-size: 1.1rem;
}

.sa-media-dialog__close {
    padding: 0 0.35rem;
    border: none;
    background: none;
    color: var(--sa-text-muted);
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
}

.sa-media-dialog__close:hover {
    color: var(--sa-text);
}

/* Le corps défile, pas la fenêtre : l'en-tête et son bouton de fermeture restent
   atteignables sur une image haute. */
.sa-media-dialog__body {
    padding: 1rem;
    max-height: calc(90vh - 3.5rem);
    overflow-y: auto;
}

/* Propriétés, point focal, recadrage : trois enregistrements distincts, que le filet
   sépare pour qu'on ne prenne pas le bouton de l'un pour celui de l'autre. */
.sa-media-edit__section + .sa-media-edit__section {
    margin-top: 1.5rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--sa-border);
}

.sa-media-edit__title {
    margin: 0 0 0.5rem;
    font-size: 1.05rem;
}

.sa-media-edit__alert {
    margin-top: 1rem;
}

/* Recadrage depuis le sélecteur d'image de mise en avant : pastille posée au coin de
   la vignette, symétrique du bouton de retrait rouge de l'autre côté.

   Fond emprunté à l'encre de la barre latérale, seule surface du thème qui reste
   sombre en clair comme en sombre (#171a2b / #0d0f1a) : le blanc y tient largement
   les 4,5:1 dans les deux, ce que l'orange d'accent ne fait pas (2,9:1). */
.media-picker-crop {
    position: absolute;
    top: -8px;
    left: -8px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: var(--sa-sidebar-bg);
    color: #fff;
    font-size: 11px;
    text-decoration: none;
}

.media-picker-crop:hover,
.media-picker-crop:focus {
    color: #fff;
    background: var(--sa-sidebar-bg-alt);
    text-decoration: none;
}

/* Un vrai anneau en `outline`, pas un `box-shadow` : la pastille est ronde et sombre,
   le contour par défaut du navigateur s'y perd. */
.media-picker-crop:focus-visible {
    outline: 2px solid var(--sa-primary);
    outline-offset: 2px;
}

/* ==========================================================================
   Mosaïque de la médiathèque et popup de choix d'un visuel (issue #24)
   ========================================================================== */

/* Grille auto-remplie plutôt que les quatre colonnes flottantes d'AdminLTE : la popup
   est large, quatre vignettes par ligne y occupaient chacune un quart de l'écran. Le
   nombre de colonnes suit la place disponible, il n'a pas à être décidé ici. */
.sa-media-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 0.75rem;
    padding: 0.25rem;
}

.sa-media-grid__tile {
    position: relative;
}

.sa-media-grid__link {
    position: relative;
    display: block;
    overflow: hidden;
    border: 1px solid var(--sa-border);
    border-radius: 6px;
    background: var(--sa-surface-muted);
}

/* Le carré. `object-fit: cover` recadre à l'affichage : la vignette « admin » de
   Sonata reste ce qu'elle est sur le stockage (cf. le gabarit de la mosaïque). */
.sa-media-grid__img {
    display: block;
    width: 100%;
    aspect-ratio: 1;
    object-fit: cover;
}

/* Le nom du fichier, l'unique endroit où il s'affiche encore : au survol, par-dessus
   l'image, et jamais au repos. */
.sa-media-grid__hover {
    position: absolute;
    inset: auto 0 0 0;
    padding: 0.35rem 0.5rem;
    background: rgba(0, 0, 0, 0.72);
    color: #fff;
    font-size: 0.75rem;
    line-height: 1.3;
    opacity: 0;
    transition: opacity 0.15s;
}

.sa-media-grid__link:hover .sa-media-grid__hover,
.sa-media-grid__link:focus-visible .sa-media-grid__hover {
    opacity: 1;
}

/* Le seul texte permanent de la tuile : la fiche d'où vient le visuel. Une ligne, jamais
   deux — la grille resterait alignée mais les tuiles n'auraient plus la même hauteur. */
.sa-media-grid__fiche {
    display: block;
    margin-top: 0.3rem;
    overflow: hidden;
    color: var(--sa-text-muted);
    font-size: 0.75rem;
    line-height: 1.3;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Posée sur l'image et non sous elle : sous la vignette, elle repoussait la légende et
   ajoutait une bande vide aux écrans qui n'ont pas d'action de masse. */
.sa-media-grid__check {
    position: absolute;
    top: 6px;
    left: 6px;
    z-index: 2;
    margin: 0;
    width: 16px;
    height: 16px;
    accent-color: var(--sa-primary);
    cursor: pointer;
}

.sa-media-grid__link:focus-visible {
    outline: 2px solid var(--sa-primary);
    outline-offset: 2px;
}

/* Filtre par fiche de la popup : des liens, mais lus comme des pastilles. La barre les
   pose à gauche, en face du bouton d'envoi. */
.sa-media-scope {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    padding: 8px 0;
}

.sa-media-scope__link {
    padding: 0.2rem 0.65rem;
    border: 1px solid var(--sa-border-strong);
    border-radius: 999px;
    color: var(--sa-text-muted);
    font-size: 0.8rem;
    line-height: 1.6;
    text-decoration: none;
}

.sa-media-scope__link:hover,
.sa-media-scope__link:focus {
    border-color: var(--sa-primary);
    color: var(--sa-primary);
    text-decoration: none;
}

.sa-media-scope__link.is-active {
    border-color: var(--sa-primary);
    background: var(--sa-primary);
    color: #fff;
}

/* Récapitulatif des erreurs de formulaire, en tête d'écran d'édition : la liste est déjà
   dans une .alert, elle n'a pas besoin de sa marge basse par défaut. */
.sa-form-errors {
    margin: 0.5rem 0 0;
    padding-left: 1.25rem;
}

/* ==========================================================================
   Recherche rapide, en tête d'un listing (cf. templates/admin/CRUD/base_list.html.twig)
   ========================================================================== */

/* La barre de titre de la liste passe en flex quand elle porte une recherche rapide, et
   seulement là : Bootstrap y range les dépliants par `float: right`, et un flottant ne sait
   pas « prendre ce qui reste ». Le seul moyen en flottants serait un conteneur de bloc
   formaté (`overflow: hidden`), qui rognerait la liste des propositions. Le flux flex ignore
   les `float`, l'ordre du document suffit alors à ranger la barre. */
.navbar-default:has(.sa-quick-search) .container-fluid,
.navbar-default:has(.sa-quick-search) .navbar-collapse {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
}

.navbar-default:has(.sa-quick-search) .navbar-collapse {
    flex: 1 1 auto;
    min-width: 0;
}

/* Le `margin-right: -15px` que Bootstrap donne aux groupes de droite compensait leur
   flottement dans une barre à gouttières ; il n'a plus rien à compenser. */
.navbar-default:has(.sa-quick-search) .navbar-right {
    float: none;
    margin-right: 0;
}

.sa-quick-search {
    position: relative;
    /* Le champ prend ce que les dépliants laissent : ce qui compte ici est la place offerte
       aux titres proposés en dessous, pas la taille du champ lui-même. `min-width` pour que
       la barre se replie proprement plutôt que de le réduire à sa loupe. */
    flex: 1 1 auto;
    min-width: 14rem;
    margin: 0;
}

.sa-quick-search__icon {
    position: absolute;
    top: 50%;
    left: 11px;
    transform: translateY(-50%);
    color: var(--sa-text-muted);
    /* La loupe est un décor : le clic doit tomber dans le champ qu'elle recouvre. */
    pointer-events: none;
}

.sa-quick-search__input {
    padding-left: 32px;
}

.sa-quick-search__results {
    position: absolute;
    z-index: 900;
    right: 0;
    left: 0;
    /* Plus large que le champ si la barre l'a comprimé : c'est la liste qui doit être lisible,
       pas le champ qui doit être large. Elle déborde alors vers la droite, sous les
       dépliants. */
    min-width: min(34rem, 80vw);
    max-height: 60vh;
    margin: 4px 0 0;
    padding: 4px;
    overflow-y: auto;
    list-style: none;
    border: 1px solid var(--sa-border);
    border-radius: 8px;
    background: var(--sa-surface);
    box-shadow: var(--sa-shadow-hover);
}

.sa-quick-search__link {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    padding: 7px 10px;
    border-radius: 6px;
    color: var(--sa-text);
    text-decoration: none;
}

.sa-quick-search__link:hover,
.sa-quick-search__link:focus,
.sa-quick-search__link:focus-visible {
    color: var(--sa-text);
    background: var(--sa-surface-muted);
    text-decoration: none;
}

/* Le survol et le focus se ressemblent, et c'est voulu : les flèches parcourent la liste
   comme la souris. L'anneau les distingue pour qui navigue au clavier. */
.sa-quick-search__link:focus-visible {
    outline: 2px solid var(--sa-accent);
    outline-offset: -2px;
}

.sa-quick-search__title {
    /* `min-width: 0` sans quoi un titre long pousserait la pastille hors du lien au lieu de
       se laisser tronquer : un élément flex ne descend pas sous sa taille de contenu. */
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sa-quick-search__badge {
    flex: none;
    padding: 1px 8px;
    border: 1px solid var(--sa-border-strong);
    border-radius: 999px;
    color: var(--sa-text-muted);
    font-size: 11px;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.sa-quick-search__message {
    padding: 7px 10px;
    color: var(--sa-text-muted);
    font-style: italic;
}

/* Rappel d'usurpation de l'en-tête (issue #314).

   Un aplat plein et non une commande ronde comme ses voisines : il n'est pas là pour se
   fondre dans la barre mais pour ne pas se laisser oublier, et c'est la seule chose qui
   distingue un back-office amputé d'un back-office en panne. La couleur ne dit rien à elle
   seule — l'icône et le nom du compte emprunté portent la même information.

   Le sélecteur reprend la chaîne d'ancêtres d'AdminLTE pour la même raison que
   `.sa-header-btn` : sans elle, `.navbar-custom-menu .nav > li > a` impose son gabarit. */

.skin-black .main-header .navbar .navbar-custom-menu .nav > li > a.sa-impersonation {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin: 8px 6px;
    padding: 0 12px;
    height: 34px;
    border-radius: 999px;
    background: #8a3b12;
    color: #fff;
    font-size: 13px;
}

.skin-black .main-header .navbar .navbar-custom-menu .nav > li > a.sa-impersonation:hover,
.skin-black .main-header .navbar .navbar-custom-menu .nav > li > a.sa-impersonation:focus {
    background: #a8491a;
    color: #fff;
}

.sa-impersonation__exit {
    padding-left: 8px;
    border-left: 1px solid rgba(255, 255, 255, 0.4);
    text-decoration: underline;
}

/* Réactions des lecteurs dans la liste des articles (issue #33). Un zéro s'estompe : la
   colonne se lit d'un coup d'œil pour les articles qui ont fait réagir. */
.sa-reactions-cell {
    display: inline-flex;
    gap: 10px;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.sa-reactions-cell__item {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    color: var(--sa-text);
}

.sa-reactions-cell__item--none {
    color: var(--sa-text-muted);
}
