/* ==========================================================================
   NAVIGATION COMPONENTS
   ========================================================================== */

/* Sidebar Navigation */
.sidebar {
  position: fixed;
  top: 0;
  left: 0;
  width: var(--sidebar-width);
  height: 100vh;
  background: var(--bg);
  color: var(--gray-600);
  /* Self-contained chrome — its own column, own grid. Vertical rhythm
     shared with the topbar via --chrome-pad-y. */
  padding: var(--chrome-pad-y) var(--chrome-pad-x-self);
  overflow-y: auto;
  z-index: var(--z-chrome);
  display: flex;
  flex-direction: column;
}

.sidebar-nav {
  flex: 1 1 auto;
}

.sidebar-brand {
  font-size: 1.5rem;
  font-weight: bold;
  margin-bottom: var(--space-4);
  text-align: center;
}

/* ⚠ UNE PASTILLE CARRÉE, PAS UNE AFFICHE. La boîte valait 210×150 avec 20 px
   de rembourrage — taillée pour un logotype en bandeau. Les onze marques de la
   plateforme sont aujourd'hui des SVG CARRÉS (viewBox 1500×1500, 2000×2000
   pour comptoir) : la hauteur gagnait, la marque se peignait donc à 110×110 et
   le bandeau mangeait 150 px en haut de la barre. Sortir du mode rail faisait
   passer le logo de 44 à 110 px — le geste se lisait comme un zoom sur
   l'icône, pas comme un dépliage du menu (constat Olivier : « l'icône devient
   énorme »). 64 px reste au-dessus des 44 px du rail sans rien écraser, et
   rend 86 px de hauteur à la navigation.
   Pas de rembourrage : la boîte EST la taille du logo, et `margin: 0 auto` la
   centre dans les 250 px. */
.sidebar-brand-logo {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  box-sizing: border-box;
  margin: 0 auto;
}

.sidebar-brand-logo img,
.sidebar-brand-logo svg {
  display: block;
  max-width: 100%;
  max-height: 100%;
  width: auto !important;  /* override inline width="100%" on fallback SVGs */
  height: auto !important;
  object-fit: contain;
}

/* App Launcher (cross-app navigation) — styled to sit flush with the nav list */
.app-launcher {
  position: relative;
}

.app-launcher-trigger {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-3) var(--space-4);
  background: transparent;
  border: none;
  border-radius: var(--radius);
  color: inherit;
  font-size: inherit;
  font-weight: inherit;
  cursor: pointer;
  text-align: left;
  transition: background var(--transition), color var(--transition);
}

.app-launcher-trigger:hover,
.app-launcher.is-open .app-launcher-trigger {
  background: var(--color-surface-2);
  color: var(--primary);
}

.app-launcher-popover {
  position: absolute;
  bottom: 100%;
  left: var(--space-3);
  right: var(--space-3);
  z-index: var(--z-chrome-2);
  margin-bottom: var(--space-1);
  padding: var(--space-3);
  max-height: calc(100vh - 140px);
  overflow-y: auto;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.12);
}

.app-launcher-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-2);
}

.app-launcher-tile {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1 / 1;
  padding: var(--space-2);
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: var(--color-surface-2);
  text-decoration: none;
  transition: background var(--transition), border-color var(--transition), transform var(--transition);
}

.app-launcher-tile:hover {
  background: var(--bg);
  border-color: var(--primary);
  transform: translateY(-1px);
}

.app-launcher-tile.is-current {
  border-color: var(--primary);
  background: var(--bg);
  cursor: default;
  pointer-events: none;
}

.app-launcher-tile img,
.app-launcher-tile svg {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  display: block;
}

.app-launcher-tile-fallback {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--text-secondary);
}

.app-launcher-divider {
  height: 1px;
  background: var(--border);
  margin: var(--space-3) 0;
}

.app-launcher-links {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.app-launcher-link {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-2);
  border-radius: var(--radius);
  color: var(--text-secondary);
  text-decoration: none;
  font-size: 0.8125rem;
  transition: background var(--transition), color var(--transition);
}

.app-launcher-link:hover {
  background: var(--color-surface-2);
  color: var(--primary);
}

.app-launcher-link-favicon {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  min-width: 20px;
  object-fit: contain;
  border-radius: 3px;
}

.app-launcher-link-label {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sidebar-nav ul {
  list-style: none;
  padding: 0;
  margin: 0;
}

.sidebar-nav a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius);
  transition: background var(--transition), color var(--transition);
}

.sidebar-nav a:hover,
.sidebar-nav .active > a {
  background: var(--color-surface-2);
  color: var(--primary);
}

.menu-label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

/* Submenu System */
.has-submenu > a {
  cursor: pointer;
}

.submenu {
  max-height: 0;
  overflow: hidden;
  transition: max-height var(--transition);
  margin-top: 4px;
}

.has-submenu.open .submenu {
  max-height: none;
}

.submenu li {
  padding-left: 20px;
}

.submenu li a {
  padding: var(--space-3) var(--space-4);
}

/* Tab Navigation */
.nav-tabs-container {
  margin-bottom: var(--space-6);
  border-bottom: 1px solid var(--border);
}

.nav-tabs {
  display: flex;
  gap: var(--space-2);
}

/* ── Slot de droite : les contrôles de la liste SUR la ligne des onglets ─────
   Rendu par `tabs(['right' => …])`. Une page à onglets qui ne porte que trois
   contrôles de liste (recherche, deux cases d'option) empilait jusqu'ici deux
   bandes pleine largeur — la bande d'onglets, puis une `.table-toolbar` de
   48 px + 16 px de marge. Le slot les met sur la MÊME ligne.
   `:has()` plutôt qu'une classe sur le conteneur : la mise en page découle de
   la PRÉSENCE du slot, elle n'a pas à être déclarée deux fois (même motif que
   `.nav-tabs-container:has(.nav-tabs--pill)` plus bas).
   Ce n'est PAS un remplacement de `.table-toolbar` : dès qu'une liste porte un
   bouton « Nouveau », une bascule de vue ou des exports, la barre d'outils
   reste la bonne place — le slot ne sait pas se replier sur deux niveaux. */
