/* AutoPoster iOS — l'habillage de l'interface.
 *
 * Le VOCABULAIRE VISUEL est repris d'AutoPoster (le projet Android voisin) :
 * memes variables `--ap-*`, memes noms de classes (`panel`, `btn`, `mini`,
 * `badge`, `chip`, `pill`, `navitem`, `navgrp`, `muted`). C'est la seule
 * chose qu'on reprend telle quelle -- parce que c'est du CSS, et que c'est la
 * partie qu'on voit. Le reste du projet est reecrit.
 *
 * Ce qui n'est PAS repris : Tailwind et sa chaine de build. Les directives
 * `@tailwind` / `@layer` d'AutoPoster ne veulent rien dire hors de cette
 * chaine -- ici le fichier est servi tel quel par FastAPI, sans etape de
 * compilation. C'est tout l'interet : pas de `dist/` qui puisse etre perime
 * sans que rien ne le dise.
 *
 * ⚠️ Regle de conception, pas une option : LES TABLES TIENNENT DANS L'ECRAN,
 * sans defilement horizontal. (`.tight`, `.ellips`, `.tip` viendront avec les
 * premieres tables, au lot 1.)
 */

:root {
  --ap-bg: #0d1117;
  --ap-panel: #161b22;
  --ap-panel2: #1c2330;
  --ap-line: #2a3140;
  --ap-txt: #e6edf3;
  --ap-dim: #8b949e;
  --ap-chip: #21262d;
  --ap-accent: #4c9aff;
  --ap-ok: #3fb950;
  --ap-fail: #f85149;
  --ap-warn: #d29922;
  --ap-btntxt: #08131f;
}

