/* Personnalisation de la base (Pico CSS), et composants propres a PostsDeck.
   On surcharge les variables de la base plutot que de redefinir ses composants :
   c'est ce qui permet de suivre ses mises a jour sans tout reprendre.

   Theme sombre : le meme attribut data-theme="dark"/"light" que la base pilote
   deja pour ses propres jetons commande ici les jetons de statut, pour que les
   deux basculent toujours ensemble. */

/* Les jetons de couleur du theme clair, et POURQUOI ils ne sont pas dans le
   `:root` juste en dessous.

   La base ne declare pas ses couleurs sous `:root` mais sous
   `:root:not([data-theme=dark])`, dont la specificite (0,2,0) l'emporte sur
   celle de `:root` (0,1,0) quel que soit l'ordre de chargement. Les redefinir
   dans `:root` ne servait donc a rien : mesure au navigateur, la valeur
   calculee de --pico-primary etait #0172ad, le bleu-vert de la base, et non le
   #1D6EF5 du projet. L'application se rendait en bleu-vert depuis le debut, et
   le seul test qui gardait la couleur d'accent cherchait la chaine dans le
   fichier, ou elle etait bien presente.

   On reprend donc exactement le selecteur de la base. Il ne matche pas quand
   data-theme vaut "dark", et le bloc sombre plus bas gagne dans les deux
   autres cas (meme specificite, mais il vient apres).

   Ce qui n'est pas une couleur (rayon, police, densite) reste dans `:root` :
   la base declare ces jetons la sous `:root` tout court, et design.css, charge
   apres, l'emporte deja. */
:root:not([data-theme="dark"]) {
  --pico-primary: #1D6EF5;
  /* Mesure : #1559d0 sur le fond de survol du bouton secondaire (la primaire a
     25% posee sur du blanc) ne donnait que 4,43:1, sous le seuil du texte.
     #1250bc y monte a 5,15:1 et reste le meme bleu, une nuance plus profond. */
  --pico-primary-hover: #1250bc;
  /* La base distingue la couleur d'accent (--pico-primary, du TEXTE et des
     bordures) du fond de ses boutons pleins (--pico-primary-background).
     design.css ne redefinissait que la premiere : `.bouton` portait donc un
     bleu, et un `<button>` nu, sans classe, en portait un autre. Deux bleus
     pour deux boutons voisins, « Enregistrer » et « Retour aux publications »
     cote a cote dans l'editeur. */
  --pico-primary-background: #1D6EF5;
  --pico-primary-hover-background: #1250bc;
  --pico-primary-focus: rgba(29, 110, 245, 0.25);
  --pico-primary-inverse: #ffffff;
}

:root {
  --pico-border-radius: 10px;
  --pico-font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --pico-line-height: 1.6;

  /* Densite : PostsDeck est un outil de travail, pas une page de lecture. La
     base est calibree pour du contenu qu'on lit, PostsDeck est un outil ou
     l'on passe la journee et ou l'on veut voir plusieurs publications sans
     defiler. --pico-font-size pilote le rem de toute la page (100% = 16px) ;
     93.75% ramene le texte courant a 15px et annule au passage la montee en
     taille que la base applique elle meme aux grands ecrans, puisque cette
     regle, non conditionnee par un media query, l'emporte a toutes les
     largeurs. --pico-spacing et les deux jetons de champ de formulaire
     perdent environ quinze pour cent : c'est ce qui resserre cartes,
     compteurs et boutons sans toucher une seule regle une par une. */
  --pico-font-size: 93.75%;
  --pico-spacing: 0.85rem;
  --pico-form-element-spacing-vertical: 0.64rem;
  --pico-form-element-spacing-horizontal: 0.85rem;

  --largeur-lecture: 68ch;
  --transition: 150ms ease;

  /* Teinte de survol. La base n'expose AUCUN fond de survol discret : ses
     jetons de fond sont des fonds de bouton PLEINS. design.css utilisait
     --pico-secondary-background a sept endroits comme s'il en etait un ; il
     vaut #525f7a, un ardoise opaque prevu pour porter du texte blanc. Mesure
     au navigateur, theme clair : le lien actif du rail ecrivait a 1,19:1
     dessus, son icone etait donc invisible. Une teinte translucide, elle,
     s'assombrit ou s'eclaircit avec le fond qu'elle recouvre et garde le
     contraste du texte a peu pres intact. */
  --fond-survol: rgba(55, 60, 68, 0.09);

  --statut-brouillon: #5b6572;
  --statut-brouillon-fond: #f2f4f7;
  --statut-a-relire: #b54708;
  --statut-a-relire-fond: #fffaeb;
  --statut-pret: #067647;
  --statut-pret-fond: #ecfdf3;
  /* Ce jeton A ETE le bleu de marque exact (#1D6EF5). Mesure : sur son propre
     fond de badge (#eff6ff), il ne donnait que 4,19:1, le seul des six statuts
     sous le seuil. #1a5fd6 monte a 5,28:1 sur le badge et a 5,75:1 sur une
     carte, sans changer de famille de bleu. La marque, elle, garde #1D6EF5 :
     c'est --pico-primary, juste au dessus, et ce n'est jamais du texte pose
     sur un fond colore. */
  --statut-programme: #1a5fd6;
  --statut-programme-fond: #eff6ff;
  --statut-publie: #6941c6;
  --statut-publie-fond: #f4f3ff;
  --statut-a-reprogrammer: #b42318;
  --statut-a-reprogrammer-fond: #fef3f2;
}

/* Le theme sombre est declare DEUX fois, et il le faut : la requete media pour
   le choix « automatique » (aucun data-theme pose), le bloc d'attribut pour un
   choix force au bouton. CSS ne sait pas reunir les deux conditions en une
   seule regle. Les deux blocs doivent donc porter EXACTEMENT les memes
   declarations, et c'est un test qui le tient
   (test_les_deux_declarations_du_theme_sombre_sont_identiques), pas la
   relecture : deux copies divergent, ce n'est qu'une question de temps.

   Deux choses s'y ajoutent, mesurees et non supposees :

   - La primaire. design.css ne la redefinissait QUE dans `:root`, dont la
     specificite (0,1,0) perd contre le `:root:not([data-theme])` de la base
     (0,2,0). Consequence : en theme sombre venu du systeme, l'application
     rendait le bleu de la base (#01aaff) et non le sien, alors qu'au bouton
     elle rendait bien #1D6EF5. Le meme theme sombre, deux bleus, selon le
     chemin par lequel on y arrivait. Et #1D6EF5 en texte sur une carte sombre
     ne donne que 3,74:1. #6aa6ff monte a 6,91:1 sur carte et 7,30:1 sur la
     page ; son inverse passe au sombre (#0d1117, 7,67:1) sans quoi le libelle
     d'un bouton plein deviendrait illisible sur ce bleu clair.
   - Le texte attenue. Celui de la base (#7b8495) tombait entre 4,31:1 et
     4,48:1 sur les fonds de statut, la ou vit le nom de regle d'un
     signalement : sous le seuil. #8b94a5 ne descend jamais sous 5,31:1, tous
     fonds confondus. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --pico-primary: #6aa6ff;
    --pico-primary-hover: #93bdff;
    --pico-primary-background: #6aa6ff;
    --pico-primary-hover-background: #93bdff;
    --pico-primary-focus: rgba(106, 166, 255, 0.28);
    --pico-primary-inverse: #0d1117;
    --pico-muted-color: #8b94a5;
    --fond-survol: rgba(194, 199, 208, 0.14);
    --statut-brouillon: #98a2b3;
    --statut-brouillon-fond: #1c2126;
    --statut-a-relire: #fdb022;
    --statut-a-relire-fond: #2b1d0e;
    --statut-pret: #47cd89;
    --statut-pret-fond: #10241b;
    --statut-programme: #6aa6ff;
    --statut-programme-fond: #0f1d33;
    --statut-publie: #b692f6;
    --statut-publie-fond: #1e1633;
    --statut-a-reprogrammer: #f97066;
    --statut-a-reprogrammer-fond: #2b1614;
  }
}

[data-theme="dark"] {
  --pico-primary: #6aa6ff;
  --pico-primary-hover: #93bdff;
  --pico-primary-background: #6aa6ff;
  --pico-primary-hover-background: #93bdff;
  --pico-primary-focus: rgba(106, 166, 255, 0.28);
  --pico-primary-inverse: #0d1117;
  --pico-muted-color: #8b94a5;
  --fond-survol: rgba(194, 199, 208, 0.14);
  --statut-brouillon: #98a2b3;
  --statut-brouillon-fond: #1c2126;
  --statut-a-relire: #fdb022;
  --statut-a-relire-fond: #2b1d0e;
  --statut-pret: #47cd89;
  --statut-pret-fond: #10241b;
  --statut-programme: #6aa6ff;
  --statut-programme-fond: #0f1d33;
  --statut-publie: #b692f6;
  --statut-publie-fond: #1e1633;
  --statut-a-reprogrammer: #f97066;
  --statut-a-reprogrammer-fond: #2b1614;
}

/* Largeur. Le texte long reste borne, parce qu'un oeil se perd en revenant a la
   ligne au dela d'une certaine longueur. Les zones de donnees, elles, prennent la
   place disponible : c'est ce que Damien demandait. */
.contenu { max-width: 900px; margin: 0 auto; padding: var(--pico-spacing); }
/* Pas de plafond : le tableau de bord des publications doit utiliser
   toute la largeur reelle de l'ecran, pas une valeur arbitraire
   (1600px genait 300px sur un ecran de 1920, la place qui manquait
   pour une troisieme colonne de cartes fidele). */
.contenu--large { max-width: none; }
.lecture { max-width: var(--largeur-lecture); }

/* Mise en page a deux zones : la barre travaille a gauche, le contenu respire.
   Remplace l'ancien entete horizontal : sur les outils de reference (Buffer,
   Post Planner, Canva), la navigation vit sur le cote et laisse la largeur au
   contenu. */
/* 15rem : le rail d'icones de 72px est revenu a une barre ecrite le 02/10/2026.
   Historique : 232px (barre ecrite), puis 72px (quatre icones, libelles
   masques), puis 15rem. Le constat d'origine, une barre ecrite que l'oeil ne
   lisait pas, est renverse : avec les libelles, les quatre entrees se lisent
   sans survol et sans deviner une icone, ce qui compte pour un produit ouvert
   a des clients. Prix assume et mesure : sur 1920px, le contenu perd sa
   troisieme colonne de cartes. */
.appli { display: grid; grid-template-columns: 15rem minmax(0, 1fr); min-height: 100vh; }

/* Regression mesuree (playwright, harnais mesure-responsive.mjs) apres
   l'elargissement des compteurs : minmax(0, 1fr) sur la piste de grille ne
   suffit pas a lui seul, et min-width:0 sur l'enfant de grille NON PLUS,
   verifie a la main avant d'ecrire cette regle (le debordement persistait
   avec min-width:0 seul). Mesure directe (getBoundingClientRect) : la piste
   se resolvait bien a 375px a 375px d'ecran (.appli faisait 375px), mais
   main.contenu, lui, s'affichait a 971px, sa taille min-content, comme s'il
   ignorait purement et simplement la piste qui le contient. .compteurs a
   beau etre deja en overflow-x:auto plus bas dans l'arbre, cela ne protege
   que sa PROPRE contribution de taille min-content a SES ANCETRES, pas la
   largeur EFFECTIVE que main choisit de prendre une fois la piste resolue :
   le defilement interne au bandeau ne force donc pas main a s'etirer sur la
   piste plutot que sur son contenu. width:100% force explicitement cet
   etirement (la "stretch" par defaut d'un item de grille ne suffisait pas
   ici en pratique, mesure a l'appui). Verifie par playwright : scrollWidth
   == clientWidth aux quatre largeurs testees (1280, 768, 375, 320px) apres
   ce correctif, alors qu'il y avait debordement aux trois plus etroites
   avant, avec min-width:0 seul comme sans lui. */