.nav-tabs-container:has(> .nav-tabs-right) {
  display: flex;
  /* `flex-end`, pas `center` : le soulignement de l'onglet actif est une
     bordure BASSE de 3 px — elle doit rester sur la ligne du conteneur, quelle
     que soit la hauteur du slot (le champ de recherche fait 50 px, un onglet
     47). Même montage que `.scope-tabs` / `.scope-tabs-search` ci-dessous. */
  align-items: flex-end;
  gap: var(--space-4);
}
/* Les onglets gardent leur largeur propre : c'est le slot qui absorbe le reste. */
.nav-tabs-container:has(> .nav-tabs-right) > .nav-tabs {
  flex: 0 1 auto;
  min-width: 0;
}
.nav-tabs-right {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-left: auto;
  padding-bottom: var(--space-1);
  flex-shrink: 0;
}
/* Masqué par le JS de la page (`style.display = 'none'` / `''`) : `''` rend la
   main à la feuille, donc à `flex` ci-dessus — ne pas poser `display` en ligne
   pour le RÉ-afficher. */
@media (max-width: 900px) {
  /* Le slot passe SOUS les onglets : un champ de 400 px dans une bande qui
     défile horizontalement (règle mobile de responsive.css) est introuvable.
     Ce sont alors les onglets seuls qui défilent. */
  .nav-tabs-container:has(> .nav-tabs-right) {
    flex-wrap: wrap;
    overflow-x: visible;
  }
  .nav-tabs-container:has(> .nav-tabs-right) > .nav-tabs {
    max-width: 100%;
    overflow-x: auto;
    scrollbar-width: none;
  }
  .nav-tabs-right {
    margin-left: 0;
    width: 100%;
    flex-wrap: wrap;
    padding-bottom: var(--space-2);
  }
}

/* ── LE GRAS DE L'ONGLET ACTIF NE DOIT DÉCALER PERSONNE (2026-09-14) ────────
   `.nav-tab.active` passe en 600 : le libellé s'élargit de un à deux pixels, et
   toute la bande glisse à chaque clic (constat Olivier).

   La largeur du GRAS est donc réservée d'avance. Une grille d'une seule colonne
   reçoit deux choses : le libellé (rangée 1) et un jumeau en gras (rangée 2,
   `::after`, repris de `data-label`). La colonne prend la largeur du PLUS LARGE
   des deux — donc toujours celle du gras — et le jumeau, de hauteur nulle et
   masqué, n'ajoute ni pixel vertical ni texte lisible.

   ⚠ `visibility: hidden` ET `height: 0` : le premier le retire de l'écran et
   des lecteurs d'écran, le second de la hauteur de la rangée. L'un sans l'autre
   laisse soit un doublon visible, soit un onglet deux fois trop haut. */
.nav-tab-label {
  display: inline-grid;
}
.nav-tab-label::after {
  content: attr(data-label);
  font-weight: 600;
  height: 0;
  overflow: hidden;
  visibility: hidden;
  pointer-events: none;
}

.nav-tab {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  background: transparent;
  border: 1px solid transparent;
  border-bottom: 3px solid transparent;
  color: var(--text-secondary);
  cursor: pointer;
  font-size: 0.875rem;
  font-weight: 500;
  transition: all var(--transition);
}

/* rustine `[hidden]` RETIRÉE le 2026-09-06 : `base.css` porte la règle globale
 * (CSS-06). Le commentaire qu'elle portait énonçait déjà la règle générale — le
 * style UA `[hidden]{display:none}` perd face à TOUTE règle auteur, donc le
 * `display:inline-flex` ci-dessus laissait visible un onglet replié par JS
 * (airfact /jobs/N, onglets sous « Plus », 2026-08-27) — sans jamais en tirer
 * la conséquence : on a corrigé 83 fois au cas par cas avant de l'écrire une
 * seule fois. */

/* Lock the icon size INSIDE every tab to a single value, regardless of
 * what the page passes to icon($name, $size). The codebase historically
 * mixed 14, 15, 16 across hand-rolled vs helper-emitted tabs; this rule
 * guarantees identical visual output platform-wide.
 *
 * !important is intentional: it beats the inline width/height attributes
 * that icon() emits on the SVG. */
.nav-tab svg {
  width: 14px !important;
  height: 14px !important;
  flex-shrink: 0;
}

.nav-tab:hover {
  color: var(--primary);
  background: var(--gray-50);
}

.nav-tab.active,
.nav-tab.is-active {
  color: var(--primary);
  border-bottom-color: var(--primary);
  background: var(--gray-50);
}

/* ── Onglets de PORTÉE — « Mes X » / « Partagés avec moi » ─────────────────
 * À distinguer des `.nav-tab`, qui changent de contenu : ceux-ci changent
 * l'ENSEMBLE regardé, la même liste avec une autre portée de données. Plus
 * discrets (2 px de soulignement, pas de fond au survol), et ils accueillent
 * à droite le champ de recherche de la liste.
 *
 * Promus ici le 2026-08-01 : /projects les dessinait en styles inline dans la
 * vue ET en JS (`b.style.borderBottomColor = …`), /configurator/dossiers en
 * avait sa propre copie dans dossiers.css. Deux dessins pour un même geste. */
.scope-tabs {
  display: flex;
  align-items: center;
  gap: 4px;
  margin: var(--space-2) 0 var(--space-3);
  border-bottom: 1px solid var(--border);
}
.scope-tab {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: var(--space-2) var(--space-4);
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  align-self: flex-end;
  margin-bottom: -1px;
  font-size: 0.8125rem;
  font-weight: 500;
  font-family: inherit;
  color: var(--text-muted);
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}
.scope-tab:hover { color: var(--text-primary); }
.scope-tab.is-active,
.scope-tab.active {
  color: var(--primary);
  border-bottom-color: var(--primary);
  font-weight: 600;
}
.scope-tab svg { flex-shrink: 0; }
/* Le champ de recherche de la liste, poussé à droite de la barre d'onglets. */
.scope-tabs-search {
  margin-left: auto;
  padding-bottom: var(--space-2);
  flex-shrink: 0;
}
@media (max-width: 720px) {
  .scope-tabs { flex-wrap: wrap; }
  .scope-tabs-search { margin-left: 0; width: 100%; padding-bottom: 6px; }
  .scope-tabs-search .filter-search { width: 100%; }
}

/* Count badge inside a tab — single source of truth for tab counts.
 * `.count-badge` and `.nav-tab-count` are deliberate aliases so every
 * tab across the platform renders an identical count pill regardless of
 * whether the page uses the tabs() helper (which emits .nav-tab-count)
 * or hand-rolled HTML (which historically used .count-badge). */