html { background: var(--ap-bg); }
body {
  margin: 0;
  background: var(--ap-bg);
  color: var(--ap-txt);
  font: 14px/1.5 -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

a { color: var(--ap-accent); text-decoration: none; }
a:hover { text-decoration: underline; }

input, select, textarea {
  background: var(--ap-panel2); border: 1px solid var(--ap-line);
  color: var(--ap-txt); border-radius: 8px; padding: 7px 10px;
  font-size: 13px; outline: none; font-family: inherit;
  /* ⚠️ Un `<input>` a une largeur PROPRE (attribut `size`, ~210 px) qui ne
     descend pas toute seule : sans ces deux lignes il deborde de sa cellule
     sur un ecran etroit, et pousse la page avec lui. */
  max-width: 100%; box-sizing: border-box;
}
input:focus, select:focus, textarea:focus { border-color: var(--ap-accent); }

/* ------------------------------------------------------------ ossature */
.app { display: flex; min-height: 100vh; }

.side {
  width: 216px; flex: 0 0 216px; padding: 18px 12px;
  background: var(--ap-panel); border-right: 1px solid var(--ap-line);
  overflow: hidden; transition: width .15s ease, padding .15s ease;
}
.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

/* ⚠️ Repli du menu : classe posee sur <html>, pas sur `.side` -- le script
   anti-sursaut de `base.html` doit pouvoir l'ajouter AVANT que `.side` soit
   peinte, et il n'a alors accede qu'a `<html>` (le `<body>` n'existe pas
   encore a ce moment-la). */
html.side-collapsed .side { width: 60px; flex: 0 0 60px; padding: 18px 6px; }

.marque-row { display: flex; align-items: center; justify-content: space-between;
              gap: 6px; padding: 0 10px 10px; }
.marque { font-size: 15px; font-weight: 700; white-space: nowrap; overflow: hidden; }
.marque .ios { color: var(--ap-accent); }
.marque.gros { font-size: 19px; text-align: center; padding-bottom: 18px; }
html.side-collapsed .marque .txt { display: none; }
html.side-collapsed .marque-row { justify-content: center; padding: 0 0 10px; }

.side-toggle {
  flex: 0 0 auto; width: 24px; height: 24px; padding: 0; border-radius: 7px;
  background: transparent; border: 1px solid var(--ap-line); color: var(--ap-dim);
  font-size: 13px; font-family: inherit; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.side-toggle:hover { background: var(--ap-panel2); color: var(--ap-txt); }

/* La barre du haut porte trois zones : OU je suis, DANS QUEL ETAT est le
   systeme, QUI je suis. La zone du milieu prend la place qui reste et se
   retrecit la premiere -- c'est celle qui contient du texte sacrifiable. */
.topbar {
  position: sticky; top: 0; z-index: 30;
  display: flex; align-items: center; gap: 14px;
  padding: 10px 22px; border-bottom: 1px solid var(--ap-line);
  background: var(--ap-bg);
}
/* ⚠️ `flex: 0 0 auto` ne cede JAMAIS, et c'est ce qui debordait. Mesure du
 * 2026-09-07 : sur `/comptes/<id>`, dont le fil d'Ariane porte le nom du
 * compte, la barre du haut faisait 403 px pour une fenetre de 390 -- les
 * pastilles d'etat sortaient de l'ecran. Sur `/alertes`, fil plus court, rien
 * ne debordait : le defaut ne se voyait donc que sur certaines pages, ce qui
 * est la meilleure facon de ne jamais etre reproduit.
 *
 * ⚠️ Anterieur au chantier des alertes, trouve en le mesurant. `min-width: 0`
 * est le complement indispensable de `overflow: hidden` dans un conteneur
 * flex : sans lui, la taille minimale d'un element flex reste sa taille de
 * contenu, et il refuse de retrecir quoi qu'on ecrive d'autre.
 */
.fil { font-size: 14px; font-weight: 600; flex: 0 1 auto;
       min-width: 0; overflow: hidden; text-overflow: ellipsis;
       white-space: nowrap; }
.etat-zone { flex: 1 1 auto; min-width: 0; display: flex; justify-content: center; }
.qui { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; }
.inline { display: inline; margin: 0; }

.contenu { padding: 22px; min-width: 0; }

/* ---------------------------------------------------------------- menu */
.navgrp {
  font-size: 11px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--ap-dim); margin: 16px 10px 5px; font-weight: 600;
}
/* Le groupe Outils se plie (2026-10-15) : plie par defaut, deplie s'il
   contient la page ouverte. Menu reduit en icones : tout reste visible. */
button.navgrp-pli { display: flex; align-items: center; justify-content: space-between;
  width: calc(100% - 20px); background: none; border: 0; padding: 0; cursor: pointer;
  font-family: inherit; text-align: left; }
button.navgrp-pli:hover { color: var(--ap-txt); }
button.navgrp-pli:focus-visible { outline: 2px solid var(--ap-accent); outline-offset: 2px; }
.pli-fleche { transition: transform .15s; }
html.outils-plies .pli-fleche { transform: rotate(-90deg); }
html.outils-plies .navgrp-items:not(.ici) { display: none; }
html.side-collapsed .navgrp-items { display: block !important; }
.navitem {
  display: flex; align-items: center; gap: 10px; width: 100%;
  color: var(--ap-dim); padding: 8px 10px; border-radius: 9px;
  font-size: 13.5px; font-weight: 500; box-sizing: border-box;
  transition: background .12s, color .12s;
}
a.navitem:hover { background: var(--ap-panel2); color: var(--ap-txt);
                  text-decoration: none; }
.navitem.active { background: rgba(76,154,255,.14); color: var(--ap-accent); }
/* Un ecran non livre n'est pas un lien mort : il se voit, et il se dit. */
.navitem.soon-item { opacity: .55; cursor: default; }
.navitem .ico { flex: 0 0 18px; font-size: 15px; line-height: 1; text-align: center; }
.navitem .lbl { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.navitem .tag {
  margin-left: auto; font-size: 10px; padding: 1px 7px; border-radius: 20px;
  background: rgba(210,153,34,.15); color: var(--ap-warn); font-weight: 600;
}
/* Replie : l'icone seule, centree, avec l'infobulle `title` du lien pour
   nommer l'ecran -- rien d'invente, `libelle` est deja porte par le lien. */
html.side-collapsed .navgrp { display: none; }
html.side-collapsed .navitem { position: relative; justify-content: center; gap: 0; padding: 8px 0; }
html.side-collapsed .navitem .lbl,
html.side-collapsed .navitem .tag { display: none; }
/* Le compteur d'alertes reste visible replie -- en pastille sur l'icone,
   pas dans le texte qui a disparu avec le reste du libelle. */
html.side-collapsed .navitem .tag.alerte {
  display: block; position: absolute; top: 2px; right: 14px;
  margin: 0; padding: 0; min-width: 14px; height: 14px; line-height: 14px;
  font-size: 9px; text-align: center; border-radius: 50%;
}

/* ------------------------------------------------------------- panneaux */
.panel {
  background: var(--ap-panel); border: 1px solid var(--ap-line);
  border-radius: 14px; padding: 20px 22px; margin-bottom: 22px;
  /* ⚠️ Sans ceci, une `min-width` posee sur un panneau vaut pour son
     CONTENU : le panneau fait alors cette largeur PLUS ses 44 px de padding,
     et deborde de son conteneur d'exactement le padding. Un debordement de
     46 px sur l'ecran Reels venait de la, et de rien d'autre. */
  box-sizing: border-box;
}
.panel h2 { font-size: 15px; margin: 0 0 16px; font-weight: 600; }
.muted { color: var(--ap-dim); }

/* -------------------------------------------------------------- boutons */
.btn {
  background: var(--ap-accent); border: none; color: var(--ap-btntxt);
  font-weight: 700; padding: 9px 16px; border-radius: 9px; cursor: pointer;
  font-size: 13px; font-family: inherit;
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
}
.btn.big { width: 100%; margin-top: 16px; }
.btn:disabled { opacity: .5; cursor: default; }
.mini {
  background: var(--ap-panel2); border: 1px solid var(--ap-line);
  color: var(--ap-txt); border-radius: 7px; padding: 5px 10px;
  font-size: 12px; cursor: pointer; font-family: inherit;
  transition: border-color .12s, color .12s;
}
.mini:hover { border-color: var(--ap-accent); color: var(--ap-accent); }
/* L'onglet choisi (les téléphones de la colonne Comptes, 2026-09-22) : un
   bouton `.mini` qui dit son état par `aria-pressed`, lu aussi par un
   lecteur d'écran. */
.mini[aria-pressed="true"] { border-color: var(--ap-accent);
                             color: var(--ap-accent); font-weight: 700; }

/* ------------------------------------------------- badges, chips, pastilles */
.badge { display: inline-block; padding: 2px 9px; border-radius: 20px;
         font-size: 12px; font-weight: 600; }
.b-ok   { background: rgba(63,185,80,.15);  color: var(--ap-ok); }
.b-fail { background: rgba(248,81,73,.15);  color: var(--ap-fail); }
.b-run  { background: rgba(76,154,255,.15); color: var(--ap-accent); }
.b-wait { background: rgba(210,153,34,.15); color: var(--ap-warn); }
.chip { display: inline-block; padding: 1px 8px; border-radius: 20px;
        background: var(--ap-chip); color: var(--ap-dim); font-size: 11px; }
.pill { display: inline-flex; align-items: center; gap: 7px; padding: 5px 12px;
        border-radius: 20px; background: var(--ap-chip); font-size: 13px; }
.dot { width: 9px; height: 9px; border-radius: 50%; background: var(--ap-dim); }
.dot.on   { background: var(--ap-ok); box-shadow: 0 0 8px var(--ap-ok); }
.dot.off  { background: var(--ap-fail); }
.dot.halt { background: var(--ap-warn); }

label.f { display: block; font-size: 12px; color: var(--ap-dim);
          margin: 12px 0 4px; }
.big { width: 100%; box-sizing: border-box; }

/* ------------------------------------------------------------ connexion */
.centre { display: flex; align-items: center; justify-content: center;
          min-height: 100vh; padding: 20px; }
.carte-connexion { width: 100%; max-width: 340px; margin: 0; }
.erreur {
  background: rgba(248,81,73,.12); border: 1px solid rgba(248,81,73,.35);
  color: var(--ap-fail); border-radius: 9px; padding: 9px 12px;
  font-size: 13px; margin: 0 0 4px;
}

/* ================================================================
 * LOTS 1 à 3 — tables, badges « pourquoi », chips de filtre.
 *
 * ⚠️ CONTRAINTE DE CONCEPTION, PAS UNE OPTION : les tables tiennent dans
 * l'écran, SANS défilement horizontal — une barre de défilement en bas de
 * page coupe des colonnes entières hors de vue, et on lit alors une ligne
 * sans savoir ce qui manque à droite. Quatre leviers, aucun ne retire une
 * information : le texte VA À LA LIGNE au lieu de pousser la table (règle
 * `th, td` ci-dessous), padding compact (`.tight`), colonne longue coupée
 * avec le texte entier dans l'infobulle (`.ellips`), et la raison d'un échec
 * repliée dans le badge lui-même plutôt que dans une colonne de plus.
 *
 * ⚠️ Le levier du repli n'est pas cosmétique : la colonne WebDriverAgent
 * porte des PHRASES du superviseur, dont la longueur ne se décide pas ici.
 * Tant qu'elles étaient en `nowrap`, la table s'élargissait de tout ce que le
 * superviseur avait à dire — dire plus rendait l'écran moins lisible.
 * ================================================================ */

table { width: 100%; border-collapse: collapse; font-size: 13px; }
th, td { text-align: left; padding: 11px 13px;
         border-bottom: 1px solid var(--ap-line);
         white-space: normal; overflow-wrap: break-word;
         vertical-align: top; }

/* ⚠️ CE QUI NE VA JAMAIS A LA LIGNE, et pourquoi la liste est courte.
   Une pastille coupee de son libelle (« ● » sur une ligne, « en ligne » sur
   la suivante) ne se lit plus, et un bouton casse en deux n'a plus la forme
   d'un bouton. Tout le reste — phrases du superviseur, IPv6, identifiants de
   conteneur — se replie plutot que d'elargir la table. */
.chip, .badge, .btn, .mini, .num, .nowrap { white-space: nowrap; }

/* ⚠️ LA PASTILLE EST LA SEULE EXCEPTION A L'EXCEPTION, et c'est mesure : elle
   porte les libelles les plus longs de l'ecran (« WebDriverAgent ne repond
   pas », « image de developpement absente ») et c'est elle qui fixait la
   largeur minimale de deux colonnes. En `inline-flex`, son texte se replie
   SANS quitter sa pastille : le point reste a gauche, centre, et le libelle
   passe a la ligne a cote. Rien n'est coupe de rien. */
.pill { max-width: 100%; white-space: normal; }
th { color: var(--ap-dim); font-weight: 600; font-size: 11px;
     text-transform: uppercase; letter-spacing: .05em; }
tbody tr { transition: background .12s; }
tbody tr:hover { background: var(--ap-panel2); }
tbody tr:last-child td { border-bottom: none; }

/* 9 px -> 7 px le 2026-09-06 : douze colonnes, c'est vingt-quatre paddings.
   Deux pixels de moins sur chacun rendent 48 px a la table -- de quoi tenir
   dans un portable de 1280 px sans rien retirer de l'ecran. */
.tight th, .tight td { padding: 7px; font-size: 12.5px; }
.tight th { font-size: 10.5px; }
.num { text-align: right; font-variant-numeric: tabular-nums; }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }

/* ⚠️ `break-word` PARTOUT, `anywhere` ICI SEULEMENT -- et la difference n'est
   pas cosmetique. `anywhere` autorise le navigateur a couper N'IMPORTE OU, y
   compris pour CALCULER la largeur minimale d'une colonne : mis sur toutes
   les cellules, il a donne « VER / SIO / N » et « ja / ma / is ». Une colonne
   reduite a une lettre par ligne tient dans l'ecran, et ne se lit plus.
   `break-word` garde la largeur du mot le plus long comme minimum, ce qui est
   exactement ce qu'on veut -- sauf pour les jetons SANS espace (IPv6,
   identifiant de conteneur) qui, eux, elargiraient la table a eux seuls. */
.mono, .wrapcell, .whyraw, .usercell { overflow-wrap: anywhere; }

/* ⚠️ LE PLANCHER VA AVEC `anywhere`, il n'est pas decoratif. Sans lui, la
   colonne des usernames pouvait etre reduite a une lettre par ligne -- c'est
   la faute « VER / SIO / N » du 2026-09-06, dans laquelle la mesure passe au
   vert pendant que l'ecran devient illisible. 92 px tiennent une douzaine de
   caracteres, donc deux lignes pour un username d'Instagram, jamais douze. */
.usercell { min-width: 92px; }

/* ⚠️ Un plafond PLUS BAS que `.ellips`, et seulement pour l'identifiant de
   conteneur. Mesure du 2026-09-06, cinquante comptes aux valeurs reelles :
   cette seule colonne pesait 164 px et debordait la page a 1280. Tronquer y
   est sans perte -- le titre porte l'identifiant entier, et c'est le NOM du
   conteneur, juste au-dessus, qui sert a lire. Le plafond de `.ellips` reste
   a 150 px partout ailleurs : la colonne Telephone, elle, n'a pas
   d'infobulle, et y couper enleverait vraiment quelque chose. */
.ellips.uuid { max-width: 118px; }

/* Un nom long est coupé proprement au lieu de pousser la table vers la
   droite ; l'infobulle donne le texte entier. */
/* ⚠️ 190 px -> 150 px le 2026-09-06, apres mesure : c'est un PLAFOND, donc
   la colonne prend exactement cette largeur meme quand son contenu est court.
   Sur l'ecran Comptes, l'identifiant de conteneur elide reservait 208 px avec
   son padding -- a lui seul, il faisait deborder un ecran de 1280 px. Le
   texte entier reste dans l'infobulle : on n'a rien perdu. */
.ellips { display: block; max-width: 150px; overflow: hidden;
          text-overflow: ellipsis; white-space: nowrap; }
.wrapcell { white-space: normal; word-break: break-word; max-width: 420px; }

.row { display: flex; flex-wrap: wrap; gap: 18px; }
.row > .panel { flex: 1; min-width: min(320px, 100%); }

.empty { color: var(--ap-dim); padding: 34px 24px; text-align: center;
         font-size: 13px; }

/* ------------------------------------------ le badge « pourquoi ? »
 * §42 d'AutoPoster : LA RAISON D'UN ÉCHEC EST LISIBLE SUR LA LIGNE, et le
 * message d'origine s'affiche TEL QUEL — sélectionnable, jamais reformulé.
 * C'est ce texte qu'on recopie.
 *
 * `<details>` est natif : pas une ligne de JavaScript. */
details.why { display: inline-block; position: relative; }
details.why > summary { list-style: none; cursor: pointer; }
details.why > summary::-webkit-details-marker { display: none; }
.why-sum { display: inline-flex; align-items: center; gap: 4px; }
.whyi { opacity: .55; }
details.why[open] > summary { outline: 1px solid var(--ap-accent);
                              outline-offset: 1px; border-radius: 20px; }
.whypop {
  position: absolute; z-index: 40; top: calc(100% + 6px); left: 0;
  min-width: min(260px, 90vw); max-width: min(460px, 90vw);
  background: var(--ap-panel2); border: 1px solid var(--ap-line);
  border-radius: 10px; padding: 10px 12px;
  box-shadow: 0 14px 34px rgba(0, 0, 0, .55);
  white-space: normal;
}
.whylab { font-size: 13px; font-weight: 600; margin-bottom: 7px; }
.whyraw {
  margin: 0; padding: 7px 8px; max-height: 220px; overflow: auto;
  background: var(--ap-bg); border: 1px solid var(--ap-line);
  border-radius: 8px; color: var(--ap-txt); font-size: 12px; line-height: 1.45;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  white-space: pre-wrap; overflow-wrap: anywhere; user-select: text;
}
.whypop table th { text-transform: none; letter-spacing: 0; font-size: 11px; }
.whypop table td, .whypop table th { padding: 4px 8px; }

/* ---------------------------------------- un panneau replie dans un panneau
 * Le reglage du scroll autour de la publication (2026-09-20) : il a de
 * bonnes valeurs par defaut et ne demande rien. Deplie a cote du bouton qui
 * publie, il inviterait a regler avant chaque lot ce qui n'a pas besoin de
 * l'etre ; replie, il reste a un clic de qui le cherche. */
details.replie { border: 1px solid var(--ap-line); border-radius: 10px;
                 padding: 10px 12px; background: var(--ap-panel2); }
details.replie > summary { cursor: pointer; }
details.replie[open] > summary { margin-bottom: 4px; }

/* ------------------------------------------------- l'aide ⓘ d'un champ
 * Wilfried, 2026-09-19 : chaque champ de la fourchette doit dire ce que son
 * nombre fait sur le telephone. Meme mecanique que le badge « pourquoi ? »
 * ci-dessus, et pour la meme raison : `<details>` est natif, donc l'aide
 * s'ouvre meme si le JavaScript de la page est tombe.
 *
 * ⚠️ Le ⓘ est SUR LA LIGNE DU LIBELLE, pas sous elle : `label.f` est un bloc
 * (il porte sa propre marge), donc les deux vivent dans un `.champ-titre`
 * qui reprend la marge a son compte. Sans ca, l'icone tombait a la ligne et
 * poussait le champ vers le bas, un champ sur deux. */
.champ-titre { display: flex; align-items: baseline; gap: 6px;
               margin: 12px 0 4px; }
.champ-titre label.f { margin: 0; }
details.aide > summary { font-size: 13px; opacity: .55; cursor: help;
                         line-height: 1; }
details.aide > summary:hover { opacity: 1; }
details.aide[open] > summary { opacity: 1; color: var(--ap-accent); }
details.aide .whypop { font-size: 13px; line-height: 1.5;
                       color: var(--ap-txt); font-weight: normal; }

/* La lecture en clair d'une probabilite (« environ 1 Reel sur 7 »), ecrite
 * par `fourchette.js` sous le champ. Vide tant que le JavaScript n'a pas
 * parle : une ligne qui promet une lecture et reste blanche inquiete plus
 * qu'elle n'aide. */
.champ-lecture { margin: 4px 0 0; font-size: 12px; }
.champ-lecture:empty { display: none; }

/* ----------------------------------------------- chips de filtre */
.tools { display: flex; gap: 10px; margin-bottom: 14px; flex-wrap: wrap;
         align-items: center; }
.period { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 6px;
          padding: 3px 8px; background: var(--ap-panel2);
          border: 1px solid var(--ap-line); border-radius: 10px; }
.period .lbl { font-size: 11px; font-weight: 600; letter-spacing: .06em;
               text-transform: uppercase; color: var(--ap-dim);
               margin-right: 2px; }
.pchip { border: 1px solid transparent; color: var(--ap-dim); font-size: 12px;
         padding: 4px 9px; border-radius: 7px; display: inline-flex;
         align-items: center; gap: 6px; }
a.pchip:hover, button.pchip:hover { color: var(--ap-txt); background: rgba(127,127,127,.12);
                text-decoration: none; }
.pchip.active { background: rgba(76,154,255,.16); border-color: var(--ap-accent);
                color: var(--ap-accent); }
button.pchip { font-family: inherit; background: none; cursor: pointer; }
/* Les pastilles de TÉLÉPHONE du suivi des tâches (2026-10-04) : même dessin
   que `.pchip`, sous un autre nom parce qu'on peut en allumer plusieurs. */
.tchip { border: 1px solid transparent; color: var(--ap-dim); font-size: 12px;
         padding: 4px 9px; border-radius: 7px; display: inline-flex;
         align-items: center; gap: 6px; }
button.tchip { font-family: inherit; background: none; cursor: pointer; }
a.tchip:hover, button.tchip:hover { color: var(--ap-txt); background: rgba(127,127,127,.12);
                text-decoration: none; }
.tchip.active { background: rgba(76,154,255,.16); border-color: var(--ap-accent);
                color: var(--ap-accent); }
/* LE TYPE D'UNE TÂCHE (2026-10-04) : une couleur par type, toujours la même. */
.badge.t-reel   { background: rgba(76,154,255,.16);  color: #4c9aff; }
.badge.t-story  { background: rgba(219,97,162,.18);  color: #e377b5; }
.badge.t-scroll { background: rgba(210,153,34,.16);  color: var(--ap-warn); }
.badge.t-warm   { background: rgba(240,136,62,.16);  color: #f0883e; }
.badge.t-setup  { background: rgba(63,185,80,.15);   color: var(--ap-ok); }
.badge.t-vues   { background: rgba(163,113,247,.16); color: #a371f7; }
.sel-barre button[disabled] { opacity: .45; cursor: default; }

/* Colonnes triables (lot « File d'attente », 2026-09-03) : un triangle
   discret indique la colonne et le sens actifs, jamais visible ailleurs.

   ⚠️ PAS de `white-space: nowrap` ici, et il y en avait un jusqu'au
   2026-09-06. Il ne coûtait rien sur la file d'attente, dont les titres font
   un mot (« Compte », « Essais »). Rendus triables, les titres de l'écran
   Comptes en font deux ou trois, et un titre qui ne se replie pas ÉLARGIT la
   table, qui pousse la page : mesuré à +106 px à 1280 px de large. C'est
   exactement la cause de juin, `nowrap` sur `th, td`, revenue par une autre
   porte. Un titre se replie ; seul le triangle reste collé à son dernier mot. */
th[data-tri] { cursor: pointer; user-select: none; }
th[data-tri] .tri-ind { white-space: nowrap; }
th[data-tri]:hover { color: var(--ap-txt); }
th[data-tri] .tri-ind { font-size: 9px; margin-left: 3px; color: var(--ap-accent); }

/* ------------------------------------------------- jauge batterie */
.bar { height: 7px; border-radius: 6px; background: var(--ap-line);
       overflow: hidden; width: 52px; display: inline-block;
       vertical-align: middle; margin-right: 6px; }
.bar > i { display: block; height: 100%; }

/* Une ligne d'alerte dans la trace d'un run se voit sans qu'on la cherche. */
.ligne-alerte { background: rgba(248,81,73,.07); }

/* Le badge du menu compte les alertes OUVERTES — dérivé, jamais stocké. */
.navitem .tag.alerte { background: rgba(248,81,73,.18); color: var(--ap-fail); }

/* --------------------------------------------- formulaires (lot 4+) */
select.big, input.big { box-sizing: border-box; }
.cell { width: 34px; }
input[type=checkbox] { accent-color: var(--ap-accent); width: 15px;
                       height: 15px; cursor: pointer; vertical-align: middle; }
.panel form .row > div { margin-bottom: 6px; }

/* --------------------------------------------------- compteurs (KPI) */
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
        gap: 12px; margin-bottom: 20px; }
.kpi { background: var(--ap-panel); border: 1px solid var(--ap-line);
       border-radius: 14px; padding: 14px 16px; }
.kpi-v { font-size: 24px; font-weight: 700; }
.kpi-k { color: var(--ap-dim); font-size: 11px; text-transform: uppercase; }
.kpi-s { color: var(--ap-accent); font-size: 12px; }
.btn.danger { background: var(--ap-fail); }

/* ==================================================================
 * PASSE DE DESIGN — 2026-09-05
 *
 * Demande de Wilfried : « l'interface est trop bizarre, pas fluide, pas
 * intuitive ». Le vocabulaire visuel n'était pas en cause : il vient déjà
 * d'AutoPoster Android et il est bon. Ce qui manquait tient en quatre points,
 * et chacun était une gêne réelle, pas une préférence :
 *
 *   1. AUCUN ÉTAT GLOBAL. « Est-ce que je peux poster maintenant ? » n'avait
 *      de réponse que sur *Téléphones*. La barre du haut la porte désormais
 *      partout, et elle est vivante.
 *   2. SOMBRE UNIQUEMENT. Trois états maintenant : système, clair, sombre.
 *   3. LE CLAVIER ÉTAIT INVISIBLE. Aucun `:focus-visible` nulle part.
 *   4. RIEN NE TENAIT EN PLACE. Barre du haut et en-têtes de table
 *      disparaissaient dès qu'on faisait défiler une longue liste.
 * ================================================================== */

/* ------------------------------------------------------------- thèmes
 * 🔑 LE SOMBRE EST LE DÉFAUT, et il ne dépend de RIEN. Décision de Wilfried,
 * 2026-09-05 : « thème noir par défaut ».
 *
 * Il y avait avant un `@media (prefers-color-scheme: light)` qui basculait
 * l'interface en clair quand le SYSTÈME était réglé en clair. C'était une
 * bonne idée pour un site public et une mauvaise ici : cet écran se regarde
 * toute la journée, l'habitude est sombre, et un poste changé de réglage
 * aurait fait blanchir l'outil sans que personne l'ait demandé. Un défaut
 * tenu par un réglage extérieur est un défaut qui change tout seul.
 *
 * Reste donc DEUX états, tous deux explicites : sombre (le défaut) et clair
 * (choisi, et retenu dans `localStorage`).
 *
 * ⚠️ Les couleurs de SENS (ok / fail / warn / accent) changent de valeur mais
 * jamais de rôle : le vert reste le vert. Ce sont des couleurs qui informent,
 * pas des couleurs de surface. */
:root[data-theme="clair"] {
  --ap-bg: #ffffff;      --ap-panel: #f6f8fa;  --ap-panel2: #eef1f4;
  --ap-line: #d0d7de;    --ap-txt: #1f2328;    --ap-dim: #59636e;
  --ap-chip: #eaeef2;    --ap-accent: #0969da; --ap-ok: #1a7f37;
  --ap-fail: #cf222e;    --ap-warn: #9a6700;   --ap-btntxt: #ffffff;
  --ap-ombre: rgba(31, 35, 40, .10);
}
:root { --ap-ombre: rgba(0, 0, 0, .45); }
html, body { transition: background .15s, color .15s; }

/* --------------------------------------------------------- le clavier
 * Un anneau de focus VISIBLE, partout, et seulement au clavier
 * (`:focus-visible`, jamais `:focus` : au clic il serait du bruit).
 * Sans lui, naviguer au clavier revenait à cliquer les yeux fermés. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--ap-accent);
  outline-offset: 2px;
  border-radius: 6px;
}

/* ------------------------------------------------- la barre d'état
 * Elle vit dans la barre du haut, sur tous les écrans. Trois éléments, du
 * plus important au plus sacrifiable : le VERDICT (jamais coupé), le DÉTAIL
 * (coupé en premier, entier dans l'infobulle), les COMPTEURS. */
.etat { display: flex; align-items: center; gap: 10px; min-width: 0; }
.etat-verdict { flex: 0 0 auto; font-weight: 600; }
.etat-detail {
  flex: 1 1 auto; min-width: 0; font-size: 12.5px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.etat-chips { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; }
.etat-chips .chip { padding: 3px 9px; font-size: 11.5px; }
a.chip { text-decoration: none; transition: border-color .12s, color .12s; }
a.chip:hover { color: var(--ap-txt); text-decoration: none; }
.chip-run { background: rgba(76,154,255,.16); color: var(--ap-accent); }
.chip-alerte { background: rgba(248,81,73,.16); color: var(--ap-fail); }

/* Le verdict porte sa couleur, pas seulement sa pastille : une pastille de
   9 px ne se voit pas du coin de l'œil, et c'est précisément comme ça qu'on
   lit cette barre — sans la regarder. */
.etat-verdict:has(.dot.on)   { background: rgba(63,185,80,.14);  color: var(--ap-ok); }
.etat-verdict:has(.dot.off)  { background: rgba(248,81,73,.14);  color: var(--ap-fail); }
.etat-verdict:has(.dot.halt) { background: rgba(210,153,34,.14); color: var(--ap-warn); }

/* ------------------------------------------------------------- le motif
 * Le nom de l'arret, en deux mots, lu sur la ligne sans rien deplier
 * (2026-09-08). Discret : c'est une etiquette, pas une alarme -- la pastille
 * rouge de l'etat porte deja l'alarme, et deux rouges cote a cote ne disent
 * pas deux fois plus.
 */
.motif {
  color: var(--ap-dim); font-size: 12px; margin-top: 3px;
  overflow-wrap: break-word;
}

/* ------------------------------------------------------------- la modale
 *
 * Reprise d'AutoPoster Android (Wilfried, 2026-09-08) : le detail s'ouvre
 * PAR-DESSUS la page, pas en dessous de la liste. En dessous, on ouvrait une
 * tache et il fallait descendre pour la lire.
 *
 * 🔑 ELLE NE CHANGE RIEN AU FOND : le lot ouvert vit toujours dans l'URL
 * (`?lot=`), le serveur rend toujours la page entiere, et fermer reste un
 * LIEN vers `/activite`. Rien ici ne depend de JavaScript -- ni l'ouverture,
 * ni la fermeture, ni le partage d'un lien vers un lot precis, ni le retour
 * arriere du navigateur.
 *
 * Le fond est un `<a>` FRERE de la carte, jamais son parent : parent, il
 * rendrait la carte entiere cliquable, et lire une ligne fermerait la vue.
 */
.modale {
  position: fixed; inset: 0; z-index: 60; overflow: auto;
  padding: 40px 16px;
  display: flex; align-items: flex-start; justify-content: center;
}
.modale-fond {
  position: fixed; inset: 0;
  background: rgba(0, 0, 0, .6);
}
.modale-carte {
  position: relative;      /* au-dessus du fond, sans z-index a surenchere */
  width: 100%; max-width: 940px; margin: 0 auto;
}
/* L'en-tete collant se colle au haut de son conteneur qui defile. Ici c'est
 * la modale, pas la page : les 52 px de la barre du haut n'y ont plus de
 * sens et laisseraient un trou. */
.modale thead th { top: 0; }

@media (max-width: 720px) {
  .modale { padding: 12px 8px; }
}

/* ------------------------------------------------- ce qui tient en place
 * Une table de cinquante lignes fait défiler son en-tête hors de l'écran, et
 * on lit alors des colonnes sans savoir lesquelles. */
.panel thead th {
  position: sticky; top: 52px; z-index: 2;
  background: var(--ap-panel);
}
.marque { text-decoration: none; color: inherit; display: block; }
a.marque:hover { text-decoration: none; color: var(--ap-accent); }

/* ------------------------------------------------------------- reliefs
 * En thème clair, une bordure seule ne détache pas un panneau du fond :
 * l'ombre fait le travail que le contraste faisait en sombre. */
.panel, .kpi { box-shadow: 0 1px 2px var(--ap-ombre); }
.panel { transition: border-color .12s; }
.btn { transition: filter .12s, transform .06s; }
.btn:hover:not(:disabled) { filter: brightness(1.08); }
.btn:active:not(:disabled) { transform: translateY(1px); }

/* --------------------------------------------------- bandeaux de message
 * Un message de succès et une erreur ne se ressemblaient pas assez : les
 * deux étaient un `.panel` gris, et un « compte remis en service » avait
 * exactement la tête d'un « mot de passe refusé ». */
.panel > p.erreur:only-child, .panel > p.muted:only-child { margin: 0; }
.bandeau {
  display: flex; align-items: flex-start; gap: 10px;
  border-radius: 12px; padding: 12px 16px; margin-bottom: 18px;
  border: 1px solid var(--ap-line); background: var(--ap-panel);
  font-size: 13px;
}
.bandeau.ok   { border-color: rgba(63,185,80,.4);  background: rgba(63,185,80,.10); }
.bandeau.mal  { border-color: rgba(248,81,73,.4);  background: rgba(248,81,73,.10); color: var(--ap-fail); }
.bandeau .ic { flex: 0 0 auto; }

/* ---------------------------------------------------------- les vides
 * Un écran vide doit dire ce qu'il attend, pas seulement qu'il est vide. */
.empty { display: flex; flex-direction: column; align-items: center; gap: 8px; }
.empty .grand { font-size: 26px; opacity: .5; }

/* ------------------------------------------------------- les compteurs */
.kpi { transition: border-color .12s; }
a.kpi { display: block; text-decoration: none; color: inherit; }
a.kpi:hover { border-color: var(--ap-accent); text-decoration: none; }
.kpi-v { line-height: 1.2; font-variant-numeric: tabular-nums; }
.kpi-k { letter-spacing: .05em; font-weight: 600; }

/* --------------------------------------------------- écrans étroits
 * En dessous de 980 px le menu se replie tout seul, sans effacer le choix
 * de l'utilisateur : c'est une règle de MISE EN PAGE, pas un réglage. Elle
 * se lève d'elle-même quand la fenêtre s'élargit. */
@media (max-width: 980px) {
  .side { width: 60px; flex: 0 0 60px; padding: 18px 6px; }
  .side .navgrp, .side .marque .txt,
  .side .navitem .lbl, .side .navitem .tag:not(.alerte) { display: none; }
  .side .navitem { justify-content: center; gap: 0; padding: 8px 0; }
  .marque-row { justify-content: center; padding: 0 0 10px; }
  .side-toggle { display: none; }
  .etat-detail { display: none; }
  .contenu { padding: 16px; }
}
/* ⚠️ EN DESSOUS DE 1280 px, LE REPLI NE SUFFIT PLUS -- et il faut le dire,
 * sinon la regle « pas de defilement horizontal » passe pour fausse.
 *
 * 🚨 LE SEUIL ETAIT A 1100 px, ET IL ETAIT TROP BAS (2026-09-08). Entre 1101
 * et 1274 px, la table de la file ne tenait deja plus dans son panneau et
 * AUCUNE regle ne la faisait defiler : c'est la PAGE qui debordait, de 22 px
 * a 1200 et de 72 px a 1150. Comptes debordait de meme. Le defaut vivait la
 * depuis le premier jour, et la sonde ne pouvait pas le voir : sa liste de
 * largeurs sautait de 1280 a 1024, c'est-a-dire par-dessus toute la bande.
 * Deux largeurs de plus, et il est apparu.
 *
 * > Neuf largeurs bien choisies ne prouvent rien si le trou entre deux
 * > d'entre elles est exactement l'endroit ou ca casse.
 *
 * Le seuil est desormais MESURE, pas choisi : la table de la file demande
 * 987 px de largeur minimale, le panneau en offre la largeur de la fenetre
 * moins 287, donc il faut 1274 px pour la contenir. 1280 est la premiere
 * largeur courante au-dessus.
 *
 * Dix colonnes ne tiennent pas dans neuf cents pixels : la somme de leurs
 * largeurs MINIMALES depasse la fenetre, et aucune coupure de mot n'y change
 * rien. Trois issues possibles, une seule acceptable :
 *
 *   retirer des colonnes  ->  on cache une information sans le dire. Non.
 *   `overflow-x: hidden`  ->  la barre disparait, le texte avec. Non.
 *   le TABLEAU defile      ->  tout reste atteignable, et LA PAGE, elle, ne
 *                              defile jamais. C'est celle-la.
 */
@media (max-width: 1280px) {
  /* ⚠️ `> form` AJOUTE LE 2026-09-07, et ce n'est pas un detail de selecteur.
   * Le tableau « Lancer en lot » est le SEUL de l'application enveloppe dans
   * un <form> (il porte le bouton qui publie) : la regle ci-dessus ne le
   * voyait pas, et lui seul debordait donc la PAGE au lieu de defiler dans
   * son panneau. Mesure du 2026-09-07, vrai Chromium, 50 lignes realistes :
   * a 390 px, panneau 285 px, table 474 -- la barre horizontale interdite.
   *
   * ⚠️ La sonde de largeur ne pouvait pas le voir : sa passe dense vise
   * `document.querySelector('table.tight')`, c'est-a-dire la PREMIERE table
   * de la page -- `#compte_liste`, jamais `#lot_table`. Le lot n'a donc
   * jamais ete mesure qu'avec la ligne unique de la base bouchonnee, qui
   * tient partout. Corrige dans `tools/probe_largeur.py` le meme jour.
   *
   * ⚠️ Et c'est bien CETTE issue-la, pas `table-layout: fixed` : essaye le
   * 2026-09-07, il faisait tenir la table en coupant « Alexandra
   * Montgomery-Reid » lettre par lettre sur onze lignes. Ca tenait, et ca ne
   * se lisait plus -- exactement ce que la docstring de la sonde raconte
   * deja pour `overflow-wrap: anywhere`. La mesure disait oui, l'oeil disait
   * non.
   */
  .panel:has(> table),
  .panel > div:has(> table),
  .panel > form:has(> table) { overflow-x: auto; }

  /* Un conteneur qui defile CLIPPE ce qui en sort. La bulle « pourquoi »
     s'ouvre donc EN PLACE, sous le badge, au lieu de flotter au-dessus --
     sinon elle serait coupee net au bord du panneau. */
  .panel:has(> table) .whypop,
  .panel > div:has(> table) .whypop,
  .panel > form:has(> table) .whypop {
    position: static; margin-top: 6px; max-width: none;
  }

  /* L'en-tete collant se colle au haut de la PAGE. Dans un conteneur qui
     defile il se collerait au haut du conteneur, c'est-a-dire nulle part.
     ⚠️ C'est le PRIX du seuil releve : entre 1100 et 1280 px, l'en-tete
     cesse de suivre le defilement. Il se paie sur trois largeurs, contre une
     barre horizontale de PAGE qui, elle, casse toute la mise en page. */
  .panel:has(> table) thead th,
  .panel > div:has(> table) thead th,
  .panel > form:has(> table) thead th { position: static; }
}

/* ⚠️ ET ENTRE 1280 ET 1450 px, LE REPLI N'EXISTE PAS ENCORE -- il faut donc
 * que la table RETRECISSE toute seule. Mesure du 2026-09-08, file d'attente,
 * 50 lignes aux valeurs reelles : a 1366 px elle deborde de 76 px avec des
 * colonnes tronquees a 150 px, et elle tient a 110. On prend 100, pour la
 * marge.
 *
 * Le texte n'est pas perdu : ces cellules portent leur valeur entiere en
 * `title`, et la colonne se lit en entier des que la fenetre s'elargit. On
 * tronque un peu plus tot plutot que de perdre l'en-tete collant sur un
 * ecran de portable -- c'est le seul reglage qui achete les deux.
 *
 * ⚠️ Aucune valeur inferieure ne sauve 1280 px : a 90 px la page deborde
 * encore de 40. En dessous de 1280, c'est le repli qui travaille, pas la
 * troncature. Les deux regles ne se remplacent pas, elles se relaient.
 */
@media (max-width: 1450px) {
  .ellips { max-width: 100px; }
}

@media (max-width: 720px) {
  .qui-nom { display: none; }
  .topbar { padding: 8px 14px; gap: 8px; }
  .etat-chips .chip { padding: 3px 7px; }
  /* Deux panneaux cote a cote n'ont plus de place : ils se suivent, et leur
     largeur minimale ne doit plus rien imposer. */
  .row > .panel { min-width: 0; }
}

/* Le bouton de thème reprend la forme du bouton de repli : même taille, même
   place, deux commandes d'affichage côte à côte. */
#theme_toggle { font-size: 12px; }

/* ------------------------------------------------------------- accueil
 * Le verdict en grand, parce que c'est la première chose qu'on vient lire,
 * et une seule phrase en dessous : ce qui bloque, ou ce qui est prêt. */
.accueil-tete { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
.etat-verdict.gros { font-size: 15px; padding: 8px 16px; }
.accueil-detail { margin: 0; font-size: 15px; }
.accueil-tete p.muted { margin: 0; font-size: 13px; }
.actions { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 16px 0 0; }
.actions .mini { padding: 8px 12px; }

/* ------------------------------------------------- compteur de sélection
 * Trois nombres distincts, permanents, sous la recherche. Cocher cinquante
 * cases en aveugle etait la vraie gene de l'ecran Reels. */
.compteur-sel {
  display: block; margin: 0 0 10px; padding: 8px 12px;
  background: var(--ap-panel2); border: 1px solid var(--ap-line);
  border-radius: 9px; font-size: 12.5px; color: var(--ap-dim);
  font-variant-numeric: tabular-nums;
}
.compteur-sel strong { color: var(--ap-txt); font-size: 13.5px; }
.bandeau[hidden] { display: none !important; }
/* Le premier groupe du menu n'a pas d'etiquette : il ne doit pas non plus
   trainer la marge que l'etiquette portait. */
.side .navgrp:first-of-type { margin-top: 6px; }

/* Un `<input type=file>` doit rester dans le DOM pour que le clic du
   `<label>` l'atteigne : `display:none` empêche certains navigateurs
   d'ouvrir le sélecteur. On le rend invisible autrement. */
.champ-cache {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
kbd {
  font: 11px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  background: var(--ap-chip); border: 1px solid var(--ap-line);
  border-radius: 5px; padding: 1px 5px; color: var(--ap-txt);
}

/* ------------------------------------------------- envoi progressif
 * Une barre qui bouge vaut mieux qu'une page figée : elle dit que ça avance,
 * et elle dit où on en est. Le texte porte le NOM du fichier en cours et le
 * décompte -- « 7/30 envoyée(s) » sans dire lequel n'aide pas à comprendre
 * une lenteur. */
.progres { margin: 4px 0 10px; }
.progres-bar {
  height: 8px; border-radius: 6px; background: var(--ap-line);
  overflow: hidden; width: 100%;
}
.progres-bar > i {
  display: block; height: 100%; background: var(--ap-accent);
  transition: width .18s linear;
}
.progres-txt {
  margin-top: 5px; font-size: 12px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* --------------------------------------------------- aperçu des vidéos
 * Une carte par vidéo : la vidéo à gauche, ce qu'on en sait et sa légende à
 * droite. La vidéo est LOCALE (`blob:`), elle ne coûte pas un octet de
 * réseau -- c'est ce qui rend l'aperçu possible avant l'envoi. */
.apercu {
  display: flex; gap: 12px; align-items: flex-start;
  padding: 10px; margin-bottom: 8px;
  background: var(--ap-panel2); border: 1px solid var(--ap-line);
  border-radius: 11px;
}
.apercu-video {
  flex: 0 0 180px; width: 180px; max-height: 200px;
  border-radius: 8px; background: #000; display: block;
}
.apercu-illisible {
  display: flex; align-items: center; text-align: center;
  padding: 10px; font-size: 11.5px; background: var(--ap-bg);
  border: 1px dashed var(--ap-line); min-height: 100px;
}
.apercu-droite { flex: 1 1 auto; min-width: 0; }
.apercu-legende {
  margin-top: 8px; resize: vertical; min-height: 60px;
  font-family: inherit; line-height: 1.45;
}
@media (max-width: 720px) {
  .apercu { flex-direction: column; }
  .apercu-video { flex: 0 0 auto; width: 100%; }
}


/* --------------------------------------------- deux colonnes, deux défilements
 * Wilfried, 2026-09-05 : « si je défile la partie import des vidéos, je
 * voudrais que cette colonne uniquement soit défilée ». Les deux panneaux de
 * l'écran Reels sont côte à côte mais indépendants : la liste des comptes a
 * déjà sa propre zone qui défile, celle des aperçus n'en avait pas et
 * entraînait toute la page avec elle. */
#import_apercus {
  margin: 10px 0;
  /* ⚠️ « Le double » ne peut pas être pris au pied de la lettre (Wilfried,
   * 2026-09-06) : 62vh doublés font 124vh, c'est-à-dire une zone plus haute
   * que l'écran — elle recommencerait à entraîner la page, exactement ce que
   * le défilement séparé venait de corriger.
   *
   * On prend donc TOUT ce qui reste réellement disponible : la hauteur de la
   * fenêtre moins la barre du haut et le haut du panneau. En pratique cela
   * fait passer la zone de ~62vh à ~85vh, et surtout cela ne laisse plus un
   * seul pixel inutilisé — c'était là le vrai gaspillage, pas la valeur. */
  max-height: calc(100vh - 165px);
  min-height: 320px;
  overflow-y: auto; overscroll-behavior: contain;
  padding-right: 4px;

  /* 🔑 PLUS DE CARTES AVANT DE DEVOIR DÉFILER, et pas des cartes plus
   * grandes. Wilfried, 2026-09-06, après une première lecture de travers de
   * ma part.
   *
   * La zone était déjà à sa hauteur maximale : la seule marge restante était
   * la LARGEUR, et elle était gaspillée. Une carte occupait toute la colonne
   * pour une vidéo de 180 px et un champ de légende étiré sur 600 -- personne
   * n'écrit une légende sur une seule ligne de six cents pixels.
   *
   * En grille, la même carte à la même taille tient en deux exemplaires par
   * rangée sur un écran large : **deux fois plus de vidéos visibles, sans
   * rien rétrécir**. `auto-fill` fait le calcul tout seul -- une colonne sur
   * un écran étroit, trois sur un très large -- donc aucun point de bascule
   * à choisir, et rien à revoir le jour où l'écran change. */
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(360px, 100%), 1fr));
  gap: 8px;
  align-content: start;
}

/* ⚠️ La ligne de tête et la barre d'envoi traversent TOUTE la grille. Sans
   ça elles deviendraient des cases parmi les autres, et « Envoyer » se
   retrouverait à côté d'une vidéo. */
#import_apercus > .apercu-tete,
#import_apercus > .tools { grid-column: 1 / -1; }

/* En grille, la marge du bas de chaque carte est portée par `gap`. */
#import_apercus > .apercu { margin-bottom: 0; }
/* La barre « Envoyer / Annuler » ne doit PAS partir hors de vue quand on
   fait défiler dix-neuf aperçus : elle reste collée en bas de la zone. */
#import_apercus .tools {
  position: sticky; bottom: 0; z-index: 2;
  background: var(--ap-panel); margin: 0; padding: 10px 0 2px;
  border-top: 1px solid var(--ap-line);
}

/* Le bouton d'agrandissement. Le lecteur natif replie ses commandes quand il
   est étroit -- à 180 px, le plein écran finit dans un menu « ⋮ » que
   personne ne va chercher. Un bouton visible vaut mieux qu'une commande
   cachée dans un menu du navigateur. */
.apercu-agrandir { margin-top: 6px; }
.apercu-haut { display: flex; align-items: baseline; gap: 8px; }

/* La ligne de tête des aperçus : ce qu'on envoie, et de quoi replier les
   vidéos. Collée en haut de la zone qui défile — sinon le bouton part hors
   de vue au deuxième aperçu, et il est justement là pour ça. */
.apercu-tete {
  position: sticky; top: 0; z-index: 2;
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
  background: var(--ap-panel); padding: 2px 0 10px;
  border-bottom: 1px solid var(--ap-line); margin-bottom: 10px;
}
.apercu-tete .muted { font-size: 13px; }

/* ⚠️ REPLIER N'EST PAS SUPPRIMER. Les cartes restent — nom, compte, légende —
   et seule la vidéo disparaît. C'est le mode « je ne fais qu'écrire des
   légendes » : dix-neuf vidéos à 200 px de haut, ce sont quatre mètres de
   défilement pour taper trois lignes. */
.apercus-replies .apercu-video,
.apercus-replies .apercu-agrandir { display: none; }
.apercus-replies .apercu { padding: 8px 10px; }
.apercus-replies .apercu-legende { min-height: 44px; }

/* ------------------------------------------- le menu replié : de VRAIES icônes
 * Wilfried, 2026-09-06 : « au repli, les icônes sont trop petites ».
 *
 * Elles gardaient les 15 px qu'elles ont à côté d'un libellé — une taille
 * pensée pour ACCOMPAGNER un mot, pas pour le remplacer. Replié, l'icône
 * n'est plus une décoration : c'est le seul nom de l'écran, et la seule
 * cible du clic. Elle doit être lisible et facile à viser.
 *
 * ⚠️ Les deux replis, pas un. Le menu se replie de deux façons — par le
 * bouton (`html.side-collapsed`, un choix qui se retient) et par la largeur
 * de la fenêtre (une règle de mise en page). Ne traiter que le premier
 * laisserait des icônes minuscules sur un écran étroit, où l'on vise déjà
 * moins bien. */
html.side-collapsed .navitem .ico,
.side.replie-large .navitem .ico { font-size: 22px; flex: 0 0 auto; }
html.side-collapsed .navitem,
.side.replie-large .navitem { padding: 11px 0; border-radius: 11px; }
html.side-collapsed .marque-row,
.side.replie-large .marque-row { padding-bottom: 14px; }

/* Le compteur d'alertes suit l'icône : posé pour une pastille de 15 px, il
   se retrouvait au milieu d'une de 22. */
html.side-collapsed .navitem .tag.alerte,
.side.replie-large .navitem .tag.alerte {
  top: 4px; right: 9px; min-width: 16px; height: 16px; line-height: 16px;
  font-size: 10px;
}

@media (max-width: 980px) {
  .side .navitem .ico { font-size: 22px; flex: 0 0 auto; }
  .side .navitem { padding: 11px 0; border-radius: 11px; }
  .side .marque-row { padding-bottom: 14px; }
  .side .navitem .tag.alerte {
    top: 4px; right: 9px; min-width: 16px; height: 16px; line-height: 16px;
    font-size: 10px;
  }
}

/* ------------------------------------- les deux colonnes de geste du lot
 * Ajoutees le 2026-09-07 avec le retrait de lignes. Elles ne portent qu'une
 * case et un bouton : sans largeur imposee, le tableau leur donnait la meme
 * part qu'a une legende et repoussait le reste vers le debordement -- la
 * regle « jamais de barre horizontale » (Wilfried, 2026-09-04) se perd
 * toujours par une colonne qui prend plus que ce qu'elle montre.
 */
.colmarque { width: 40px; white-space: nowrap; text-align: center; }
.colmarque .mini { padding: 3px 8px; line-height: 1; }

/* ------------------------------------------- la raison d'un arret de compte
 * Posee le 2026-09-07 avec la page d'une alerte et l'historique d'un compte.
 *
 * ⚠️ C'EST UN PLAFOND, ET IL EST INDISPENSABLE DANS UNE TABLE. Le message
 * d'echec est du texte libre : il fait 250 caracteres pour une
 * PUBLICATION_INCERTAINE. `.whyraw` le replie bien (`white-space: pre-wrap`),
 * mais en `table-layout: auto` une colonne prend la largeur MAX-CONTENT de ce
 * qu'elle contient, c'est-a-dire la ligne DEPLIEE. Mesure du 2026-09-07 :
 * colonne « Raison » a 1773 px, page a 2460, debordement a TOUTES les
 * largeurs -- 1920 compris.
 *
 * Le plafond retire ce message du calcul de la colonne. `min(46ch, 100%)`
 * plutot qu'une valeur en pixels : 46 caracteres est une longueur de ligne
 * qui se lit, et le `100%` empeche de depasser un conteneur plus etroit
 * (l'infobulle `.whypop`, qui a deja son propre maximum).
 *
 * ⚠️ C'est l'inverse exact du piege de `.ellips { max-width: 150px }`, qui
 * imposait un MINIMUM a la table du lot le meme jour. Le meme mot, `max-width`,
 * fait l'un ou l'autre selon que le contenu se replie ou non.
 */
.raison { max-width: min(46ch, 100%); }

/* Une fiche « intitule / valeur » : la colonne de gauche ne prend que sa
 * place, au lieu de la moitie du panneau. `width: 1%` est l'idiome des tables
 * en `table-layout: auto` -- une largeur qu'on ne peut pas satisfaire, donc le
 * navigateur donne le minimum, c'est-a-dire le contenu.
 */
.fiche th { width: 1%; white-space: nowrap; }

/* ---------------------------------------------- le calendrier des lots
 * Pose le 2026-09-07 avec l'ecran Activite, sur le modele d'AutoPoster
 * Android.
 *
 * ⚠️ SEPT COLONNES EGALES, ET QUI TIENNENT DANS L'ECRAN. `minmax(0, 1fr)`
 * plutot que `1fr` : la taille minimale d'une piste de grille vaut
 * `min-content` par defaut, donc une case dont le contenu ne se replie pas
 * (« 3 lots ») elargit la grille AU-DELA du panneau. C'est exactement la
 * barre horizontale qu'on s'interdit depuis le 2026-09-04, et elle se
 * gagnerait ici sur une seule journee chargee.
 */
.cal-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
            gap: 5px; }
.cal-h { color: var(--ap-dim); font-size: 10.5px; text-align: center;
         padding-bottom: 2px; }
.cal-d { border: 1px solid var(--ap-line); border-radius: 8px;
         background: var(--ap-panel2); min-height: 52px; padding: 5px 6px;
         cursor: pointer; overflow: hidden; }
.cal-d:hover { border-color: var(--ap-accent); }
.cal-d .n { font-size: 12px; color: var(--ap-dim); }
.cal-d .b { margin-top: 3px; font-size: 10.5px; color: var(--ap-accent);
            background: rgba(76,154,255,.14); border-radius: 20px;
            padding: 1px 6px; display: inline-block; max-width: 100%;
            overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Un jour d'un AUTRE mois reste cliquable : il porte parfois un lot, et une
 * case qu'on voit sans pouvoir l'ouvrir apprend a douter de l'ecran. */
.cal-d.other { opacity: .38; }
.cal-d.today { border-color: var(--ap-dim); }
.cal-d.sel { border-color: var(--ap-accent); background: rgba(76,154,255,.12); }

@media (max-width: 700px) {
  .cal-d { min-height: 42px; padding: 4px; }
  .cal-d .b { padding: 1px 5px; font-size: 9.5px; }
}

/* ------------------------------------------------------- l'écran Vues
 * Étape 5 du tracking des vues (2026-09-21), sur la maquette validée par
 * Wilfried : des tuiles de chiffres, des gains qui disent leur sens par un
 * signe ÉCRIT (la couleur ne le porte jamais seule), des courbes dessinées
 * par `static/vues.js` avec les variables du thème, et des lignes qui
 * s'ouvrent d'un clic. Le tri des colonnes est celui de toutes les tables
 * (`vivant.js`, `th[data-tri] .tri-ind`). */
.vues-entete { display: flex; justify-content: space-between; align-items: center;
               gap: 12px; flex-wrap: wrap; margin-bottom: 14px; }
.fil-ariane { color: var(--ap-dim); font-size: 13px; margin: 0 0 6px; }
.titre-page { font-size: 20px; font-weight: 650; margin: 0 0 14px; }
.titre-page > .muted { font-size: 13px; font-weight: 400; margin-left: 8px; }
.legende-reel { display: inline-block; max-width: min(460px, 100%); overflow: hidden;
                text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; }
.tuiles { display: flex; gap: 14px; flex-wrap: wrap; }
.tuile { flex: 1 1 200px; min-width: 0; background: var(--ap-panel2);
         border: 1px solid var(--ap-line); border-radius: 10px; padding: 12px 14px; }
.tuile-titre, .tuile-sous { color: var(--ap-dim); font-size: 12.5px; }
.tuile-valeur { font-size: 24px; font-weight: 650; margin: 2px 0;
                font-variant-numeric: tabular-nums; }
.gain-hausse { color: var(--ap-ok); font-weight: 600; }
.gain-baisse { color: var(--ap-fail); font-weight: 600; }
.gain-stable { color: var(--ap-dim); }
/* LA GRILLE « COMME SUR INSTAGRAM » de la page d'un compte (2026-10-04) :
   un cadre de la largeur d'un téléphone, trois colonnes de vignettes en
   portrait, qui se déroule dans sa hauteur. */
.row-insta { display: flex; gap: 14px; flex-wrap: wrap; align-items: flex-start; }
/* Plus large depuis le 2026-10-04 (Wilfried : « il y a de la place ») : la
   grille prend toute la largeur, le tableau passe dessous, et les vignettes
   se rangent par autant de colonnes que la largeur en permet. */
.row-insta > .insta-panel { flex: 1 1 100%; min-width: 0; }
.row-insta > .insta-voisin { flex: 1 1 100%; min-width: 0; margin-top: 0 !important; }
.insta { width: 100%; max-height: 720px; overflow-y: auto;
         background: #000; color: #fff; border-radius: 22px;
         border: 1px solid rgba(127,127,127,.35); scrollbar-width: thin; }
.insta-entete { padding: 16px 16px 12px; position: sticky; top: 0; z-index: 2;
                background: #000; border-bottom: 1px solid rgba(255,255,255,.12); }
.insta-nom { font-weight: 700; font-size: 16px; margin-bottom: 10px; }
.insta-chiffres { display: flex; gap: 28px; }
.insta-chiffres div { display: flex; flex-direction: column; line-height: 1.2; }
.insta-chiffres strong { font-size: 15px; }
.insta-chiffres span { font-size: 12px; color: rgba(255,255,255,.7); }
.insta-grille { display: grid; gap: 2px;
                grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); }
.insta-survol { position: absolute; left: 0; right: 0; top: 0; padding: 8px 8px 22px;
                display: none; flex-direction: column; gap: 2px; font-size: 12px;
                background: linear-gradient(rgba(0,0,0,.82), rgba(0,0,0,0));
                z-index: 1; }
.insta-survol .muted { color: rgba(255,255,255,.75); }
.insta-case:hover .insta-survol { display: flex; }
.insta-case:hover .insta-marque { display: none; }
.insta-case { position: relative; display: block; aspect-ratio: 125 / 222;
              background: linear-gradient(160deg, #2a2a2e, #141416);
              color: #fff; overflow: hidden; text-decoration: none; }
.insta-case:hover { text-decoration: none; filter: brightness(1.12); }
.insta-case video { position: absolute; inset: 0; width: 100%; height: 100%;
                    object-fit: cover; }
.insta-vide { position: absolute; inset: 0; display: flex; align-items: center;
              justify-content: center; color: rgba(255,255,255,.45); font-size: 13px; }
.insta-vues { position: absolute; left: 7px; bottom: 6px; display: inline-flex;
              align-items: center; gap: 3px; font-size: 12px; font-weight: 600;
              text-shadow: 0 1px 3px rgba(0,0,0,.9); }
.insta-marque { position: absolute; right: 5px; top: 5px; font-size: 10.5px;
                font-weight: 700; padding: 1px 6px; border-radius: 10px;
                background: rgba(0,0,0,.62); color: #fff; }
.insta-marque.insta-hausse { color: #5fe07a; }
.insta-marque.insta-baisse { color: #ff7b72; }

/* La petite courbe du tableau des vues (2026-10-04) : une forme, sans axe. */
.mini-courbe { display: block; margin: 3px 0 0 auto; }
.mini-courbe polyline { fill: none; stroke: var(--ap-accent); stroke-width: 1.5;
                        stroke-linejoin: round; stroke-linecap: round; }
.alerte-releve { margin-top: 4px; font-size: 12px; }
tr.cliquable { cursor: pointer; }
/* Les courbes vont par deux, côte à côte quand elles ont chacune 420 px, et
 * l'une sous l'autre sinon : une courbe réduite à 130 px de large (vu à
 * 390 px) ne se lit plus. `.row > .panel` les laissait rétrécir côte à côte
 * sous 720 px. */
.row-courbes > .panel { flex: 1 1 420px; min-width: 0; }
.courbe { position: relative; }
.courbe svg { display: block; width: 100%; height: auto; overflow: visible; }
.courbe-grille { stroke: var(--ap-line); stroke-width: 1; }
.courbe-texte { fill: var(--ap-dim); font-size: 11px; }
.courbe-ligne { fill: none; stroke: var(--ap-accent); stroke-width: 2;
                stroke-linejoin: round; stroke-linecap: round; }
.courbe-point { fill: var(--ap-accent); stroke: var(--ap-panel); stroke-width: 2; }
.courbe-valeur { fill: var(--ap-txt); font-size: 12px; font-weight: 600; }
.courbe-repere { stroke: var(--ap-dim); stroke-width: 1; stroke-dasharray: 3 3; }
.courbe-bulle { position: absolute; z-index: 2; pointer-events: none;
                background: var(--ap-panel2); border: 1px solid var(--ap-line);
                border-radius: 8px; padding: 6px 9px; font-size: 12px;
                box-shadow: 0 4px 14px var(--ap-ombre); white-space: nowrap; }
.courbe-bulle-date { color: var(--ap-dim); }


/* ------------------------------------------- aperçu des images de story
 * Wilfried, 2026-09-30 : une image déposée se MONTRE, comme une vidéo sur
 * Reels. Une vignette au format d'une story (9 sur 16), son nom dessous. La
 * petite est celle d'une ligne du lot. */
.vignettes-story {
  display: flex; flex-wrap: wrap; gap: 10px; margin: 8px 0 4px 0;
}
.vignette-story {
  display: flex; flex-direction: column; gap: 3px;
  width: 96px; font-size: 11.5px; text-decoration: none; color: inherit;
}
.vignette-story img {
  width: 96px; height: 170px; object-fit: cover; display: block;
  border-radius: 8px; background: #000; border: 1px solid var(--ap-line);
}
.vignette-story.petite { width: 54px; margin-bottom: 4px; }
.vignette-story.petite img { width: 54px; height: 96px; border-radius: 6px; }
.lien-etat { font-size: 11.5px; min-height: 15px; }
/* État des neuf étapes : colonnes contenues dans le panneau à grande largeur.
   Le défilement aux petites largeurs reste celui des tableaux communs. */
.setup-table { width: 100%; }
.setup-table th { font-size: 10px; }
.setup-table .mini, .setup-table summary { white-space: normal; font-size: 11px; }
.setup-table input { width: 100%; min-width: 0; box-sizing: border-box; }
.setup-table pre { white-space: pre-wrap; overflow-wrap: anywhere; }
.setup-table td:first-child a { display: block; max-width: min(135px, 100%); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }

/* Le raccourci « prendre la main » de la barre du haut (2026-10-03). */
.etat-mains { display: inline-flex; gap: 6px; margin-left: 10px; flex-wrap: wrap; }
.etat-mains form { margin: 0; }
/* Le temps restant d'un telephone, pose juste apres son bouton (2026-10-04). */
.etat-mains .etat-reste { align-self: center; padding: 3px 8px; font-size: 11.5px;
                          text-decoration: none; white-space: nowrap; }
.etat-mains .main-prise { border-color: #d9822b; color: #d9822b; font-weight: 600; }

/* LES PASTILLES À CHOIX UNIQUE (Créer des comptes, 2026-10-05) : le dessin
   de `.tchip`, posé sur un bouton radio caché. Le choix se voit sans script. */
label.tchip.choix { cursor: pointer; position: relative; }
label.tchip.choix input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; }
label.tchip.choix:hover { color: var(--ap-txt); background: rgba(127,127,127,.12); }
label.tchip.choix:has(input:checked) { background: rgba(76,154,255,.16); border-color: var(--ap-accent);
                color: var(--ap-accent); }
label.tchip.choix:has(input:disabled) { opacity: .45; cursor: not-allowed; }
label.tchip.choix:has(input:focus-visible) { outline: 2px solid var(--ap-accent); outline-offset: 2px; }

/* Une création relancée : la ligne reste pour l historique, grisée (2026-10-07). */
tr.relancee td { opacity: .55; }

/* Relancer une vidéo depuis Vues (2026-10-15) : le bouton sur la vignette,
   les pastilles de ce qui est déjà reparti, et le panier en bas d'écran. */
.relance-btn { position: absolute; right: 5px; bottom: 5px; z-index: 2;
               width: 28px; height: 28px; border-radius: 50%; border: 0;
               background: var(--ap-accent); color: var(--ap-btntxt);
               font-size: 20px; font-weight: 700; line-height: 28px;
               text-align: center; cursor: pointer;
               box-shadow: 0 1px 4px rgba(0,0,0,.6); }
.relance-btn:hover { filter: brightness(1.15); }
.relance-pastilles { position: absolute; left: 5px; top: 5px; z-index: 2;
                     display: flex; flex-direction: column; gap: 3px; }
.relance-pastilles span { font-size: 10.5px; font-weight: 700; padding: 1px 6px;
                          border-radius: 10px; background: rgba(0,0,0,.72); color: #fff; }
.relance-pastilles .p-essai { color: #f2cc60; }
.relance-pastilles .p-repost { color: #79c0ff; }
.insta-case:hover .relance-pastilles { display: none; }
.relance-menu { position: fixed; z-index: 60; background: var(--ap-panel);
                border: 1px solid var(--ap-line); border-radius: 8px; padding: 6px;
                display: flex; flex-direction: column; gap: 4px; min-width: 190px;
                box-shadow: 0 6px 24px var(--ap-ombre, rgba(0,0,0,.45)); }
.relance-menu .deja, .panier-liste .deja { font-size: 12px; color: var(--ap-dim);
  padding: 2px 6px; max-width: 280px; white-space: normal; }
.famille-texte { font-size: 12px; max-width: 260px; white-space: normal; }
.relance-menu .titre { font-size: 12px; color: var(--ap-dim); padding: 2px 6px; }
.relance-menu button { text-align: left; background: var(--ap-chip); color: var(--ap-txt);
                       border: 1px solid var(--ap-line); border-radius: 6px;
                       padding: 7px 9px; cursor: pointer; font: inherit; }
.relance-menu button:hover { border-color: var(--ap-accent); }
.panier { position: fixed; left: 50%; transform: translateX(-50%); bottom: 14px;
          z-index: 55; width: min(720px, calc(100% - 32px));
          background: var(--ap-panel); border: 1px solid var(--ap-accent);
          border-radius: 10px; box-shadow: 0 6px 24px var(--ap-ombre, rgba(0,0,0,.45)); }
.panier-barre { display: flex; align-items: center; gap: 8px; padding: 9px 12px;
                flex-wrap: wrap; }
.panier-barre strong { margin-right: auto; }
.panier button { background: var(--ap-chip); color: var(--ap-txt); cursor: pointer;
                 border: 1px solid var(--ap-line); border-radius: 6px; padding: 6px 10px;
                 font: inherit; }
.panier button.principal { background: var(--ap-accent); color: var(--ap-btntxt);
                           border-color: var(--ap-accent); font-weight: 700; }
.panier-liste { max-height: 40vh; overflow: auto; border-top: 1px solid var(--ap-line);
                padding: 6px 12px; }
.panier-liste div { display: flex; align-items: center; gap: 8px; padding: 4px 0;
                    border-bottom: 1px solid var(--ap-line); font-size: 13px; }
.panier-liste div:last-child { border-bottom: 0; }
.panier-liste span.genre { font-weight: 700; min-width: 64px; }
.panier-liste span.quoi { margin-right: auto; color: var(--ap-dim); }
.panier-message { padding: 8px 12px; border-top: 1px solid var(--ap-line); font-size: 13px; }
.panier-case { font-size: 13px; color: var(--ap-dim); display: inline-flex; align-items: center;
               gap: 4px; cursor: pointer; }

/* « Ça explose » en haut de Vues (2026-10-15). */
.explose { margin-bottom: 14px; border-color: var(--accent, #4b9cff); }
.explose-tete { display: flex; flex-wrap: wrap; gap: 8px 16px; align-items: baseline; justify-content: space-between; }
.explose-seuil { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; font-size: 13px; }
.explose-seuil input[type=number] { width: 110px; padding: 4px 8px; }
.explose-liste { display: grid; gap: 10px; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); margin-top: 10px; }
.explose-ligne { display: flex; gap: 10px; align-items: flex-start; min-width: 0; }
.explose-case { width: 96px; flex: 0 0 96px; }
.explose-infos { display: flex; flex-direction: column; gap: 3px; min-width: 0; font-size: 13px; }
.explose-gain { font-size: 18px; font-weight: 700; }
@media (max-width: 520px) { .explose-liste { grid-template-columns: 1fr; } }

/* Case « Masquer les comptes en pause » (2026-10-16). */
.bascule-pause { margin: 0 0 10px; font-size: .9em; }
.bascule-pause label { cursor: pointer; }

/* Zero releve incomplet (2026-10-21) : le panneau se voit. */
.panel.incomplets { border-left: 4px solid var(--ap-fail, #c0392b); }

/* Panneaux repliables (2026-10-28, `repli.js`). */
.panel.replie > :not(.repli-tete) { display: none; }
.panel.replie { padding-bottom: 6px; }
.repli-titre { cursor: pointer; user-select: none; }
.repli-btn { font: inherit; background: none; border: 0; color: var(--ap-dim); cursor: pointer;
  padding: 0 6px 0 0; width: 1.4em; text-align: left; }
.repli-btn:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }

/* Choisir les colonnes d'un tableau (2026-10-28, `colonnes.js`). */
.colonnes-barre { position: relative; display: flex; justify-content: flex-end; margin: 0 0 6px; }
.colonnes-menu { position: fixed; z-index: 50;
  background: var(--ap-panel2); border: 1px solid var(--ap-line); border-radius: 8px;
  box-shadow: 0 6px 18px var(--ap-ombre, rgba(0,0,0,.4)); padding: 8px 10px;
  display: grid; gap: 4px; min-width: 200px; max-height: 70vh; overflow: auto; }
.colonnes-menu[hidden] { display: none; }
.colonnes-menu label { display: flex; gap: 6px; align-items: center; white-space: nowrap; cursor: pointer; }
.colonnes-menu .mini { margin-top: 4px; justify-self: start; }