.appli > main { min-width: 0; width: 100%; }

.barre-laterale {
  display: flex; flex-direction: column; gap: 1.5rem;
  padding: 1.25rem 1rem;
  border-right: 1px solid var(--pico-muted-border-color);
  background: var(--pico-card-background-color);
  position: sticky; top: 0; height: 100vh;
}

/* Le nom "PostsDeck" revient dans la barre : le rail fait 15rem, le
   pictogramme et le mot tiennent cote a cote. C'est le meme fichier que le
   favicon de l'onglet, servi une seule fois. width et height sont aussi poses
   sur la balise (voir pages.py) pour que la barre ne se decale pas entre le
   premier rendu et l'arrivee de l'image. */
.barre-marque { display: flex; align-items: center; gap: 0.65rem; }
.barre-marque img { display: block; width: 32px; height: 32px; }
.barre-marque-nom { font-weight: 700; letter-spacing: -0.01em; }
.barre-nav { display: flex; flex-direction: column; gap: 0.25rem; }

/* `color: inherit` et non `var(--pico-color)`, sur les quatre elements en
   icone du projet (lien du rail, crayon d'action, bouton de menu, vignette du
   calendrier). La declaration disait deja qu'ils voulaient la couleur du texte
   courant ; ce n'est pas ce qu'ils obtenaient. La base redefinit --pico-color
   DANS `a` (vers la couleur de lien) et DANS `button` (vers l'inverse de la
   primaire) : `var(--pico-color)` y lit donc la valeur du contexte, pas celle
   de la racine. Mesure au navigateur avant correction : le bouton de menu en
   icone rendait `rgb(255, 255, 255)` sur un fond transparent, soit une icone
   blanche sur une page blanche. `inherit` prend la couleur du parent, qui est
   bien celle du texte courant, dans les deux themes. */
.barre-nav a {
  display: flex; align-items: center; gap: 0.75rem;
  min-width: 44px;
  padding: 0.6rem 0.8rem; border-radius: 10px;
  border-left: 3px solid transparent;
  text-decoration: none; color: inherit;
  transition: background var(--transition);
}

.barre-nav a:hover { background: var(--fond-survol); }
.barre-nav a:focus-visible { outline: 2px solid var(--pico-primary); outline-offset: 2px; }

/* Le libelle est visible dans la barre ecrite (voir .barre-nav a span plus
   bas) : aria-current="page" reste la source de verite pour les lecteurs
   d'ecran, la bordure et le gras portent la meme information pour l'oeil. */
.barre-nav a[aria-current="page"] {
  background: var(--fond-survol);
  border-left-color: var(--pico-primary);
  font-weight: 700;
}

/* Barre ecrite : une pastille avatar + nom, puis les outils (bascule de theme
   et deconnexion) a droite. Disposition reprise de la maquette validee. */
.barre-pied {
  margin-top: auto; display: flex; flex-direction: column; gap: 0.9rem;
  padding-top: 0.9rem; border-top: 1px solid var(--pico-muted-border-color);
}
.barre-pied-utilisateur {
  display: flex; align-items: center; gap: 0.65rem;
  padding: 0.4rem; border-radius: 999px;
  background: var(--fond-survol);
  border: 1px solid var(--pico-muted-border-color);
  min-width: 0;
}
.barre-pied-outils { display: flex; align-items: center; justify-content: space-between; }
/* Le nom en toutes lettres tient maintenant dans la barre de 15rem, aux cotes
   de l'avatar (voir .apercu-avatar, meme composant que dans l'apercu
   LinkedIn). aria-label sur le lien de deconnexion garde le nom accessible. */