.nav-tab-count,
.nav-tab .count-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 0.375rem;
  border-radius: 100px;
  font-size: 0.6875rem;
  font-weight: 700;
  background: var(--gray-200);
  color: var(--text-muted);
  transition: all var(--transition);
  /* Reset the legacy .count-badge margin (badges.css gave it a left margin
   * that the helper version doesn't have — kill it for tab contexts). */
  margin-left: 0;
}

.nav-tab.active .nav-tab-count,
.nav-tab.is-active .nav-tab-count,
.nav-tab.active .count-badge,
.nav-tab.is-active .count-badge {
  background: var(--primary);
  color: var(--surface);
}

/* Utility overrides for nav-tabs-container */
.nav-tabs-container.border-0 { border-bottom: 0; }
.nav-tabs-container.mb-0 { margin-bottom: 0; }

/* =========================================================
   nav-tabs — variant: pill
   Tabs sit in a gray pill tray; the active tab is a white
   rounded chip. Compact, good for toolbar switches.
   ========================================================= */
.nav-tabs--pill {
  display: inline-flex;
  gap: 0.25rem;
  padding: 0.25rem;
  background: var(--gray-100);
  border-radius: 0.625rem;
}
.nav-tabs--pill .nav-tab {
  padding: 0.4rem 0.9rem;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--text-muted);
  border: 1px solid transparent;
  border-bottom: 1px solid transparent;
  border-radius: 0.5rem;
  background: transparent;
  transition: color var(--transition), background var(--transition), box-shadow var(--transition);
}
.nav-tabs--pill .nav-tab:hover {
  color: var(--text);
  background: rgba(255, 255, 255, 0.6);
}
.nav-tabs--pill .nav-tab.active,
.nav-tabs--pill .nav-tab.is-active {
  color: var(--text);
  background: var(--surface);
  border-color: transparent;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06), 0 1px 3px rgba(0, 0, 0, 0.04);
}
.nav-tabs--pill .nav-tab-count {
  background: var(--gray-200);
  color: var(--text-muted);
}
.nav-tabs--pill .nav-tab.active .nav-tab-count,
.nav-tabs--pill .nav-tab.is-active .nav-tab-count {
  background: var(--primary);
  color: var(--surface);
}
/* Pill trays typically don't want a wrapper bottom border */
.nav-tabs-container:has(.nav-tabs--pill) { border-bottom: 0; }

/* =========================================================
   nav-tabs — variant: in-header
   Tabs live inside a panel/drawer header row. The tabs define
   their own vertical height via padding; the active tab's
   bottom border sits flush with the header's bottom border.

   If the host header has padding (e.g. .xori-fab-header with
   padding: 0.75rem 1rem), cancel the vertical padding on the
   tabs wrapper so the active underline lands on the header's
   bottom border — see .xori-fab-header override in xori-fab.css.
   ========================================================= */
.nav-tabs--in-header {
  display: flex;
  flex: 1;
  min-width: 0;
  align-self: stretch;
  gap: 0;
}
.nav-tabs--in-header .nav-tab {
  padding: 0.75rem 1rem;
  border: none;
  border-bottom: 2px solid transparent;
  font-size: 0.8125rem;
  border-radius: 0;
}
.nav-tabs--in-header .nav-tab:hover {
  background: transparent;
  color: var(--primary);
}
.nav-tabs--in-header .nav-tab.active,
.nav-tabs--in-header .nav-tab.is-active {
  background: transparent;
  border-bottom-color: var(--primary);
  font-weight: 600;
}

.tab-content {
  display: none;
}

.tab-content.active {
  display: block;
}

/* Mobile Header */
.mobile-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--header-height);
  background: var(--bg);
  color: var(--gray-700);
  display: none;
  align-items: center;
  justify-content: space-between;
  padding: 0 var(--space-4);
  z-index: 999; /* un cran SOUS --z-chrome : la sidebar dépliée doit passer devant */
}

.hamburger-btn {
  background: none;
  border: none;
  color: var(--gray-700);
  cursor: pointer;
  padding: var(--space-2);
}

/* .view-toggle / .view-btn supprimés le 2026-08-28 — zéro markup consommateur
   sur toute la plateforme (seul un no-op de catalog-core.js les nommait) ;
   la bascule de vue canonique est .btn-group (buttons.css). */

/* Submenu Toggle Icons */
.submenu-toggle {
  cursor: pointer;
}

.submenu-toggle .icon-cont:last-child svg,
.submenu-toggle .icon-cont:last-child > * {
  transition: transform var(--transition);
  transform-origin: center;
}

.has-submenu.open .submenu-toggle .icon-cont:last-child svg,
.has-submenu.open .submenu-toggle .icon-cont:last-child > * {
  transform: rotate(180deg);
}

.submenu-toggle > .icon-cont:nth-child(2) svg,
.submenu-toggle > .icon-cont:nth-child(2) > * {
  transition: transform var(--transition);
  transform-origin: center;
}

.has-submenu.open .submenu-toggle > .icon-cont:nth-child(2) svg,
.has-submenu.open .submenu-toggle > .icon-cont:nth-child(2) > * {
  transform: rotate(180deg);
}

.submenu-toggle .menu-label .icon-cont,
.submenu-toggle .menu-label .icon-cont svg,
.submenu-toggle .menu-label .icon-cont > * {
  transform: none !important;
}

/* Sidebar Notification Badges */
.sidebar-badge {
  font-size: 0.6rem;
  padding: 0;
  min-width: 1.1rem;
  height: 1.1rem;
  line-height: 1.1rem;
  text-align: center;
  border-radius: 50%;
  margin-left: auto;
  flex-shrink: 0;
}

/* Sélecteur d'organisation — ne rend que pour un compte rattaché à PLUSIEURS
   organisations. Il vit DANS le pied collant : au-dessus du menu il défilait
   hors du champ dès que la liste était longue, alors que c'est le seul endroit
   d'où l'on change d'organisation. Padding horizontal nul : le pied en porte
   déjà un, et la rangée doit s'aligner sur le bouton qui la suit. */
.sidebar-tenant {
  padding: 0 0 var(--space-3);
  margin-bottom: var(--space-2);
  border-bottom: 1px solid var(--border);
}

