/* =============================================================
   theme.css — Design sobre et professionnel (inspiré d'Odoo)
   ============================================================= */

/* === BASE === */
*, *::before, *::after {
    box-sizing: border-box;
}

body {
    font-family: var(--font-family);
    font-size: var(--font-size-base);
    background-color: var(--background-color);
    color: var(--text-color);
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    margin: 0;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
}

/* === APP SHELL (sidebar + topbar) ===
   Layout premium : sidebar sombre fixe (collapsible à 64px, off-canvas sous
   1024px) + topbar sticky au-dessus du contenu. Remplace l'ancien header
   plein-largeur unique (voir git history) — .app-shell est le nouveau
   conteneur racine posé directement dans <body> par base.html/base_page.html,
   qui inclut sidebar.html (partial dédié) et header.html (devenu la topbar). */
.app-shell {
    display: flex;
    min-height: 100vh;
    align-items: stretch;
}

.app-main {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 0;
    margin-left: var(--sidebar-width, 256px);
    transition: margin-left var(--transition-fast, 150ms ease);
}

.app-shell.sidebar-collapsed .app-main {
    margin-left: var(--sidebar-width-collapsed, 64px);
}

/* === SIDEBAR === */
.sidebar {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: var(--sidebar-width, 256px);
    background: var(--color-sidebar, #1e293b);
    color: var(--color-sidebar-text, #e2e8f0);
    display: flex;
    flex-direction: column;
    z-index: 1040;
    /* Séparation nette d'avec le contenu quel que soit le thème configuré
       (GlobalSettings.sidebar_background_color peut être clair ou sombre,
       voir templates/partials/head.html) : un simple aplat de couleur sans
       aucune profondeur au bord droit lit "plat", pas "premium". */
    box-shadow: 1px 0 0 rgba(0, 0, 0, .06), 6px 0 24px -12px rgba(0, 0, 0, .25);
    transition: width var(--transition-fast, 150ms ease), transform var(--transition-fast, 150ms ease);
}

.sidebar-header {
    position: relative;
    display: flex;
    align-items: center;
    padding: 0.625rem 0.875rem;
    flex-shrink: 0;
    min-height: var(--topbar-height, 56px);
    /* color-mix sur --color-sidebar-text plutôt qu'un blanc/noir transparent
       codé en dur : reste un liséré discret quelle que soit la couleur de
       fond/texte choisie dans GlobalSettings (texte clair sur fond sombre
       PAR DÉFAUT, mais l'inverse est une configuration valide, voir capture
       d'écran de référence : sidebar blanche/texte vert). */
    border-bottom: 1px solid color-mix(in srgb, var(--color-sidebar-text, #e2e8f0) 12%, transparent);
}

.sidebar-logo {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    text-decoration: none;
    /* BUG CONFIRMÉ (corrigé) : #fff codé en dur ignorait
       GlobalSettings.sidebar_text_color — invisible (blanc sur blanc) dès
       qu'un fond de sidebar clair est configuré (voir la config verte/blanche
       de référence de cette session). Le médaillon .sidebar-logo-icon garde
       son propre dégradé de marque (--gradient-accent), lui volontairement
       fixe quel que soit le thème sidebar. */
    color: var(--color-sidebar-text, #e2e8f0);
    min-width: 0;
}

.sidebar-logo:hover { color: var(--color-sidebar-text, #e2e8f0); text-decoration: none; }

.sidebar-logo-icon {
    width: 28px;
    height: 28px;
    border-radius: var(--radius-sm, 6px);
    background: var(--gradient-accent);
    box-shadow: var(--gradient-shadow);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    font-size: 1rem;
    overflow: hidden;
}

.sidebar-logo-icon img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}

.sidebar-logo-text {
    font-weight: 700;
    font-size: 0.875rem;
    letter-spacing: 0.01em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Ancré au bord droit de la sidebar (à moitié en dehors) plutôt que rangé
   dans le flux du header : à 64px de large replié, il n'y a pas la place
   d'aligner logo + bouton côte à côte, alors qu'un handle flottant sur la
   bordure reste valable quelle que soit la largeur de la sidebar. */
.sidebar-collapse-btn {
    position: absolute;
    top: 1rem;
    right: -12px;
    background: var(--color-sidebar-hover, #334155);
    border: 2px solid var(--color-bg);
    color: #fff;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    z-index: 5;
    transition: background var(--transition-fast, 150ms ease);
}

.sidebar-collapse-btn i { font-size: 0.7rem; }

.sidebar-collapse-btn:hover {
    background: var(--color-accent);
}

.sidebar-nav {
    flex: 1;
    overflow-y: auto;
    padding: 0.375rem 0.5rem 0.75rem;
    scrollbar-width: thin;
    scrollbar-color: color-mix(in srgb, var(--color-sidebar-text, #e2e8f0) 30%, transparent) transparent;
}
/* Scrollbar fine et teintée façon "app" plutôt que la barre système par
   défaut (WebKit/Blink ignorent scrollbar-width/scrollbar-color ci-dessus
   sans ces pseudo-éléments dédiés — jusqu'ici seul .table-responsive en
   bénéficiait, la sidebar affichait la scrollbar OS brute sur Chrome/Edge). */
.sidebar-nav::-webkit-scrollbar { width: 6px; }
.sidebar-nav::-webkit-scrollbar-track { background: transparent; }
.sidebar-nav::-webkit-scrollbar-thumb {
    background-color: color-mix(in srgb, var(--color-sidebar-text, #e2e8f0) 25%, transparent);
    border-radius: var(--radius-pill);
}
.sidebar-nav::-webkit-scrollbar-thumb:hover {
    background-color: color-mix(in srgb, var(--color-sidebar-text, #e2e8f0) 45%, transparent);
}

.sidebar-section { margin-bottom: 0.625rem; }

/* Séparateur discret entre sections (pas sur la première) : rythme la liste
   sans la surcharger de traits, cohérent avec le liséré du header ci-dessus. */
.sidebar-section + .sidebar-section {
    border-top: 1px solid color-mix(in srgb, var(--color-sidebar-text, #e2e8f0) 8%, transparent);
    padding-top: 0.375rem;
}

.sidebar-section-label {
    font-size: 0.625rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--color-sidebar-section-label, rgba(255,255,255,.35));
    padding: 0.375rem 0.5rem 0.25rem;
    white-space: nowrap;
    overflow: hidden;
}

/* Sections repliables (Favoris/Récents/Modules, voir _sidebar_nav.html) :
   le label devient un <button> — layout géré ici, apparence de base héritée
   de .sidebar-section-label (les deux classes cohabitent sur l'élément). */
.sidebar-section-toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.4rem;
    width: 100%;
    background: transparent;
    border: none;
    /* `font: inherit` (raccourci) remettait la TAILLE et la GRAISSE à celles du
       parent, et `text-transform`/`letter-spacing: inherit` annulaient les
       capitales : « Récents » et « Modules » sortaient en 16 px normal à côté
       de « NAVIGATION » en petites capitales. Seule la famille est héritée —
       le reste vient de .sidebar-section-label, comme le dit le commentaire. */
    font-family: inherit;
    color: inherit;
    cursor: pointer;
    border-radius: var(--radius-sm);
    transition: color var(--transition-fast, 150ms ease);
}
.sidebar-section-toggle:hover { color: var(--color-sidebar-text, inherit); }
.sidebar-section-toggle:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 1px; }
.sidebar-section-caret { font-size: 0.6rem; transition: transform .15s ease; flex-shrink: 0; }
.sidebar-section.is-closed .sidebar-section-caret { transform: rotate(-90deg); }
.sidebar-section.is-closed .sidebar-section-body { display: none; }
/* Rail replié (icônes seules) : le bouton de section disparaît (règle
   .sidebar-section-label existante plus bas), donc l'état "replié" mémorisé
   n'est plus atteignable — on force l'affichage des icônes plutôt que de
   figer une section masquée et inaccessible. */
.app-shell.sidebar-collapsed .sidebar-section.is-closed .sidebar-section-body { display: block; }

.sidebar-link {
    position: relative;
    display: flex;
    align-items: center;
    gap: 0.625rem;
    /* Compact sur bureau (~32 px) ; le doigt garde ses 44 px (règle
       `pointer: coarse` en fin de feuille). */
    padding: 0.375rem 0.625rem;
    /* Rayon "medium" (pas -sm) plutôt qu'un simple arrondi de bouton : lu
       comme une pastille pleine au survol/actif façon HubSpot, plus
       accueillant qu'un rectangle à peine adouci. */
    border-radius: var(--radius, 8px);
    color: var(--color-sidebar-text, #e2e8f0);
    text-decoration: none;
    font-size: 0.8125rem;
    font-weight: 500;
    white-space: nowrap;
    transition: background var(--transition-fast, 150ms ease), color var(--transition-fast, 150ms ease), transform var(--transition-fast, 150ms ease);
    margin-bottom: 0.125rem;
}

.sidebar-link i {
    font-size: 1rem;
    /* Sans ça l'icône hérite de la hauteur de ligne du corps (1,6 × 16 px) et
       fixe à elle seule la hauteur du lien : 42 px au lieu de ~32. */
    line-height: 1;
    width: 17px;
    text-align: center;
    flex-shrink: 0;
}

.sidebar-link-label {
    overflow: hidden;
    text-overflow: ellipsis;
}

.sidebar-link:hover {
    background: var(--color-sidebar-hover, #334155);
    color: #fff;
    text-decoration: none;
    transform: translateX(2px);
}

.sidebar-link:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: -2px;
}

/* Ligne active en aplat teinté (pas la pastille pleine sombre d'avant) :
   fond très légèrement accentué + texte qui vire vers l'accent + liseré fin
   sur le bord INTÉRIEUR (celui qui touche le contenu, pas le bord extérieur
   de l'écran) — plus doux et "convivial" qu'un bloc plein, cf. référence
   utilisateur. color-mix sur --color-sidebar/--color-sidebar-text (jamais un
   blanc/noir fixe) : reste correct que la sidebar soit sombre par défaut ou
   personnalisée en clair par l'admin (voir GlobalSettings.sidebar_*). Le
   texte ne vire qu'à moitié vers l'accent (color-mix 50%, pas 100%) pour ne
   jamais perdre le contraste garanti par --color-sidebar-text de base. */
.sidebar-link.active {
    background: color-mix(in srgb, var(--color-accent) 12%, var(--color-sidebar));
    color: color-mix(in srgb, var(--color-accent) 50%, var(--color-sidebar-text));
    font-weight: 600;
}

.sidebar-link.active:hover { transform: none; }

.sidebar-link.active::before {
    content: '';
    position: absolute;
    right: 0;
    top: 3px;
    bottom: 3px;
    width: 3px;
    border-radius: 3px 0 0 3px;
    background: var(--color-accent);
}

/* Indentation par profondeur portée par la RANGÉE (.sidebar-link-row), pas
   par le lien : le lien n'a plus besoin de son propre padding puisque la
   case à cocher/pastille de repli (.sidebar-tree-toggle/-spacer, voir plus
   bas) occupe déjà la colonne d'indentation. */
.sidebar-depth-1 { margin-left: 1rem; }
.sidebar-depth-2 { margin-left: 2rem; }

/* Rangée module + repli d'arbre + étoile favori (Modules, voir
   _sidebar_nav.html — Favoris/Récents restent de simples <a>, pas de repli
   puisque ce sont des listes plates, pas une arborescence). */
.sidebar-link-row {
    display: flex;
    align-items: center;
    gap: 0.125rem;
}
.sidebar-link-row .sidebar-link {
    flex: 1;
    min-width: 0;
}

/* Chevron de repli d'un nœud parent (arbre Modules) : masque/affiche ses
   descendants (JS dans scripts.html, algorithme en un seul passage sur la
   liste à plat en pré-ordre) — voir aussi .sidebar-section-toggle pour le
   même principe au niveau d'une section entière. .sidebar-tree-spacer
   occupe la même largeur pour qu'une feuille sans enfant reste alignée avec
   ses voisines qui ONT un chevron. */
.sidebar-tree-toggle, .sidebar-tree-spacer {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.sidebar-tree-toggle {
    background: transparent;
    border: none;
    padding: 0;
    cursor: pointer;
    color: var(--color-sidebar-text-muted, rgba(255,255,255,.45));
    border-radius: var(--radius-sm);
    transition: color var(--transition-fast), background var(--transition-fast);
}
.sidebar-tree-toggle:hover { color: var(--color-sidebar-text, #fff); background: var(--color-sidebar-hover); }
.sidebar-tree-toggle:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 1px; }
.sidebar-tree-toggle i { font-size: 0.65rem; transition: transform .15s ease; }
.sidebar-tree-toggle[aria-expanded="false"] i { transform: rotate(-90deg); }
.sidebar-row-hidden { display: none; }

/* Icône générique (valeur par défaut Module.icon = "bi-gear-fill", jamais
   personnalisée pour la plupart des modules) : masquée dans l'arbre déplié
   pour ne pas répéter le même glyphe sur chaque ligne sans rien apporter —
   texte + indentation suffisent. Réaffichée uniquement rail replié (icônes
   seules), seul contexte où une ancre visuelle par ligne redevient
   nécessaire ; les icônes personnalisées (m.icon ≠ "bi-gear-fill") restent
   toujours visibles, elles. */
.sidebar-link-icon-generic { display: none; }
.app-shell.sidebar-collapsed .sidebar-link-icon-generic { display: inline-flex; }

/* Étoile favori : visible seulement au survol/focus de la rangée, ou déjà
   activée (une étoile pleine EST une information — quel module est épinglé
   — donc jamais masquée ; seule l'étoile vide "pas encore favori" est un
   contrôle discret plutôt qu'une décoration permanente sur chaque ligne). */
.sidebar-favorite-btn {
    background: transparent;
    border: none;
    color: var(--color-sidebar-text-muted, rgba(255,255,255,.4));
    width: 24px;
    height: 24px;
    border-radius: var(--radius-sm);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    font-size: 0.75rem;
    opacity: 0;
    transition: color var(--transition-fast), background var(--transition-fast), opacity var(--transition-fast);
}
.sidebar-link-row:hover .sidebar-favorite-btn,
.sidebar-favorite-btn:focus-visible,
.sidebar-favorite-btn.is-favorite {
    opacity: 1;
}
.sidebar-favorite-btn:hover { background: var(--color-sidebar-hover); color: #fff; }
.sidebar-favorite-btn.is-favorite { color: #f5c14b; }
.app-shell.sidebar-collapsed .sidebar-favorite-btn { display: none; }

/* Collapsé (desktop) : icônes seules, labels et libellés de section masqués. */
.app-shell.sidebar-collapsed .sidebar {
    width: var(--sidebar-width-collapsed, 64px);
}
.app-shell.sidebar-collapsed .sidebar-logo-text,
.app-shell.sidebar-collapsed .sidebar-section-label,
.app-shell.sidebar-collapsed .sidebar-link-label {
    display: none;
}
.app-shell.sidebar-collapsed .sidebar-depth-1,
.app-shell.sidebar-collapsed .sidebar-depth-2 {
    margin-left: 0;
}
.app-shell.sidebar-collapsed .sidebar-tree-toggle,
.app-shell.sidebar-collapsed .sidebar-tree-spacer {
    display: none;
}
.app-shell.sidebar-collapsed .sidebar-link {
    justify-content: center;
}
.app-shell.sidebar-collapsed .sidebar-collapse-btn i {
    transform: rotate(180deg);
}

/* Info-bulle façon app (pas le title="" natif du navigateur : lent à
   apparaître, style OS non maîtrisé) quand le rail est réduit aux icônes —
   seul contexte où le libellé du lien est invisible. data-tooltip porte le
   même texte que l'ancien title="", voir _sidebar_nav.html.
   BUG CONFIRMÉ (corrigé) : une première version utilisait un ::after
   positionné en absolute sur .sidebar-link — invisible en pratique car
   .sidebar-nav a overflow-y:auto, et la spec CSS force alors overflow-x à
   'auto' également (confirmé via getComputedStyle en Puppeteer : overflowX
   valait "auto" bien que seul overflow-y soit déclaré), donc le conteneur
   scrollable clippait la bulle qui dépasse à droite du rail. Remplacé par un
   unique élément position:fixed positionné en JS (voir #sidebarTooltip dans
   templates/partials/scripts.html) : un position:fixed échappe au clipping
   par overflow d'un ancêtre scrollable, contrairement à absolute. */
.sidebar-tooltip {
    position: fixed;
    transform: translateY(-50%);
    background: #111827;
    color: #fff;
    padding: 0.35rem 0.7rem;
    border-radius: var(--radius-sm);
    font-size: 0.75rem;
    font-weight: 600;
    white-space: nowrap;
    box-shadow: var(--shadow-md);
    z-index: 1060;
    pointer-events: none;
    opacity: 0;
    visibility: hidden;
    transition: opacity .12s ease;
}
.sidebar-tooltip.is-visible { opacity: 1; visibility: visible; }
@media (prefers-reduced-motion: reduce) {
    .sidebar-tooltip { transition: none; }
}

.sidebar-overlay {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, 0.6);
    backdrop-filter: blur(2px);
    z-index: 1035;
}

/* Off-canvas sous 1024px : sidebar masquée hors écran par défaut, ouverte
   via la classe .sidebar-open posée sur .app-shell (bouton hamburger de la
   topbar, voir partials/scripts.html). */
@media (max-width: 1023.98px) {
    .sidebar {
        transform: translateX(-100%);
        width: var(--sidebar-width, 256px) !important;
    }
    .app-main {
        margin-left: 0 !important;
    }
    .app-shell.sidebar-open .sidebar {
        transform: translateX(0);
    }
    .app-shell.sidebar-open .sidebar-overlay {
        display: block;
    }
    .app-shell.sidebar-collapsed:not(.sidebar-open) .sidebar-logo-text,
    .app-shell.sidebar-collapsed:not(.sidebar-open) .sidebar-section-label,
    .app-shell.sidebar-collapsed:not(.sidebar-open) .sidebar-link-label {
        display: block;
    }
    .sidebar-collapse-btn { display: none !important; }
}

/* === TOPBAR === */
.topbar {
    background: var(--color-header, var(--color-surface, #fff));
    /* Repli sur --shadow-sm si l'admin n'a pas encore de valeur (héritage
       config existante) : --header-box-shadow (GlobalSettings.header_box_shadow,
       voir head.html) est désormais réellement appliquée ici — elle ne l'était
       pas avant, la topbar retombait toujours sur --shadow-sm quel que soit le
       réglage admin. */
    box-shadow: var(--header-box-shadow, var(--shadow-sm));
    position: sticky;
    top: 0;
    z-index: 1000;
    /* min-height (pas height) : GlobalSettings.header_padding (--header-padding,
       appliquée ci-dessous sur .topbar-content) peut être plus généreuse que les
       56px par défaut — une hauteur fixe aurait tronqué le contenu dès que
       l'admin agrandit le padding vertical. */
    min-height: var(--topbar-height, 56px);
    display: flex;
    align-items: center;
    flex-shrink: 0;
}

.topbar-content {
    display: flex;
    align-items: center;
    justify-content: space-between;
    /* GlobalSettings.header_padding (--header-padding) — comme --header-box-shadow
       au-dessus, cette variable existait déjà mais n'était consommée nulle part :
       le champ "Padding header" du formulaire de configuration n'avait donc
       aucun effet visible. */
    padding: var(--header-padding, 0 1.5rem);
    box-sizing: border-box;
    width: 100%;
    min-height: 100%;
    gap: 0.75rem;
}

.topbar-left {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-shrink: 0;
}

/* .sidebar-toggle-btn / .topbar-right .btn-outline-light / .profile-btn vivent
   sur le fond du HEADER (--color-header), pas sur le fond de page — ils
   utilisaient jusqu'ici --color-text/--color-border/--color-text-muted
   (globaux, pensés pour le fond de PAGE, presque toujours clair) : dès que
   l'admin configurait un header sombre ou saturé, ces icônes/bordures/libellés
   restaient dans des tons sombres et devenaient quasi invisibles sur leur
   propre fond. Voir --color-header-* dans design-tokens.css. */
.sidebar-toggle-btn {
    background: transparent;
    border: 1px solid var(--color-header-border);
    color: var(--color-header-text);
    width: 34px;
    height: 34px;
    border-radius: var(--radius-sm);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background var(--transition-fast), border-color var(--transition-fast);
}
.sidebar-toggle-btn:hover { background: var(--color-header-hover); }
@media (min-width: 1024px) {
    .sidebar-toggle-btn { display: none; }
}

.topbar-right {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    flex-shrink: 0;
}

.topbar-right .btn-outline-light {
    border: 1px solid var(--color-header-border);
    color: var(--color-header-text-muted);
    background: transparent;
    font-size: 0.8125rem;
    padding: 0.3125rem 0.75rem;
    border-radius: var(--radius-sm);
    font-weight: 500;
    transition: background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
    line-height: 1.4;
}

.topbar-right .btn-outline-light:hover {
    background: var(--color-header-hover);
    border-color: var(--color-header-border);
    color: var(--color-header-text);
}

.profile-btn {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    background: transparent;
    border: 1px solid var(--color-header-border);
    color: var(--color-header-text);
    padding: 0.25rem 0.625rem 0.25rem 0.25rem;
    border-radius: var(--radius-pill);
    cursor: pointer;
    transition: background var(--transition-fast);
    text-decoration: none;
    font-size: 0.8125rem;
    font-weight: 500;
    line-height: 1.4;
}

.profile-btn:hover {
    background: var(--color-header-hover);
    color: var(--color-header-text);
    text-decoration: none;
}

.profile-avatar {
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: var(--gradient-accent);
    color: white;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: 0.6875rem;
    flex-shrink: 0;
    overflow: hidden;
}

/* === MAIN CONTENT === */
.main-content {
    flex: 1;
    padding: 1rem 2rem 1.5rem;
    max-width: 1600px;
    margin: 0 auto;
    width: 100%;
    background: var(--background-color);
}

/* === PAGE HEADER === */
.page-header-section {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 0.75rem;
    padding-bottom: 0.5rem;
    border-bottom: 1px solid var(--border-color, #e5e7eb);
    flex-wrap: wrap;
    gap: 0.75rem;
    background-color: transparent;
    padding-top: 0;
}

/* Bootstrap applique p-2/p-3 (utilitaires !important) sur ce même élément dans
   la plupart des templates — on les recadre ici en !important pour garder un
   bloc titre compact partout sans devoir toucher chaque gabarit un par un. */
.page-header-section.p-2,
.page-header-section.p-3 {
    padding: 0.5rem 0.75rem !important;
}

/* Base de 16 rem, pas 0 : avec `flex: 1` seul, le bloc titre ne forçait
   jamais le retour à la ligne et les actions l'écrasaient — au téléphone, un
   titre de trois mots s'affichait un mot par ligne (fiche workflow, mesuré).
   Les actions passent désormais SOUS le titre quand la place manque. */
.page-header-left {
    flex: 1 1 16rem;
    min-width: 0;
}

.page-title {
    font-size: 1.15rem;
    font-weight: 600;
    color: var(--text-color);
    margin: 0;
    letter-spacing: -0.01em;
    line-height: 1.3;
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.page-title i {
    font-size: 1.05rem;
    color: var(--color-primary-text);
}

.page-subtitle {
    color: var(--text-secondary-color);
    margin: 0;
    font-size: 0.8125rem;
    line-height: 1.4;
    font-weight: 400;
    max-width: 540px;
}

/* === BACK LINK === */
.back-link {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    color: var(--text-secondary-color);
    text-decoration: none;
    font-weight: 500;
    font-size: 0.8125rem;
    padding: 0.25rem 0.5rem;
    border-radius: 4px;
    background: transparent;
    transition: color 0.15s ease, background 0.15s ease;
    border: none;
    margin-bottom: 0.375rem;
    opacity: 1;
}

.back-link:hover {
    color: var(--color-primary-text);
    background: rgba(0, 0, 0, 0.04);
    transform: none;
}

.back-link i {
    font-size: 0.875rem;
    transition: none;
}

.back-link:hover i {
    transform: none;
}

.back-link-compact {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.04);
    color: var(--text-secondary-color);
    text-decoration: none;
    transition: background 0.15s ease, color 0.15s ease;
    flex-shrink: 0;
    margin-right: 0.5rem;
}

.back-link-compact:hover {
    background: rgba(0, 0, 0, 0.08);
    color: var(--color-primary-text);
    transform: none;
}

/* === CARDS === */
.card {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow);
    background: var(--color-surface);
    transition: box-shadow var(--transition-fast, 150ms ease);
}

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

/* Cards réellement cliquables (lien entier, ou opt-in explicite) : lift au
   survol en plus de l'intensification d'ombre — réservé aux cards
   interactives, pas à toutes les cards (beaucoup sont de simples conteneurs
   de contenu statique, un hover-lift y serait trompeur). */
a.card:hover,
.card-clickable:hover {
    transform: translateY(-4px);
}

.card-header {
    background: var(--color-surface-secondary);
    border-bottom: 1px solid var(--color-border);
    /* Densités compactes (refonte « premium & compacte », oct. 2026) :
       en-tête 0,625/1 rem, corps 1 rem, pied 0,625/1 rem — mêmes valeurs
       pour les modales plus bas. */
    padding: 0.625rem 1rem;
    font-weight: 600;
    font-size: 0.875rem;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    color: var(--color-text);
}

.card-body {
    padding: 1rem;
}

.card-footer {
    background: var(--color-surface-secondary);
    border-top: 1px solid var(--color-border);
    padding: 0.625rem 1rem;
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}

/* === TOUCHES PREMIUM : icônes en dégradé, stat cards, shimmer === */

/* Carré/rond à fond dégradé primary→accent — pour icônes de card, avatars de
   section, en-têtes de rapport. Basé sur les vraies couleurs de marque
   (jamais une couleur en dur), voir --gradient-accent dans design-tokens.css. */
.icon-gradient {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--gradient-accent);
    box-shadow: var(--gradient-shadow);
    color: #fff;
    border-radius: var(--radius);
    flex-shrink: 0;
    width: 42px;
    height: 42px;
    font-size: 1.125rem;
}
.icon-gradient.icon-gradient-sm { width: 32px; height: 32px; font-size: 0.9375rem; border-radius: var(--radius-sm); }
.icon-gradient.icon-gradient-lg { width: 56px; height: 56px; font-size: 1.5rem; }
.icon-gradient.icon-gradient-circle { border-radius: 50%; }

/* Bandeau de section / en-tête de rapport en dégradé, texte blanc uppercase. */
.gradient-banner {
    background: var(--gradient-accent);
    color: #fff;
    border-radius: var(--radius-lg);
    padding: 1rem 1.25rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 600;
}

/* Stat card : liseré dégradé de 3px en haut + lift léger au survol. */
.stat-card {
    position: relative;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow);
    padding: 1.125rem 1.25rem;
    overflow: hidden;
    transition: transform var(--transition-fast), box-shadow var(--transition-fast);
}
.stat-card::before {
    content: '';
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 3px;
    background: var(--gradient-accent);
}
.stat-card:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-md);
}

/* Barre de progression avec reflet qui traverse en boucle. */
@keyframes shimmer {
    0% { transform: translateX(-100%); }
    100% { transform: translateX(100%); }
}
.progress-shimmer {
    position: relative;
    overflow: hidden;
}
.progress-shimmer::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.35), transparent);
    animation: shimmer 1.6s infinite;
}

/* === EMPTY STATE ===
   Définition canonique, chargée sur toute page (via theme.css). Remplace 9
   redéfinitions locales quasi identiques trouvées dans le projet (audit UX
   2026-07-08) — les pages qui ont besoin d'une variante réellement différente
   (plus grande, couleur de module, contrainte de grille…) gardent une petite
   surcharge locale ciblée plutôt qu'une copie complète de la règle. */
.empty-state {
    text-align: center;
    padding: 3rem 2rem;
    color: var(--text-secondary-color);
}

/* Seule l'icône d'ILLUSTRATION (enfant direct, cas des 12 états vides du
   projet) est grande. `.empty-state i` grossissait aussi à 3 rem l'icône d'un
   bouton (« Créer le premier champ ») ou celle glissée dans une phrase
   d'aide, et sa spécificité (0,1,1) battait les utilitaires Tailwind
   (`tw-text-2xl`) posés sur l'icône : `:where()` la ramène à zéro — valeur
   par défaut conservée, tout utilitaire ou règle de page l'emporte. */
:where(.empty-state > i) {
    font-size: 3rem;
    margin-bottom: 1rem;
    opacity: 0.35;
}

.empty-state h4,
.empty-state h5 {
    color: var(--text-color);
    margin-bottom: 0.5rem;
}

/* === BUTTONS === */
.btn {
    font-weight: 500;
    border-radius: var(--radius);
    font-size: 0.875rem;
    transition: background-color var(--transition-fast), border-color var(--transition-fast), box-shadow var(--transition-fast), color var(--transition-fast);
    padding: 0.5rem 1rem;
    line-height: 1.4;
}

.btn:disabled,
.btn.disabled {
    opacity: 0.5;
}

/* Override forcé: supprimer tous les gradients des boutons primaires (inline styles inclus) —
   voir memory feedback_no_gradient_primary_buttons, choix de design délibéré. */
.btn-primary {
    background: var(--color-primary) !important;
    background-color: var(--color-primary) !important;
    background-image: none !important;
    border-color: var(--color-primary) !important;
    color: white !important;
}

.btn-primary:hover,
.btn-primary:focus {
    background: var(--color-primary-hover) !important;
    background-color: var(--color-primary-hover) !important;
    border-color: var(--color-primary-hover) !important;
    color: white !important;
    box-shadow: var(--shadow) !important;
    transform: none !important;
}

.btn-primary:active {
    transform: none !important;
    box-shadow: none !important;
}

/* Accent : même principe que primary (fond plein, pas de dégradé) mais sur la
   couleur d'accentuation — pour distinguer une action secondaire "positive"
   d'une action primaire sans redescendre au simple outline. */
.btn-accent {
    background-color: var(--color-accent);
    border-color: var(--color-accent);
    color: #fff;
}

.btn-accent:hover {
    filter: brightness(0.92);
    border-color: var(--color-accent);
    color: #fff;
    box-shadow: var(--shadow);
}

.btn-secondary {
    background-color: var(--color-surface);
    border-color: var(--color-border);
    color: var(--color-text);
}

.btn-secondary:hover {
    background-color: var(--color-surface-secondary);
    border-color: var(--color-border);
    color: var(--color-text);
}

/* Ghost : transparent au repos, ne se révèle (fond + bordure) qu'au survol —
   pour les actions tertiaires (icônes de toolbar, actions "discrètes"). */
.btn-ghost {
    background-color: transparent;
    border-color: transparent;
    color: var(--color-text-muted);
}

.btn-ghost:hover {
    background-color: var(--color-surface-secondary);
    border-color: var(--color-border);
    color: var(--color-text);
}

.btn-outline-primary,
.btn-outline-accent,
.btn-outline-success,
.btn-outline-warning,
.btn-outline-danger,
.btn-outline-info {
    background: transparent;
    border-width: 1.5px;
}

/* Bordure ET texte par le jeton de texte : la bordure d'un bouton est une limite
   de composant (WCAG 1.4.11, 3:1) — la marque brute n'y tenait pas en sombre. */
.btn-outline-primary { border-color: var(--color-primary-text); color: var(--color-primary-text); }
.btn-outline-primary:hover,
.btn-outline-primary:focus,
.btn-outline-primary:active,
.btn-outline-primary.active,
.btn-outline-primary.show { background-color: var(--color-primary); border-color: var(--color-primary); color: #fff; transform: none; }

.btn-outline-accent { border-color: var(--color-accent-text); color: var(--color-accent-text); }
.btn-outline-accent:hover,
.btn-outline-accent:focus,
.btn-outline-accent:active,
.btn-outline-accent.active,
.btn-outline-accent.show { background-color: var(--color-accent); border-color: var(--color-accent); color: #fff; transform: none; }

.btn-outline-success { border-color: var(--readable-success-text); color: var(--readable-success-text); }
.btn-outline-success:hover,
.btn-outline-success:focus,
.btn-outline-success:active,
.btn-outline-success.active,
.btn-outline-success.show { background-color: var(--readable-success-solid); border-color: var(--readable-success-solid); color: #fff; transform: none; }

.btn-outline-warning { border-color: var(--readable-warning-text); color: var(--readable-warning-text); }
.btn-outline-warning:hover,
.btn-outline-warning:focus,
.btn-outline-warning:active,
.btn-outline-warning.active,
.btn-outline-warning.show { background-color: var(--readable-warning-solid); border-color: var(--readable-warning-solid); color: #fff; transform: none; }

.btn-outline-danger { border-color: var(--readable-danger-text); color: var(--readable-danger-text); }
.btn-outline-danger:hover,
.btn-outline-danger:focus,
.btn-outline-danger:active,
.btn-outline-danger.active,
.btn-outline-danger.show { background-color: var(--readable-danger-solid); border-color: var(--readable-danger-solid); color: #fff; transform: none; }

.btn-outline-info { border-color: var(--readable-info-text); color: var(--readable-info-text); }
.btn-outline-info:hover,
.btn-outline-info:focus,
.btn-outline-info:active,
.btn-outline-info.active,
.btn-outline-info.show { background-color: var(--readable-info-solid); border-color: var(--readable-info-solid); color: #fff; transform: none; }

/* Aplats pleins à texte blanc : les teintes « signal » (#ef4444, #f59e0b,
   #10b981) ne portent que 3,76 / 2,15 / 2,54:1 sous du blanc. Les jetons
   `--readable-*-solid` (design-tokens.css) gardent la couleur et tiennent
   4,5:1. Les couleurs de marque, elles, ne sont pas concernées. */
.btn-danger {
    background-color: var(--readable-danger-solid);
    border-color: var(--readable-danger-solid);
    color: white;
}

.btn-danger:hover {
    filter: brightness(0.9);
    border-color: var(--readable-danger-solid);
    color: white;
    box-shadow: var(--shadow);
    transform: none;
}

.btn-warning {
    background-color: var(--readable-warning-solid);
    border-color: var(--readable-warning-solid);
    color: white;
}

.btn-warning:hover {
    filter: brightness(0.9);
    border-color: var(--readable-warning-solid);
    color: white;
    transform: none;
}

.btn-success {
    background-color: var(--readable-success-solid);
    border-color: var(--readable-success-solid);
    color: white;
}

.btn-success:hover {
    filter: brightness(0.9);
    border-color: var(--readable-success-solid);
    color: white;
    transform: none;
}

.btn-sm {
    padding: 0.25rem 0.625rem;
    font-size: 0.8125rem;
    border-radius: var(--radius-sm);
}

.btn-lg {
    padding: 0.625rem 1.25rem;
    font-size: 1rem;
    border-radius: var(--radius);
}

/* Taille "icon" : bouton carré pour un contenu icône seule (pas de label texte). */
.btn-icon {
    width: 34px;
    height: 34px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.btn-icon.btn-sm { width: 28px; height: 28px; }
.btn-icon.btn-lg { width: 42px; height: 42px; }

/* === FORMS === */
.form-control,
.form-select {
    border: 2px solid #94a3b8;
    border-radius: var(--radius);
    padding: 0.375rem 0.75rem;
    font-size: 0.9375rem;
    color: var(--color-text);
    background-color: var(--color-surface);
    box-shadow: inset 0 1px 3px rgba(15, 23, 42, 0.06);
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
    line-height: 1.5;
}

.form-control:focus,
.form-select:focus {
    border-color: var(--color-accent);
    box-shadow: var(--focus-ring);
    outline: none;
    background-color: var(--color-surface);
}

.form-control::placeholder {
    color: var(--color-text-muted);
}

/* Variantes de taille de Bootstrap. La règle `.form-control, .form-select`
   ci-dessus a la MÊME spécificité que `.form-control-sm` & co. et elle est
   chargée APRÈS bootstrap.min.css : elle les écrasait, et les 139 champs
   « petits » de l'application sortaient à la taille normale (mesuré : 39 px
   et police 15 px sur la checklist d'une tâche, déclarée form-control-sm).
   Valeurs alignées sur les barres d'outils denses du projet (13 px). */
.form-control-sm,
.form-select-sm {
    padding-top: 0.25rem;
    padding-bottom: 0.25rem;
    padding-left: 0.5rem;
    font-size: 0.8125rem;
    border-radius: var(--radius-sm);
}
.form-control-sm { padding-right: 0.5rem; }
.form-control-lg,
.form-select-lg {
    padding-top: 0.5rem;
    padding-bottom: 0.5rem;
    padding-left: 1rem;
    font-size: 1.0625rem;
}
.form-control-lg { padding-right: 1rem; }
/* Le raccourci `padding` de la règle de base rabattait aussi la marge DROITE
   d'un <select> à 0,75 rem : un libellé long passait sous le chevron.
   (Bootstrap la remet à 0,75 rem pour [multiple] et [size], plus spécifiques.) */
.form-select { padding-right: 2.25rem; }

.form-label {
    font-weight: 600;
    font-size: 0.8rem;
    letter-spacing: 0.01em;
    color: var(--color-text);
    margin-bottom: 0.375rem;
    display: block;
}

/* Hints/erreurs sous le champ : bordure gauche colorée plutôt que du simple
   texte teinté, pour que l'information reste repérable même sans percevoir
   la couleur (contour + fond, pas seulement une teinte de texte). Accent
   teinté à la couleur de marque (color-mix, jamais un gris plat) : signale
   "c'est une aide", cohérent avec le reste du système sans devenir aussi
   voyant qu'une erreur — la retenue vient du dosage à 22%, pas de l'absence
   de couleur. */
.form-text {
    font-size: 0.8125rem;
    line-height: 1.55;
    color: var(--color-text-muted);
    margin-top: 0.4rem;
    padding: 0.05rem 0 0.05rem 0.7rem;
    border-left: 2px solid color-mix(in srgb, var(--color-primary) 22%, var(--color-border));
}

.invalid-feedback {
    display: block;
    font-size: 0.8125rem;
    color: var(--readable-danger-text);
    margin-top: 0.375rem;
    padding-left: 0.6rem;
    border-left: 3px solid var(--color-danger);
}

.valid-feedback {
    display: block;
    font-size: 0.8125rem;
    color: var(--color-success);
    margin-top: 0.375rem;
    padding-left: 0.6rem;
    border-left: 3px solid var(--color-success);
}

.form-control.is-invalid,
.form-select.is-invalid {
    border-color: var(--color-danger);
}

.form-control.is-valid,
.form-select.is-valid {
    border-color: var(--color-success);
}

.form-check-input:checked {
    background-color: var(--color-primary);
    border-color: var(--color-primary);
}

/* Contour d'une case ou d'un bouton radio NON coché : 1 px #e5e7eb sur blanc
   = 1,2:1, sous le seuil WCAG 1.4.11 (3:1) — « Requis », « Obligatoire »…
   étaient presque invisibles. Mélange texte/surface : 4,0:1 sur une carte et
   3,2:1 sur le fond de page en clair, ≥ 5:1 en sombre. Sous :where() : les
   états focus, invalide et coché (plus spécifiques) gardent leur couleur. */
.form-check-input:where(:not(:checked):not(:indeterminate)) {
    border-color: color-mix(in srgb, var(--color-text) 60%, var(--color-surface));
}

.form-check-input:focus {
    box-shadow: var(--focus-ring);
    border-color: var(--color-primary);
}

.input-group-text {
    border: 2px solid #94a3b8;
    background: var(--color-surface-secondary);
    color: var(--color-text-muted);
    font-size: 0.875rem;
}

/* === TABLES === */
.table {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    font-size: 0.875rem;
}

.table thead th {
    background: var(--color-surface-secondary);
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    padding: 0.5rem 0.75rem;
    border-bottom: 1px solid var(--color-border);
    white-space: nowrap;
    position: sticky;
    top: 0;
    z-index: 2;
}

.table tbody td {
    padding: 0.5rem 0.75rem;
    border-bottom: 1px solid var(--color-border);
    color: var(--color-text);
    vertical-align: middle;
}

.table tbody tr:last-child td {
    border-bottom: none;
}

/* `.table-sm` de Bootstrap (0,1,1) perdait contre `.table tbody td` (0,1,2) :
   un tableau déclaré compact gardait les marges normales. */
.table-sm thead th,
.table-sm tbody td {
    padding: 0.3125rem 0.5rem;
}

.table tbody tr:hover td {
    background-color: var(--color-surface-secondary);
}

.table-bordered {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.table-bordered thead th:first-child {
    border-radius: var(--radius-lg) 0 0 0;
}

.table-bordered thead th:last-child {
    border-radius: 0 var(--radius-lg) 0 0;
}

/* Scrollbar fine sur les conteneurs de tableau qui scrollent horizontalement/
   verticalement (table-responsive, listes longues...). */
.table-responsive,
.sidebar-nav {
    scrollbar-width: thin;
    scrollbar-color: #94a3b8 transparent;
}
.table-responsive::-webkit-scrollbar { width: 8px; height: 8px; }
.table-responsive::-webkit-scrollbar-track { background: transparent; }
.table-responsive::-webkit-scrollbar-thumb { background-color: #94a3b8; border-radius: var(--radius-pill); }

/* === BADGES === Pills pastel : fond clair + texte foncé de la même teinte
   (au lieu d'un aplat de couleur + texte blanc), variantes dédiées en dark
   mode (teintes inversées : fond foncé, texte clair) plutôt qu'une simple
   inversion de contraste automatique. */
.badge {
    font-size: 0.72rem;
    font-weight: 600;
    padding: 0.2rem 0.6rem;
    border-radius: var(--radius-pill);
    letter-spacing: 0.02em;
}

.badge.bg-primary { background-color: var(--color-primary) !important; }
/* Fond = jeton neutre lisible : --color-text-muted devenait GRIS CLAIR en sombre
   sous un texte blanc (2,63:1). */
.badge.bg-secondary { background-color: var(--readable-neutral-solid) !important; color: #fff !important; }

.badge.bg-success { background-color: #d1fae5 !important; color: #065f46 !important; }
.badge.bg-warning { background-color: #fef3c7 !important; color: #92400e !important; }
.badge.bg-danger { background-color: #fee2e2 !important; color: #991b1b !important; }
.badge.bg-info { background-color: #dbeafe !important; color: #1e40af !important; }

:root[data-bs-theme="dark"] .badge.bg-success { background-color: rgba(16, 185, 129, 0.18) !important; color: #6ee7b7 !important; }
:root[data-bs-theme="dark"] .badge.bg-warning { background-color: rgba(245, 158, 11, 0.18) !important; color: #fcd34d !important; }
:root[data-bs-theme="dark"] .badge.bg-danger { background-color: rgba(239, 68, 68, 0.18) !important; color: #fca5a5 !important; }
:root[data-bs-theme="dark"] .badge.bg-info { background-color: rgba(59, 130, 246, 0.18) !important; color: #93c5fd !important; }

/* Les règles sombres de ce fichier visent :root[data-bs-theme="dark"], que le
   script de tête (partials/head.html) pose dans les DEUX cas : choix explicite
   ET préférence système. Elles visaient :root[data-theme="dark"], posé
   seulement par le choix explicite — un poste réglé en sombre au niveau du
   système n'en recevait aucune (alertes en texte foncé sur fond sombre). */
:root[data-bs-theme="dark"] { --bs-secondary-color: rgba(173, 181, 189, 0.75); }

/* === NAV TABS === Façon Excel : fond légèrement différent au repos, l'onglet
   actif se "détache" du fond (surface + ombre vers le haut) avec une barre
   colorée primary de 3px au sommet. */
.nav-tabs {
    border-bottom: 1px solid var(--color-border);
    gap: 0.125rem;
}

.nav-tabs .nav-link {
    position: relative;
    border: 1px solid transparent;
    color: var(--color-text-muted);
    padding: 0.625rem 1rem;
    font-weight: 500;
    font-size: 0.9375rem;
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
    transition: color var(--transition-fast), background var(--transition-fast), box-shadow var(--transition-fast);
    margin-bottom: -1px;
    background: var(--color-surface-secondary);
}

.nav-tabs .nav-link:hover {
    color: var(--color-primary-text);
    background: var(--color-surface-secondary);
    border-color: transparent;
}

.nav-tabs .nav-link.active {
    color: var(--color-primary-text);
    background: var(--color-surface);
    border-color: var(--color-border) var(--color-border) transparent;
    box-shadow: 0 -2px 6px rgba(15, 23, 42, 0.04);
    font-weight: 600;
}

.nav-tabs .nav-link.active::before {
    content: '';
    position: absolute;
    top: -1px;
    left: 0;
    right: 0;
    height: 3px;
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
    background: var(--color-primary);
}

/* === FOOTER === */
/* Copyright à gauche, liens légaux à droite ; empilés et centrés au
   téléphone. Les liens sont dans leur propre zone (<nav>), pas au fil d'une
   phrase : pas besoin du soulignement permanent pour les distinguer du texte
   (WCAG 1.4.1), il revient au survol et au focus. */
.main-footer {
    background: var(--surface-alt-color, #f9fafb);
    border-top: 1px solid var(--border-color, #e5e7eb);
    padding: 0.875rem 2rem;
    margin-top: auto;
}

.main-footer-inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.375rem 1.5rem;
    font-size: 0.8125rem;
    line-height: 1.5;
}

.main-footer-copy {
    margin: 0;
    color: var(--text-secondary-color);
}

.main-footer-links {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.25rem 1.25rem;
}

.main-footer-links a {
    color: var(--text-secondary-color);
    font-weight: 500;
    text-decoration: none;
    text-underline-offset: 3px;
    border-radius: var(--radius-sm, 0.375rem);
    transition: color 0.15s ease;
}

.main-footer-links a:hover,
.main-footer-links a:focus-visible {
    color: var(--color-primary-text, var(--text-color));
    text-decoration: underline;
}

@media (max-width: 767.98px) {
    .main-footer-inner {
        flex-direction: column;
        justify-content: center;
        text-align: center;
    }

    .main-footer-links {
        justify-content: center;
    }
}

/* === ALERTS ===
   Barre d'accent à gauche (4px, pleine couleur) plutôt qu'un contour fin
   sur les 4 côtés : la couleur ne sert alors qu'à UN seul repère net (le
   sens du message), le reste du cadre reste discret (fond très légèrement
   teinté, coin arrondi assorti à --radius-lg) — un ton plus "carte
   éditoriale" qu'un bloc plat entièrement bordé. Icône dans .alert i:first-child
   agrandie et posée sur la couleur d'accent (pas le texte atténué) pour
   rester le repère visuel principal au premier coup d'œil. */
.alert {
    border: 1px solid transparent;
    border-left-width: 4px;
    border-radius: var(--radius-lg, var(--radius));
    padding: 0.85rem 1.1rem;
    font-size: 0.9375rem;
    line-height: 1.55;
    display: flex;
    align-items: flex-start;
    gap: 0.65rem;
    box-shadow: var(--shadow-sm, none);
}

.alert > i:first-child,
.alert > .bi:first-child {
    font-size: 1.05em;
    margin-top: 0.1em;
    flex-shrink: 0;
}

/* BUG CONFIRMÉ (corrigé) : le contenu texte d'un .alert forme un item flex
   anonyme (icône = 1er item, tout le reste = 2e) dont la largeur minimale
   se cale sur son plus long mot INSÉCABLE — une URL sans espace (voir
   l'onglet SSO, callback OAuth) débordait donc du cadre au lieu de passer
   à la ligne, sur les 2 thèmes. `overflow-wrap` seul ne suffit pas ici : un
   `<code>` reste par défaut aussi peu sécable qu'un mot normal tant que
   rien ne l'autorise explicitement à couper au milieu.  */
.alert {
    overflow-wrap: anywhere;
}
/* `.alert` (15 px, même spécificité, chargé après Bootstrap) écrasait
   `.small` : une alerte déclarée petite sortait à la taille normale. */
.alert.small {
    font-size: 0.8125rem;
}
.alert code {
    overflow-wrap: anywhere;
    word-break: break-all;
}

.alert-success {
    background-color: color-mix(in srgb, var(--color-success) 10%, var(--color-surface));
    border-color: color-mix(in srgb, var(--color-success) 30%, transparent);
    border-left-color: var(--color-success);
    color: var(--readable-success-text);
}

.alert-danger,
.alert-error {
    background-color: color-mix(in srgb, var(--color-danger) 8%, var(--color-surface));
    border-color: color-mix(in srgb, var(--color-danger) 28%, transparent);
    border-left-color: var(--color-danger);
    color: var(--readable-danger-text);
}

.alert-warning {
    background-color: color-mix(in srgb, var(--color-warning) 10%, var(--color-surface));
    border-color: color-mix(in srgb, var(--color-warning) 30%, transparent);
    border-left-color: var(--color-warning);
    color: var(--readable-warning-text);
}

.alert-info {
    background-color: color-mix(in srgb, var(--color-info) 8%, var(--color-surface));
    border-color: color-mix(in srgb, var(--color-info) 26%, transparent);
    border-left-color: var(--color-info);
    color: #1d4ed8;
}

:root[data-bs-theme="dark"] .alert-success { background-color: rgba(16, 185, 129, 0.12); border-color: rgba(16, 185, 129, 0.35); border-left-color: var(--color-success); color: #6ee7b7; }
:root[data-bs-theme="dark"] .alert-danger,
:root[data-bs-theme="dark"] .alert-error { background-color: rgba(239, 68, 68, 0.12); border-color: rgba(239, 68, 68, 0.35); border-left-color: var(--color-danger); color: #fca5a5; }
:root[data-bs-theme="dark"] .alert-warning { background-color: rgba(245, 158, 11, 0.12); border-color: rgba(245, 158, 11, 0.35); border-left-color: var(--color-warning); color: #fcd34d; }
:root[data-bs-theme="dark"] .alert-info { background-color: rgba(59, 130, 246, 0.12); border-color: rgba(59, 130, 246, 0.35); border-left-color: var(--color-info); color: #93c5fd; }

/* .alert-light (blocs "instructions"/notes neutres, pas un statut succès/erreur/
   info/warning) n'a jusqu'ici aucune règle dédiée : elle héritait du gris
   Bootstrap brut, invisible en dark mode (aucune des 4 surcharges
   :root[data-bs-theme="dark"] ci-dessus ne la couvre). Alignée sur les tokens
   comme les 4 autres, avec un accent neutre (--color-border) plutôt qu'une
   couleur de statut : ce n'est ni un succès, ni une alerte. */
.alert-light {
    background-color: var(--color-surface-secondary);
    border-color: var(--color-border);
    border-left-color: color-mix(in srgb, var(--color-primary) 35%, var(--color-border));
    color: var(--color-text);
}

/* === PAGE CONTAINER === */
.page-container {
    background: var(--surface-color, white);
    border-radius: 8px;
    border: 1px solid var(--border-color, #e5e7eb);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
    overflow: hidden;
    margin-top: 0;
    padding: 1.25rem;
}

/* === SETTINGS CONTAINER === */
.settings-container {
    background: var(--surface-color, white);
    border-radius: 8px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
    overflow: hidden;
    border: 1px solid var(--border-color, #e5e7eb);
    margin-top: 1rem;
}

/* === SECTION TITLE === */
.section-title {
    font-size: 1rem;
    font-weight: 600;
    color: var(--text-color);
    margin-top: 1.5rem;
    margin-bottom: 1rem;
    padding-bottom: 0.5rem;
    border-bottom: 1px solid var(--border-color, #e5e7eb);
}

.section-title:first-child {
    margin-top: 0;
}

/* === MODALS === */
.modal-backdrop {
    background: rgba(15, 23, 42, 0.6);
}
.modal-backdrop.show {
    opacity: 1;
    backdrop-filter: blur(4px);
}

@keyframes modalSlideUp {
    from { opacity: 0; transform: translateY(16px); }
    to { opacity: 1; transform: translateY(0); }
}

.modal.show .modal-dialog {
    animation: modalSlideUp 0.2s ease;
}

.modal-content {
    border: none;
    border-radius: var(--radius-lg);
    box-shadow: 0 20px 60px rgba(15, 23, 42, 0.2);
    overflow: hidden;
}

.modal-header {
    border-bottom: 1px solid var(--color-border);
    padding: 0.75rem 1rem;
    background: var(--color-surface-secondary);
}

.modal-title {
    font-size: 1rem;
    font-weight: 600;
    color: var(--color-text);
}

.modal-body {
    padding: 1rem;
}

.modal-footer {
    border-top: 1px solid var(--color-border);
    padding: 0.625rem 1rem;
    background: var(--color-surface-secondary);
}

/* === RECHERCHE TRANSVERSE (dropdown + page pleine, partagent
   index/partials/_global_search_results.html) ===
   Deux densités du même vocabulaire visuel : `.search-hero`/résultats en
   cartes pour la page pleine, `.search-result-item--compact` (sans le
   surtitre "search-result-meta" ni le sur-échantillon de description) pour
   le dropdown du header, où l'espace est compté. */
.search-hero {
    max-width: 640px;
    margin: 0 auto 1.75rem;
}
.search-hero-input-row {
    position: relative;
    display: flex;
    align-items: center;
}
.search-hero-icon {
    position: absolute;
    left: 1.1rem;
    font-size: 1.1rem;
    color: var(--color-text-muted);
    pointer-events: none;
}
.search-hero-input {
    width: 100%;
    padding: 0.9rem 6.5rem 0.9rem 2.75rem;
    font-size: 1.05rem;
    border: 2px solid var(--color-border);
    border-radius: var(--radius-lg);
    background: var(--color-surface);
    color: var(--color-text);
    box-shadow: var(--shadow-sm);
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.search-hero-input:focus {
    outline: none;
    border-color: var(--color-accent);
    box-shadow: var(--focus-ring);
}
.search-hero-submit {
    position: absolute;
    right: 0.4rem;
    top: 50%;
    transform: translateY(-50%);
}

.search-results-summary {
    font-size: 0.85rem;
    color: var(--color-text-muted);
    margin-bottom: 1.25rem;
}

.search-result-section { margin-bottom: 1.75rem; }
.search-result-section:last-child { margin-bottom: 0; }

.search-result-section-header {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.75rem;
    padding: 0 0.1rem;
}
.search-result-section-title {
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-text-muted);
}
.search-result-section-count {
    font-size: 0.68rem;
    font-weight: 600;
    color: var(--color-text-muted);
    background: var(--color-surface-secondary);
    border-radius: var(--radius-pill);
    padding: 0.1rem 0.5rem;
}

.search-result-list {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.search-result-item {
    display: flex;
    align-items: flex-start;
    gap: 0.85rem;
    padding: 0.85rem 1rem;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    text-decoration: none;
    color: inherit;
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast), transform var(--transition-fast);
}
.search-result-item:hover {
    border-color: var(--color-accent);
    box-shadow: var(--shadow);
    transform: translateY(-1px);
    text-decoration: none;
    color: inherit;
}

.search-result-icon {
    width: 38px;
    height: 38px;
    border-radius: var(--radius-sm);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    font-size: 1rem;
}
.search-result-icon-modules { background: color-mix(in srgb, var(--color-primary) 14%, transparent); color: var(--color-primary-text); }
.search-result-icon-tasks { background: color-mix(in srgb, var(--color-info) 14%, transparent); color: var(--color-info); }
.search-result-icon-kanban_cards { background: color-mix(in srgb, #8b5cf6 16%, transparent); color: #8b5cf6; }
.search-result-icon-table_rows { background: color-mix(in srgb, var(--color-success) 14%, transparent); color: var(--color-success); }
.search-result-icon-studio_sections { background: color-mix(in srgb, var(--color-warning) 14%, transparent); color: var(--color-warning); }

/* === CHAMPS PERSONNALISÉS KANBAN (gestion) ===
   Un champ personnalisé (KanbanCardField) porte une information réelle -
   sa famille de type (texte, nombre, date, choix...) - qui mérite d'être
   lisible en un coup d'œil plutôt que réduite à une seule icône neutre.
   Réutilise l'idiome déjà établi par .search-result-icon-* (teinte
   color-mix() + icône pleine couleur) plutôt que d'inventer un nouveau
   langage visuel ; 8 familles de couleur, choisies pour rester
   distinguables y compris en mode sombre. */
.field-type-icon {
    width: 38px;
    height: 38px;
    border-radius: var(--radius-sm);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    font-size: 1.05rem;
}
.field-type-icon--text     { background: color-mix(in srgb, var(--color-info) 14%, transparent); color: var(--color-info); }
.field-type-icon--number   { background: color-mix(in srgb, var(--color-success) 14%, transparent); color: var(--color-success); }
.field-type-icon--datetime { background: color-mix(in srgb, #8b5cf6 15%, transparent); color: #8b5cf6; }
.field-type-icon--boolean  { background: color-mix(in srgb, #14b8a6 15%, transparent); color: #14b8a6; }
.field-type-icon--choice   { background: color-mix(in srgb, var(--color-warning) 16%, transparent); color: var(--color-warning); }
.field-type-icon--media    { background: color-mix(in srgb, #ec4899 14%, transparent); color: #ec4899; }
.field-type-icon--link     { background: color-mix(in srgb, var(--color-primary) 14%, transparent); color: var(--color-primary-text); }
.field-type-icon--other    { background: color-mix(in srgb, #64748b 14%, transparent); color: #64748b; }

/* Même palette, déclinée en pastille de texte pour le libellé du type
   (cohérence visuelle icône <-> pastille sur la même ligne). */
.field-type-pill {
    display: inline-flex;
    align-items: center;
    font-size: 0.75rem;
    font-weight: 600;
    padding: 0.15rem 0.55rem;
    border-radius: var(--radius-pill);
}
/* Le fond garde la teinte vive ; le TEXTE prend la nuance lisible de la même
   teinte (700 en clair, 300 en sombre) — la teinte vive sur sa propre teinte à
   12 % ne tenait que 1,93 à 3,61:1. */
.field-type-pill--text     { background: color-mix(in srgb, var(--color-info) 12%, transparent); color: var(--readable-info-text); }
.field-type-pill--number   { background: color-mix(in srgb, var(--color-success) 12%, transparent); color: var(--readable-success-text); }
.field-type-pill--datetime { background: color-mix(in srgb, #8b5cf6 13%, transparent); color: #6d28d9; }
.field-type-pill--boolean  { background: color-mix(in srgb, #14b8a6 13%, transparent); color: #0f766e; }
.field-type-pill--choice   { background: color-mix(in srgb, var(--color-warning) 14%, transparent); color: var(--readable-warning-text); }
.field-type-pill--media    { background: color-mix(in srgb, #ec4899 12%, transparent); color: #be185d; }
.field-type-pill--link     { background: color-mix(in srgb, var(--color-primary) 12%, transparent); color: var(--color-primary-text); }
.field-type-pill--other    { background: color-mix(in srgb, #64748b 12%, transparent); color: #475569; }
:root[data-bs-theme="dark"] .field-type-pill--datetime { color: #c4b5fd; }
:root[data-bs-theme="dark"] .field-type-pill--boolean  { color: #5eead4; }
:root[data-bs-theme="dark"] .field-type-pill--media    { color: #f9a8d4; }
:root[data-bs-theme="dark"] .field-type-pill--other    { color: #cbd5e1; }

/* Liste compacte (rangées plates, pas de carte/ombre par ligne — voir
   feedback_odoo_compact_list_pattern) plutôt qu'une grille de cartes comme
   role_list.html : ces champs se scannent verticalement, l'ordre d'affichage
   compte (colonne dédiée), une grille aurait cassé cette lecture séquentielle. */
.field-row-list {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    overflow: hidden;
    box-shadow: var(--shadow-sm);
}
.field-row {
    display: flex;
    align-items: flex-start;
    gap: 0.9rem;
    padding: 0.85rem 1.1rem;
    border-bottom: 1px solid var(--color-border);
    transition: background-color var(--transition-fast);
}
.field-row:last-child { border-bottom: none; }
.field-row:hover { background-color: var(--color-surface-secondary); }

.field-row-order {
    flex-shrink: 0;
    width: 1.75rem;
    height: 1.75rem;
    border-radius: var(--radius-pill);
    background: var(--color-surface-secondary);
    color: var(--color-text-muted);
    font-size: 0.75rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-top: 0.1rem;
}

.field-row-body { flex: 1 1 auto; min-width: 0; }

.field-row-meta {
    font-size: 0.8125rem;
    color: var(--color-text-muted);
    margin-top: 0.2rem;
}

.field-row-actions {
    display: flex;
    gap: 0.4rem;
    flex-shrink: 0;
    margin-top: 0.1rem;
}

/* === FORMULAIRE DE CHAMP PERSONNALISÉ KANBAN === */
.field-form-section {
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    padding: 1.1rem 1.25rem;
    margin-bottom: 1.25rem;
}
.field-form-section-title {
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--color-text-muted);
    margin-bottom: 0.9rem;
}

.search-result-body { flex: 1 1 auto; min-width: 0; }
.search-result-title {
    font-weight: 600;
    font-size: 0.9375rem;
    color: var(--color-text);
    margin: 0 0 0.15rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.search-result-meta {
    font-size: 0.76rem;
    color: var(--color-text-muted);
    margin: 0 0 0.3rem;
}
.search-result-snippet {
    font-size: 0.8375rem;
    color: var(--color-text-muted);
    line-height: 1.45;
    margin: 0;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}
.search-result-item mark {
    background: color-mix(in srgb, var(--color-warning) 35%, transparent);
    color: var(--color-text);
    border-radius: 2px;
    padding: 0 0.1rem;
}

/* Densité compacte pour le dropdown du header. */
.search-result-item--compact {
    padding: 0.5rem 0.75rem;
    gap: 0.6rem;
    border-radius: var(--radius-sm);
    border-color: transparent;
    background: transparent;
}
.search-result-item--compact:hover {
    background: var(--color-surface-secondary);
    border-color: transparent;
    transform: none;
    box-shadow: none;
}
.search-result-item--compact .search-result-icon {
    width: 28px;
    height: 28px;
    font-size: 0.8rem;
}
.search-result-item--compact .search-result-title { font-size: 0.85rem; }

.search-result-seeall {
    display: block;
    text-align: center;
    padding: 0.6rem;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--color-primary-text);
    border-top: 1px solid var(--color-border);
    text-decoration: none;
    transition: background var(--transition-fast);
}
.search-result-seeall:hover {
    background: var(--color-surface-secondary);
    color: var(--color-primary-text);
    text-decoration: none;
}

/* === COMMAND PALETTE (Ctrl/Cmd+K) === */
.cmdk-overlay {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, 0.6);
    backdrop-filter: blur(4px);
    z-index: 1200;
    padding-top: 12vh;
}
.cmdk-overlay.show { display: block; }

.cmdk-panel {
    max-width: 560px;
    margin: 0 auto;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    box-shadow: 0 20px 60px rgba(15, 23, 42, 0.25);
    overflow: hidden;
    animation: modalSlideUp 0.15s ease;
}

.cmdk-input-row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.9rem 1.1rem;
    border-bottom: 1px solid var(--color-border);
    color: var(--color-text-muted);
}
.cmdk-input {
    flex: 1;
    border: none;
    outline: none;
    background: transparent;
    color: var(--color-text);
    font-size: 1rem;
}
.cmdk-input::placeholder { color: var(--color-text-muted); }
.cmdk-esc {
    font-size: 0.68rem;
    color: var(--color-text-muted);
    background: var(--color-surface-secondary);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 0.1rem 0.4rem;
}

.cmdk-results {
    max-height: 60vh;
    overflow-y: auto;
    padding: 0.5rem;
}
.cmdk-section-label {
    font-size: 0.68rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    padding: 0.5rem 0.6rem 0.3rem;
}
.cmdk-item {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    padding: 0.55rem 0.65rem;
    border-radius: var(--radius-sm);
    color: var(--color-text);
    text-decoration: none;
    font-size: 0.9rem;
}
.cmdk-item i { color: var(--color-text-muted); width: 18px; text-align: center; }
.cmdk-item.is-active {
    background: var(--color-primary);
    color: #fff;
}
.cmdk-item.is-active i { color: #fff; }
.cmdk-empty {
    padding: 1.5rem 1rem;
    text-align: center;
    color: var(--color-text-muted);
    font-size: 0.9rem;
}
.cmdk-loading {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.6rem 0.65rem;
    color: var(--color-text-muted);
    font-size: 0.82rem;
}

/* === DROPDOWNS === */
@keyframes dropdownSlideUp {
    from { opacity: 0; transform: translateY(6px); }
    to { opacity: 1; transform: translateY(0); }
}

.dropdown-menu {
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-md);
    padding: 0.25rem;
    animation: dropdownSlideUp 0.15s ease;
}

.dropdown-item {
    border-radius: var(--radius-sm);
    padding: 0.375rem 0.75rem;
    font-size: 0.9375rem;
    color: var(--color-text);
    transition: background var(--transition-fast);
}

.dropdown-item:hover {
    background: var(--color-surface-secondary);
    color: var(--color-text);
}

.dropdown-divider {
    border-color: var(--color-border);
    margin: 0.25rem 0;
}

/* === BREADCRUMB === */
.breadcrumb {
    font-size: 0.875rem;
    margin-bottom: 0;
}

.breadcrumb-item + .breadcrumb-item::before {
    color: var(--text-secondary-color);
}

/* === TOASTS === */
@keyframes toastSlideInRight {
    from { opacity: 0; transform: translateX(24px); }
    to { opacity: 1; transform: translateX(0); }
}

.toast-container {
    position: fixed;
    bottom: 1.25rem;
    right: 1.25rem;
    z-index: 1055;
    max-width: 380px;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.toast {
    box-shadow: var(--shadow-md);
    border: 1px solid var(--color-border);
    border-left: 4px solid var(--color-text-muted);
    border-radius: var(--radius);
    min-width: 300px;
    background: var(--color-surface);
    animation: toastSlideInRight 0.2s ease;
}

.toast-header {
    background: var(--color-surface-secondary);
    color: var(--color-text);
    border-bottom: 1px solid var(--color-border);
    padding: 0.625rem 0.875rem;
    font-weight: 600;
    font-size: 0.875rem;
}

.toast-body {
    background: var(--color-surface);
    color: var(--color-text);
    padding: 0.75rem 0.875rem;
    font-size: 0.9375rem;
}

/* === MODULE CARDS (page d'accueil) ===
   Consomme les tokens --module-* (déclarés par index.html depuis GlobalSettings,
   voir index/context_processors.py et config/models.py) avec les mêmes valeurs
   par défaut qu'avant : rendu identique tant que ces réglages ne sont pas
   personnalisés, mais le theming devient enfin réellement effectif (audit UX
   2026-07-08 : ces variables étaient déjà émises mais jamais consommées ici). */
.modules-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(var(--module-card-min-width, 280px), 1fr));
    align-items: stretch;
    gap: var(--module-grid-gap, 0.75rem);
    margin-bottom: 1rem;
}

/* Certains modules sont rendus en <div class="module-card-wrapper"><a class="module-card">
   (ceux qui portent un bouton "gérer les utilisateurs"/modifier/supprimer en
   position absolue à côté), d'autres directement en <a class="module-card">
   sans wrapper (accueil, section Administration). Le wrapper est donc le grid
   item pour les premiers, .module-card lui-même pour les seconds — sans ce
   height:100%, seul le wrapper s'étirait à la hauteur de la ligne (stretch par
   défaut de CSS Grid), la carte visible à l'intérieur restait à sa hauteur de
   contenu naturelle : deux cartes de la même ligne semblaient de hauteurs
   différentes alors que leurs cellules de grille faisaient la même hauteur. */
.module-card-wrapper {
    height: 100%;
}

.module-card {
    background: var(--surface-color, white);
    border-radius: var(--module-card-border-radius, 8px);
    padding: var(--module-card-padding, 1.5rem);
    box-shadow: var(--module-card-box-shadow, 0 1px 2px rgba(15, 23, 42, 0.04), 0 1px 3px rgba(15, 23, 42, 0.04));
    transition: box-shadow 0.2s ease, border-color 0.2s ease;
    cursor: pointer;
    border: 1px solid var(--module-card-border-color, #e5e7eb);
    text-decoration: none;
    color: inherit;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    position: relative;
    overflow: hidden;
    box-sizing: border-box;
    height: 100%;
    min-height: var(--module-card-min-height, 160px);
}

.module-card::before {
    content: none;
}

.module-card:hover {
    transform: var(--module-card-hover-transform, none);
    box-shadow: var(--module-card-hover-box-shadow, 0 10px 28px rgba(15, 23, 42, 0.09), 0 2px 6px rgba(15, 23, 42, 0.06));
    border-color: var(--module-card-hover-border-color, var(--primary-color));
    text-decoration: none;
    color: inherit;
    background: var(--surface-color, white);
}

.module-card:active {
    transform: none;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

.module-icon {
    width: var(--module-icon-size, 48px);
    height: var(--module-icon-size, 48px);
    border-radius: var(--module-icon-border-radius, 8px);
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 1rem;
    font-size: var(--module-icon-font-size, 1.375rem);
    flex-shrink: 0;
    transition: none;
    box-shadow: var(--module-icon-box-shadow, none);
}

.module-card:hover .module-icon {
    transform: var(--module-icon-hover-transform, none);
}

.module-name {
    font-size: var(--module-name-font-size, 1rem);
    font-weight: 600;
    color: var(--text-color);
    margin-bottom: 0.375rem;
    line-height: 1.3;
}

.module-description {
    font-size: var(--module-description-font-size, 0.875rem);
    color: var(--text-secondary-color);
    line-height: 1.5;
    flex: 1;
    margin-bottom: 0;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.module-arrow {
    position: absolute;
    bottom: 1.25rem;
    right: 1.25rem;
    color: var(--text-secondary-color);
    opacity: 0;
    transition: opacity 0.15s ease;
    font-size: 1rem;
}

.module-card:hover .module-arrow {
    opacity: 1;
    color: var(--color-primary-text);
}

/* Animation d'entrée légère */
@keyframes fadeInUp {
    from {
        opacity: 0;
        transform: translateY(12px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.module-card {
    animation: fadeInUp 0.3s ease-out forwards;
    opacity: 0;
}

.module-card:nth-child(1) { animation-delay: 0.03s; }
.module-card:nth-child(2) { animation-delay: 0.06s; }
.module-card:nth-child(3) { animation-delay: 0.09s; }
.module-card:nth-child(4) { animation-delay: 0.12s; }
.module-card:nth-child(5) { animation-delay: 0.15s; }
.module-card:nth-child(6) { animation-delay: 0.18s; }
.module-card:nth-child(7) { animation-delay: 0.21s; }
.module-card:nth-child(8) { animation-delay: 0.24s; }
.module-card:nth-child(n+9) { animation-delay: 0.27s; }

/* === AUTH PAGES === */
.auth-page-body {
    background: #f5f6f8;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    font-family: var(--font-family);
    padding: 1rem;
}

.auth-logo-top {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    color: var(--color-primary-text);
    text-decoration: none;
    font-size: 1.25rem;
    font-weight: 700;
    margin-bottom: 1.5rem;
}

.auth-logo-top:hover {
    color: var(--secondary-color);
    text-decoration: none;
}

.auth-logo-top i {
    font-size: 1.5rem;
}

.auth-container {
    background: var(--surface-color, white);
    border-radius: 8px;
    box-shadow: 0 2px 16px rgba(0, 0, 0, 0.08);
    border: 1px solid var(--border-color, #e5e7eb);
    padding: 2rem;
    max-width: 420px;
    width: 100%;
}

.auth-header {
    text-align: center;
    margin-bottom: 1.5rem;
}

.auth-header h1 {
    color: var(--text-color);
    font-weight: 600;
    margin-bottom: 0.375rem;
    font-size: 1.375rem;
    line-height: 1.3;
}

.auth-header p {
    color: var(--text-secondary-color);
    font-size: 0.875rem;
    margin-bottom: 0;
    line-height: 1.4;
}

.auth-link {
    text-align: center;
    margin-top: 1.25rem;
    padding-top: 1.25rem;
    border-top: 1px solid var(--border-color, #e5e7eb);
}

.auth-link p {
    margin-bottom: 0.375rem;
    font-size: 0.875rem;
    color: var(--text-secondary-color);
}

.auth-link a {
    color: var(--color-primary-text);
    text-decoration: none;
    font-weight: 500;
}

.auth-link a:hover {
    text-decoration: underline;
}

.auth-footer {
    margin-top: 1.5rem;
    text-align: center;
    color: var(--text-secondary-color);
    font-size: 0.8125rem;
}

/* === RESPONSIVE === */
@media (max-width: 768px) {
    .main-content {
        padding: 1rem;
    }

    .header-content {
        padding: 0 1rem;
    }

    .page-title {
        font-size: 1.125rem;
    }

    .page-header-section {
        gap: 0.5rem;
    }

    .main-footer {
        padding: 0.75rem 1rem;
    }

    .modules-grid {
        grid-template-columns: 1fr 1fr;
        gap: 0.75rem;
    }

    /* Plus de hauteur minimale propre au mobile : la carte est une rangée
       (icône à gauche) et suit le réglage `module_card_min_height`, sinon
       chaque module occupait 110 px pour deux lignes de texte. */
    .module-card {
        padding: 0.875rem;
    }

    .auth-container {
        padding: 1.5rem;
    }

    .page-container {
        padding: 1rem;
        border-radius: 6px;
    }
}

@media (max-width: 480px) {
    .modules-grid {
        grid-template-columns: 1fr;
    }

    .header-right .d-none {
        display: none !important;
    }

    .auth-container {
        padding: 1.25rem;
    }
}

/* ===== ERPNext-style Record Form Drawer ===== */
.record-form-drawer .modal-dialog {
    margin: 1.75rem auto;
    max-width: 800px;
    transform: none;
    transition: none;
}

.record-form-drawer.modal.fade .modal-dialog,
.record-form-drawer.modal.show .modal-dialog {
    transform: none;
}

.record-form-drawer .modal-content {
    border-radius: var(--border-radius-medium);
    border: none;
    box-shadow: var(--box-shadow-large);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    max-height: min(90vh, 900px);
}

.record-form-drawer form {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
}

.record-form-drawer .modal-header {
    background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
    /* BUG CONFIRMÉ (corrigé) : --header-text-color est pensée pour le fond
       clair de la topbar (voir templates/partials/head.html) ; réutilisée ici
       via var(), elle écrasait le blanc générique de .modal-title dès que
       l'admin la configurait avec une couleur foncée (ex: #1a5412), rendant
       le titre illisible sur ce dégradé --primary-color/--secondary-color.
       La modale "Modifier" (sans .record-form-drawer) n'était pas concernée
       et restait donc lisible — d'où le texte forcé en blanc ici aussi. */
    color: #ffffff;
    border-bottom: none;
    padding: 1rem 1.25rem 0.75rem;
    flex-shrink: 0;
}

.record-form-drawer .modal-header .btn-close {
    filter: invert(1);
    opacity: 0.9;
}

.record-form-drawer .modal-header .btn-close:hover {
    opacity: 1;
}

.record-form-drawer .modal-title {
    color: #ffffff;
}

.record-form-drawer .modal-body {
    flex: 1;
    overflow-y: auto;
    padding: 1.25rem;
    scrollbar-width: thin;
    scrollbar-color: #cbd5e0 transparent;
}

.record-form-drawer .drawer-footer {
    background: #f8f9fa;
    border-top: 1px solid #e9ecef;
    padding: 0.75rem 1.25rem;
    flex-shrink: 0;
}

/* Le badge et la barre de progression vivent DANS l'en-tête au fond dégradé
   coloré (primary -> secondary) : des couleurs pensées pour un fond clair
   (texte gris #6c757d de .text-muted, pastille #f0f2f5, piste #e9ecef) y
   sont peu lisibles quel que soit le thème (clair ou sombre), le fond de
   référence ici est toujours la couleur de la marque, pas celle de la page.
   Blanc semi-transparent, cohérent avec .modal-title/--header-text-color
   juste au-dessus. */
.record-form-drawer .drawer-table-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.75rem;
    color: rgba(255, 255, 255, 0.95);
    background: rgba(255, 255, 255, 0.18);
    border-radius: 4px;
    padding: 2px 8px;
    margin-top: 4px;
}

.record-form-drawer .modal-header .text-muted {
    color: rgba(255, 255, 255, 0.85);
}

.record-form-drawer .drawer-progress-bar {
    height: 3px;
    background: rgba(255, 255, 255, 0.25);
    border-radius: 2px;
    margin-top: 8px;
    overflow: hidden;
}

.record-form-drawer .drawer-progress-fill {
    height: 100%;
    background: #ffffff;
    border-radius: 2px;
    transition: width 0.3s ease;
}

.create-row-modal-intro {
    background: color-mix(in srgb, var(--color-primary) 6%, var(--color-surface));
    border: 1px solid color-mix(in srgb, var(--color-primary) 20%, var(--color-surface));
    border-radius: 12px;
    padding: 0.9rem 1rem;
    margin-bottom: 1rem;
    box-shadow: 0 6px 18px rgba(15, 23, 42, 0.05);
    color: var(--color-text);
}

.create-row-icon-pill {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border-radius: 999px;
    background: color-mix(in srgb, var(--color-primary) 14%, transparent);
    color: var(--color-primary-text);
    flex-shrink: 0;
}

.create-row-form-shell {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

/* Quick Entry toggle bar */
.quick-entry-bar {
    background: color-mix(in srgb, var(--color-primary) 5%, var(--color-surface));
    border: 1px solid color-mix(in srgb, var(--color-primary) 18%, var(--color-surface));
    border-radius: 8px;
    padding: 0.5rem 0.85rem;
    margin-bottom: 1rem;
}

.quick-entry-toggle-btn {
    font-size: 0.8rem;
    color: var(--color-primary-text);
    white-space: nowrap;
}

.quick-entry-toggle-btn:hover {
    color: var(--secondary-color);
}

.optional-field.hidden-optional {
    display: none !important;
}

/* Keyboard hint pill */
.drawer-kbd-hint {
    font-size: 0.72rem;
    color: var(--text-secondary-color, #6c757d); /* #9ca3af échouait le contraste WCAG AA (~2.8:1) sur fond blanc */
}

.drawer-kbd-hint kbd {
    background: #e9ecef;
    border: 1px solid #d1d5db;
    border-radius: 3px;
    padding: 1px 5px;
    font-size: 0.7rem;
    color: #4b5563;
    font-family: var(--font-family);
}

/* === PAGES D'ERREUR (403/404/500) ===
   Définition canonique : les 3 templates (templates/403.html, 404.html, 500.html)
   dupliquaient chacun ce même bloc à l'octet près (audit UX 2026-07-08). */
.error-page {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 60vh;
    padding: var(--spacing-xlarge);
}
.error-card {
    background: var(--surface-color, white);
    border-radius: 8px;
    box-shadow: var(--box-shadow-medium);
    padding: 2.5rem;
    max-width: 480px;
    width: 100%;
    text-align: center;
    border: 1px solid var(--border-color, rgba(0, 0, 0, 0.06));
}
.error-page .error-icon {
    width: 80px;
    height: 80px;
    margin: 0 auto 1.5rem;
    border-radius: 50%;
    background: var(--primary-color);
    color: white;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 2.5rem;
}
.error-page h1 {
    font-size: var(--font-size-h1);
    font-weight: var(--font-weight-bold);
    color: var(--text-color);
    margin-bottom: 0.5rem;
}
.error-page .error-code {
    font-size: 0.875rem;
    color: var(--text-secondary-color);
    margin-bottom: 1.25rem;
}
.error-page p {
    color: var(--text-secondary-color);
    line-height: 1.6;
    margin-bottom: 1.5rem;
}
.error-page .btn-home {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: var(--button-padding);
    background: var(--primary-color);
    color: var(--button-text-color);
    border: var(--button-border);
    border-radius: var(--button-border-radius);
    font-weight: var(--button-font-weight);
    text-decoration: none;
    transition: var(--transition);
    box-shadow: var(--box-shadow-small);
}
.error-page .btn-home:hover {
    transform: var(--button-hover-transform);
    box-shadow: var(--button-hover-shadow);
    color: var(--button-text-color);
}

@media (max-width: 576px) {
    .record-form-drawer .modal-dialog {
        max-width: calc(100vw - 1rem);
        margin: 0.5rem auto;
    }

    .record-form-drawer .modal-content {
        border-radius: var(--border-radius-medium);
    }
}

/* Évite le flash du contenu Alpine.js non initialisé (x-cloak), défini une
   seule fois ici plutôt que redondamment dans chaque template qui utilise Alpine. */
[x-cloak] { display: none !important; }

/* ==================== Sélecteur d'utilisateurs (partials/user_picker.html) ====================
   Composant unique remplaçant les 5 implémentations de sélection d'utilisateurs
   historiquement dupliquées (tag-input maison, checkboxes sans recherche, etc.)
   — voir audit_permissions_modules.md §1.3 et §4 (P1). Basé sur les tokens de
   thème existants, donc déjà correct en dark mode sans overrides séparés. */
.user-picker {
    position: relative;
}

.user-picker-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-bottom: 0.5rem;
}

.user-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.2rem 0.5rem 0.2rem 0.2rem;
    background: var(--surface-alt-color, #f9fafb);
    border: 1px solid var(--border-color, #e5e7eb);
    border-radius: 999px;
    font-size: 0.8125rem;
    color: var(--text-color);
    max-width: 100%;
}

.user-chip-avatar {
    flex: 0 0 auto;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--primary-color, #667eea) 0%, var(--secondary-color, #764ba2) 100%);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.625rem;
    font-weight: 700;
    line-height: 1;
}

.user-chip-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.user-chip-remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    border: none;
    border-radius: 50%;
    background: transparent;
    color: var(--text-secondary-color);
    font-size: 0.9rem;
    line-height: 1;
    cursor: pointer;
    padding: 0;
}

.user-chip-remove:hover {
    background: var(--border-color, #e5e7eb);
    color: var(--text-color);
}

.user-picker-input-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.user-picker-input-row .form-control {
    flex: 1 1 auto;
}

.user-picker-dropdown {
    position: absolute;
    z-index: 1055;
    left: 0;
    right: 0;
    margin-top: 0.25rem;
    max-height: 280px;
    overflow-y: auto;
    background: var(--surface-color, #fff);
    border: 1px solid var(--border-color, #e5e7eb);
    border-radius: var(--border-radius-medium, 8px);
    box-shadow: var(--box-shadow-medium, 0 2px 8px rgba(0, 0, 0, 0.08));
    padding: 0.3rem;
}

.user-picker-option {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.45rem 0.6rem;
    border-radius: var(--border-radius-small, 4px);
    cursor: pointer;
    color: var(--text-color);
}

.user-picker-option .user-chip-avatar {
    width: 26px;
    height: 26px;
    font-size: 0.6875rem;
}

.user-picker-option.is-highlighted,
.user-picker-option:hover {
    background: var(--surface-alt-color, #f9fafb);
}

.user-picker-empty {
    padding: 0.6rem;
    color: var(--text-secondary-color);
    font-size: 0.8125rem;
}

/* ==================== Avatar utilisateur partagé (initiale + couleur par hash) ====================
   Une seule définition pour représenter une personne partout où elle apparaît
   (membres de carte Kanban, assigné à/Cc d'une tâche, avatars compacts des
   listes/kanban de tâches) — remplace 3 définitions locales auparavant
   dupliquées et incohérentes (tailles 19/20/30px, couleurs fixes différentes).
   `<span class="member-avatar" data-username="...">Initiale</span>` : la
   couleur de fond est posée en JS (static/js/avatar_color.js::
   applyAvatarColors, hash déterministe du nom d'utilisateur), pas ici, pour
   que deux personnes distinctes n'aient jamais le même avatar sans dépendre
   d'une palette statique à tenir à jour. `background` ci-dessous n'est qu'un
   repli avant l'exécution du script (évite un flash de cercle transparent). */
.member-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background: var(--primary-color, #0d6efd);
    color: #fff;
    font-size: 0.75rem;
    font-weight: 700;
    line-height: 1;
    flex-shrink: 0;
}
.member-avatar.sm {
    width: 22px;
    height: 22px;
    font-size: 0.65rem;
}
.member-avatar-empty {
    background: var(--surface-alt-color, #e9ecef);
    color: var(--text-secondary-color);
}
.member-avatars {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}
/* Pile d'avatars qui se chevauchent légèrement (façon Trello) — pour une
   rangée de tâche/carte compacte où l'espace horizontal est limité, à la
   différence de .member-avatars (espacés) utilisé dans les panneaux plus
   larges (sidebar de carte Kanban). */
.avatar-stack {
    display: flex;
    align-items: center;
}
.avatar-stack .member-avatar {
    margin-left: -8px;
    box-shadow: 0 0 0 2px var(--surface-color, #fff);
}
.avatar-stack .member-avatar:first-child {
    margin-left: 0;
}

/* === GESTIONNAIRE DE TÂCHES : composants partagés Liste/Kanban/Calendrier ===
   Densité façon Odoo : radius réduit (--border-radius-small plutôt que
   medium), tuiles/rangées plates (pas de translateY au survol, juste un
   changement de fond ou de bordure), texte plus petit. Voir memory
   feedback_no_gradient_primary_buttons (pas de gradient sur .btn-primary,
   déjà en place) et project_task_manager_premium_design_juillet2026. */
.task-hero-icon {
    display: inline-flex; align-items: center; justify-content: center;
    width: 26px; height: 26px; border-radius: var(--border-radius-small, 6px);
    background: rgba(var(--bs-primary-rgb, 13, 110, 253), .12);
    flex-shrink: 0;
}
.task-hero-icon i { color: var(--color-primary-text); font-size: 0.9rem; }

.view-switch {
    display: inline-flex; align-items: center; gap: 2px;
    background: var(--surface-alt-color, #f3f4f6);
    border: 1px solid var(--border-color, #e5e7eb);
    border-radius: 999px; padding: 2px;
}
.view-switch-link {
    display: inline-flex; align-items: center; gap: 0.3rem;
    padding: 0.28rem 0.65rem; border-radius: 999px;
    font-size: 0.78rem; font-weight: 500;
    color: var(--text-secondary-color); text-decoration: none;
    transition: background .15s ease, color .15s ease; white-space: nowrap;
}
.view-switch-link:hover { color: var(--text-color); background: rgba(0, 0, 0, .04); }
.view-switch-link.active {
    background: var(--surface-color, #fff); color: var(--color-primary-text);
    box-shadow: 0 1px 3px rgba(0, 0, 0, .12); font-weight: 600;
}
.view-switch-link:focus-visible { outline: 2px solid var(--primary-color); outline-offset: 2px; }

.task-cta-btn { display: inline-flex; align-items: center; gap: 0.4rem; }

.task-kpi-row {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 0.5rem; margin-bottom: 0.5rem;
}
.task-kpi-tile {
    position: relative; overflow: hidden;
    display: flex; align-items: center; gap: 0.6rem;
    background: var(--surface-color, #fff);
    border: 1px solid var(--border-color, #e5e7eb);
    border-radius: var(--radius, 8px);
    padding: 0.55rem 0.75rem;
    transition: border-color var(--transition-fast), transform var(--transition-fast), box-shadow var(--transition-fast);
    /* Tuile = lien cliquable (filtre la liste des tâches) : neutralise le
       style <a> par défaut (souligné, couleur bleue), garde l'apparence
       d'une carte. */
    text-decoration: none; color: inherit; cursor: pointer;
}
.task-kpi-tile:hover, .task-kpi-tile:focus-visible {
    border-color: rgba(var(--bs-primary-rgb, 13,110,253), .35);
    transform: translateY(-2px);
    box-shadow: var(--shadow);
    color: inherit;
}
.task-kpi-tile:focus-visible {
    outline: 2px solid var(--bs-primary, #0d6efd);
    outline-offset: 2px;
}
/* Tuile correspondant au filtre actuellement appliqué à la liste. */
.task-kpi-tile--active {
    border-color: rgba(var(--bs-primary-rgb, 13,110,253), .5);
    background: rgba(var(--bs-primary-rgb, 13,110,253), .06);
}
.task-kpi-tile--active::after { height: 4px; }
/* Fine liseré de couleur en haut de chaque tuile : renforce l'association
   visuelle icône/couleur/métrique sans alourdir (juste 3px, pas un bandeau). */
.task-kpi-tile::after {
    content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px;
}
.task-kpi-tile--total::after { background: var(--bs-primary, #0d6efd); }
.task-kpi-tile--todo::after { background: var(--bs-warning, #ffc107); }
.task-kpi-tile--progress::after { background: var(--bs-info, #0dcaf0); }
.task-kpi-tile--overdue::after { background: var(--bs-danger, #dc3545); }

.task-kpi-icon {
    width: 30px; height: 30px; border-radius: var(--border-radius-small, 6px); flex-shrink: 0;
    display: flex; align-items: center; justify-content: center; font-size: 0.9rem;
}
.task-kpi-text { display: flex; flex-direction: column; line-height: 1.15; min-width: 0; }
.task-kpi-value { font-size: 1.35rem; font-weight: 700; letter-spacing: -0.01em; color: var(--text-color); font-variant-numeric: tabular-nums; }
.task-kpi-label { font-size: 0.64rem; color: var(--text-secondary-color); font-weight: 600; text-transform: uppercase; letter-spacing: .05em; }

.task-kpi-tile--total .task-kpi-icon { background: rgba(var(--bs-primary-rgb, 13,110,253), .12); color: var(--bs-primary, #0d6efd); }
.task-kpi-tile--todo .task-kpi-icon { background: rgba(var(--bs-warning-rgb, 255,193,7), .18); color: var(--bs-warning, #ffc107); }
.task-kpi-tile--progress .task-kpi-icon { background: rgba(var(--bs-info-rgb, 13,202,240), .18); color: var(--bs-info, #0dcaf0); }
.task-kpi-tile--overdue .task-kpi-icon { background: rgba(var(--bs-danger-rgb, 220,53,69), .12); color: var(--bs-danger, #dc3545); }
.task-kpi-tile--overdue .task-kpi-value { color: var(--bs-danger, #dc3545); }

/* Barre de filtres compacte façon Odoo : une seule rangée, pas de <label>
   empilé au-dessus de chaque champ (gain d'une ligne entière de hauteur) —
   chaque contrôle porte son nom via aria-label (les <label> visuels d'origine
   n'avaient pas d'attribut for="", donc n'étaient déjà pas programmatiquement
   associés à leur champ ; aria-label est un gain d'accessibilité, pas une
   régression) et une icône préfixe explicite le remplace visuellement. */
.task-filter-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
.task-filter-grow { flex: 1 1 200px; }
.task-search-wrap, .task-select-wrap { position: relative; }
.task-search-wrap i, .task-select-wrap i {
    position: absolute; left: 0.55rem; top: 50%; transform: translateY(-50%);
    color: var(--text-secondary-color); font-size: 0.72rem; pointer-events: none;
}
.task-search-wrap input { padding-left: 1.8rem; }
.task-select-wrap select { padding-left: 1.7rem; min-width: 132px; }
.task-filter-toolbar .form-control, .task-filter-toolbar .form-select {
    font-size: 0.8125rem; padding-top: 0.3rem; padding-bottom: 0.3rem;
}
.task-filter-reset-btn { display: inline-flex; align-items: center; justify-content: center; width: 30px; padding: 0; flex-shrink: 0; }

/* Filtre actif : bordure + icône teintées pour repérer d'un coup d'œil quels
   critères sont appliqués (le reset lui-même ne s'active visuellement que
   s'il y a quelque chose à réinitialiser, voir task_list.html). */
.task-select-wrap.is-active select, .task-search-wrap.is-active input {
    border-color: var(--primary-color); background: rgba(var(--bs-primary-rgb, 13,110,253), .05);
}
.task-select-wrap.is-active i, .task-search-wrap.is-active i { color: var(--color-primary-text); }
.task-filter-reset-btn:not(.is-active) { opacity: .4; pointer-events: none; }
.task-filter-reset-btn.is-active { border-color: var(--bs-danger, #dc3545); color: var(--bs-danger, #dc3545); }
.task-filter-reset-btn.is-active:hover { background: var(--bs-danger, #dc3545); color: #fff; }

/* ===== Bascule "terminé" en un clic (cercle à cocher, façon Todoist/Linear) :
   POST direct sur le même endpoint quick_update_task_status que les dropdowns
   statut/priorité, juste avec status=done/todo — aucune nouvelle route. */
.task-check-btn {
    width: 21px; height: 21px; border-radius: 50%; border: none; background: transparent; padding: 0;
    display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
    color: var(--border-color, #cbd5e1); font-size: 1.1rem; line-height: 1;
    transition: color .15s ease, transform .1s ease; cursor: pointer;
}
.task-check-btn:hover { color: var(--bs-success, #198754); transform: scale(1.1); }
.task-check-btn:focus-visible { outline: 2px solid var(--primary-color); outline-offset: 2px; }
.task-check-btn-done { color: var(--bs-success, #198754); }
.task-check-btn-done:hover { color: var(--text-secondary-color, #6c757d); }

/* ===== Priorité : icône seule (plus de pastille pleine) — l'information est
   déjà portée par le liséré de couleur à gauche de la rangée (.task-card::before),
   la répéter en pilule pleine était redondant. Reprend le langage visuel des
   boutons Modifier/Supprimer déjà présents dans la rangée (icône + fond au survol). */
.task-icon-btn {
    width: 24px; height: 24px; border-radius: 50%; border: none; background: transparent; padding: 0;
    display: inline-flex; align-items: center; justify-content: center; font-size: 0.72rem;
    transition: background .15s ease;
}
.task-icon-btn:hover { background: var(--surface-alt-color, rgba(0, 0, 0, .06)); }
.task-priority-btn-low i { color: var(--text-secondary-color); }
.task-priority-btn-normal i { color: var(--bs-primary, #0d6efd); }
.task-priority-btn-high i { color: var(--bs-warning, #ffc107); }
.task-priority-btn-urgent i { color: var(--bs-danger, #dc3545); }

/* ===== Modales Créer/Modifier une tâche (_task_modals.html + _task_form_modal.html,
   partagées Liste/Kanban/Calendrier/Détail — task_detail.html a sa propre instance
   de la modale Modifier mais réutilise le même fragment de champs). En-tête
   dépouillé (surface + icône teintée façon .task-hero-icon) plutôt qu'un bandeau
   plein var(--primary-color) : cohérent avec le reste du gestionnaire de tâches,
   où les couleurs s'utilisent en accent, jamais en aplat. */
.task-modal-header {
    background: var(--surface-color, #fff);
    border-bottom: 1px solid var(--border-color, #e5e7eb);
}
.task-modal-header .modal-title { font-size: 1rem; font-weight: 600; display: flex; align-items: center; gap: 0.6rem; color: var(--text-color); }

.task-form-fields .form-control, .task-form-fields .form-select {
    font-size: 0.8125rem; border-radius: var(--border-radius-small, 6px);
    padding-top: 0.35rem; padding-bottom: 0.35rem;
}
.task-form-fields textarea.form-control { font-size: 0.8125rem; }
.task-form-fields input[type="range"] { accent-color: var(--primary-color); }

/* Titre : champ principal de la modale, mis en avant façon "titre de page"
   plutôt qu'un simple champ parmi d'autres. Le <label> existe mais reste
   visually-hidden — il donne son nom accessible au champ, que le placeholder
   seul ne garantit pas.

   SPÉCIFICITÉ RELEVÉE À DESSEIN. La règle d'origine s'écrivait
   `.task-form-title-group input` (une classe + un élément) et se faisait
   battre en silence par `.task-form-fields .form-control` juste au-dessus
   (deux classes) : le titre était rendu à 13 px, exactement comme « Heures
   estimées ». La mise en avant existait dans la feuille et n'atteignait jamais
   l'écran — mesuré en navigateur, invisible à la lecture du code. Ne pas
   réduire ces sélecteurs. */
/* Le champ garde l'HABILLAGE STANDARD — fond, bordure, anneau de focus — et ne
   se distingue que par la TYPOGRAPHIE.

   Il était auparavant rendu sans fond ni bordure, un simple trait sous le
   texte : la mise en avant « titre de page » venait au prix de l'affordance,
   et le champ principal du formulaire ne se lisait plus comme un champ. La
   hiérarchie doit se jouer sur la taille et la graisse, jamais en retirant ce
   qui signale qu'on peut écrire là.

   Aucun `background` ni `border` n'est redéclaré ici À DESSEIN : ils sont
   hérités de `.form-control`, donc identiques aux autres champs dans les deux
   thèmes, aujourd'hui comme après un changement de palette. Les redéclarer,
   c'était s'engager à les maintenir en double. */
.task-form-fields .task-form-title-group input.form-control {
    font-size: 1.1rem;
    font-weight: 600;
    line-height: 1.35;
    padding: 0.5rem 0.75rem;
}
.task-form-fields .task-form-title-group input.form-control::placeholder {
    font-weight: 500; opacity: 1; color: var(--task-muted, #575e66);
}

/* --- « Plus d'options » : les trois champs rarement touchés ---
   Catégories, heures estimées et progression allongeaient la modale de 200 px
   pour des valeurs qu'on renseigne une fois sur dix. Repliés par défaut, mais
   dépliés d'office si l'un d'eux porte déjà une valeur (voir
   _task_form_modal.html) — modifier une tâche ne doit jamais masquer ce
   qu'elle contient. */
.task-form-more-toggle {
    display: inline-flex; align-items: center; gap: 0.4rem;
    background: none; border: none; padding: 0.4rem 0; margin-bottom: 0.2rem;
    font-size: 0.7rem; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;
    color: var(--task-muted, #575e66);
}
.task-form-more-toggle:hover { color: var(--text-color); }
.task-form-more-toggle i { transition: transform .15s ease; font-size: 0.8rem; }
.task-form-more-toggle.is-open i { transform: rotate(90deg); }
/* État par défaut porté par le CSS, pas par JavaScript : le formulaire est
   correct avant même qu'Alpine ne démarre. */
.task-form-more { display: none; padding-top: 0.35rem; }
.task-form-more.is-open { display: block; }

/* --- Curseur de progression ---
   Il n'affichait AUCUNE valeur : on le déplaçait sans savoir où on l'amenait. */
.task-form-range { display: flex; align-items: center; gap: 0.6rem; }
.task-form-range input[type="range"] { flex: 1; min-width: 0; }
.task-form-range-value {
    min-width: 3.1rem; text-align: right;
    font-size: 0.78rem; font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--text-color);
}

/* --- Bannière de brouillon ---
   Rendue par task_manager.js quand une saisie non soumise est retrouvée à
   l'ouverture de la modale. Discrète (elle annonce une bonne nouvelle, pas une
   erreur) mais explicite sur l'action de repli : on doit pouvoir repartir des
   valeurs enregistrées d'un seul clic. */
.task-draft-banner {
    display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;
    padding: 0.45rem 0.7rem; margin-bottom: 0.9rem;
    border: 1px dashed var(--border-color, #e5e7eb);
    border-radius: var(--border-radius-small, 6px);
    background: var(--surface-alt-color, #f8f9fb);
    font-size: 0.75rem;
    color: var(--task-muted, #575e66);
}
.task-draft-banner i { color: var(--color-primary-text); }
.task-draft-banner strong { color: var(--text-color); font-weight: 600; }
.task-draft-banner button {
    margin-left: auto; background: none; border: none; padding: 0;
    font-size: 0.75rem; font-weight: 600; text-decoration: underline;
    color: var(--task-accent-readable, var(--primary-color));
}

.task-form-label {
    display: block; font-size: 0.7rem; font-weight: 600; text-transform: uppercase;
    letter-spacing: .04em; color: var(--text-secondary-color); margin-bottom: 0.3rem;
}
.task-form-section-label {
    font-size: 0.7rem; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;
    color: var(--task-muted, #575e66);
    padding-top: 0.55rem; margin-bottom: 0.5rem;
    border-top: 1px solid var(--border-color, #e5e7eb);
}
/* Les phrases d'aide héritaient de --text-secondary-color (#6c757d) : 4,69:1
   sur du blanc pur, mais 3,68:1 mesuré sur la surface réelle de la modale —
   sous le seuil AA. Même jeton dédié que le reste du gestionnaire de tâches. */
.task-form-fields .form-text { color: var(--task-muted, #575e66); }

/* ===== Skeleton loaders génériques (BUG CORRIGÉ : aucun n'existait dans tout
   le projet — le chargement htmx de listes comme le gestionnaire de tâches ou
   le dropdown de recherche transverse n'avait auparavant aucun indicateur
   visuel). Combinés à hx-indicator="#idDuSkeleton" (htmx bascule
   automatiquement la classe htmx-indicator, voir htmx.config.
   includeIndicatorStyles, actif par défaut) — voir index/templates/index/
   task_list.html et templates/partials/header.html pour des exemples d'usage. */
.skeleton-line {
    height: 0.9em;
    border-radius: var(--radius-sm, 4px);
    background: linear-gradient(90deg, var(--surface-alt-color, #eee) 25%, var(--border-color, #e0e0e0) 37%, var(--surface-alt-color, #eee) 63%);
    background-size: 400% 100%;
    animation: skeleton-shimmer 1.4s ease infinite;
}
.skeleton-row {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.65rem 0.5rem;
}
.skeleton-row .skeleton-line:first-child { width: 2rem; height: 2rem; border-radius: 50%; flex-shrink: 0; }
.skeleton-row .skeleton-line:last-child { flex: 1; }
@keyframes skeleton-shimmer {
    0% { background-position: 100% 50%; }
    100% { background-position: 0 50%; }
}
@media (prefers-reduced-motion: reduce) {
    .skeleton-line { animation: none; }
}

/* Petit spinner de chargement de la recherche transverse du header (voir
   templates/partials/header.html) — .header-search-form n'a pas de CSS dédié
   par ailleurs (mise en page héritée des utilitaires Bootstrap/flex du
   topbar), donc positionnement minimal et sûr en absolu à droite du champ. */
.header-search-form { position: relative; }
.header-search-spinner {
    position: absolute;
    right: 0.75rem;
    top: 50%;
    transform: translateY(-50%);
}

/* Tour de bienvenue (P4-24, voir static/js/onboarding_tour.js) — z-index
   au-dessus de tout le reste, y compris .cmdk-overlay (1200, le plus élevé
   jusqu'ici) : c'est la toute première chose qu'un nouvel utilisateur sans
   module voit. */
.onboarding-tour-overlay {
    position: fixed;
    inset: 0;
    z-index: 1300;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(15, 23, 42, 0.55);
    padding: 1rem;
}
.onboarding-tour-card {
    width: 100%;
    max-width: 420px;
    background: var(--color-surface);
    color: var(--color-text);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
    padding: 2rem 1.75rem 1.25rem;
}
.onboarding-tour-step {
    text-align: center;
}
.onboarding-tour-icon {
    font-size: 2.25rem;
    color: var(--color-primary-text);
    margin-bottom: 0.75rem;
}
.onboarding-tour-step h2 {
    font-size: 1.15rem;
    font-weight: 600;
    margin-bottom: 0.5rem;
}
.onboarding-tour-step p {
    color: var(--color-text-muted);
    margin-bottom: 0;
}
.onboarding-tour-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-top: 1.5rem;
    padding-top: 1rem;
    border-top: 1px solid var(--color-border);
}
.onboarding-tour-dots {
    display: flex;
    gap: 0.4rem;
}
.onboarding-tour-dot {
    width: 0.5rem;
    height: 0.5rem;
    border-radius: var(--radius-pill);
    background: var(--color-border);
    transition: background-color var(--transition-fast);
}
.onboarding-tour-dot.active {
    background: var(--color-primary);
}
.onboarding-tour-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

/* ==========================================================================
   ÉCHÉANCE DE SESSION (static/js/session_timeout.js)
   Centré en bas : à gauche il couvrait la navigation latérale, et le bas à
   droite appartient aux toasts (« Session rétablie » s'y affiche à la reprise).
   Couleurs par jetons : les trois états de thème sans variante dédiée.
   ========================================================================== */
.session-timeout-panel {
    position: fixed;
    left: 50%;
    transform: translateX(-50%);
    bottom: 1rem;
    z-index: 1085;
    display: flex;
    gap: 0.75rem;
    align-items: flex-start;
    width: min(420px, calc(100vw - 2rem));
    padding: 0.9rem 1rem;
    background: var(--color-surface);
    color: var(--color-text);
    border: 1px solid var(--color-border);
    border-left: 4px solid var(--readable-warning-solid, #b45309);
    border-radius: var(--radius-lg, 12px);
    box-shadow: var(--shadow-md);
}
.session-timeout-panel[hidden] {
    display: none;
}
.session-timeout-panel.is-expired {
    border-left-color: var(--readable-danger-solid, #b91c1c);
}
.session-timeout-icon {
    flex: 0 0 auto;
    font-size: 1.25rem;
    line-height: 1;
    color: var(--readable-warning-text, #92400e);
}
.session-timeout-panel.is-expired .session-timeout-icon {
    color: var(--readable-danger-text, #991b1b);
}
.session-timeout-body {
    flex: 1 1 auto;
    min-width: 0;
}
.session-timeout-title {
    margin: 0 0 0.15rem;
    font-weight: 600;
}
.session-timeout-text {
    margin: 0 0 0.6rem;
    color: var(--readable-muted, var(--color-text-muted));
    font-size: 0.875rem;
}
.session-timeout-time {
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
    color: var(--color-text);
    font-weight: 600;
}
.session-timeout-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}
/* La couleur de marque employée en TEXTE tombe sous 3:1 en sombre : le lien
   prend le jeton lisible, décliné pour les trois états de thème. */
.session-timeout-actions .btn-link {
    color: var(--readable-accent-text);
}
@media (pointer: coarse) {
    .session-timeout-actions .btn {
        min-height: 44px;
    }
}

/* ==========================================================================
   RÉSEAU FAIBLE (static/js/lazy_libs.js, static/js/saisie_resiliente.js,
   static/js/pwa.js) — chargements à la demande, économie de données, envois
   en attente, copie hors ligne. Jetons de couleur seulement : les trois états
   de thème sans variante dédiée.
   ========================================================================== */
/* Bibliothèque en cours de téléchargement (grille, graphique, calendrier) ou
   en échec : la zone le DIT au lieu de rester vide — une grille vide se lit
   comme « aucune ligne ». */
.lazy-lib-state {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    min-height: 120px;
    padding: 1rem;
    color: var(--readable-muted, var(--color-text-muted));
    font-size: 0.875rem;
    text-align: center;
}

/* Graphique non tracé en économie de données : un geste, et son poids. */
.save-data-chart {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.6rem;
    min-height: 160px;
    padding: 1rem;
    text-align: center;
    color: var(--readable-muted, var(--color-text-muted));
    font-size: 0.875rem;
    border: 1px dashed var(--color-border);
    border-radius: var(--radius, 8px);
}
.save-data-chart p { margin: 0; }
.save-data-chart-size { font-weight: 400; opacity: 0.85; }

/* Envois en attente de réseau : même place et même dessin que l'échéance de
   session (centré en bas), remonté au-dessus d'elle si les deux coexistent. */
.saisie-panel {
    position: fixed;
    left: 50%;
    transform: translateX(-50%);
    bottom: 1rem;
    z-index: 1086;
    width: min(440px, calc(100vw - 2rem));
    padding: 0.9rem 1rem;
    background: var(--color-surface);
    color: var(--color-text);
    border: 1px solid var(--color-border);
    border-left: 4px solid var(--readable-warning-solid, #a35c00);
    border-radius: var(--radius-lg, 12px);
    box-shadow: var(--shadow-md);
}
.saisie-panel[hidden] { display: none; }
.saisie-panel.is-online { border-left-color: var(--readable-success-solid, #0d6b52); }
body:has(.session-timeout-panel:not([hidden])) .saisie-panel { bottom: 9rem; }
.saisie-panel-head {
    display: flex;
    gap: 0.6rem;
    align-items: flex-start;
}
.saisie-panel-head .bi { font-size: 1.15rem; line-height: 1.3; color: var(--readable-warning-text, #92400e); }
.saisie-panel.is-online .saisie-panel-head .bi { color: var(--readable-success-text, #0d6b52); }
.saisie-panel-title { margin: 0 0 0.15rem; font-weight: 600; }
.saisie-panel-text { margin: 0; color: var(--readable-muted, var(--color-text-muted)); font-size: 0.875rem; }
.saisie-panel-list { list-style: none; margin: 0.6rem 0 0; padding: 0; }
.saisie-panel-list li {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem 0.5rem;
    padding: 0.45rem 0;
    border-top: 1px solid var(--color-border);
    font-size: 0.875rem;
}
.saisie-panel-label { flex: 1 1 12rem; min-width: 0; overflow-wrap: anywhere; }
.saisie-panel-list .btn-link { color: var(--readable-accent-text); }
@media (pointer: coarse) {
    .saisie-panel-list .btn { min-height: 44px; }
}

/* « Hors connexion » : discret, en bas à gauche, tant que le navigateur
   n'a pas de réseau. */
.reseau-etat {
    position: fixed;
    left: 1rem;
    bottom: 1rem;
    z-index: 1084;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.35rem 0.75rem;
    border-radius: 999px;
    background: var(--color-surface);
    color: var(--color-text);
    border: 1px solid var(--color-border);
    box-shadow: var(--shadow-sm);
    font-size: 0.8125rem;
    font-weight: 600;
}
.reseau-etat[hidden] { display: none; }
.reseau-etat .bi { color: var(--readable-warning-text, #92400e); }

/* Page relue depuis le cache du service worker faute de réseau. */
.offline-copy-banner {
    position: fixed;
    top: 0.5rem;
    left: 50%;
    transform: translateX(-50%);
    z-index: 1090;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 0.75rem;
    width: min(640px, calc(100vw - 1rem));
    padding: 0.6rem 0.9rem;
    background: var(--color-surface);
    color: var(--color-text);
    border: 1px solid var(--color-border);
    border-left: 4px solid var(--readable-warning-solid, #a35c00);
    border-radius: var(--radius, 8px);
    box-shadow: var(--shadow-md);
    font-size: 0.875rem;
}
.offline-copy-banner > span { flex: 1 1 16rem; min-width: 0; }
.offline-copy-banner .bi { color: var(--readable-warning-text, #92400e); }

/* Codes de secours de la double authentification (accounts/two_factor_backup_codes.html) */
.backup-codes-list {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.4rem 1.5rem;
    margin: 0.75rem 0 1rem;
    padding-left: 1.5rem;
}
.backup-codes-list code {
    font-size: 1rem;
    letter-spacing: 0.06em;
    color: var(--color-text);
    font-variant-numeric: tabular-nums;
}


/* ==========================================================================
   LISIBILITÉ — composants partagés (P2, octobre 2026)
   Mesuré par le robot d'audit sur 107 écrans × clair/sombre/mobile. Les règles
   sombres visent :root[data-bs-theme="dark"] (choix explicite ET préférence
   système, voir plus haut).
   ========================================================================== */
/* Utilitaires Bootstrap à !important : ils lisaient la marque brute. */
.text-primary,
.link-primary {
    color: var(--color-primary-text) !important;
}

/* Contour secondaire : le gris Bootstrap #6c757d tombait à 3,44:1 en sombre et
   à 3,68:1 sur un fond de page teinté. Le survol (fond plein) reste celui de
   Bootstrap, lisible. */
.btn-outline-secondary {
    --bs-btn-color: var(--readable-muted);
    --bs-btn-border-color: var(--readable-muted);
}

/* Code en ligne : le rose Bootstrap #d63384 ne tenait que 3,53 à 4,31:1 sur
   les surfaces claires ; #b6256d garde la teinte et tient 4,74:1 au pire. En
   sombre, la valeur native de Bootstrap (#e685b5) tient déjà 5,74:1. */
:root {
    --bs-code-color: #b6256d;
}
:root[data-bs-theme="dark"] {
    --bs-code-color: #e685b5;
}

/* .bg-light / .bg-white : couleurs FIXES chez Bootstrap. En sombre elles
   restaient claires sous un texte devenu clair — 1,14 à 1,21:1, c'est-à-dire
   invisible (formulaires d'action, de courriel, blocs « pre »). */
:root[data-bs-theme="dark"] .bg-white {
    background-color: var(--color-surface) !important;
}
:root[data-bs-theme="dark"] .bg-light {
    background-color: var(--color-surface-secondary) !important;
}

/* Utilitaires de texte « signal » (Bootstrap, !important) : #ef4444, #10b981,
   #f59e0b, #3b82f6 ne tiennent que 3,76 / 2,54 / 2,15 / 3,68:1 sur blanc. */
.text-danger  { color: var(--readable-danger-text) !important; }
.text-success { color: var(--readable-success-text) !important; }
.text-warning { color: var(--readable-warning-text) !important; }
.text-info    { color: var(--readable-info-text) !important; }

/* Messages de validation des formulaires (.invalid-feedback, .valid-feedback). */
:root[data-bs-theme] {
    --bs-form-invalid-color: var(--readable-danger-text);
    --bs-form-invalid-border-color: var(--readable-danger-text);
    --bs-form-valid-color: var(--readable-success-text);
}

/* Aplats « signal » portant du texte blanc (en-têtes de carte, étapes) :
   mêmes jetons que les boutons pleins. Les variantes teintées
   (`.bg-opacity-*`) et les badges, qui ont leurs propres règles, restent tels
   quels — de même que l'icône de module de l'accueil (`.module-icon`), qui
   n'est PAS un aplat : home.css lui donne un fond teinté et une icône dans la
   couleur pleine. Avec ses trois classes (0,3,0), cette règle battait les deux
   de home.css (0,2,0) : fond plein ET icône de la même couleur, carrés vides
   sur « Facturation », « Support client », « Ressources humaines ». */
.bg-success:not(.badge):not([class*="bg-opacity"]):not(.module-icon) { background-color: var(--readable-success-solid) !important; }
.bg-danger:not(.badge):not([class*="bg-opacity"]):not(.module-icon)  { background-color: var(--readable-danger-solid) !important; }
.bg-info:not(.badge):not([class*="bg-opacity"]):not(.module-icon)    { background-color: var(--readable-info-solid) !important; }

/* Pastilles « claires » en sombre : `.bg-light` devient une surface sombre
   (plus haut), le texte forcé sombre (`.text-dark`, couleur du badge) doit
   suivre — sinon 1,07:1. */
:root[data-bs-theme="dark"] .bg-light.text-dark,
:root[data-bs-theme="dark"] .bg-white.text-dark,
:root[data-bs-theme="dark"] .badge.bg-light {
    color: var(--color-text) !important;
}

/* ==========================================================================
   CIBLES TACTILES — pointeur grossier uniquement (WCAG 2.5.8 : 24 × 24 au
   minimum, 44 px pour la navigation). Souris et clavier : rien ne change.
   Même principe que task_manager.css : quand la densité visuelle compte, c'est
   la ZONE tactile qui grandit (pseudo-élément), pas le dessin.
   Mesuré avant : 2 973 cibles sous 24 px sur 107 écrans au doigt.
   ========================================================================== */
@media (pointer: coarse) {
    /* Arbre des modules (tiroir latéral de 228 px) : chevron 18 → 32 × 44,
       zone de 44 × 44 par pseudo-élément. 44 px de LARGE, ajoutés à
       l'indentation de profondeur, ne laissaient que ~140 px au libellé. */
    .sidebar-tree-toggle,
    .sidebar-tree-spacer {
        width: 32px;
        height: 44px;
    }
    .sidebar-tree-toggle { position: relative; }
    .sidebar-tree-toggle::after {
        content: '';
        position: absolute;
        top: 0;
        left: 50%;
        width: 44px;
        height: 44px;
        transform: translateX(-50%);
    }
    .sidebar-link-row { min-height: 44px; }

    /* Cases et interrupteurs Bootstrap : 16 → 26 px. Le décalage négatif de
       Bootstrap est en em : il suit, sinon la case chevauche son libellé. */
    .form-check-input[type="checkbox"]:not([role="switch"]),
    .form-check-input[type="radio"] {
        width: 1.625rem;
        height: 1.625rem;
    }
    .form-switch .form-check-input,
    .form-check-input[role="switch"] {
        width: 2.875rem;
        height: 1.625rem;
    }
    .form-check { padding-left: 2.25rem; min-height: 1.75rem; }
    .form-check .form-check-input { margin-left: -2.25rem; margin-top: 0; }
    .form-switch { padding-left: 3.5rem; }
    .form-switch .form-check-input { margin-left: -3.5rem; }
    input[type="checkbox"]:not(.form-check-input):not(.btn-check),
    input[type="radio"]:not(.form-check-input):not(.btn-check) {
        min-width: 24px;
        min-height: 24px;
    }

    /* Liens isolés : sur un lien EN LIGNE, le rembourrage vertical élargit la
       zone sans rien déplacer. */
    .breadcrumb a,
    .main-footer a,
    .auth-footer a,
    .matrix-table a,
    .notif-list-title {
        padding-block: 0.3rem;
    }

    /* Petites commandes isolées : zone de 44 px autour du dessin. */
    .user-chip-remove,
    .kanban-column-search-toggle,
    .wf-var-badge,
    .ag-header-cell-menu-button {
        position: relative;
    }
    .user-chip-remove::after,
    .kanban-column-search-toggle::after,
    .wf-var-badge::after,
    .ag-header-cell-menu-button::after,
    .dashboard-favorite-btn::after {
        content: '';
        position: absolute;
        top: 50%;
        left: 50%;
        width: 44px;
        height: 44px;
        transform: translate(-50%, -50%);
    }
    .dashboard-favorite-btn { position: relative; }

    /* Boutons-liens compacts (`p-0` les prive de tout rembourrage, en
       !important) : la zone grandit en hauteur seulement. Le caret d'un
       .dropdown-toggle occupe déjà ::after, d'où l'exclusion. */
    :where(.btn-link.btn-sm:not(.dropdown-toggle)) { position: relative; }
    .btn-link.btn-sm:not(.dropdown-toggle)::after {
        content: '';
        position: absolute;
        left: 0;
        right: 0;
        top: 50%;
        height: 44px;
        transform: translateY(-50%);
    }
    /* En-têtes de colonnes de la matrice des permissions : texte vertical,
       liens de 20 px de large. */
    .matrix-table a,
    .matrix-module-header a { padding-inline: 0.15rem; }

    /* ── 44 px : la coquille, présente sur toutes les pages ──────────────
       Mesuré : 2 404 liens de barre latérale à 37 px, et les commandes
       d'en-tête à 30-34 px, sur 98 écrans sur 107. Tous sont des conteneurs
       flex : la hauteur minimale centre déjà leur contenu. */
    .sidebar-link,
    .sidebar-section-toggle,
    .sidebar-logo,
    .profile-btn { min-height: 44px; }
    .sidebar-toggle-btn { width: 44px; height: 44px; }
    .topbar-right .btn-outline-light {
        min-width: 44px;
        min-height: 44px;
        padding-top: calc((44px - 1.4em - 2px) / 2);
        padding-bottom: calc((44px - 1.4em - 2px) / 2);
    }
    /* Bouton retour : pastille de 28 px conservée, zone de 44 px. */
    .back-link-compact { position: relative; }
    .back-link-compact::after {
        content: '';
        position: absolute;
        top: 50%;
        left: 50%;
        width: 44px;
        height: 44px;
        transform: translate(-50%, -50%);
    }

    /* ── 44 px : champs et boutons de formulaire ────────────────────────
       Un <input>/<select> centre son texte de lui-même : la hauteur
       minimale suffit. Les variantes -sm (barres de filtres, tableaux
       denses) restent au seuil AA de 24 px — les passer à 44 doublerait la
       hauteur de chaque liste au téléphone. Un champ ordinaire placé dans
       un .input-group-sm suit son groupe (mesuré : la recherche d'un
       tableau débordait de son groupe de 32 px, texte décalé sous la loupe). */
    .form-control:not(.form-control-sm):not(textarea):not(.form-control-color):not(.input-group-sm > *),
    .form-select:not(.form-select-sm):not(.input-group-sm > *) { min-height: 44px; }
    /* Un bouton accolé à ce champ serait ÉTIRÉ à 44 px avec son texte resté
       en haut : il prend 44 px par son propre rembourrage, texte centré. */
    .input-group:not(.input-group-sm) > .btn:not(.btn-sm) {
        padding-top: calc((44px - 1.4em - 2px) / 2);
        padding-bottom: calc((44px - 1.4em - 2px) / 2);
    }
    /* Un .btn ne centre PAS son texte verticalement (inline-block) : on
       garde son dessin (38 px) et sa zone tactile passe à 44 px par
       ::before — 3 px de part et d'autre, sans empiéter sur un voisin.
       ::after reste libre pour le caret des .dropdown-toggle. */
    /* :where() = spécificité nulle : un bouton positionné par sa propre
       règle (flottant, fixé) le reste. Un bouton explicitement statique
       renonce à la zone : son ::before s'échapperait vers un ancêtre. */
    :where(.btn:not(.btn-sm):not(.btn-link):not(.btn-close):not(.btn-action)) { position: relative; }
    .btn.position-static::before,
    .btn-link.btn-sm.position-static::after { content: none; }
    /* .btn-action (gestion des utilisateurs) porte son propre ::before
       (onde au survol) : il en est exclu. */
    .btn:not(.btn-sm):not(.btn-link):not(.btn-close):not(.btn-action)::before {
        content: '';
        position: absolute;
        left: 0;
        right: 0;
        top: 50%;
        height: 44px;
        min-height: 100%;
        transform: translateY(-50%);
    }
}