.barre-pied .meta {
  display: inline; font-size: 0.85rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Libelles visibles dans la barre ecrite. aria-label sur chaque lien reste
   l'unique nom accessible, il prime sur ce span quoi qu'il en soit. */
.barre-nav a span { display: inline; }

/* Deux colonnes sur grand ecran : la liste travaille, la semaine surveille. */
/* La file d'attente est un panneau utilitaire (dates, heures, titres tronques
   par ellipse), pas une zone de lecture. Mesure (Playwright) : son groupe de
   date le plus long ("Mercredi 27/08") commence a se couper en dessous de
   140px de large, rembourrage de carte compris. Elle a d'abord ete fixe a
   200px, juges trop etroits a l'usage, puis a 260px, toujours fixes. Le
   proprietaire les trouve toujours trop etroits, et une largeur fixe ne se
   lit pas pareil selon l'ecran : 260px valent 20% d'une fenetre de 1280px et
   10% d'une fenetre de 2560px. D'ou un clamp, dont les trois termes sont
   mesures au navigateur et non choisis a vue :

   - 260px de plancher, inchange, et c'est le plus grand plancher possible.
     A 1920px de fenetre, la troisieme colonne de cartes reclame
     3 x 503px + 2 x 11.25px = 1531.5px de piste ; une fois le rail (72px),
     le rembourrage du contenu (2 x 15px) et l'ecart de grille (15px) retires,
     il reste exactement 271.5px pour la file. Cette troisieme colonne a ete
     gagnee au tour du rail d'icones (barre de navigation ramenee de 232px a
     72px) sur demande explicite du proprietaire : la file ne la reprend pas.
   - 380px de plafond : c'est la largeur max-content de la colonne, mesuree
     a 379.1px sur la file reelle. Au dela elle n'affiche rien de plus, elle
     ouvre du vide. Les titres d'entree cessent d'etre tronques des 340px.
   - 13.5vw entre les deux : 260 / 0.135 = 1926px, soit juste au dessus de la
     fenetre de 1920px ou la troisieme colonne se joue. La file ne commence
     donc a grandir qu'une fois cette largeur passee (aucune colonne de cartes
     perdue jusqu'a 2422px, voir le rapport de la tache 9 pour la bande
     2423-2500px, seule ou une colonne apparait plus tard qu'avant), elle
     atteint 345.6px a 2560px (13.5% de la fenetre au lieu de 10%) et son
     plafond a 2815px.

   Le reste de la largeur va toujours a la pile de cartes (minmax(0, 1fr))
   plutot qu'a une part proportionnelle de la fenetre : voir
   .tableau-bord .pile plus bas. */
.tableau-bord {
  display: grid;
  grid-template-columns: minmax(0, 1fr) clamp(260px, 13.5vw, 380px);
  gap: var(--pico-spacing);
  align-items: start;
}
/* top: 1rem, pas 5rem : l'ancien entete horizontal sticky justifiait ce
   decalage, la barre laterale ne s'empile plus au dessus du contenu. */
.tableau-bord aside { position: sticky; top: 1rem; }

/* minmax(0, 1fr) et non 1fr : `1fr` vaut `minmax(auto, 1fr)`, et le `auto`
   prend pour base la taille min-content de l'item de la piste. Mesure au
   navigateur a 320px, file d'attente garnie : la colonne de droite a 324.3px
   de min-content (l'entete « File d'attente » et son bouton « Ouvrir le
   calendrier »), la piste se calait dessus et la page debordait de 19px
   (scrollWidth 339 pour clientWidth 320). Le zero en borne basse autorise la
   piste a descendre sous le min-content de son contenu, qui se tronque alors
   a l'interieur au lieu d'elargir la page. Le debordement n'apparaissait
   qu'avec des publications reellement programmees : une file vide reste sous
   le seuil, ce qui explique qu'il ait pu passer inapercu. */
@media (max-width: 1024px) {
  .tableau-bord { grid-template-columns: minmax(0, 1fr); }
  .tableau-bord aside { position: static; }
}

/* Carte : le seul composant que la base ne fournit pas pour des balises
   autres que <article>. On la pose sur les memes jetons que la base emploie
   pour son propre <article>, afin qu'un theme suive les deux. */
.carte {
  background: var(--pico-card-background-color);
  border: 1px solid var(--pico-card-border-color, var(--pico-muted-border-color));
  border-radius: var(--pico-border-radius);
  padding: var(--pico-spacing);
  box-shadow: var(--pico-card-box-shadow);
}
.carte--etroite { max-width: 420px; margin: 0 auto; }

/* Fil des etapes de l'assistant d'installation. Le numero vient d'un compteur
   CSS, donc l'ordre des `<li>` suffit : rien a recopier dans le HTML. */
.etapes-installation {
  counter-reset: etape;
  display: flex; flex-wrap: wrap; gap: 0.5rem 1.25rem;
  margin: 0 0 1.25rem; padding: 0; list-style: none;
  font-size: 0.85rem; color: var(--pico-muted-color);
}
.etapes-installation li {
  counter-increment: etape;
  display: flex; align-items: center; gap: 0.5rem;
}
.etapes-installation li::before {
  content: counter(etape);
  display: grid; place-items: center;
  width: 1.5rem; height: 1.5rem; border-radius: 50%;
  background: var(--pico-muted-border-color); color: var(--pico-color);
  font-weight: 700; font-size: 0.75rem;
}
.etapes-installation li[aria-current="step"] { color: var(--pico-primary); font-weight: 700; }
.etapes-installation li[aria-current="step"]::before {
  background: var(--pico-primary); color: var(--pico-primary-inverse);
}
.recap-installation { margin: 0 0 1.5rem; padding-left: 1.1rem; }
.recap-installation li { margin-bottom: 0.4rem; }

/* Champ piege du formulaire d'inscription : hors de l'ecran, jamais atteint au
   clavier (`tabindex="-1"`), ignore des lecteurs d'ecran (le conteneur porte
   `aria-hidden`). Il ne sert qu'aux robots qui remplissent tout. */
.piege {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
.tableau-defilant { overflow-x: auto; }

/* Dans la colonne d'action, un lien-bouton prend la meme largeur que les
   boutons de formulaire voisins : la base blocifie ces derniers, pas lui.
   Mesure : « Jetons MCP » faisait 106px a cote de « Desactiver » a 420px, et
   se lisait comme une action mineure alors que c'est la seule porte d'entree
   d'un agent. */
.tableau-defilant td .bouton { width: 100%; }

/* L'identite d'un compte, en une seule cellule. Le nom et l'identifiant
   occupaient deux colonnes alors que l'un ne se lit jamais sans l'autre, et le
   nom se retrouvait seul au milieu d'une ligne tres haute. `min-width: 0`
   partout sur la chaine : sans lui, un nom long impose sa largeur min-content a
   la cellule et le tableau deborde de la carte. */
.compte-identite { display: flex; align-items: center; gap: 0.6rem; min-width: 0; }
.compte-noms { display: flex; flex-direction: column; line-height: 1.3; min-width: 0; }
.compte-nom { font-weight: 650; overflow-wrap: anywhere; }
.compte-identifiant {
  font-size: 0.8rem; color: var(--pico-muted-color); overflow-wrap: anywhere;
}

/* Les actions d'une ligne : l'action courante, puis le repli. Deux elements la
   ou il y en avait quatre empiles, dont un champ de saisie.

   Empilees par defaut, cote a cote des que la place existe (voir le media query
   plus bas) : c'est ce qui ramene la hauteur de ligne au niveau du contenu
   plutot qu'a celui de la pile d'actions. */
.actions-ligne { display: flex; flex-direction: column; gap: 0.4rem; }

/* Le repli des actions sensibles.

   IL NE PROTEGE RIEN. Il range une action rare pour qu'elle cesse d'occuper la
   meme place qu'une action courante sur chaque ligne, en permanence ; c'est la
   route qui refuse, exactement comme avant. Voir `repli_sensible()` dans
   pages.py, et le test qui poste ces adresses avec un compte auteur.

   `details.repli-sensible` et non `.repli-sensible` : la base porte
   `details summary:not([role])` (0,1,2) sur la couleur du sommaire, et un
   `.repli-sensible summary` (0,1,1) perdrait la comparaison quel que soit
   l'ordre de chargement. Meme piege que celui des couleurs de la base plus haut
   dans ce fichier, mesure au navigateur : la couleur calculee etait celle de
   l'accordeon de la base, pas celle ecrite ici.

   La bordure ambre porte le meme sens que `.sensible` a l'interieur, et le
   sommaire garde le plancher tactile de 44px : c'est desormais la SEULE porte
   vers ces actions, elle ne peut pas etre plus petite qu'un bouton. */
.repli-sensible {
  margin: 0;
  border: 1px solid var(--statut-a-relire);
  border-radius: var(--pico-border-radius);
}
details.repli-sensible summary {
  display: flex; align-items: center; gap: 0.4rem;
  min-height: 44px; padding: 0 0.85rem;
  cursor: pointer;
  font-size: 0.85rem; font-weight: 600;
  color: var(--statut-a-relire);
}
/* La base pousse le sommaire ouvert d'un cran (`details[open]>summary`,
   specificite 0,1,2) : ici le corps du repli est deja separe par un filet, la
   marge ne ferait qu'un trou. */
details.repli-sensible[open] > summary { margin-bottom: 0; }
details.repli-sensible summary:focus-visible {
  outline: 2px solid var(--pico-primary); outline-offset: 2px;
}
.repli-sensible-corps {
  display: flex; flex-direction: column; gap: 0.5rem;
  padding: 0.6rem 0.85rem 0.85rem;
  border-top: 1px solid var(--pico-muted-border-color);
}
.repli-sensible form { display: flex; flex-direction: column; gap: 0.35rem; margin: 0; }
.repli-sensible label { font-size: 0.8rem; color: var(--pico-muted-color); }
.repli-sensible input, .repli-sensible button { margin: 0; }
.repli-sensible p { margin: 0; }

/* Sur un telephone, un tableau d'administration ne defile plus : il s'empile.
   PostsDeck se consulte par VPN depuis un telephone, c'est le mode d'acces
   retenu, et le defilement horizontal a l'interieur d'une carte n'a aucune
   affordance : les actions etaient simplement hors de vue.

   Mesure au navigateur reel, pas approximee. Largeur min-content du tableau :
   487px pour les comptes, 423px pour les jetons. La casse commence a 520px de
   fenetre pour les comptes (« Desactiver » et « Reinitialiser le mot de passe »
   sortent de l'ecran) et a 480px pour les jetons (« Renouveler » et
   « Revoquer » sortent a 400px). Le seuil est pose un cran au dessus de la
   premiere casse : la largeur depend des donnees, un nom de compte plus long
   rapproche le moment ou elle survient.

   Les roles ARIA sont ecrits dans le HTML et non deduits du `display` :
   changer le `display` d'un element de tableau lui retire son role implicite
   dans l'arbre d'accessibilite, et la structure disparaitrait pour un lecteur
   d'ecran precisement sur l'appareil ou ce gabarit s'applique. */
@media (max-width: 560px) {
  .tableau-defilant thead { display: none; }
  .tableau-defilant table,
  .tableau-defilant tbody,
  .tableau-defilant tr,
  .tableau-defilant td { display: block; width: auto; }
  .tableau-defilant tr {
    padding: 0.85rem 0;
    border-bottom: 1px solid var(--pico-muted-border-color);
  }
  .tableau-defilant tr:last-child { border-bottom: none; }
  .tableau-defilant td { border: none; padding: 0.2rem 0; }
  /* Le libelle de colonne revient DANS la cellule : sans lui, la valeur
     empilee n'est plus rattachee a rien. */
  .tableau-defilant td::before {
    content: attr(data-libelle);
    display: block;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--pico-muted-color);
  }
  /* Une cellule qui traverse toute la ligne est un message, pas une valeur :
     elle n'a aucun libelle de colonne a porter. */
  .tableau-defilant td[colspan]::before { content: none; }
  .tableau-defilant td form { margin: 0.35rem 0 0; }
  .tableau-defilant td button,
  .tableau-defilant td input { width: 100%; }
  /* Les formulaires du repli sont deja espaces par le `gap` de son corps : la
     marge ci-dessus ferait un double intervalle. Selecteur plus specifique
     (0,2,2 contre 0,1,2), sans quoi la regle de la ligne l'emporterait quel
     que soit l'ordre. */
  .tableau-defilant td .repli-sensible form { margin: 0; }
}

/* Les deux actions d'une ligne cote a cote, des que la largeur le permet.

   POURQUOI 900px. Ce n'est pas la largeur ou le tableau deborderait : mesure au
   navigateur, actions cote a cote et replis fermes, sa largeur min-content est
   de 469,3px pour les comptes et 452,8px pour les jetons (475,7 et 457,2 replis
   ouverts). Il tiendrait donc bien plus bas. C'est la largeur ou `.contenu`
   cesse de grandir (max-width: 900px, en tete de ce fichier) : en dessous,
   chaque pixel donne a une seconde colonne de boutons est pris au nom du
   compte, qui est ce que la ligne raconte. C'est aussi, exactement, le seuil ou
   le rail passe en bandeau du bas (`@media (max-width: 900px)` plus haut) :
   au dessus on est sur un poste, en dessous sur un appareil qu'on tient.

   Ce que ca gagne, mesure aux memes conditions, quatre comptes : 111,4px de
   hauteur de ligne empilee contre 60,3px cote a cote, et 233,1px avant ce
   tour. C'est le vide autour du nom du compte, le reproche exact du
   proprietaire.

   `flex: 1 1 0` donne aux deux actions la MEME largeur, ce que
   `.tableau-defilant td .bouton { width: 100% }` faisait dans la pile. Ce
   dernier est neutralise ici par un selecteur plus specifique (0,3,2 contre
   0,2,2), sans etre retire : il vaut toujours pour un lien-bouton pose
   directement dans une cellule, ailleurs que dans une ligne d'actions. */
@media (min-width: 900px) {
  .actions-ligne { flex-direction: row; flex-wrap: wrap; align-items: stretch; }
  .actions-ligne > * { flex: 1 1 auto; min-width: 0; }
  .tableau-defilant td .actions-ligne .bouton { width: auto; }
  /* OUVERT, le repli reprend toute la largeur de la cellule et passe a la
     ligne : partage en deux, il laisserait moins de 120px a un champ de mot de
     passe et a son libelle. Ferme, il reste a cote de l'action courante. */
  .actions-ligne .repli-sensible[open] { flex-basis: 100%; }
}

/* Messages d'alerte et de succes. Ils portaient une classe et role="alert"
   depuis le premier ecran, mais AUCUNE regle ne les visait : rendus en texte
   nu, ils se confondaient avec le corps de la page. La page /demo les montre
   cote a cote depuis le debut, et c'est bien la qu'on aurait du le voir.

   Ce qui a rendu la chose intenable : l'ecran des jetons MCP affiche le secret
   UNE SEULE FOIS, il est stocke hache et personne ne peut le relire ensuite.
   La phrase qui dit de le copier maintenant doit se distinguer du texte
   courant, sinon l'utilisateur ferme l'onglet et le jeton est perdu.

   Les couleurs reprennent des jetons de statut deja definis, donc deja
   redefinis pour le theme sombre : rien de neuf a tenir en double. La bordure
   gauche epaisse porte l'information par la FORME autant que par la couleur. */
.alerte, .succes {
  border: 1px solid; border-left-width: 4px;
  border-radius: var(--pico-border-radius);
  padding: 0.7rem 0.85rem; margin: var(--pico-spacing) 0;
}
.alerte {
  color: var(--statut-a-reprogrammer);
  background: var(--statut-a-reprogrammer-fond);
  border-color: var(--statut-a-reprogrammer);
}
.succes {
  color: var(--statut-pret);
  background: var(--statut-pret-fond);
  border-color: var(--statut-pret);
}
.alerte > :last-child, .succes > :last-child { margin-bottom: 0; }

/* Le secret d'un jeton fait 47 caracteres sans espace : sans coupure il fait
   deborder la boite sur un ecran etroit. `user-select: all` le selectionne
   entier d'un seul clic, ce qui est toujours ce qu'on veut d'un secret qu'on
   ne reverra pas. */
.succes code { word-break: break-all; user-select: all; }

/* Marge dessus/dessous : detache le bandeau de la ligne de titre et du
   contenu qui suit, a la demande du proprietaire.

   justify-content: flex-start EST une surcharge, pas une preference. Le
   bandeau est un <nav>, et la base pico porte `nav{justify-content:
   space-between}` : en plein ecran l'espace libre partait dans les
   intervalles, et le `gap: 0.5rem` ci-dessous ne s'appliquait tout
   simplement jamais. Mesure au navigateur reel (playwright, harnais
   mesure-compteurs.mjs) a 1920px : 107.8px entre deux compteurs au lieu des
   7.5px voulus, sept compteurs de 168px etales sur les 1823px du bandeau.
   C'est ce qu'a vu le proprietaire, et ce n'etait ni une grille en 1fr (ce
   bandeau n'est plus une grille) ni la largeur des compteurs. */
.compteurs {
  display: flex; justify-content: flex-start; gap: 0.5rem; overflow-x: auto;
  margin: var(--pico-spacing) 0; padding-bottom: 0.25rem;
}

/* Largeur mesuree au navigateur reel (Playwright), pas devinee : a
   0.85rem, "A reprogrammer" (le plus long des libelles, voir
   LIBELLES_STATUT dans pages.py) a besoin de 100.55px dans la police
   system-ui reellement rendue (mesure par un Range sur le noeud texte, qui
   ignore l'etirement flex masquant la largeur intrinseque). 144px de largeur
   minimale = 100.55px de texte + 25.5px de rembourrage (2 x 0.85rem) + 2px de
   bordure, soit 128.05px, plus 16px de marge.
   L'ancienne valeur de 168px portait une marge de confort d'une trentaine de
   pourcents qu'aucune mesure ne justifiait : les sept compteurs faisaient
   1221px et le bandeau defilait DEJA a 1280px d'ecran (mesure : scrollWidth
   1221 contre clientWidth 1183), la plus courante des largeurs de plein
   ecran. A 144px le groupe fait 1053px et tient sans defiler.

   flex-shrink: 0, parce que la non-troncature ne doit pas dependre du nombre
   ci-dessus : un item de flex retrecit jusqu'a sa min-width, donc une police
   rendue plus large qu'ici (system-ui n'est pas la meme police d'une machine
   a l'autre) tronquerait le libelle sous la pression du bandeau. A shrink 0
   la boite grandit avec son contenu et c'est le bandeau qui defile, ce qu'il
   sait deja faire.

   main.contenu force sa piste de grille quelle que soit la largeur de ce
   bandeau (regle .appli > main plus haut) : ces valeurs ne reproduisent pas
   le debordement horizontal deja corrige, reverifie au harnais playwright
   apres ce changement (voir le rapport pour les six lignes). */
.compteur {
  display: flex; flex-direction: column; gap: 2px; flex-shrink: 0;
  min-width: 144px; padding: 0.85rem;
  border: 1px solid var(--pico-muted-border-color);
  border-radius: var(--pico-border-radius);
  background: var(--pico-card-background-color);
  text-decoration: none; color: inherit;
  transition: border-color var(--transition), transform var(--transition);
}

.compteur:hover { border-color: var(--pico-primary); transform: translateY(-1px); }
.compteur--actif { border-color: var(--pico-primary); box-shadow: 0 0 0 1px var(--pico-primary); }
.compteur-nombre { font-size: 1.6rem; font-weight: 700; line-height: 1.1; }
/* nowrap en plus de la largeur mesuree : meme si un navigateur rend le
   libelle plus large que prevu, il ne doit jamais se couper sur deux
   lignes ni se cesurer. */
.compteur-libelle { font-size: 0.85rem; color: var(--pico-muted-color); white-space: nowrap; }

/* Code couleur par statut : le meme jeton que le badge, applique au nombre et
   a la bordure du compteur, pour que le bandeau se lise d'un coup d'oeil. */
.compteur--brouillon { border-color: var(--statut-brouillon); }
.compteur--brouillon .compteur-nombre { color: var(--statut-brouillon); }
.compteur--a-relire { border-color: var(--statut-a-relire); }
.compteur--a-relire .compteur-nombre { color: var(--statut-a-relire); }
.compteur--pret { border-color: var(--statut-pret); }
.compteur--pret .compteur-nombre { color: var(--statut-pret); }
.compteur--programme { border-color: var(--statut-programme); }
.compteur--programme .compteur-nombre { color: var(--statut-programme); }
.compteur--publie { border-color: var(--statut-publie); }
.compteur--publie .compteur-nombre { color: var(--statut-publie); }
.compteur--a-reprogrammer { border-color: var(--statut-a-reprogrammer); }
.compteur--a-reprogrammer .compteur-nombre { color: var(--statut-a-reprogrammer); }

.badge {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 2px 10px; border-radius: 999px;
  font-size: 0.75rem; font-weight: 600; white-space: nowrap;
}
.badge::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.badge--brouillon { color: var(--statut-brouillon); background: var(--statut-brouillon-fond); }
.badge--a-relire { color: var(--statut-a-relire); background: var(--statut-a-relire-fond); }
.badge--pret { color: var(--statut-pret); background: var(--statut-pret-fond); }
.badge--programme { color: var(--statut-programme); background: var(--statut-programme-fond); }
.badge--publie { color: var(--statut-publie); background: var(--statut-publie-fond); }
.badge--a-reprogrammer { color: var(--statut-a-reprogrammer); background: var(--statut-a-reprogrammer-fond); }

/* Roles et etats de l'administration des comptes.
   Constat du proprietaire : « rien ne distingue visuellement un validateur d'un
   auteur, ni un compte actif d'un compte desactive, autrement que par le
   libelle d'un bouton ». Le role se rendait en texte brut minuscule, l'etat en
   pastille grise identique dans les deux cas.

   Ces quatre badges reprennent des JETONS DE STATUT existants et n'inventent
   aucune couleur. Ce n'est pas de l'economie : ces jetons sont deja declares
   pour les deux themes et deja mesures au seuil de 4,5:1 par
   `test_web_contraste`. Une teinte inventee ici serait la seule de la feuille
   sans pendant sombre, et personne ne s'en apercevrait avant de basculer le
   theme.

   Le desactive prend l'ambre (« a relire ») et non le gris : un compte coupe
   n'est pas une erreur, mais ce n'est pas l'etat nominal, et c'est justement ce
   qu'on cherche du regard dans une liste. Le gris va au role d'auteur, ou il
   dit « le cas courant » plutot que « eteint ».

   Le role remplace la pastille de `.badge` par une ICONE : le sens ne passe
   jamais par la couleur seule, et une puce ronde plus une icone diraient deux
   fois la meme chose. L'etat, lui, garde la pastille des badges de statut. */
.badge--role::before { content: none; }
.badge--role-validateur { color: var(--statut-pret); background: var(--statut-pret-fond); }
.badge--role-auteur { color: var(--statut-brouillon); background: var(--statut-brouillon-fond); }
.badge--actif { color: var(--statut-pret); background: var(--statut-pret-fond); }
.badge--desactive { color: var(--statut-a-relire); background: var(--statut-a-relire-fond); }

.etiquette {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 999px;
  border: 1px solid var(--pico-muted-border-color);
  font-size: 0.8rem;
  color: var(--pico-muted-color);
}

/* Cartes de publication : plus de respiration verticale que le reste de
   l'interface, a la demande explicite du proprietaire. Ceci va a l'inverse
   de la densification du tour precedent, et c'est voulu : la densite visait
   les formulaires et les champs, pas les cartes. Ne pas toucher a
   --pico-spacing ni aux jetons de champ de formulaire pour ca. Le gap espace
   les trois blocs de la carte (ligne de titre, apercu, ligne de
   metadonnees) sans avoir a ajouter une marge a chacun. */
/* max-width : sans plafond, une seule colonne dans une piste large (le 1fr
   de .tableau-bord .pile n'a alors aucun autre item avec qui partager
   l'espace) s'etirerait sans limite, rouvrant le rectangle presque vide du
   depart (mesure : jusqu'a 927px de carte pour un apercu toujours bride a
   560px).

   Le plafond a d'abord ete pose a 600px, soit les 560px de largeur maximale
   de l'apercu plus 37.5px de rembourrage de carte (1.25rem de chaque cote) :
   au dela, une carte plus large n'aurait effectivement rien rendu de plus
   fidele. Le proprietaire trouve ces cartes trop larges ; le plafond descend
   a 560px.

   Ce reglage n'est PAS sans effet sur la fidelite, contrairement a ce que
   laisse croire la phrase « l'apercu est de toute facon bride a 560px » : a
   600px de carte l'apercu atteignait ses 560px, a 560px de carte il n'en
   fait plus que 520.5px. Mesure au navigateur sur les apercus reels, en
   comptant les caracteres reellement poses sur la ligne la plus remplie
   (rectangles de Range, caractere par caractere, pas une division
   longueur / nombre de lignes) : 75 caracteres par ligne en mediane a 600px
   de carte, 72 a 560px, contre 63 au plancher de fidelite de 503px. Le
   plafond reste donc largement au dessus du plancher ; il coute trois
   caracteres par ligne, et c'est un arbitrage de gout assume, demande, et
   mesure, pas une simplification gratuite. */
.publication {
  display: flex; flex-direction: column; gap: 1rem;
  padding: 1.25rem; max-width: 560px;
  transition: border-color var(--transition), transform var(--transition);
}
.publication:hover { border-color: var(--pico-primary); transform: translateY(-1px); }
.publication-titre { font-size: 1.1rem; font-weight: 650; margin: 0; }
/* La date, sortie du pied de carte pour prendre sa propre ligne sous le titre.
   Elle n'est PAS du .meta : le pied est une enumeration de metadonnees egales
   entre elles, cette ligne est une information de premier plan dans un outil de
   programmation. D'ou une taille et une couleur intermediaires, entre le titre
   et le pied. La marge negative de rattrapage a disparu avec la cause qu'elle
   compensait : c'est .publication-entete qui espace, desormais. */
/* Deux jetons LOCAUX, et c'est ce qui evite un selecteur groupe plus bas :
   l'etat « en retard » les repose, l'icone et le relatif suivent sans avoir a
   etre nommes une seconde fois. Une regle par etat, pas une liste de
   descendants a tenir a jour a chaque ajout dans la ligne. */
.publication-quand {
  --teinte-quand: var(--pico-color);
  --teinte-quand-attenuee: var(--pico-muted-color);
  display: flex; align-items: center; gap: 0.35rem; flex-wrap: wrap;
  margin: 0; font-size: 0.85rem; font-weight: 550;
  color: var(--teinte-quand);
}
.publication-quand svg { flex: none; color: var(--teinte-quand-attenuee); }

/* Le point median est un ORNEMENT : pose par la feuille de style, il n'entre
   pas dans le texte et n'est donc pas lu par un lecteur d'ecran, qui entendrait
   sinon un point au milieu de chaque date. */
.publication-quand-relatif { color: var(--teinte-quand-attenuee); font-weight: 500; }
.publication-quand-relatif::before { content: "·"; margin-right: 0.35rem; }

/* Le seul etat colore de cette ligne : un creneau MANQUE, c'est a dire une date
   passee sur une publication qui n'est pas partie. Meme jeton que
   « a reprogrammer », les signalements et les pannes, deja redefini pour les
   deux themes. La teinte double le mot « en retard », elle ne le remplace pas.

   Aucune echelle de teintes par echeance n'est posee ici, volontairement : la
   carte porte deja un badge de statut colore juste au dessus, et une seconde
   echelle en dessous se lirait comme un second statut. */
.publication-quand--retard {
  --teinte-quand: var(--statut-a-reprogrammer);
  --teinte-quand-attenuee: var(--statut-a-reprogrammer);
}
/* Haut de carte : le titre, sa date, et le contenu qui suit ne doivent pas se
   toucher. La marge vit sur le BLOC titre + date, pas sur la ligne de titre
   seule : posee la, elle separait le titre de sa propre date, et laissait
   0,9 rem de vide au milieu de ce qui se lit d'un seul tenant (le gap de 1rem
   du conteneur, plus 0,5rem ici, moins une marge negative de rattrapage).
   S'ajoute au gap de .publication ci-dessus pour une separation nettement plus
   marquee que le reste de la carte, exactement comme avant. */
.publication-entete { display: flex; flex-direction: column; gap: 0.2rem; margin-bottom: 0.5rem; }
.publication-pied {
  display: flex; flex-wrap: wrap; gap: 1rem; align-items: center;
  font-size: 0.8rem; color: var(--pico-muted-color);
  margin-top: 0.25rem; padding-top: 0.85rem;
  border-top: 1px solid var(--pico-muted-border-color);
}

/* L'apercu : ce que verra le lecteur, pas ce que contient la base. Le point
   commun de Buffer, Post Planner et Canva : le post s'affiche comme il
   apparaitra vraiment, avatar, nom et coupure du « voir plus » compris. */
.apercu {
  border: 1px solid var(--pico-muted-border-color);
  border-radius: 12px;
  padding: 1.1rem;
  background: var(--pico-background-color);
  max-width: 560px;
}

.apercu-entete { display: flex; gap: 0.5rem; align-items: center; margin-bottom: 0.85rem; }

/* Avatar : manifestement trop grand a cote du reste une fois le tour densifie. */
.apercu-avatar {
  display: grid; place-items: center;
  width: 32px; height: 32px; border-radius: 50%;
  background: var(--pico-primary); color: var(--pico-primary-inverse);
  font-weight: 700; font-size: 0.7rem; flex: none;
}

/* Carre pour une page, rond pour un profil : la convention de LinkedIn lui-meme,
   et la seule difference que l'oeil attrape sans lire quand il parcourt la pile
   de cartes. Le nom et le sous-titre disent deja le canal, mais ils se lisent,
   ils ne se reperent pas. */
.apercu-avatar--page { border-radius: 6px; }

.apercu-identite { display: flex; flex-direction: column; line-height: 1.3; min-width: 0; }
.apercu-nom { font-weight: 650; }
.apercu-sous-titre, .apercu-quand { font-size: 0.78rem; color: var(--pico-muted-color); }
/* Le texte de l'apercu est celui qu'on relit vraiment : on densifie autour de
   lui (avatar, marges, rembourrage), jamais lui. Le font-size est fixe en rem
   independamment de la baisse de --pico-font-size, pour rester aussi
   confortable qu'avant la densification, pas plus petit. */
.apercu-texte {
  white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word;
  margin: 0; font-size: 1rem; line-height: 1.55;
}
.apercu-voir-plus { color: var(--pico-muted-color); font-weight: 600; }
.apercu-hashtags { margin: 0.65rem 0 0; }
.apercu-hashtag { color: var(--pico-primary); font-weight: 550; font-size: 0.85rem; }

.apercu-actions {
  display: flex; justify-content: space-around; gap: 0.5rem;
  margin-top: 0.75rem; padding-top: 0.6rem;
  border-top: 1px solid var(--pico-muted-border-color);
  font-size: 0.8rem; color: var(--pico-muted-color);
}

/* La file d'attente : ce qui sort, jour par jour, trous compris. Un jour sans
   rien de prevu se montre au meme titre qu'un jour charge : un trou dans le
   calendrier editorial est une information. */
.file { display: flex; flex-direction: column; gap: 0.75rem; }
.file-jour-titre { font-size: 0.85rem; margin: 0 0 0.4rem; display: flex; gap: 0.5rem; align-items: baseline; }

/* Hauteur manifestement trop genereuse pour une ligne de file d'attente :
   le rembourrage se resserre, le plancher tactile de 44px (regle plus bas)
   garantit que la ligne reste cliquable confortablement malgre tout. */
.file-entree {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.35rem 0.5rem; border-radius: 8px;
  background: var(--pico-background-color);
  border-left: 3px solid var(--pico-primary);
  text-decoration: none; color: inherit; font-size: 0.8rem;
  transition: transform var(--transition);
}

.file-entree:hover { transform: translateX(2px); }
.file-heure { font-weight: 650; flex: none; }
.file-titre {
  flex: 1; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}
.file-vide { font-style: italic; opacity: 0.7; margin: 0; }

/* Anneau de clavier explicite pour les cartes cliquables recentes (compteurs,
   entrees de la file d'attente) : sans lui, l'anneau par defaut de la base
   est masque des qu'elles portent une autre regle de box-shadow (compteur
   actif), ce qui romprait la coherence avec le reste de l'interface. */
.compteur:focus-visible,
.file-entree:focus-visible {
  box-shadow: 0 0 0 var(--pico-outline-width) var(--pico-primary-focus);
}

/* Sous-navigation de l'espace Admin, en pastilles. L'entree active se marque
   par une pastille pleine et une bordure, pas seulement une teinte. */
.sous-menu {
  display: inline-flex; flex-wrap: wrap; gap: 0.35rem;
  margin: 0.5rem 0 1.25rem;
}

.sous-menu a {
  display: inline-flex; align-items: center;
  min-height: 44px; padding: 0.35rem 0.9rem;
  border: 1px solid var(--pico-muted-border-color); border-radius: 999px;
  text-decoration: none; color: inherit;
  transition: background var(--transition), border-color var(--transition);
}

.sous-menu a:hover { background: var(--fond-survol); }
.sous-menu a:focus-visible { outline: 2px solid var(--pico-primary); outline-offset: 2px; }

.sous-menu a[aria-current="page"] {
  background: var(--fond-survol);
  border-color: var(--pico-primary);
  font-weight: 700;
}

.rangee { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }
.pousse { margin-left: auto; }

/* En-tete de page : le titre et sa phrase d'etat, empiles, les actions restant
   poussees a droite par .pousse. */
.entete-titre { display: flex; flex-direction: column; gap: 0.1rem; }
.sous-titre { margin: 0; color: var(--pico-muted-color); font-size: 0.9rem; }

/* Puces de filtre : le canal, a cote des compteurs qui filtrent deja le statut.
   Une puce est une commande secondaire, elle reste sous la hauteur d'un
   compteur sans descendre sous une cible raisonnable. */
.filtres {
  display: flex; justify-content: flex-start; gap: 0.45rem; flex-wrap: wrap;
  margin-bottom: 1rem;
}
.puce-filtre {
  display: inline-flex; align-items: center;
  padding: 0.35rem 0.8rem;
  border: 1px solid var(--pico-card-border-color, var(--pico-muted-border-color));
  border-radius: 999px;
  color: var(--pico-muted-color);
  font-size: 0.82rem;
  text-decoration: none;
}
.puce-filtre:hover { color: var(--pico-color); border-color: var(--pico-muted-color); }
.puce-filtre--actif {
  color: var(--pico-primary);
  border-color: var(--pico-primary);
  background: var(--pico-primary-focus);
}
/* Empilement vertical simple : reste le comportement par defaut de .pile
   (utilise par exemple par la page de demonstration du systeme de design).
   Le tableau de bord des publications le change en grille de cartes
   ci-dessous, sans toucher a ce cas general. */
.pile { display: flex; flex-direction: column; gap: 0.75rem; }

/* Grille de cartes de publication : le nombre de colonnes suit la place
   disponible plutot que d'enumerer des paliers a la main.
   L'aperçu LinkedIn existe pour montrer OU le texte se coupe vraiment sur
   LinkedIn, coupure du « voir plus » comprise (charte.COUPURE_VOIR_PLUS) :
   une carte trop etroite ne ment pas seulement sur l'esthetique, elle ment
   sur l'endroit ou LinkedIn coupe. 503px = le plancher en dessous duquel le
   retour a la ligne affiche ne ressemble plus a celui de LinkedIn : 430px de
   texte + 33px de rembourrage d'apercu + 37.5px de rembourrage de carte.
   Ces 430px avaient ete convertis en « 50 caracteres par ligne » avec une
   largeur de caractere de 8.4px, mesuree sur un echantillon alphanumerique
   sans espaces ; recompte au navigateur sur le texte reellement affiche
   (rectangles de Range, caractere par caractere), le plancher rend en fait
   63 caracteres par ligne en mediane. C'est la largeur, 503px, qui fait foi
   et qui n'a pas bouge : le nombre de caracteres n'en est qu'une lecture, et
   elle depend de la police que le navigateur resout derriere system-ui.
   1fr en second terme du minmax : la fraction d'espace restante etire les
   cartes plutot que de laisser une marge inutilisee a droite de la derniere
   colonne, l'etirement etant borne par le plafond de la carte elle-meme
   (max-width, voir .publication plus haut). Le nombre de colonnes s'ajuste
   avec le plancher (503px), pas avec ce plafond : c'est ce qui permet trois
   colonnes des que trois fois 503px (plus les deux ecarts) tient dans la
   largeur disponible. Voir le rapport de la tache 9 pour le tableau complet
   des largeurs mesurees. La largeur de carte n'est PAS monotone : elle chute
   a chaque apparition de colonne (600 -> 505 a 1392px par exemple), ce qui
   est mathematiquement inevitable des lors que le nombre de colonnes se
   decide sur le plancher, et deja constate et assume au tour precedent. */
.tableau-bord .pile {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(503px, 100%), 1fr));
  gap: 0.75rem;
}
.meta { color: var(--pico-muted-color); font-size: 0.85rem; }
.indicateur--alerte { color: var(--statut-a-reprogrammer); font-weight: 600; }
/* Le conseil, sur une carte : plus discret que l'alerte, et distinct d'elle
   AUTREMENT QUE PAR LA TEINTE. Son libelle porte le mot « conseil » en toutes
   lettres, sa graisse est celle du texte courant, et il vient toujours APRES
   l'alerte dans la rangee (voir `_indicateurs_gravite`). Trois signaux, dont
   deux qu'un daltonien lit sans difficulte. */