/* Pied de barre — la bascule du mode rail. `.sidebar-nav { flex: 1 1 auto }`
   le colle en bas tout seul : pas de `position: absolute`. */
.sidebar-foot {
  /* COLLANT EN BAS, et ce n'est pas cosmétique : `.sidebar` défile
     (`overflow-y: auto`) et build monte à onze entrées. Sur un écran court, le
     pied sortait du champ — or c'est la SEULE sortie du mode rail. Un mode
     dont on ne peut pas sortir sans deviner qu'il faut faire défiler la barre
     n'est pas un mode, c'est un piège. Le fond est OBLIGATOIRE : sans lui les
     rangées défilent visiblement DERRIÈRE le bouton. */
  position: sticky;
  /* ⚠ `bottom: 0` NE SUFFIT PAS, et le défaut est de 16 px exactement.
     `.sidebar` porte `padding: var(--chrome-pad-y) …` : l'élément collant
     s'arrête au bas de la boîte de CONTENU, pas au bas du cadre de défilement.
     Il restait donc une bande de 16 px sous le bouton, dans laquelle on voyait
     défiler l'entrée suivante du menu (constat Olivier : « Fournisseurs » sous
     « Réduire le menu », mesuré à 700 px de haut). On décale le collage de la
     hauteur du padding, et on rend cette bande au bouton via son propre
     `padding-bottom` — sans quoi il collerait au bon endroit mais son fond
     ne couvrirait toujours pas ce qui passe dessous. */
  bottom: calc(var(--chrome-pad-y) * -1);
  margin-top: var(--space-2);
  /* ⚠ LA MARGE NÉGATIVE VA AVEC LE `bottom` NÉGATIF, et l'oublier se voit.
     `bottom` ne règle que la position COLLÉE ; à sa place naturelle (menu qui
     ne déborde pas, ou défilé jusqu'en bas) le pied s'arrêtait au bas de la
     boîte de contenu, donc 16 px plus haut. Avec un `padding-bottom` de 16 px
     par-dessus, cela faisait jusqu'à 32 px de vide sous le bouton (constat
     Olivier : « pourquoi autant d'espace »). La marge négative fait rejoindre
     au pied le bas du cadre dans les DEUX états, et le rembourrage retombe à
     une respiration de bouton. */
  margin-bottom: calc(var(--chrome-pad-y) * -1);
  padding-top: var(--space-2);
  padding-bottom: var(--space-2);
  background: var(--bg);
  border-top: 1px solid var(--border);
}

.sidebar-rail-toggle {
  display: flex;
  align-items: center;
  width: 100%;
  padding: var(--space-3) var(--space-4);
  border: none;
  border-radius: var(--radius);
  background: none;
  color: var(--text-muted);
  font: inherit;
  font-size: 0.875rem;
  text-align: left;
  cursor: pointer;
  transition: background var(--transition), color var(--transition);
}

.sidebar-rail-toggle:hover {
  background: var(--color-surface-2);
  color: var(--primary);
}

.sidebar-rail-toggle .icon-cont svg {
  transition: transform var(--transition);
}

/* ──────────────────────────────────────────────────────────────────────────
   MODE RAIL — la barre se réduit à sa colonne d'icônes et se DÉPLIE AU SURVOL
   EN RECOUVRANT le contenu (2026-09-12).

   Le levier est `--sidebar-width`, redéfini sur <html> : layout.css (une copie
   par app, quatre fichiers identiques) en tire le `margin-left`/`width` de
   `.main-content` et le `left` de `.app-topbar`. Les quatre apps suivent sans
   être touchées. La classe est posée AVANT LE PREMIER PEINT par le script en
   ligne du <head> (layout.php) — via core.js, qui est `defer`, la page se
   peindrait à 250 px puis sauterait à 68.

   ⚠ LE SURVOL NE CHANGE QUE LA LARGEUR DE L'<aside>, JAMAIS LE JETON.
   L'aside est `position: fixed` : il flotte au-dessus du contenu, donc rien ne
   se redispose. Animer le jeton ferait respirer la page ENTIÈRE — topbar
   comprise — chaque fois que le curseur frôle le bord gauche.

   ⚠ L'ICÔNE NE BOUGE PAS d'un état à l'autre : le padding horizontal du lien
   est forcé à 12 px en mode rail, donc le centre de l'icône tombe à
   12 (aside) + 12 (lien) + 10 (demi-icône) = 34 px = la moitié des 68 px du
   rail. Seul le LIBELLÉ apparaît. C'est ce qui fait lire le dépliage comme un
   glissement et non comme un saut.

   Le dépliage au survol REMPLACE les volants de sous-menu : survoler ouvre
   toute la barre à 250 px, où l'accordéon existant fonctionne tel quel. Il
   remplace aussi les infobulles — le libellé est là, entier, à sa place.

   Le mobile (≤ 768 px) est HORS de ce bloc : il garde son tiroir hors-champ.
   ────────────────────────────────────────────────────────────────────────── */
@media (min-width: 769px) {

  html.sidebar-rail { --sidebar-width: var(--sidebar-rail-width); }

  /* ── LES TROIS TEMPS DU GESTE, NOMMÉS UNE FOIS ──────────────────────────
     Tout ce qui bouge ici se cale dessus : le délai avant d'ouvrir, le délai
     avant de refermer, la durée de la course. Les nommer est ce qui permet à
     l'opacité du texte de se poser exactement À LA FIN de l'élargissement
     (`ouvre + duree`) sans recopier ce nombre à la main — et c'est ce qui
     remplace les douze sélecteurs à suffixes qu'il fallait tenir à jour pour
     `rail-now` : un clic n'a plus qu'à redéfinir UN jeton. */
  html.sidebar-rail {
    --rail-ouvre: 2000ms;
    --rail-ferme: 260ms;
    --rail-duree: 180ms;
  }

  html.sidebar-rail .sidebar {
    /* Le contenu déborde du rail : il est COUPÉ, pas replié. */
    overflow-x: hidden;
    /* Délai d'ENTRÉE : on traverse ce bord vingt fois par heure pour aller au
       contenu. Sans les 2 s de `transition-delay` posées sur la règle de
       survol, la barre s'ouvrirait à chaque passage. Le délai de SORTIE
       (260 ms, ici) l'empêche de se refermer sur un micro-écart de souris. */
    transition: width var(--rail-duree) ease var(--rail-ferme),
                box-shadow var(--rail-duree) ease var(--rail-ferme);
  }

  /* Compagnon OBLIGATOIRE de la coupe : sans `nowrap`, « Commandes
     fournisseurs » se casse en trois lignes dans 44 px et la rangée GRANDIT
     au lieu de se couper. */
  html.sidebar-rail .sidebar .menu-label { white-space: nowrap; }

  /* ── DEUX GARDES SUR LE SURVOL, et aucune sur le clavier ────────────────
     `rail-armed` — `:hover` est ÉVALUÉ À L'ARRIVÉE SUR LA PAGE, d'après la
     position du curseur, sans qu'aucun mouvement n'ait eu lieu. En cliquant
     une icône du rail, on atterrit donc sur la page suivante avec la souris
     pile sur la barre : le volet s'ouvrait tout seul, sans qu'on ait rien
     demandé (constat Olivier). core.js ne pose `rail-armed` qu'au PREMIER
     `pointermove` de la page : tant que la souris n'a pas bougé, elle n'est
     pas en train de survoler, elle est juste restée là.

     `rail-hush` — qui connaît son menu clique sans vouloir lire les libellés.
     core.js muselle le dépliage sur un `pointerdown` reçu alors que la barre
     est encore étroite, jusqu'à ce que le curseur ressorte.

     Le délai d'ouverture est à 2 s pour la même raison, poussé jusque-là le
     14-09 sur demande d'Olivier : un clic sûr le bat de très loin, donc rien
     ne commence même à s'animer ; seule une lecture délibérée — une PAUSE
     franche sur le rail — ouvre le panneau.

     ⚠ LE CLAVIER N'EST GARDÉ PAR NI L'UN NI L'AUTRE : `:has(:focus-visible)`
     est sa seule porte vers les libellés, et il ne pose ni `pointermove` ni
     `pointerdown`. C'est pour ça que les deux branches sont ÉCRITES À PART au
     lieu d'un `:is()` commun.
     ─────────────────────────────────────────────────────────────────────── */

  /* ⚠ `:is(:hover, :has(:focus-visible))` ET SURTOUT PAS `:focus-within`.
     `:focus-within` est vrai après un CLIC DE SOURIS : cliquer « Réduire le
     menu » laissait le focus sur le bouton, donc le panneau restait déplié à
     250 px une fois la souris partie — le mode rail ne se voyait jamais.
     `:focus-visible` ne s'allume que sur un parcours CLAVIER, ce qui est la
     seule raison d'avoir ajouté un déclencheur de focus.
     `:is()` est FORGIVING : là où `:has()` n'existerait pas, il est ignoré et
     `:hover` continue de marcher. Dans une liste de sélecteurs ordinaire, un
     `:has()` inconnu invaliderait la règle ENTIÈRE, survol compris. */
  html.sidebar-rail.rail-armed:not(.rail-hush) .sidebar:hover,
  html.sidebar-rail .sidebar:has(:focus-visible) {
    width: var(--sidebar-width-full);
    /* Elle FLOTTE : sans l'ombre, le panneau déplié se confond avec le
       contenu qu'il recouvre — mesuré à l'écran, le bord droit était
       indiscernable du tableau qui passe dessous.
       ⚠ PAS `--shadow-lg` : les trois ombres du socle tombent VERS LE BAS,
       elles sont taillées pour une carte. Un panneau VERTICAL a besoin d'une
       ombre qui part vers la DROITE, sinon elle ne dessine rien là où se joue
       le recouvrement. D'où la valeur littérale — le vocabulaire n'a pas
       d'ombre latérale, et en inventer une pour un seul usage figerait un
       nom que personne d'autre n'emploierait. */
    box-shadow: 10px 0 28px -10px rgb(0 0 0 / 0.22);
    transition-delay: var(--rail-ouvre), var(--rail-ouvre);
  }

  /* ⚠ LE CLAVIER NE PAIE PAS L'ATTENTE DU SURVOL. Le délai d'entrée est une
     garde contre un survol AMBIGU ; un parcours clavier ne l'est pas, et
     `:has(:focus-visible)` est sa SEULE porte vers les libellés — le faire
     patienter 2 s rendrait la barre inutilisable au Tab. Même levier que
     `rail-now` (on redéfinit le jeton, pas douze délais), mais porté par le
     sélecteur : tout ce qui lit `--rail-ouvre` dans la barre — largeur,
     sous-menu ouvert, sélecteur d'organisation, et le `calc` qui pose
     l'opacité des libellés à la FIN de la course — suit sans être touché. */
  html.sidebar-rail .sidebar:has(:focus-visible) { --rail-ouvre: 0ms; }

  /* Le padding qui aligne l'icône sur l'axe du rail — cf. l'en-tête. */
  html.sidebar-rail .sidebar-nav a,
  html.sidebar-rail .sidebar-rail-toggle {
    padding-left: var(--space-3);
    padding-right: var(--space-3);
  }

  /* ── UN LIBELLÉ EST LÀ, OU IL N'EST PAS LÀ ──────────────────────────────
     Ce qui s'efface : le texte, les pastilles, le chevron d'accordéon. On joue
     l'OPACITÉ, pas `display` — `display` ne s'anime pas, donc il ne peut pas
     être RETARDÉ, et c'est le retard qui fait tout le travail ci-dessous.

     ⚠ MAIS ON NE LA FOND PAS. Un fondu de 140 ms peint le libellé à 0,03 puis
     0,48 puis 0,83 : du gris pâle sur le fond de la barre, illisible, et par-
     dessus le marché COUPÉ, puisque à ces instants-là le panneau ne fait que
     184 puis 223 px (mesuré image par image — 7 images à chaque ouverture,
     constat Olivier : « soit on le voit soit on le voit pas, ça doit pas être
     entre les deux »). La durée est donc NULLE : seul le délai place la
     bascule. Le texte n'apparaît qu'une fois la barre PLEINE et s'éteint
     quand elle commence à se refermer — jamais dans un panneau à moitié
     large, jamais à moitié transparent.

     Ici, le délai de l'effacement est exactement celui de la FERMETURE : le
     texte part à l'instant où la largeur se met à tomber. Pas avant (on
     verrait un rectangle de 250 px vide), pas après (le bord le rognerait). */
  html.sidebar-rail .sidebar .menu-text,
  html.sidebar-rail .sidebar .sidebar-badge,
  html.sidebar-rail .sidebar .submenu-toggle > .icon-cont:last-child {
    opacity: 0;
    transition: opacity 0s linear var(--rail-ferme);
  }

  html.sidebar-rail.rail-armed:not(.rail-hush) .sidebar:hover .menu-text,
  html.sidebar-rail .sidebar:has(:focus-visible) .menu-text,
  html.sidebar-rail.rail-armed:not(.rail-hush) .sidebar:hover .sidebar-badge,
  html.sidebar-rail .sidebar:has(:focus-visible) .sidebar-badge,
  html.sidebar-rail.rail-armed:not(.rail-hush) .sidebar:hover .submenu-toggle > .icon-cont:last-child,
  html.sidebar-rail .sidebar:has(:focus-visible) .submenu-toggle > .icon-cont:last-child {
    opacity: 1;
    /* La largeur est acquise à `ouvre + duree`, et c'est là — pas avant — que
       le libellé se pose. Le `calc` est ce qui rend la promesse TENABLE quand
       un clic met `--rail-ouvre` à 0 : le texte attend toujours la fin de
       l'élargissement au lieu de se peindre dans un rail de 68 px. */
    transition: opacity 0s linear calc(var(--rail-ouvre) + var(--rail-duree));
  }

  /* La marque devient une pastille de 44 px ALIGNÉE SUR LA COLONNE D'ICÔNES.
     Le SVG de marque est carré (viewBox 1500×1500) : il se réduit sans se
     déformer.
     ⚠ Elle garde cette taille MÊME DÉPLIÉE — la faire grandir au survol
     pousserait toute la navigation 106 px plus bas à chaque passage. */
  html.sidebar-rail .sidebar-brand { margin-bottom: var(--space-3); }
  html.sidebar-rail .sidebar-brand-logo {
    width: 44px;
    height: 44px;
    padding: 0;
    margin: 0;
  }

  /* Le sélecteur d'organisation ne se lit pas dans 44 px : il se replie avec
     le reste et revient au survol, au rythme du panneau. Comme il est DANS le
     pied, il grandit vers le HAUT, dans le mou que `.sidebar-nav` lui cède —
     rien ne se décale sous lui.
     ⚠ C'est le seul endroit d'où l'on change d'organisation, la topbar n'en a
     pas : il doit rester ATTEIGNABLE, pas seulement visible. */
  html.sidebar-rail .sidebar-tenant {
    max-height: 0;
    padding-bottom: 0;
    margin-bottom: 0;
    border-bottom-color: transparent;
    overflow: hidden;
    opacity: 0;
    /* Même règle que les libellés : sa boîte s'ouvre, son contenu se POSE à la
       fin. L'opacité n'avait ici aucun délai — il s'effaçait en fondu dès le
       premier instant de la sortie, panneau encore plein. */
    transition: max-height var(--rail-duree) ease var(--rail-ferme),
                padding var(--rail-duree) ease var(--rail-ferme),
                margin var(--rail-duree) ease var(--rail-ferme),
                opacity 0s linear var(--rail-ferme);
  }

  html.sidebar-rail.rail-armed:not(.rail-hush) .sidebar:hover .sidebar-tenant,
  html.sidebar-rail .sidebar:has(:focus-visible) .sidebar-tenant {
    max-height: 8rem;
    padding-bottom: var(--space-3);
    margin-bottom: var(--space-2);
    opacity: 1;
    /* ⚠ La transition est réécrite EN ENTIER : un `transition-delay` unique
       s'appliquerait AUSSI à l'opacité et écraserait le `calc` qui la place à
       la fin de la course. */
    transition: max-height var(--rail-duree) ease var(--rail-ouvre),
                padding var(--rail-duree) ease var(--rail-ouvre),
                margin var(--rail-duree) ease var(--rail-ouvre),
                opacity 0s linear calc(var(--rail-ouvre) + var(--rail-duree));
  }

  /* UN SOUS-MENU LAISSÉ OUVERT NE DOIT PAS SURVIVRE AU REPLI. On ouvre
     l'accordéon dans le panneau déplié, on s'en va — et les icônes de ses
     trois enfants restaient peintes dans le rail (mesuré : x = 44 px), en
     retrait, orphelines de leur parent et impossibles à rattacher à quoi que
     ce soit. Le rail ne montre QUE le premier niveau.
     Pas de `!important`, VÉRIFIÉ et non supposé : `core.js` ne pose un
     `max-height` en ligne que PENDANT l'animation et l'efface à
     `transitionend` (relevé au repos : aucun style en ligne). Cette règle est
     donc plus spécifique que `.has-submenu.open .submenu` et gagne seule.
     Elle ne porte QUE sur l'état AU REPOS : déplié, elle ne s'applique pas et
     l'accordéon s'anime normalement. */
  html.sidebar-rail .sidebar .has-submenu.open .submenu {
    max-height: 0;
    /* Exactement le rythme de la largeur : les deux se referment ENSEMBLE. */
    transition: max-height var(--rail-duree) ease var(--rail-ferme);
  }

  /* ⚠ ET SURTOUT L'INVERSE — c'est le défaut qu'Olivier a vu : au survol, le
     sous-menu se rouvrait à l'instant 0 alors que le panneau attend 200 ms
     avant de s'élargir. Mesuré : à t = 120 ms, barre encore à 68 px et
     sous-menu déjà à 288 px de haut. On voyait donc les icônes des enfants
     surgir dans le rail étroit, en retrait, pendant ~300 ms avant que le
     panneau s'ouvre. Le sous-menu grandit maintenant AVEC la largeur, mêmes
     délai et durée : un seul geste au lieu de deux.
     `40rem` et non `none` : une valeur `none` n'est pas interpolable, elle
     basculerait d'un coup et on retomberait sur le même surgissement. 40 rem
     couvre largement le plus gros sous-menu du parc (6 entrées ≈ 240 px).

     ⚠ LA HAUTEUR CIBLE EST CELLE DU CONTENU, et c'est tout le sujet. Avec un
     plafond générique (40 rem), le sous-menu atteignait sa hauteur réelle
     — 288 px — à 45 % de sa course, donc BIEN AVANT que la largeur finisse :
     mesuré, il avait l'air ouvert quand le panneau n'était qu'à 153 px. Mêmes
     délai et durée ne veulent PAS dire « finissent ensemble » dès que l'une
     des deux courses vise un plafond qu'elle n'atteint pas.
     Les DÉCALER (le contenu après le panneau) supprimait le défaut mais
     rendait l'ouverture poussive. La vraie réponse est de viser juste :
     `--sub-n`, posé par `_sbRenderSubmenu` avec le nombre d'enfants VISIBLES,
     × 3 rem par rangée (48 px, MESURÉ). La course s'achève alors pile avec la
     largeur, dans les deux sens, et le geste est un seul mouvement.
     Le repli `12` ne sert que si la propriété manque : il déborde, donc il ne
     COUPE jamais — c'est le bon sens d'erreur. */
  html.sidebar-rail.rail-armed:not(.rail-hush) .sidebar:hover .has-submenu.open .submenu,
  html.sidebar-rail .sidebar:has(:focus-visible) .has-submenu.open .submenu {
    max-height: calc(var(--sub-n, 12) * 3rem);
    transition: max-height var(--rail-duree) ease var(--rail-ouvre);
  }


  /* ── UN CLIC EST EXPLICITE : IL NE PAIE PAS LE DÉLAI DU SURVOL ──────────
     Les 2 s protègent d'un survol AMBIGU — on traverse ce bord vingt fois
     par heure pour aller au contenu. Un clic, lui, ne l'est pas.
     Or le délai s'appliquait AUSSI à la variation de `max-height` que core.js
     déclenche en ouvrant un accordéon : panneau DÉJÀ déplié, cliquer un
     sous-menu mettait 376 ms à montrer quoi que ce soit, contre 3 ms en barre
     fixe (mesuré, constat Olivier). Le mode rail rendait lent un geste qui est
     instantané partout ailleurs.
     core.js pose `rail-now` au `pointerdown` d'un déclencheur de sous-menu et
     le retire quand le curseur sort. Les durées ne changent pas — seul le
     DÉLAI tombe à zéro : c'est l'ATTENTE qui était de trop, pas l'animation.

     ⚠ CE QUI NE TOMBE PAS AVEC LUI : l'opacité des libellés. Elle ne lit pas
     `--rail-ouvre` mais `--rail-ouvre + --rail-duree`, donc elle attend
     toujours la FIN de l'élargissement. Mettre son délai à zéro peindrait le
     texte, plein et net, dans un rail de 68 px qui s'ouvre encore — l'autre
     façon de le montrer « entre les deux ».

     Ce bloc était douze sélecteurs qui recopiaient la garde de survol suffixe
     par suffixe, chacun à tenir à jour à chaque ajout. Un jeton dit la même
     chose en une ligne, et il n'a besoin d'aucune garde : les règles qui le
     lisent portent déjà la leur, et `rail-now` n'existe que posé par un
     `pointerdown` DANS la barre — le curseur y est, par construction. */
  html.sidebar-rail.rail-now { --rail-ouvre: 0ms; }

  /* En mode rail, le chevron indique la SORTIE du mode, pas l'entrée. */
  html.sidebar-rail .sidebar-rail-toggle .icon-cont svg { transform: rotate(180deg); }

  /* BASCULE AU CLIC. `--sidebar-width` change, donc `.main-content` saute d'un
     coup — sa marge n'est volontairement pas animée : l'animer recalculerait
     la disposition d'un grand tableau à chaque image. L'<aside> doit donc
     sauter AVEC lui, sinon on voit les deux se désynchroniser sur 180 ms.
     core.js pose cette classe le temps de deux images. */
  html.sidebar-anim-off .sidebar,
  html.sidebar-anim-off .sidebar * { transition: none !important; }
}