.indicateur--info { color: var(--statut-brouillon); font-weight: 500; }
.indicateur--ok { color: var(--statut-pret); font-weight: 600; }

.icone { width: 1em; height: 1em; vertical-align: -0.125em; flex: none; }

/* Une cible tactile confortable, y compris sur les liens qui font office de
   bouton. La densification ne doit jamais faire passer un element reellement
   cliquable sous ce plancher : barre de navigation et lignes de la file
   d'attente en font desormais partie explicitement. */
button, [role="button"], .compteur, .bouton, .barre-nav a, .file-entree {
  min-height: 44px;
}

/* min-height ne s'applique PAS a un element reste inline : la regle ci-dessus
   ne faisait donc rien sur un lien-bouton qui n'est pas lui-meme un element de
   flex. Mesure au navigateur : le lien « Retour aux publications » de l'editeur,
   place dans un <span class="pousse"> et non directement dans la rangee, tombait
   a 20px de haut, soit moins de la moitie du plancher tactile. Blocifier .bouton
   fait tenir la regle partout, y compris la ou elle tenait deja par accident
   (un lien-bouton element de flex est blocifie de toute facon). */
.bouton {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal);
  border: 1px solid var(--pico-primary);
  border-radius: var(--pico-border-radius);
  background: var(--pico-primary);
  color: var(--pico-primary-inverse);
  text-decoration: none;
  font-weight: 500;
}