/* Mobile Responsive */
@media (max-width: 768px) {
  .mobile-header { 
    display: flex; 
  }
  
  .sidebar {
    transform: translateX(-100%);
    transition: transform var(--transition);
  }
  
  .sidebar.open { 
    transform: translateX(0); 
  }
}
/* ══ NAVIGATEUR DE PÉRIODE — « ‹ [ce qu'on regarde] › [maintenant] » ═══════
   Posé dans le socle le 13-09-2026, sur demande d'Olivier (« mettre ces deux
   boutons next/previous comme ici — consistency !!! ») : le Gantt de plan et le
   facturier prévisionnel d'airfact naviguent tous deux dans le TEMPS, et
   dessinaient deux navigateurs différents.

   La forme est celle du Gantt, qui l'a inventée (`.planning-nav-*`,
   plan/public/assets/css/modules/my-planning.css) :

     <div class="period-nav">
       <button class="btn btn-ghost btn-sm" aria-label="Précédent">{chevron-left 18}</button>
       <div class="period-nav-label">
         <span class="period-nav-title">juin 2026 → mai 2027</span>
         <span class="period-nav-sub">12 mois chargés</span>
       </div>
       <button class="btn btn-ghost btn-sm" aria-label="Suivant">{chevron-right 18}</button>
       <button class="btn btn-outline btn-sm">Aujourd'hui</button>
     </div>

   ⚠ LE LIBELLÉ EST ENTRE LES DEUX FLÈCHES, et c'est la règle qui fait le
   navigateur : une flèche déplace ce qui est écrit à côté d'elle. Mises côte à
   côte d'un même bord, elles ne disent plus ce qu'elles déplacent.
   ⚠ `min-width` sur le libellé : sans lui, « mai 2027 » et « septembre 2026 »
   n'ont pas la même largeur, et les deux flèches BOUGENT d'un clic à l'autre —
   on vise un bouton qui s'est déplacé sous le curseur.
   ⚠ Les flèches sont des `btn-ghost` : elles se répètent et s'utilisent en
   rafale. Le retour au présent est un `btn-outline` — il se voit, il ne crie
   pas, et c'est le seul des trois qui ne se répète pas.

   ⚠ `.planning-nav-*` de plan est l'ANCÊTRE de ce bloc et lui survit : ses
   règles portent aussi la ligne de filtres du Gantt. À faire migrer d'un seul
   tenant, pas en passant. */