/* `.secondaire` etait posee dans le HTML depuis le premier ecran
   d'administration et n'existait dans AUCUNE feuille de style : ce n'est pas une
   classe de la base, qui dit `.secondary`. Mesure au navigateur avant
   correction : « Revoquer », « Reinitialiser le mot de passe » et « Creer »
   avaient exactement le meme fond plein et la meme couleur. Une action
   destructrice qui ressemble a une action de creation, sur des boutons voisins,
   c'est un clic malheureux qui attend.

   Vient APRES `.bouton` a dessein : les deux selecteurs ont la meme specificite,
   c'est l'ordre qui decide. Elle porte aussi sur les `<button class="secondaire">`
   des formulaires, ou elle l'emporte sur la regle d'element de la base. */
.secondaire {
  background: transparent;
  border: 1px solid var(--pico-primary);
  color: var(--pico-primary);
}
.secondaire:hover {
  background: var(--pico-primary-focus);
  border-color: var(--pico-primary-hover);
  color: var(--pico-primary-hover);
}

/* Le troisieme poids, entre `.secondaire` et `.destructif`.

   `.secondaire` ne dit que « moins importante ». Or desactiver un compte,
   reinitialiser le mot de passe de quelqu'un, renouveler ou revoquer un jeton
   coupent ou rendent un ACCES dans l'instant, et se rendaient exactement comme
   « Jetons MCP », qui ne fait que montrer une liste. Le proprietaire demande
   qu'une action sensible se distingue d'une action courante : c'est ce que
   cette classe fait, et elle le fait par la teinte ET par la place (elle ne vit
   que dans un repli, `.repli-sensible` ci-dessous).

   POURQUOI PAS `.destructif`. Cette derniere est reservee a ce qui ne se defait
   pas, et il n'y a qu'un cas dans PostsDeck : la suppression franche d'une
   publication. Un compte desactive se reactive, un mot de passe se change a
   nouveau, un jeton se recree. Leur donner le rouge des pannes les mettrait au
   meme rang que la seule action irreversible de l'application, et le rouge
   cesserait de vouloir dire quelque chose.

   Le jeton « a relire » est deja declare pour les deux themes et deja mesure
   par `test_web_contraste` : aucune couleur en dur n'entre ici.

   Vient APRES `.secondaire` pour la meme raison que `.destructif` : meme
   specificite d'une classe, c'est l'ordre qui decide. */
.sensible {
  background: transparent;
  border: 1px solid var(--statut-a-relire);
  color: var(--statut-a-relire);
}
.sensible:hover {
  background: var(--statut-a-relire-fond);
  border-color: var(--statut-a-relire);
  color: var(--statut-a-relire);
}

/* Une action qui DETRUIT ne ressemble a aucune autre. `.secondaire` existe
   deja pour ne pas confondre une action destructrice avec une creation, mais
   elle ne dit que « moins importante » : la suppression franche d'une
   publication, elle, ne se defait pas, et c'est la seule de l'application dans
   ce cas. Elle prend donc le jeton le plus alarmant de la palette, celui de
   « a reprogrammer », le meme que les signalements et les pannes de regle : il
   est deja redefini pour les deux themes, aucune couleur en dur n'entre ici.

   La teinte ne porte jamais le sens a elle seule : le bouton dit « Supprimer
   definitivement » et le repli ecrit ce qui est perdu. Elle le double.

   Vient APRES `.secondaire` pour la meme raison que `.secondaire` vient apres
   `.bouton` : meme specificite, c'est l'ordre qui decide. */
.destructif {
  background: transparent;
  border: 1px solid var(--statut-a-reprogrammer);
  color: var(--statut-a-reprogrammer);
}
.destructif:hover {
  background: var(--statut-a-reprogrammer-fond);
  border-color: var(--statut-a-reprogrammer);
  color: var(--statut-a-reprogrammer);
}

/* Bascule de theme : UN bouton, trois etats.

   Pourquoi un cycle plutot que trois boutons en groupe : la place. Le rail
   fait 72px de large et le bandeau mobile partage sa hauteur entre trois liens
   de navigation, un avatar et la deconnexion. Trois cibles de 44px empilees
   prendraient a elles seules la moitie du rail. Le reproche fait au cycle
   (« on ne sait pas ce qui vient ensuite ») est traite autrement : le nom
   accessible et la bulle d'aide de l'etat affiche disent l'etat COURANT et ce
   que le clic va faire.

   `color: inherit` et pas `var(--pico-color)` : sur un <button>, la base
   redefinit --pico-color en --pico-primary-inverse, et un bouton transparent
   se retrouverait a ecrire en blanc sur du blanc. `inherit` prend la couleur
   du texte du rail, qui est la bonne dans les deux themes. */
.theme-bascule {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px;
  padding: 0.6rem; border-radius: 8px;
  border: 1px solid transparent; background: transparent;
  color: inherit; cursor: pointer;
}
.theme-bascule:hover { background: var(--fond-survol); }
.theme-bascule:focus-visible { outline: 2px solid var(--pico-primary); outline-offset: 2px; }

/* Un seul etat visible a la fois, et c'est le CSS qui choisit, pas le script :
   l'etat affiche suit `data-choix-theme`, pose sur la racine avant le premier
   rendu. Le bouton ne clignote donc pas plus que la page.

   Les deux etats masques sont en `display: none`, ce qui les retire aussi du
   nom accessible du bouton : le nom est toujours exactement la phrase de
   l'etat courant (voir bascule_theme() dans pages.py).

   L'etat « automatique » est le defaut ecrit en dur, sans condition
   d'attribut : sans script aucun attribut n'est pose, et « automatique » est
   alors la verite, puisqu'aucun choix memorise ne peut etre applique. */
.theme-etat { display: none; align-items: center; justify-content: center; }
.theme-etat--auto { display: inline-flex; }
:root[data-choix-theme="light"] .theme-etat--auto,
:root[data-choix-theme="dark"] .theme-etat--auto { display: none; }
:root[data-choix-theme="light"] .theme-etat--clair { display: inline-flex; }
:root[data-choix-theme="dark"] .theme-etat--sombre { display: inline-flex; }

/* Les ecrans sans rail (installation, connexion) : la bascule s'aligne a droite
   au dessus du contenu, a la meme largeur que lui. Dans le flux et non en
   position fixe, pour ne jamais recouvrir un champ sur un petit ecran. */
.entete-nue {
  display: flex; justify-content: flex-end;
  max-width: 900px; margin: 0 auto;
  padding: var(--pico-spacing) var(--pico-spacing) 0;
}

/* Le lien de deconnexion n'affiche qu'une icone : sans largeur minimale, sa
   zone cliquable resterait bien en dessous du plancher tactile. */
.barre-pied a {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px;
}

/* Bouton d'action en icone seule (le crayon "Modifier" d'une carte de
   publication). Le lien n'avait avant qu'un texte brut sans fond ni
   bordure (verifie : aucune regle .bouton ne stylait la classe posee par
   pages.bouton(), seul button/[role="button"]/.bouton profitait de la regle
   des 44px) ; le fond au survol et la cible tactile clairement delimitee
   rendent cette action au moins aussi visible qu'avant, malgre l'icone
   seule a la place du mot. */
.action-icone {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px;
  border-radius: var(--pico-border-radius);
  background: transparent; color: inherit; text-decoration: none;
  transition: background var(--transition);
}
.action-icone:hover { background: var(--fond-survol); }
.action-icone:focus-visible { outline: 2px solid var(--pico-primary); outline-offset: 2px; }

/* Menus en icone (ajout, filtre) : Alpine masque le panneau ferme par CSS,
   pas en le retirant du DOM. Sans cette regle, le panneau apparaitrait un
   instant grand ouvert au premier chargement, avant qu'Alpine n'ait fini de
   s'initialiser. C'est la technique documentee par Alpine lui-meme, pas un
   mecanisme maison. */
[x-cloak] { display: none !important; }

.menu-icone-groupe { display: flex; gap: 0.4rem; }
.menu-icone { position: relative; }

.menu-icone-bouton {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px;
  padding: 0; border: 1px solid transparent; border-radius: var(--pico-border-radius);
  background: transparent; color: inherit;
  position: relative;
}

.menu-icone-bouton:hover { background: var(--fond-survol); }
.menu-icone-bouton:focus-visible {
  outline: 2px solid var(--pico-primary); outline-offset: 2px;
}

/* Un filtre actif se voit sur le bouton lui-meme : sans ca, on oublie qu'un
   filtre est pose et on croit avoir perdu des publications. */
.menu-icone-bouton--actif { color: var(--pico-primary); border-color: var(--pico-primary); }

.menu-icone-pastille {
  position: absolute; top: 6px; right: 6px;
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--pico-primary);
}

.menu-icone-panneau {
  position: absolute; top: calc(100% + 0.4rem); right: 0; z-index: 30;
  min-width: 220px; max-width: min(300px, calc(100vw - 2rem));
  padding: var(--pico-spacing);
  background: var(--pico-card-background-color);
  border: 1px solid var(--pico-muted-border-color);
  border-radius: var(--pico-border-radius);
  box-shadow: var(--pico-card-box-shadow);
}

.menu-icone-panneau form { flex-direction: column; align-items: stretch; }

@media (max-width: 768px) {
  .contenu { padding: 0.75rem; }
}

/* Sur telephone la barre laterale passe en bandeau horizontal en bas, comme
   une application mobile. */
@media (max-width: 900px) {
  /* minmax(0, 1fr) ici aussi, par coherence avec la regle de desktop et en
     defense supplementaire : c'est .appli > main { width: 100%; } ci-dessus
     qui fait le travail reel (verifie par mesure), mais la piste de grille
     elle-meme ne doit pas etre le seul endroit qui l'omette. */
  .appli { grid-template-columns: minmax(0, 1fr); }
  .barre-laterale {
    position: fixed; bottom: 0; left: 0; right: 0; top: auto;
    height: auto; flex-direction: row; align-items: center;
    border-right: none; border-top: 1px solid var(--pico-muted-border-color);
    padding: 0.5rem; z-index: 20;
  }
  /* Sur mobile la largeur ne manque pas de la meme facon qu'en rail
     etroit : les libelles des liens redeviennent visibles (l'inverse du
     rail, ou aria-label seul les portait). Le nom "PostsDeck" et le nom de
     l'utilisateur en toutes lettres restent hors du bandeau, faute de
     place pour les cinq a la fois sur un ecran de telephone ; l'avatar et
     aria-label sur le lien de deconnexion gardent l'un et l'autre
     accessibles. */
  .barre-pied .meta { display: none; }
  /* Le pictogramme sort du bandeau pour la meme raison que le nom ecrit : la
     place. Trois liens, un avatar, la bascule de theme et la deconnexion
     tiennent deja tout juste sur 375px. La marque reste dans l'onglet, ou
     elle est le favicon. */
  .barre-marque { display: none; }
  .barre-nav { flex-direction: row; flex: 1; justify-content: space-around; }
  /* Icone au dessus du libelle, pas a cote : mesure a l'appui, trois liens
     avec icone ET libelle cote a cote debordaient du bandeau des 375px de
     large (378.7px pour tenir "Publications", "Calendrier" et "Comptes" sur
     une seule ligne chacun). Empiles verticalement, chaque lien n'a besoin
     que de la largeur du plus grand des deux (l'icone ou le libelle), pas de
     leur somme : c'est aussi la convention des barres de navigation mobiles
     (icone au dessus, texte court en dessous). */
  .barre-nav a { flex-direction: column; gap: 0.15rem; padding: 0.3rem; }
  .barre-nav a span { display: inline; font-size: 0.68rem; }
  .barre-pied {
    flex-direction: row; align-items: center; gap: 0.5rem; margin: 0;
    padding: 0 0 0 0.5rem; border-top: none;
  }
  .barre-pied-utilisateur { padding: 0; background: none; border: none; }
  .barre-pied-outils { gap: 0.25rem; }
  .contenu { padding: 1rem 1rem 5rem; }
  .apercu { max-width: none; }
}