.period-nav {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.period-nav:empty { display: none; }
.period-nav-label {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 160px;
  padding: 0 0.75rem;
}
.period-nav-title {
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--text);
  line-height: 1.3;
  white-space: nowrap;
}
.period-nav-sub {
  font-size: 0.8rem;
  color: var(--text-muted);
  white-space: nowrap;
}

/* ═══════════════════════════════════════════════════════════════════════════
   LE COMPTE DANS LE PIED DE BARRE (2026-09-14, décision Olivier)
   ═══════════════════════════════════════════════════════════════════════════
   Organisation, notifications et avatar tiennent désormais dans UNE colonne :
   c'était coupé entre deux chromes.

   ⚠ LE LANCEUR D'APPS N'EST PLUS ICI (2026-09-15). Sorti le 14-09 en grille
   VISIBLE dans le pied, il est reparti le lendemain dans le menu de l'avatar à
   la demande d'Olivier : cinq tuiles permanentes pour un saut rare coûtaient
   cinq rangées de colonne, et 375 px de pied en mode rail. Son dessin vit dans
   `layout.css` (`.app-topbar-user-apps`), pas ici.

   Ce fichier ne redessine RIEN : les panneaux gardent leurs classes
   `app-topbar-*` et leur dessin (top-nav.css / layout.css). Ce qui change est la
   POSITION — en bas d'une colonne de 250 px, un panneau ne peut ni descendre ni
   s'aligner à droite.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── LA RANGÉE DU COMPTE ────────────────────────────────────────────────────
   L'avatar mène (c'est le repère de la rangée), le nom suit, la cloche ferme au
   bord. `min-width: 0` sur le nom : sans lui un flex refuse de rétrécir sous
   son contenu, et « Jean-Baptiste de La Tour » poussait la cloche hors colonne. */