/* Panneau de detail : le contenu d'une carte, en haut de la colonne de droite.
   « La carte doit passer au dessus de la file d'attente quand on clique sur une
   carte » : la file ne disparait pas, elle suit dessous.

   La tension a resoudre etait reelle : la colonne avait ete demandee PLUS
   ETROITE au tour precedent (260px, mesure), et le detail y reclame de la
   largeur pour un apercu fidele. Les deux ne tiennent pas ensemble a largeur
   fixe. La colonne reste donc etroite quand elle ne montre que la file, et ne
   s'elargit QUE pendant qu'elle montre un detail.

   clamp(300px, 44vw, 600px) : le plafond de 600px est celui, deja mesure, d'une
   carte de publication a sa largeur fidele (560px d'apercu + 37.5px de
   rembourrage de carte) ; au dela, la colonne ne rendrait pas l'apercu plus
   fidele, elle ouvrirait du vide. Le terme central est en vw, pas en
   pourcentage : une longueur en pourcentage dans une piste de grille dimensionnee
   par son contenu se resout contre une largeur indefinie, ce qui est exactement
   la boucle qu'on ne veut pas ici. 44vw laisse toujours a la pile de cartes
   0,56 x largeur - 112px, soit plus de 460px des le seuil de 1025px ou cette
   disposition en deux colonnes existe encore : la pile garde donc au moins une
   colonne de cartes au dessus du plancher de fidelite. Verifie au harnais
   playwright, panneau ouvert et panneau ferme, a 320, 375, 768 et 1280px. */
.tableau-bord--detail {
  grid-template-columns: minmax(0, 1fr) clamp(300px, 44vw, 600px);
}

/* « Si la hauteur ne suffit pas pour montrer les deux, c'est le detail qui reste
   visible et la file qui defile en dessous : c'est le detail qu'on vient
   d'ouvrir. » La colonne devient donc une boite de hauteur bornee, le panneau y
   est incompressible (flex: none) et c'est la carte de la file d'attente, elle
   seule, qui prend le defilement. Rien de tout cela ne s'applique quand le
   panneau est ferme : la colonne retrouve son comportement d'origine. */
.tableau-bord--detail aside {
  display: flex;
  flex-direction: column;
  gap: var(--pico-spacing);
  max-height: calc(100vh - 2rem);
  min-height: 0;
}

/* La file cede la hauteur AVANT le detail, pas en meme temps que lui : un
   facteur de retrait cent fois plus fort lui fait absorber la quasi totalite de
   la compression, et son plancher l'empeche de disparaitre tout a fait, parce
   qu'une file reduite a rien ne dit plus rien du calendrier editorial. */
.tableau-bord--detail .file-defilante {
  flex: 0 100 auto;
  overflow-y: auto;
  min-height: 96px;
}

.panneau-detail {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  border-color: var(--pico-primary);
  /* Le detail garde la priorite sur la file quand la hauteur manque (retrait de
     1 contre 100). Mais quand il ne tient TOUJOURS pas une fois la file reduite
     a son plancher, il defile en interne au lieu de deborder de la colonne.
     Mesure : sans overflow-y ici, playwright n'arrivait plus a atteindre le
     bouton « Copier le texte » d'une publication un peu longue a 1280x900, le
     bas du panneau tombant hors de la colonne collante. Un panneau dont les
     propres actions sont inatteignables n'est pas un panneau. */
  flex: 0 1 auto;
  min-height: 0;
  overflow-y: auto;
}

/* Le panneau prend le focus des son ouverture (tabindex="-1" + autofocus). Le
   focus n'est jamais supprime dans ce projet, il est style : l'anneau est donc
   pose explicitement ici. Il ne se montre qu'en :focus-visible, c'est a dire
   quand le panneau a ete atteint au clavier ; ouvert a la souris, le navigateur
   ne le fait pas correspondre a :focus-visible et la bordure primaire de la
   carte suffit alors a dire ou ca vient de se passer. */
.panneau-detail:focus-visible { outline: 2px solid var(--pico-primary); outline-offset: 2px; }

.panneau-etat { gap: 0.5rem; }

/* Les informations qui manquaient sur la vignette : nombre de mots, hashtags,
   auteur, derniere modification. Une liste de definitions plutot qu'un tableau :
   ce sont des paires libelle/valeur, pas des donnees a comparer entre elles. */
.panneau-infos {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 0.5rem 0.85rem;
  margin: 0;
  padding: 0.85rem 0;
  border-top: 1px solid var(--pico-muted-border-color);
  border-bottom: 1px solid var(--pico-muted-border-color);
}
.panneau-infos > div { display: flex; flex-direction: column; gap: 2px; }
.panneau-infos dt {
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--pico-muted-color); font-weight: 600;
}
.panneau-infos dd { margin: 0; font-size: 0.9rem; }

.panneau-bloc { display: flex; flex-direction: column; gap: 0.5rem; margin: 0; }
.panneau-bloc-titre {
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--pico-muted-color); font-weight: 600; margin: 0;
}
.panneau-bloc .rangee { gap: 0.4rem; }
.panneau-bloc input, .panneau-bloc button, .panneau-bloc select { margin: 0; }

/* Le bloc de suppression ferme le panneau, et le filet au dessus le detache
   des quatre actions qui, elles, se defont. Ce n'est pas de l'ornement : sans
   separation, une action irreversible s'aligne visuellement avec « Enregistrer »
   et « Copier le texte », et se lit comme une cinquieme action ordinaire. */
.panneau-bloc--destructif {
  border-top: 1px solid var(--pico-muted-border-color);
  padding-top: 0.75rem;
}

/* Le detail des signalements, la ou la vignette n'affiche qu'un nombre. Les
   messages viennent du moteur de regles, jamais d'ici : cette feuille ne fait
   que les ranger et les colorer par severite. */
.signalements { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.4rem; }
.signalement {
  display: flex; flex-direction: column; gap: 2px;
  padding: 0.5rem 0.65rem;
  border-radius: 8px;
  border-left: 3px solid;
  font-size: 0.85rem;
}
/* LES TROIS NIVEAUX DE GRAVITE, et ce qui les distingue.

   Le defaut corrige : les quatorze regles rendaient toutes le meme rose, et
   « 77 mots, la charte en demande au moins 150 » avait l'air aussi grave que
   « 3,5 h seulement, la charte impose 48 h ». La gravite vit desormais dans le
   modele de regle (`Rule.gravite`) ; cette feuille ne fait que la montrer.

   LA COULEUR N'EST JAMAIS LE SEUL SIGNAL. Chaque niveau se distingue aussi :
   - par le LIBELLE de `.signalement-niveau`, ecrit en toutes lettres ;
   - par le DESSIN de son icone (exclamation, ampoule, cercle barre) ;
   - par le STYLE du filet gauche : plein et epais pour la panne, plein pour la
     regle enfreinte, pointille pour le conseil ;
   - par sa POSITION, les lignes etant triees du plus grave au moins grave.
   Une capture en noir et blanc reste donc lisible, et un daltonien aussi. */
.signalement--alerte {
  border-color: var(--statut-a-reprogrammer);
  background: var(--statut-a-reprogrammer-fond);
}
.signalement--alerte .signalement-niveau { color: var(--statut-a-reprogrammer); }
.signalement--info {
  border-color: var(--statut-brouillon);
  border-left-style: dotted;
  background: var(--statut-brouillon-fond);
}
.signalement--info .signalement-niveau { color: var(--statut-brouillon); }

/* Le niveau, en tete de ligne. Sa teinte est celle de son niveau, et non le
   gris attenue de `.signalement-regle` juste en dessous : c'est la ligne qui
   porte la gravite, celle du dessous ne porte que le nom de la regle. */
.signalement-niveau {
  display: flex; align-items: center; gap: 0.3rem;
  font-weight: 700; font-size: 0.72rem; text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* Une regle du moteur qui n'a pas pu s'executer. Ni une alerte ni une
   information sur le texte : un TROU dans le controle. Il se distingue a la
   bordure pleine et non pointillee-legere, mais surtout a son libelle
   « Controle indisponible », parce que la couleur seule ne porte jamais un sens
   ici. Meme jeton que « a reprogrammer », le plus alarmant de la palette, et
   lisible dans les deux themes puisqu'il y est redefini. */
.signalement--panne {
  border-color: var(--statut-a-reprogrammer);
  border-left-width: 4px;
  background: var(--statut-a-reprogrammer-fond);
}
.signalement--panne .signalement-niveau { color: var(--statut-a-reprogrammer); }
.indicateur--panne { color: var(--statut-a-reprogrammer); font-weight: 700; }
.signalement-regle {
  font-weight: 600; font-size: 0.7rem; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--pico-muted-color);
}

/* Repli du presse-papier : toujours dans la page, jamais cree par le script.
   Un navigateur qui refuse l'acces au presse-papier ouvre ce bloc et selectionne
   le texte ; l'utilisateur repart avec son texte dans tous les cas. */
.panneau-repli summary { font-size: 0.8rem; color: var(--pico-muted-color); cursor: pointer; min-height: 44px; display: flex; align-items: center; }

/* Le repli de la SUPPRESSION, et lui seul. Les autres replis ouvrent un texte
   a lire ; celui-ci ouvre la seule action de l'application qui ne se defait
   pas, et son resume est ce sur quoi on clique en premier. En gris attenue, il
   se lisait comme du texte. Il prend donc l'allure de `.destructif` : meme
   jeton, meme contour, aucune couleur en dur.

   Le repli reste, il ne devient pas un bouton de suppression : ce que ce
   resume ouvre est la confirmation, pas la destruction. Le geste demande
   toujours deux actions deliberees, et c'est un test nomme qui le tient. */
.panneau-bloc--destructif .panneau-repli summary {
  color: var(--statut-a-reprogrammer);
  border: 1px solid var(--statut-a-reprogrammer);
  border-radius: var(--pico-border-radius);
  padding: 0 0.85rem;
  font-weight: 600;
}
.panneau-bloc--destructif .panneau-repli summary:hover {
  background: var(--statut-a-reprogrammer-fond);
}
.panneau-repli textarea {
  width: 100%; margin: 0.4rem 0 0; font-size: 0.85rem;
  white-space: pre; overflow-wrap: normal;
}

/* Le plancher tactile de 44px vaut ici comme partout ailleurs : le panneau
   ajoute des boutons et une croix de fermeture, ils ne font pas exception. */
.panneau-detail button, .panneau-detail .action-icone { min-height: 44px; }

/* La carte dont le detail est ouvert se signale par une forme (bordure marquee)
   autant que par une teinte, et le bouton d'ouverture porte deja aria-expanded
   pour dire la meme chose a un lecteur d'ecran. */
.publication--ouverte { border-color: var(--pico-primary); box-shadow: 0 0 0 1px var(--pico-primary); }
.publication { cursor: pointer; }
.publication-ouvrir { flex: none; }

/* Libelle reserve aux lecteurs d'ecran : le champ de date porte deja son titre
   de bloc juste au dessus, mais un champ de formulaire doit avoir SON propre
   label associe, pas un titre voisin. */