.sidebar-account {
  display: flex;
  /* `wrap` SANS CONDITION : quatre pastilles de 34 px et leurs gouttières font
     ~160 px. Elles tiennent sur une ligne dans une colonne dépliée (218 px
     utiles) et s'empilent d'elles-mêmes dans le rail. Une règle conditionnelle
     ne servirait qu'à refaire, moins bien, ce que le retour à la ligne fait. */
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  /* Pas de gouttière HORIZONTALE : la barre porte déjà la sienne
     (`--chrome-pad-x-self`, 12 px). Huit pixels de plus de chaque côté ne
     laissaient que 28 px utiles dans le rail — moins qu'une pastille de 34. */
  padding: var(--space-2) 0;
}
.sidebar-user { display: flex; align-items: center; flex: none; }
.sidebar-inbox { flex: none; }

/* ── LES PANNEAUX S'OUVRENT VERS LE HAUT, ET VERS LA DROITE ─────────────────
   Ils sont ancrés en bas d'une colonne de 250 px : vers le bas ils sortent de
   l'écran, à droite ils sortent de la colonne. On les pose donc au-dessus de
   leur déclencheur et alignés sur son bord GAUCHE, et on les laisse déborder sur
   le contenu — c'est ce que fait déjà le menu de `rowActions`. */
.sidebar-account .app-topbar-user-menu,
.sidebar-account .app-topbar-inbox-popover {
  top: auto;
  bottom: calc(100% + 8px);
  right: auto;
  left: 0;
}
/* ⚠ CES QUATRE LIGNES NE SONT QU'UN REPLI. Le script du pied repositionne le
   panneau en `position: fixed` à l'ouverture — indispensable parce que
   `.sidebar { overflow-y: auto }` rogne AUSSI l'axe horizontal (la spec coerce
   `visible` en `auto` dès qu'un axe est borné), et qu'un panneau de 280 px ne
   tient pas dans une colonne de 250. Si le script ne tournait pas, le panneau
   s'ouvrirait au moins vers le haut plutôt que vers le bas hors écran. */

/* ── MODE RAIL ──────────────────────────────────────────────
   Le pied n'a plus de LIBELLÉ à faire disparaître en rail : le lanceur d'apps
   est reparti dans le menu de l'avatar le 2026-09-15, et le menu s'ouvre en
   `position: fixed` hors de la colonne. Les deux règles d'opacité qui vivaient
   ici (`.sidebar-app-label`) sont parties avec la grille.

   ⚠ LA LEÇON DU 2026-09-14 TIENT TOUJOURS pour toute règle qu'on réécrirait
   ici : le rail ne se déplie PAS à tout survol. Il obéit à
   `html.sidebar-rail.rail-armed:not(.rail-hush) .sidebar:hover` — deux gardes
   (`rail-armed` tant que la souris n'a pas bougé, `rail-hush` quand on clique
   sans vouloir lire). Une règle bâtie sur `:not(:hover)` rendrait les libellés
   pendant qu'un rail NON ARMÉ reste à 64 px, et le pied déborderait au survol.
   Un libellé se cache par l'OPACITÉ, jamais par `display`. */


/* ── LA RANGÉE DU COMPTE EST UNE RANGÉE D'ICÔNES (2026-09-14) ───────────────
   Organisation et bascule du menu y rejoignent notifications et avatar. Elles
   prenaient chacune une rangée pleine largeur pour un geste rare — le
   sélecteur d'organisation coiffé d'un micro-libellé, la bascule doublée d'un
   « Réduire le menu ».

   Le nom du dossier courant n'est donc plus lisible au repos : il vit dans le
   `title` du bouton et en tête de son menu, coché. C'est un choix, et il ne
   coûte qu'aux comptes multi-dossiers — le bouton n'existe pas pour les autres. */
.sidebar-account .sidebar-rail-toggle {
  /* ⚠ ELLE PORTE LES DEUX CLASSES, et `.sidebar-rail-toggle` (règle plus haut
     dans ce fichier) la faisait PLEINE LARGEUR avec un rembourrage de rangée.
     Mesuré le 2026-09-14 : sans cette annulation elle rendait 20 px de large —
     une cible de clic plus petite que l'icône de ses voisines. On la ramène au
     gabarit de `.app-topbar-iconbtn` : 34 × 34, comme la cloche et l'avatar. */
  width: 34px;
  height: 34px;
  flex: none;
  padding: 0;
  /* `.sidebar-rail-toggle` pose `background: none; border: none` : sans ces
     deux lignes le chevron était le seul des quatre sans sa pastille, et la
     rangée se lisait « trois boutons et un caractère ». */
  background: var(--color-surface-2);
  border: 1px solid var(--border);
}
.sidebar-account .sidebar-rail-toggle .icon-cont { display: flex; }
.sidebar-pop { flex: none; }