.visuellement-cache {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

@media (max-width: 1024px) {
  /* La colonne de droite s'empile sous le contenu. Ouverte, elle doit passer
     AVANT la pile de cartes : sinon le panneau qu'on vient d'ouvrir d'un clic
     serait a chercher tout en bas de la page, apres dix-sept cartes. Le focus
     pose sur le panneau a son ouverture (tabindex="-1" + autofocus) finit le
     travail en l'amenant reellement a l'ecran. La colonne cesse aussi d'etre
     une boite de hauteur bornee : sur telephone, la page entiere defile. */
  /* minmax(0, 1fr), meme raison qu'au dessus : `1fr` seul se cale sur le
     min-content de la colonne de droite et fait deborder la page a 320px. */
  .tableau-bord--detail { grid-template-columns: minmax(0, 1fr); }
  .tableau-bord--detail aside { order: -1; max-height: none; }
  /* Plus de boite de hauteur bornee : ni la file ni le detail ne defilent pour
     leur propre compte sur telephone, c'est la page entiere qui defile. */
  .tableau-bord--detail .file-defilante { overflow-y: visible; min-height: 0; }
  .tableau-bord--detail .panneau-detail { overflow-y: visible; }
}

/* ---------------------------------------------------------------------------
   Editeur d'une publication
   ---------------------------------------------------------------------------
   Meme disposition que le tableau de bord, et pour la meme raison : le texte
   travaille a gauche, ce qui le juge (l'apercu et les alertes de la charte)
   reste visible a droite pendant qu'on ecrit. La colonne de droite est bornee
   a 600px, la largeur deja mesuree d'une carte de publication a sa largeur
   fidele (560px d'apercu + 37,5px de rembourrage de carte) : au dela, l'apercu
   ne serait pas plus fidele, il ouvrirait du vide, et la coupure du
   « voir plus » tomberait au mauvais endroit a l'oeil. */
.editeur {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, clamp(320px, 40vw, 600px));
  gap: var(--pico-spacing);
  align-items: start;
}
.editeur > form { min-width: 0; }
.editeur-controle { position: sticky; top: 1rem; min-width: 0; }

/* Meme seuil que le tableau de bord : en dessous, les deux colonnes s'empilent
   et l'apercu passe SOUS le champ de saisie, pas avant lui. Sur cet ecran c'est
   bien le champ qu'on vient chercher, contrairement au panneau de detail. */
@media (max-width: 1024px) {
  .editeur { grid-template-columns: 1fr; }
  .editeur-controle { position: static; }
}

/* Le champ de saisie du corps. La base (Pico) stylise deja input et select,
   pas la zone de texte a la hauteur qu'il lui faut ici. min-width: 0 est ce qui
   empeche la colonne de grille de s'elargir a la plus longue ligne collee :
   sans lui, une ligne sans espace pousse toute la mise en page et fait deborder
   la page horizontalement. */
#corps {
  width: 100%;
  min-width: 0;
  line-height: 1.6;
  resize: vertical;
}

/* Un champ en lecture seule doit SE VOIR comme tel, pas seulement s'annoncer :
   readonly (et non disabled) garde le texte selectionnable et copiable, ce que
   la lecture d'une publication d'un collegue demande. */
.editeur [readonly] { background: var(--fond-survol); cursor: default; }

/* ------------------------------------------------------------------------
   Calendrier
   ------------------------------------------------------------------------
   UNE SEULE GRILLE, DEUX MISES EN PAGE. Le meme arbre de document sert la
   grille de sept colonnes du poste et la liste chronologique du telephone :
   la feuille de style le reflue, elle ne masque pas une vue pour en montrer
   une autre. Deux arbres separes auraient donne deux fois le meme
   identifiant de formulaire pour une meme publication.

   minmax(0, 1fr) sur les pistes, et pas 1fr : une piste 1fr se resout a la
   taille min-content de son contenu quand celui-ci refuse de retrecir, et
   c'est exactement le debordement horizontal deja paye deux fois sur cet
   ecran (bandeau de compteurs, puis grille de cartes). */
.calendrier-defilant { overflow-x: auto; }

.calendrier {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 0.4rem;
  /* Sous cette largeur les sept colonnes ne veulent plus rien dire : la
     grille defile dans son conteneur plutot que d'ecraser ses cases. */
  min-width: 640px;
}

.calendrier-entete {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--pico-muted-color);
  padding: 0.25rem 0.4rem;
}

.jour {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  min-width: 0;
  min-height: 104px;
  padding: 0.4rem;
  border-radius: var(--pico-border-radius);
  background: var(--pico-card-background-color);
  border: 1px solid var(--pico-muted-border-color);
  transition: background var(--transition), border-color var(--transition);
}

/* Une case hors du mois n'est pas une cible de depot : elle n'a pas de date. */
.jour--hors-mois { background: transparent; border-color: transparent; }

/* UN JOUR DEJA PASSE. Un mois de septembre consulte en octobre n'a pas le meme
   sens qu'un mois a venir : ce qui est derriere ne se replanifie plus.

   La case prend le fond NEUTRE de la palette et un filet pointille : une
   difference de clarte et de FORME, jamais de teinte, donc lisible pour un
   daltonien comme sur une capture en noir et blanc.

   Pourquoi ce jeton et pas `transparent`, qui semblait plus simple : mesure au
   navigateur, en theme CLAIR le fond de page et le fond de carte valent tous
   les deux #fff. Une case rendue transparente n'y changeait donc strictement
   rien a l'oeil, et la distinction ne tenait plus qu'au pointille. Le fond du
   brouillon est le ton neutre de la palette, il existe dans les deux themes,
   et le texte comme le numero attenue y sont deja mesures au dessus du seuil.

   Surtout, PAS d'`opacity`. Un voile sur la case entiere aurait fait tomber le
   contraste de tout ce qu'elle contient sous le seuil, ce qui aurait ete un
   defaut d'accessibilite introduit pour en signaler un autre. */
.jour--passe { background: var(--statut-brouillon-fond); border-style: dashed; }

/* LE JOUR COURANT. La file d'attente du tableau de bord sait dire
   « Aujourd'hui 26/08 » ; le calendrier ne marquait rien du tout.

   Trois marques, dont deux hors teinte : le numero passe dans une pastille
   pleine (une FORME), le mot « Aujourd'hui » s'ecrit en toutes lettres, et le
   filet prend la couleur d'accent. Le document, lui, porte `aria-current`.

   Declare APRES `.jour--passe` et AVANT `.jour--survole`, a specificite egale :
   l'ordre est ce qui decide, et le retour de depot doit rester le dernier mot
   pendant un glisser-deposer. */
.jour--aujourdhui {
  border-color: var(--pico-primary);
  box-shadow: inset 0 0 0 1px var(--pico-primary);
}
.jour--aujourdhui .jour-numero {
  /* La pastille epouse le numero. Sans cela, l'etirement par defaut d'un
     enfant de colonne flexible en faisait un bandeau sur toute la largeur de
     la case : mesure au navigateur, pas suppose. */
  align-self: flex-start;
  background: var(--pico-primary);
  color: var(--pico-primary-inverse);
  border-radius: 999px;
  min-width: 1.55rem;
  padding: 0.05rem 0.35rem;
  text-align: center;
}
.jour-marque {
  font-size: 0.68rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--pico-primary);
}

/* Retour visuel du glisser-deposer. Il double le curseur de depot du
   navigateur, qui ne dit pas SUR QUELLE case on est. */
.jour--survole { background: var(--statut-programme-fond); border-color: var(--statut-programme); }

.jour-numero { font-size: 0.78rem; font-weight: 600; color: var(--pico-muted-color); }

/* La date complete est toujours dans le document : sur poste elle n'est lue
   que par les lecteurs d'ecran (l'en-tete de colonne, lui, est decoratif et
   masque), sur telephone elle devient le titre visible de la journee. */
.jour-complet {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

.vignette {
  display: flex;
  align-items: flex-start;
  gap: 0.2rem;
  min-width: 0;
  border-radius: var(--pico-border-radius);
  background: var(--pico-background-color);
  border-left: 3px solid var(--statut-programme);
}
.vignette--lecture { border-left-color: var(--pico-muted-border-color); }
.vignette[draggable="true"] { cursor: grab; }
.vignette[draggable="true"]:active { cursor: grabbing; }

.vignette-lien {
  display: flex;
  flex-direction: column;
  gap: 1px;
  flex: 1;
  min-width: 0;
  min-height: 44px;
  justify-content: center;
  padding: 0.3rem 0.4rem;
  font-size: 0.78rem;
  line-height: 1.35;
  color: inherit;
  text-decoration: none;
  border-radius: var(--pico-border-radius);
  transition: background var(--transition);
}
.vignette-lien:hover { background: var(--fond-survol); }
.vignette-lien:focus-visible { outline: 2px solid var(--pico-primary); outline-offset: 2px; }
.vignette-heure { font-weight: 650; }
/* Un titre sans espace ne doit pas elargir sa colonne : la grille tiendrait
   le coup, la cellule non. */
.vignette-titre { font-weight: 550; overflow-wrap: anywhere; }
.vignette-meta { overflow-wrap: anywhere; }
.vignette .badge { align-self: flex-start; }

/* Le deplacement au clavier : un <details> natif, atteignable a la
   tabulation, dont le navigateur annonce lui meme l'etat ouvert/ferme, et
   qui fonctionne sans JavaScript du tout. Il pousse la case vers le bas au
   lieu de flotter au dessus : un panneau en position absolue serait rogne
   par le defilement horizontal de .calendrier-defilant. */
.vignette-deplacer { flex: none; }
.vignette-deplacer summary { list-style: none; cursor: pointer; }
.vignette-deplacer summary::-webkit-details-marker { display: none; }
.vignette-deplacer summary:focus-visible {
  outline: 2px solid var(--pico-primary); outline-offset: 2px;
}

.vignette-formulaire {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  padding: 0.4rem 0;
}
.vignette-formulaire input, .vignette-formulaire button { margin: 0; }
.vignette-formulaire .meta { margin: 0; }

/* Le bandeau de signalements du calendrier renvoie a la publication en cause :
   ce lien est une action, il tient donc le plancher tactile comme les autres.
   MESURE, pas supposition : il tombait a 139x14px au premier passage.
   La regle vise le lien et pas le surtitre lui-meme, qui reste du texte partout
   ailleurs (panneau de detail, editeur). */
.signalement-regle a {
  display: inline-flex; align-items: center;
  min-height: 44px;
  color: inherit;
}

/* Sur telephone, la meme grille devient une liste chronologique : une
   colonne, les cases vides et hors mois retirees, et chaque journee annoncee
   par sa date en toutes lettres a la place de son seul numero.
   L'hypothese d'usage est qu'on consulte et qu'on valide au pouce, et qu'on
   redige sur poste : sept colonnes sur 375 pixels seraient illisibles. */
@media (max-width: 768px) {
  .calendrier { grid-template-columns: minmax(0, 1fr); min-width: 0; }
  .calendrier-entete { display: none; }
  .jour--hors-mois, .jour--vide { display: none; }
  .jour { min-height: 0; }
  .jour-numero { display: none; }
  /* La date complete devient le titre visible de la journee et porte deja
     « aujourd'hui » : le libelle en doublon s'efface avec le numero. */
  .jour-marque { display: none; }
  .jour-complet {
    position: static;
    width: auto; height: auto;
    margin: 0; overflow: visible; clip-path: none; white-space: normal;
    font-size: 0.85rem; font-weight: 650; text-transform: capitalize;
  }
  .vignette-lien { font-size: 0.9rem; }
}

/* ------------------------------------------------------------------------
   Publication assistee, au bas de l'editeur
   ------------------------------------------------------------------------
   Deux blocs de copie l'un sous l'autre, JAMAIS fusionnes : le lien va dans
   le premier commentaire et jamais dans le corps du post. L'espace entre
   eux est ce qui rend cette separation lisible ; .panneau-bloc, concu pour
   une colonne etroite, ne porte aucune marge propre. */
.publier { display: flex; flex-direction: column; gap: var(--pico-spacing); }
.publier h2 { margin: 0; }
.publier > p { margin: 0; }
.publier form { margin: 0; }
