/* ===========================================================================
   RadioSMP VT — feuille de style unique.

   DIRECTION : une face avant de régie. L'interface se lit à trois mètres, en
   lumière de studio comme en pleine nuit, et elle est d'abord faite de temps
   et de listes. Densité avant confort, et surtout : LA COULEUR ENCODE LA
   NATURE DU CONTENU, elle ne décore rien. On reconnaît un jingle d'une
   musique à sa teinte avant d'avoir lu son titre.

   LA RÉFÉRENCE DE DIRECTION ARTISTIQUE A CHANGÉ. Elle était `newsui.html`,
   relevée sur Zetta2GO ; c'est désormais `radiodj-web`, l'autre projet de
   l'exploitant (apps/web/src/index.css et tailwind.config.js). La demande est
   d'avoir « tout pareil » : les mêmes surfaces, la même typographie, les mêmes
   panneaux, le même accent, le même menu sur le côté.

   CE QUI NE CHANGE PAS, et c'est l'exploitant qui l'a tranché : LA COULEUR PAR
   NATURE. Cyan la musique, jaune l'habillage, rose le voice track. La
   référence range ses familles de contenu sur une échelle de GRIS neutres
   (`rule-1/2/3`), pour n'avoir qu'une seule teinte dans son interface ; ici on
   lit un conducteur de vingt lignes d'un coup d'œil parce que la teinte dit le
   type avant qu'on ait lu le titre. Les trois teintes restent donc, telles
   quelles, et elles ne s'échangent jamais.

   Sept règles tenues partout :

   1. L'arrondi est celui de la référence, et il n'a que quatre valeurs : 2 px
      l'anneau de focus, 4 px le courant (champs, boutons, cellules), 6 px ce
      qui flotte, 8 px le panneau. Jamais plus de 8 px, les jauges rondes par
      nature exceptées.
   2. Quatre teintes de CONTENU, pas une de plus : cyan la musique, jaune
      l'habillage et l'automatique, rose la voix, vert les points de cue et
      d'intro. Gris ce qui n'est rien de tout cela — la pub, le reste.
   3. L'accent BLEU est réservé à la SÉLECTION et au focus. Jamais décoratif,
      et c'est le seul endroit de l'interface qui se remplit. Il remplace le
      violet, qui faisait à la fois la sélection, le focus et la marque — trois
      questions, un seul signal. La marque redescend sur la page de connexion,
      qui est le seul endroit où elle a quelque chose à dire.
   4. Le rouge est l'ANTENNE, et lui seul (`--rec`). Ce qui détruit a sa propre
      teinte (`--danger`), et ce qui avertit la sienne (`--warn`, ambre) : un
      bouton qui supprime n'allume pas le studio, et un bandeau d'alerte n'est
      pas un jingle.
   5. Chiffres en monospace tabulaire partout : un chronomètre ne saute pas,
      et deux durées empilées s'alignent.
   6. LE CHROME REDESCEND SOUS LA DONNÉE, et c'est l'inversion de la règle qui
      portait ce numéro. Le panneau est la surface la plus claire du thème
      sombre, parce que c'est lui qui porte ce qu'on lit ; les barres, les
      en-têtes de colonne et les barres de titre n'ont plus d'aplat propre, et
      c'est un FILET en bas qui dit où chaque zone s'arrête. Un panneau ne se
      borde donc plus : il se détache par la valeur de son fond et par un pixel
      d'ombre — « une bordure de 1 px sur chaque bloc hache l'écran et nuit à
      la lecture de loin » (référence, § panneau).
   7. UNE ÉCHELLE, PAS DES TAILLES. Sept corps, trois interlettrages, quatre
      arrondis. Tout ce qui se mesure est un jeton ; rien ne se décide ligne
      par ligne. Le demi-pixel (11,5 px, 12,5 px) a disparu : il ne tombe pas
      sur la même grille de pixels d'une ligne à l'autre, et c'est ce qui
      donnait à la page son air approximatif.

   Tout est en jetons sur :root, redéfinis pour le thème clair SOUS
   `[data-theme="light"]` — et non sous une classe `.theme-light` comme la
   référence : c'est `js/theme.js` qui écrit l'attribut, et le mécanisme de
   bascule est le seul endroit où l'on ne suit pas la planche. Les deux thèmes
   sont portés, sombre par défaut.

   LES NOMS DE JETONS NE BOUGENT PAS. `--ground`, `--panel`, `--head`,
   `--line`, `--accent`, `--clip-*`, `--mark-*`, `--cat-*` sont lus par tout ce
   CSS et, pour une partie d'entre eux, DEPUIS LE JAVASCRIPT — `js/base.js` et
   `js/segue.js` les relisent au montage pour dessiner leurs formes d'onde sur
   canvas, et `js/prefs.js` les réécrit par poste. Seules les VALEURS ont
   changé, et les valeurs restent des hexadécimaux à six chiffres : `prefs.js`
   dérive ses transparences en concaténant deux caractères à la fin.

   Et la page de connexion garde son identité : c'est la seule surface de
   marque de l'application, elle porte ses violets et sa police en dur, et
   aucun de ces changements ne la touche.
   =========================================================================== */

/* ------------------------------------------------------------- La police --
   IBM Plex, servie par le même serveur que le reste. Elle est dessinée pour
   les interfaces techniques, et ça compte aux deux seuls endroits qui font
   cette application : les CHIFFRES — Plex Mono a un zéro barré qu'on ne
   confond avec rien et des chasses fixes, donc un chronomètre qui ne sautille
   pas — et le PETIT CORPS, où les contreformes de Plex Sans restent ouvertes
   à 11 px alors qu'une police d'écran plus serrée se referme. La pile système
   faisait le travail, mais elle le faisait comme un document, pas comme un
   appareil.

   Servie en local et jamais depuis un CDN : un studio tourne derrière un
   pare-feu, parfois sans route vers l'extérieur, et une antenne ne s'arrête
   pas parce qu'une fonte est injoignable. 112 ko pour les cinq fichiers, une
   seule fois, et c'est la seule ressource que la page demande en plus d'elle
   même. `swap` parce qu'un conducteur illisible pendant le chargement serait
   pire que le même conducteur dans la police du système pendant 200 ms.

   Les familles système restent déclarées derrière : si un fichier manque, la
   mise en page ne bouge pas d'un pixel de plus que les métriques. */

@font-face {
  font-family: "Plex Sans";
  src: url("font/plex-sans-var.woff2") format("woff2");
  /* Un seul fichier porte toutes les graisses : on s'autorise donc une
     nuance de poids pour hiérarchiser, là où un jeu de fichiers statiques
     aurait fait payer chaque graisse en octets. */
  font-weight: 100 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Plex Mono";
  src: url("font/plex-mono-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Plex Mono";
  src: url("font/plex-mono-500.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Plex Mono";
  src: url("font/plex-mono-600.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Plex Mono";
  src: url("font/plex-mono-700.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}

:root {
  /* ---- Surfaces ----------------------------------------------------------
     Cinq valeurs, et l'ordre compte : le fond général est le plus sombre, le
     panneau qui porte la donnée monte d'un cran, et le chrome — barres, en-
     têtes de colonnes, barres de titre — monte encore. On s'éclaircit donc en
     montant vers l'outil, et ce sont les filets sombres qui empêchent quinze
     panneaux empilés de faire un damier.

     VALEURS RELEVÉES SUR `radiodj-web` (apps/web/src/index.css, § jetons de
     l'interface) : ce sont les mesures de l'AUTRE projet de l'exploitant, qui
     devient la référence de direction artistique, et elles remplacent les gris
     neutres de `newsui.html`. Deux choses changent de fond :

     — le gris est LÉGÈREMENT REFROIDI (teinte ~215°, saturation très basse).
       La référence le dit : un gris pur paraît sale sous l'éclairage d'un
       studio, un bleu nuit saturé fatigue sur une garde de plusieurs heures.
       C'est un demi-cran de teinte, pas une couleur ;
     — le chrome REDESCEND SOUS LA DONNÉE, et c'est l'inversion de la règle 6
       d'il y a deux refontes. Le panneau est la surface la plus claire parce
       que c'est lui qui porte ce qu'on lit ; les barres et les en-têtes se
       détachent par un FILET, pas par un aplat plus clair. La référence ne
       donne d'ailleurs aucun fond propre à son en-tête de panneau — un seul
       filet en bas — et c'est ce qui lui permet d'arrondir ses panneaux sans
       qu'un bandeau clair vienne couper le coin.

     `--head` reste donc un jeton, mais il vaut désormais la même valeur que
     `--panel` : tout ce qui coiffe se fond dans son panneau, et seul son filet
     le sépare. On ne le supprime pas — trente règles le nomment, et un poste
     qui voudrait retrouver un chrome contrasté n'a qu'une valeur à changer. */
  --ground:    #090b0d;   /* --c-bg       : fond global                    */
  --bg:        #090b0d;
  --bg-sunken: #040507;   /* --c-scrim    : le cran sous le fond           */
  --head:      #12151a;   /* --c-surface  : menubar, en-têtes, thead       */
  --panel:     #12151a;   /* --c-surface  : panneaux, la surface qui porte */
  --panel-2:   #191d23;   /* --c-surface-2: surfaces surélevées            */
  --panel-3:   #222730;   /* --c-surface-3: survol, état enfoncé           */

  /* Deux filets, et ils font deux métiers — la référence les sépare de la même
     façon, sous les noms `--c-line-strong` et `--c-line` : `--line` CONTOURNE
     ce qui se saisit (champ, bouton) et doit se voir ; `--line-soft` sépare
     deux LIGNES d'une même grille et doit à peine se voir, sinon une liste de
     trente titres devient une grille de mots croisés. */
  --line:      #39414d;   /* --c-line-strong : contour des champs          */
  --line-soft: #262c34;   /* --c-line        : séparation ténue, 1,3:1     */
  /* Jeton CONSERVÉ mais vidé de son rôle : il bordait les panneaux d'un filet
     plus sombre qu'eux. La référence ne borde pas ses panneaux du tout — « un
     panneau se détache par la valeur de son fond, pas par un contour : une
     bordure de 1 px sur chaque bloc hache l'écran et nuit à la lecture de
     loin » — et c'est l'ombre portée qui prend le relais. Il garde donc la
     valeur du filet de séparation, pour les barres du haut, qui sont les
     seules à qui la référence accorde encore un trait. */
  --line-chrome: #262c34;
  --line-lit:  #ffffff08; /* le survol d'une ligne : rgba(255,255,255,.03) */

  /* Quatre valeurs de texte : titres, corps, secondaire, effacé. Les trois
     premières passent le contraste sur `--panel` ; la quatrième ne le passe
     pas et c'est voulu — elle ne porte jamais une information dont on ait
     besoin, seulement un rappel qu'on lit si on le cherche.

     Le blanc pur est parti : la référence plafonne à #e8ecf1 (15,0:1 sur le
     panneau), et deux points de luminance en moins suffisent à retirer au
     texte le papillotement qu'il a sur une dalle de studio à pleine
     luminosité. */
  --text-hi:   #e8ecf1;   /* --c-text       : titres, libellés — 15,0:1    */
  --text:      #cdd5de;   /* corps standard — 11,6:1                       */
  --text-dim:  #9aa5b1;   /* --c-text-muted : secondaire — 7,0:1           */
  --text-faint:#828d99;   /* --c-text-faint : désactivé, placeholder — 5,2:1 */

  /* ---- L'accent ----------------------------------------------------------
     LE VIOLET EST REMPLACÉ PAR LE BLEU DE LA RÉFÉRENCE (`--c-accent`,
     #4a90e2). C'est la décision de l'exploitant, et elle se tient : son
     violet était à la fois la sélection, le focus et la marque, donc le même
     signal pour trois questions différentes. Le bleu reste l'accent — la
     SÉLECTION et le FOCUS, règle 3 — et la marque redescend sur la page de
     connexion, qui est le seul endroit où elle a quelque chose à dire.

     Deux valeurs et pas une seule, parce qu'un accent ne sait pas faire les
     deux métiers. `--accent` est une SURFACE : il se remplit, et ce qu'on
     écrit dessus est de l'encre sombre (`--accent-text`, #0b0d10, 5,9:1 sur
     l'accent) — c'est le choix de la référence, et l'inverse du violet, qui
     était sombre et portait du blanc. `--accent-ink` est la version qui
     s'ÉCRIT sur le fond du studio.

     Un accent clair qui porte une encre sombre est le seul changement de ce
     bloc qui demande de l'attention : partout où du blanc était écrit en dur
     sur `--accent`, il faut `--accent-text`. Les trois endroits concernés sont
     la barre de menus, la sélection de texte et l'onglet rempli. */
  --brand:       #c66ce4;
  --accent:      #4a90e2;   /* --c-accent       : 5,6:1 sur le fond        */
  --accent-ink:  #65a4ea;   /* --c-accent-hover : la version qui s'écrit   */
  --accent-2:    #1a56bd;
  /* Le fond de la ligne active, à 0,55 d'alpha (0x8c = 140 = 0,55 × 255).
     C'était 0x59 (0,35), trop pâle pour que la ligne en cours se repère de
     loin, et `js/prefs.js` dérive toujours sa valeur de poste à 0x59 : la
     feuille est donc un cran plus affirmée que le réglage, volontairement. */
  --accent-soft: #4a90e28c;
  --accent-line: #65a4ea;
  --accent-text: #0b0d10;   /* --c-on-accent : l'encre sur l'accent        */

  /* Enregistrement et erreurs — et rien d'autre. `--c-onair` de la référence,
     qui le donne mesuré à 5,7:1 ; l'ancien #ff3333 était un rouge pur, donc
     plus saturé pour un contraste moindre. */
  --rec:      #ff5347;
  --rec-glow: #ff534755;
  /* Jeton AJOUTÉ (`--c-danger`) : la référence sépare l'ANTENNE de l'ERREUR,
     là où cette feuille les confondait sous `--rec` et poussait les boutons
     destructeurs vers le jaune d'avertissement faute de rouge disponible. Un
     bouton qui supprime et un voyant d'antenne n'ont pas à partager leur
     teinte : on n'allume pas le studio en vidant une corbeille. */
  --danger:   #f2655c;

  /* Cue et intro : le vert du système, --col-cue de la référence. Le néon est
     revenu : la référence le donne mesuré, et c'est la couleur des points de
     cue et des marqueurs d'intro. */
  --intro:      #b7ff74;
  --intro-soft: #b7ff741c;
  --intro-line: #b7ff7488;

  /* L'avertissement QUITTE le jaune de l'habillage et reprend l'ambre de la
     référence (`--c-warning`, 8,5:1). Il l'avait rejoint parce que
     `newsui.html` n'a que quatre teintes et que son jaune couvre « tags,
     automation, éléments invalides » — un bandeau d'alerte et un jingle
     portaient donc la même couleur. C'est précisément ce que la décision de
     l'exploitant interdit de laisser faire : LA TEINTE DIT LA NATURE, et le
     jaune dit l'habillage. Un avertissement n'est pas une nature de contenu,
     il a droit à sa propre teinte, et l'ambre ne se confond avec aucune des
     trois du conducteur. */
  --warn:      #e8a33d;   /* --c-warning : 8,5:1                           */
  --warn-soft: #e8a33d1c;
  --ok:        #3ec07a;   /* --c-success : 7,9:1                           */
  --ok-soft:   #3ec07a1c;

  /* ---- La matière : ce que la couleur DIT ---------------------------------
     Ces teintes ne sont pas des couleurs d'interface, ce sont des couleurs de
     contenu, et `js/prefs.js` les expose au réglage du poste. Elles ne
     servent qu'aux formes d'onde, aux bandeaux de la timeline et aux lignes
     du conducteur.

     LA TEINTE DIT LA NATURE : cyan la musique, jaune l'habillage (jingle,
     sweeper, liner), rose le voice track, gris la pub et tout le reste. Trois
     blocs empilés qui se ressemblent obligent à lire trois titres pour savoir
     où l'on est ; trois teintes se reconnaissent sans lire. Le rose n'est pas
     le rouge de l'enregistrement (règle 4), qui reste à lui seul.

     Toutes ont RETROUVÉ leur fluorescence : ce sont les valeurs mesurées de la
     référence (--col-music, --col-tag, --col-voice), et c'est elle qui fixe le
     code couleur de l'exploitation. L'adossement teinte → nature ne bouge pas
     d'un cran : cyan la musique, jaune l'habillage, rose la voix. */
  --wave:       #3bb8f8;   /* --wave-music de la référence                 */
  --wave-dim:   #3bb8f866;
  /* Fond de forme d'onde, isolé du fond global : c'est ce qui fait ressortir
     les ondes. Il suit le refroidissement des surfaces — un creux neutre au
     milieu de gris bleutés se lisait comme une tache brune. Un cran sous le
     panneau, pas au niveau du fond global : la scène est DANS un panneau. */
  --wave-bg:    #0d1014;
  --clip-music:  #72fffe;  /* cyan  — musique                              */
  --clip-jingle: #ffff76;  /* jaune — habillage, automatique               */
  --clip-voice:  #fd69b4;  /* rose  — voice track, parole                  */
  --clip-head:  #fd69b4;
  --clip-body:  #1e1e1e;
  --node:       #b7ff74;

  /* UNE COULEUR PAR REPÈRE. Les traits de la timeline se lisent de loin, en
     parlant : la teinte doit dire lequel c'est avant qu'on ait lu le mot.
     Les quatre premiers sont l'anatomie du disque — on ne les déplace pas ;
     les quatre suivants appartiennent au VT et se saisissent. Aucun n'est
     rouge (règle 4), et le vert de l'intro reste celui du chant (règle 2). */
  --mark-intro: #b7ff74;  /* intro du titre précédent      */
  --mark-fade:  #959595;  /* fondu                         */
  --mark-start: #3bb8f8;  /* StartNext du catalogue         */
  --mark-end:   #666666;  /* fin diffusée                   */
  --mark-voice: #fd69b4;  /* attaque de la voix             */
  --mark-word:  #ffff76;  /* premier mot : le duck part     */
  --mark-up:    #4ade80;  /* remontée : le duck s'arrête    */
  --mark-next:  #72fffe;  /* départ du titre suivant        */
  --mark-sing:  #b7ff74;  /* chant : la limite à ne pas passer */

  /* Catégories de cartouches. Pas de teinte nouvelle : le système n'en a que
     quatre, et une catégorie se range dans l'une d'elles — cyan ce qui est
     musical, jaune l'habillage, rose la parole, gris le reste. */
  --cat-jingle:   #ffff76;
  --cat-sweeper:  #ffff76;
  --cat-liner:    #ffff76;
  --cat-bed:      #72fffe;
  --cat-bruitage: #fd69b4;
  --cat-musique:  #72fffe;
  --cat-pub:      #959595;
  --cat-voix:     #fd69b4;
  --cat-autre:    #666666;

  /* ---- L'échelle typographique --------------------------------------------
     Six corps, et c'est tout ce que la page a le droit d'employer. Elle en
     comptait dix-huit, dont six au demi-pixel : à ce compte-là deux libellés
     voisins n'ont jamais tout à fait la même taille sans que ce soit voulu, et
     l'œil le voit même quand il ne sait pas le nommer.

     Les deux premiers corps ne servent QU'à ce qui se range en colonne ou se
     tait : en-têtes, méta, chiffres d'appoint. Le corps de l'interface est
     `--t-base` ; `--t-read` est réservé aux endroits où on lit des PHRASES —
     aides, bandeaux, notices — parce qu'un paragraphe à 12 px se parcourt mal.

     Les graisses suivent la même discipline : 400 le corps, 500 ce qui se
     repère, 600 un en-tête, 700 un chiffre qui compte. Rien entre les deux —
     les 650 d'avant ne se distinguaient de 600 sur aucune dalle.

     L'ÉCHELLE EST CELLE DE LA RÉFÉRENCE (`tailwind.config.js`, § fontSize),
     convertie du rem au pixel pour cette feuille, qui n'a pas de racine à
     16 px garantie. Chaque corps monte d'un cran : 10 px devient 11, 11
     devient 12, 12 devient 13. C'est un pixel, et c'est beaucoup — les
     contreformes d'une minuscule à 10 px se referment sur une dalle de studio
     vue à trois mètres, et c'est l'écart que l'exploitant lit comme « plus
     propre » sans savoir le nommer. La densité qu'on perd là est rendue par
     `--row-h`, qui ne monte que de deux pixels.

     Le dernier corps fait un saut franc et assumé (la référence ouvre le même
     écart à partir de son `4xl`) : il est réservé à ce qu'on lit de loin
     pendant qu'on parle. */
  --t-micro: 11px;   /* `2xs` : en-têtes de colonnes, méta de panneau       */
  --t-small: 12px;   /* `xs`  : lignes de conducteur, boutons, étiquettes   */
  --t-base:  13px;   /* `sm`  : corps de l'interface, titres de ligne       */
  --t-read:  15px;   /* `base`: ce qui se lit en phrases : aide, notices    */
  --t-lead:  17px;   /* `lg`  : titres de dialogue, horloge                 */
  --t-big:   20px;   /* `xl`  : le peu qui doit s'annoncer de loin          */
  --t-display: 40px; /* `4xl` : le chronomètre de prise, et lui seul        */

  /* Trois interlettrages, en em pour qu'ils suivent le corps. Le premier
     n'existe que pour les capitales : une capitale serrée à 11 px se lit mal,
     une minuscule espacée se lit mal aussi, donc on ne mélange pas. Les deux
     valeurs sont celles de la référence — .08em sur un titre de panneau,
     .04em sur son `2xs` courant. */
  --tr-caps:  .08em;  /* petites capitales d'en-tête          */
  --tr-wide:  .04em;  /* libellés courts qu'on veut poser     */
  --tr-none:  0;
  /* Jeton AJOUTÉ : le titre de GROUPE de la barre latérale, que la référence
     espace d'un cran de plus que ses titres de panneau (.1em contre .08em).
     Trois mots en capitales de 11 px posés seuls sur une colonne ont besoin de
     cet air-là pour ne pas se lire comme un mot unique. */
  --tr-group: .1em;
  /* `tracking-tight` de la référence, posé sur le nom du produit en tête de la
     barre latérale : un libellé en demi-gras se resserre, sinon il se lit comme
     un titre de section et non comme une marque. */
  --tr-tight: -.025em;

  /* L'ARRONDI REVIENT, et c'est le changement le plus visible de cette
     refonte. Cette feuille l'avait mis à zéro partout : une face avant de
     régie n'a pas de coins ronds. La référence tient l'inverse, avec une
     échelle courte et une règle claire en commentaire — « coins sobres : on ne
     dépasse jamais 10 px » — et c'est elle que l'exploitant veut.

     Quatre valeurs, reprises terme à terme (`sm` 2, `DEFAULT` 4, `md` 6,
     `lg` 8). Ce qui compte n'est pas qu'il y ait un rayon, c'est qu'il n'y en
     ait que quatre : les 3 px, 9 px et 999 px qui s'étaient glissés ailleurs
     disaient tous une chose différente au même écran. */
  --radius:    6px;  /* `md` : ce qui flotte — dialogue, menu, bandeau      */
  --radius-s:  4px;  /* `DEFAULT` : la valeur courante, champs et cellules  */
  --radius-xs: 2px;  /* `sm` : l'anneau de focus, les pastilles minuscules  */
  --radius-btn: 4px; /* un bouton prend la valeur courante                 */
  /* Jeton AJOUTÉ (`lg`) : le panneau, et lui seul. Il lui faut un cran de plus
     qu'un champ, sinon un panneau qui contient des champs arrondis se lit
     comme une boîte à angle droit remplie de boîtes rondes. */
  --radius-panel: 8px;
  --gap:       8px;

  --mono: "Plex Mono", "Consolas", ui-monospace, "Cascadia Mono", Menlo, monospace;
  --sans: "Plex Sans", "Segoe UI", system-ui, -apple-system, Roboto,
          "Helvetica Neue", sans-serif;

  /* La pile d'avant, gardée nommée : la page de connexion s'y accroche, pour
     que le changement de DA ne la touche pas (voir § CONNEXION). */
  --sans-legacy: "Segoe UI Variable Text", "Segoe UI", system-ui, -apple-system,
                 Roboto, "Helvetica Neue", sans-serif;

  /* La profondeur venait du filet, et elle vient maintenant de l'ombre : c'est
     l'échelle de la référence (`boxShadow`, § profondeur discrète), avec son
     raisonnement — « une ombre ne sert qu'à décoller ce qui flotte réellement
     au-dessus du contenu ». D'où deux niveaux et pas cinq.

     `--shadow-raised` est AJOUTÉ, et c'est lui qui remplace le contour des
     panneaux : un pixel d'ombre, assez pour qu'un panneau se pose sur le fond
     sans qu'un trait vienne le découper. `--shadow-overlay` est AJOUTÉ aussi,
     pour ce qui flotte pour de bon — menu, barre latérale en superposition.
     Les deux anciennes ombres restent nommées : les dialogues s'y accrochent,
     et une ombre de dialogue n'est pas une ombre de panneau. */
  --shadow-raised:  0 1px 2px 0 #0000002e;
  --shadow-overlay: 0 1px 2px 0 #00000033, 0 12px 28px -14px #00000080;
  --shadow:   0 8px 24px -10px #000000c0;
  --shadow-l: 0 24px 60px -18px #000000e0;

  /* 22 px et non 24 : une barre de titre ne porte qu'une étiquette de 10 px,
     et les deux pixels rendus à chaque panneau font une ligne de conducteur
     de plus quand il y en a six empilés. */
  /* 100 % et non 62 % : la référence donne la ligne de conducteur à SA teinte
     pleine (« Musique: color #72FFFE »), sans rabattement vers le fond du
     panneau. Le mécanisme de mélange reste en place — un poste peut le
     redescendre — mais la valeur du système est maintenant la teinte entière,
     et c'est le fond au survol (rgba blanc 3 %) qui dit l'état, comme dans la
     référence. */
  --nature-mix:   100%;
  --nature-floor: var(--panel);

  /* ---- Les noms de la référence -------------------------------------------
     La planche nomme ses jetons autrement que cette feuille. On ne renomme
     rien — le CSS et le JS lisent les noms d'ici — mais on EXPOSE les noms de
     la référence en alias, pour qu'un relevé fait sur `newsui.html` se pose
     dans cette feuille sans traduction. Ce sont des `var()` et non des copies :
     une valeur ne vit donc qu'à un seul endroit, et un réglage de poste
     (js/prefs.js) les entraîne avec lui. */
  --surf-1:    var(--panel);
  --surf-2:    var(--panel-2);
  --surf-3:    var(--head);
  --surf-4:    var(--line);
  --text-md:   var(--text);
  --text-lo:   var(--text-dim);
  --accent-a:  var(--accent-soft);
  --col-music: var(--clip-music);
  --col-tag:   var(--clip-jingle);
  --col-voice: var(--clip-voice);
  --col-cue:   var(--intro);
  --col-onair: var(--rec);
  --col-ok:    var(--ok);
  --wave-music: var(--wave);

  /* 26 px et non 22 : le titre de panneau est passé de 10 à 11 px avec
     l'échelle, et 22 px ne laissaient plus que 5 px de part et d'autre d'une
     capitale — l'étiquette touchait son filet. La référence respire beaucoup
     plus que ça (12 px de haut et de bas) ; on prend le milieu, parce que six
     panneaux empilés paient chaque pixel. */
  --panel-head-h: 26px;
  /* La hauteur d'une ligne de conducteur, et celle des intervalles qui s'y
     intercalent. Une seule valeur, parce qu'un emplacement de VT qui ne
     tombe pas exactement sur la trame des titres casse le rythme de la liste
     au seul endroit où l'animateur compte les lignes. Deux pixels de plus que
     l'ancienne valeur, pour le pixel gagné par `--t-small` ; au-delà, on
     perdrait une ligne visible par écran et demi. */
  --row-h: 24px;

  /* La largeur de la barre latérale, reprise de la référence (`spacing.sidebar`
     = 15rem = 240 px). Un jeton et non une valeur en dur : c'est la seule
     mesure que la coque et le voile de superposition partagent, et deux
     valeurs qui doivent être égales ne s'écrivent pas deux fois. */
  --rail-w: 240px;

  /* ---- La pile de cartes -------------------------------------------------
     Les mesures du `<main>` de la référence (`AppLayout.tsx`), reprises telles
     quelles : `max-w-[110rem]` et `px-4 py-5 sm:px-6 sm:py-7`. On garde la
     valeur large des deux, celle qui s'applique au-delà de 640 px — c'est la
     taille d'un écran de régie, et le petit écran reprend l'autre dans sa
     requête de média. */
  --pile-max: 110rem;
  --pile-pad-x: 24px;
  --pile-pad-y: 28px;
  /* L'air entre deux cartes. 16 px et non les 8 px de `--gap` : celui-là
     mesurait une GOUTTIÈRE entre deux colonnes, qu'on tirait à la souris. Une
     pile n'a qu'une colonne, et c'est le seul vide qui dit où une carte finit —
     il doit se voir de loin. */
  --pile-gap: 16px;
  /* Ce qu'une carte peut prendre en hauteur avant de défiler dans son coin.
     SANS CETTE BORNE la pile ne tient pas : un catalogue de trois mille
     entrées faisait une carte de cent mille pixels, et les cinq suivantes
     étaient hors de portée. Avec elle, une carte courte reste courte — c'est
     la hauteur de son contenu — et une carte longue défile sur place. */
  --pile-body-max: 68vh;
  /* La hauteur d'une scène de forme d'onde dans la pile, et c'est le jeton le
     plus délicat de la refonte. Les `<canvas>` tiraient leur hauteur d'une
     rangée `1fr` dans un panneau lui-même étiré par sa colonne ; une carte a
     la hauteur de son contenu, donc ce `1fr` valait zéro et l'onde
     disparaissait. On la DÉCLARE ici, en une seule valeur pour l'éditeur de
     transition et pour les points de montage : c'est la même affaire, et deux
     valeurs auraient divergé. */
  --pile-wave-h: 320px;
  /* Les listes qui vivent SOUS une forme d'onde, dans la même carte : les
     repères de montage. Elles ne peuvent pas compter sur `--pile-body-max`,
     qui borne la carte entière et leur laisserait tout ce que l'onde n'a pas
     pris. */
  --pile-list-max: 300px;
}

/* Le système est pensé pour le noir — c'est l'interface de studio. Le thème
   clair reste disponible pour relire un conducteur en plein jour. Ce n'est
   pas le thème sombre inversé terme à terme : sur du blanc, c'est la
   SATURATION qui porte la différence de teinte, là où sur du noir c'est la
   luminosité. Les quatre teintes du système descendent donc jusqu'à des
   valeurs qui tiennent sur du papier, et le chrome, lui, remonte — la règle 6
   s'inverse avec le fond, puisque c'est toujours le contenu qui doit être la
   surface la plus claire. */
:root[data-theme="light"] {
  /* Les surfaces de la référence (`:root.theme-light`), terme à terme. La
     séparation entre les surfaces est PLUS MARQUÉE qu'en sombre, et c'est son
     raisonnement : sur du blanc, un demi-cran de gris ne se voit pas. Le
     panneau est blanc pur et le fond nettement gris — c'est l'inverse exact du
     sombre, où le fond est le plus noir. */
  --ground:    #f3f4f6;   /* --c-bg        */
  --bg:        #f3f4f6;
  --bg-sunken: #e2e5ea;
  --head:      #ffffff;   /* --c-surface, comme en sombre : le chrome se fond */
  --panel:     #ffffff;   /* --c-surface   */
  --panel-2:   #f7f8fa;   /* --c-surface-2 */
  --panel-3:   #ebedf1;   /* --c-surface-3 */

  --line:      #c9cfd8;   /* --c-line-strong */
  --line-soft: #e2e5ea;   /* --c-line        */
  --line-chrome: #e2e5ea;
  /* Le survol d'une ligne : une encre très diluée et non du blanc. Le blanc ne
     peut rien éclaircir sur un panneau déjà blanc — la ligne survolée ne
     bougeait pas d'un cheveu en thème clair. */
  --line-lit:  #15181d08;

  --text-hi:   #15181d;   /* --c-text       : 17,8:1 */
  --text:      #2b3138;   /* corps : 13,9:1          */
  --text-dim:  #555f6b;   /* --c-text-muted : 6,5:1  */
  --text-faint:#5f6873;   /* --c-text-faint : 5,7:1  */

  /* L'accent clair n'est PAS l'accent sombre : la référence le descend à
     #1f63d6 (5,5:1 sur blanc) là où #4a90e2 n'y tiendrait que 2,8:1. Et son
     encre repasse au blanc, puisque la surface, elle, s'est assombrie. */
  --brand:       #8d2aa8;
  --accent:      #1f63d6;   /* --c-accent       */
  --accent-ink:  #1a56bd;   /* --c-accent-hover */
  --accent-2:    #174a9f;
  --accent-soft: #1f63d61f;
  --accent-line: #1f63d688;
  --accent-text: #ffffff;   /* --c-on-accent    */

  --rec:      #b81f1a;      /* --c-onair : 6,5:1 */
  --rec-glow: #b81f1a38;
  --danger:   #c02d26;      /* --c-danger : 5,8:1 */

  --intro:      #3d7a0d;
  --intro-soft: #3d7a0d1f;
  --intro-line: #3d7a0d88;

  --warn:      #8a5a0e;     /* --c-warning : 5,9:1 */
  --warn-soft: #8a5a0e1a;
  --ok:        #13714a;     /* --c-success : 6,0:1 */
  --ok-soft:   #13714a1a;

  --wave:       #1d6ea8;
  --wave-dim:   #1d6ea855;
  --wave-bg:    #ffffff;
  --clip-music:  #0e6d78;
  --clip-jingle: #7d6208;
  --clip-voice:  #b02a71;
  --clip-head:  #b02a71;
  --clip-body:  #ffffff;
  --node:       #3d7a0d;

  --mark-intro: #3d7a0d;
  --mark-fade:  #596169;
  --mark-start: #1d6ea8;
  --mark-end:   #858d95;
  --mark-voice: #b02a71;
  --mark-word:  #7d6208;
  --mark-up:    #10713c;
  --mark-next:  #0e6d78;
  --mark-sing:  #3d7a0d;

  --cat-jingle:   #7d6208;
  --cat-sweeper:  #7d6208;
  --cat-liner:    #7d6208;
  --cat-bed:      #0e6d78;
  --cat-bruitage: #b02a71;
  --cat-musique:  #0e6d78;
  --cat-pub:      #596169;
  --cat-voix:     #b02a71;
  --cat-autre:    #858d95;

  --nature-mix:   80%;
  --nature-floor: var(--text);

  /* L'ombre claire est portée par l'encre de la référence (`--c-shadow`,
     #131821) et non par du noir : un noir pur sur du blanc fait une auréole
     grise sale, alors que la même ombre teintée de l'encre du texte se lit
     comme une épaisseur. Elle est aussi plus marquée qu'en sombre — sur du
     blanc, un pixel d'ombre à 18 % ne se voit pas. */
  --shadow-raised:  0 1px 2px 0 #13182124;
  --shadow-overlay: 0 1px 2px 0 #13182133, 0 12px 28px -14px #13182140;
  --shadow:   0 8px 22px -12px #1a243333;
  --shadow-l: 0 24px 54px -20px #1a243344;
}

* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }

body {
  background: var(--bg);
  color: var(--text);
  font: 13px/1.4 var(--sans);
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}

[hidden] { display: none !important; }

/* ---------------------------------------------------------------- Icônes --
   Dessinées dans js/icons.js, injectées une fois en sprite, posées par
   `<use>`. Le trait et les jointures sont déclarés ICI et pas sur chaque
   dessin : une seule ligne à changer pour affiner tout le jeu.

   `currentColor` est le point important — une icône prend la couleur du texte
   qui l'entoure, donc elle suit les états (survol, actif, désactivé, thème
   clair) sans qu'aucune règle ne la nomme. C'est ce que les anciens glyphes
   Unicode faisaient par accident et l'emoji 🗑 ne faisait pas du tout. */

.ico {
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* Les icônes sont posées dans des lignes de texte : sans ça elles
     s'assoient sur la ligne de base et flottent trop haut. */
  vertical-align: -.14em;
}

/* Chiffres à largeur fixe : un chronomètre ne doit pas « sauter ». */
.tnum {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

* { scrollbar-width: thin; scrollbar-color: var(--line) transparent; }
*::-webkit-scrollbar { width: 9px; height: 9px; }
*::-webkit-scrollbar-thumb {
  background: var(--line);
  border: 2px solid transparent;
  border-radius: 999px;
  background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover { background: var(--text-faint); background-clip: content-box; }
*::-webkit-scrollbar-track { background: transparent; }

::selection { background: var(--accent); color: var(--accent-text); }

/* Respect de la préférence système, en une seule règle pour toute la feuille :
   « aucune animation imposée » (référence, fin d'`index.css`). Deux règles
   ciblées existaient plus bas — le point d'enregistrement qui pulse, le toast
   qui entre — et elles couvraient les deux animations du jour où elles ont été
   écrites. Celle-ci couvre aussi la barre latérale qui glisse, et celles qu'on
   n'a pas encore écrites. On ne met pas à zéro mais à 0,01 ms : une durée nulle
   empêche l'événement `transitionend`, dont dépendent les enchaînements. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
}

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

/* Le bouton de la référence : un fond, pas de contour, et c'est la VALEUR du
   fond qui monte au survol. Le contour n'a pas disparu du code — il est
   transparent — parce que l'état « onglet courant » (`.btn.is-active`) le
   colore, et parce qu'un bouton qui gagne une bordure au survol se décale d'un
   pixel si la bordure n'était pas déjà là.

   La transition passe de `linear` à la courbe de la référence
   (`cubic-bezier(0.2, 0, 0.2, 1)`, 120 ms) : c'est une sortie amortie, qui
   rend le survol moins mécanique sans le rendre lent. Elle ne porte QUE la
   couleur — la zone temps réel n'anime rien qui bouge. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  font: inherit; font-size: var(--t-small); font-weight: 500;
  color: var(--text);
  background: var(--panel-2);
  border: 1px solid transparent;
  border-radius: var(--radius-btn);
  padding: 3px 9px;
  cursor: pointer;
  white-space: nowrap;
  transition: background .12s cubic-bezier(.2, 0, .2, 1),
              border-color .12s cubic-bezier(.2, 0, .2, 1),
              color .12s cubic-bezier(.2, 0, .2, 1);
}
.btn:hover:not(:disabled) { background: var(--panel-3); }
.btn:active:not(:disabled) { background: var(--panel-3); }
/* .4 et non .38 : c'est l'opacité de la référence, et surtout c'est la seule
   valeur d'« inactif » du système — elle était écrite à trois décimales
   différentes selon le bouton. */
.btn:disabled { opacity: .4; cursor: not-allowed; }

.btn--primary {
  background: var(--accent); border-color: transparent;
  color: var(--accent-text); font-weight: 600;
}
/* La teinte survolée de la référence et non un `brightness` : un filtre
   éclaircit AUSSI le texte, donc il rabote le contraste de l'encre sombre
   posée sur l'accent, au moment précis où l'on désigne le bouton. */
.btn--primary:hover:not(:disabled) { background: var(--accent-ink); filter: none; }

.btn--ghost { background: transparent; border-color: transparent; color: var(--text-dim); }
.btn--ghost:hover:not(:disabled) { background: var(--panel-2); color: var(--text); border-color: transparent; }

.btn--icon {
  padding: 5px 7px; background: transparent; border-color: transparent;
  color: var(--text-dim);
}
.btn--icon:hover:not(:disabled) { background: var(--panel-2); color: var(--text); border-color: transparent; }

.btn--small { padding: 2px 7px; font-size: var(--t-small); }
/* Un bouton qui dit l'onglet courant — une vue, un serveur, une page.
   L'accent en TEXTE et en filet, pas en fond : un fond d'accent
   le ferait lire comme le bouton d'action de la barre, qui est juste à côté. */
.btn.is-active { color: var(--accent-ink); border-color: var(--accent); }
.btn--lg { padding: 10px 16px; font-size: var(--t-read); border-radius: var(--radius); }
.btn--block { width: 100%; }
/* `.btn-danger` de la référence : le ROUGE au repos, et le bouton se remplit
   au survol. Il prenait le jaune d'avertissement faute de rouge disponible —
   `--rec` étant réservé à l'antenne — et un bouton qui supprime était donc de
   la même couleur qu'un jingle. `--danger` lui donne sa teinte propre. */
.btn--danger { background: transparent; color: var(--danger); }
.btn--danger:hover:not(:disabled) {
  background: var(--danger); color: var(--accent-text); border-color: transparent;
}

/* L'anneau de focus, posé À L'ÉCART du bord (2 px et non 1) : « la régie se
   pilote aussi au clavier, et l'anneau est posé à l'écart du bord pour rester
   lisible sur un bouton rempli en accent » — un anneau d'accent collé à un
   bouton d'accent ne fait qu'épaissir le bouton. */
:where(button, input, select, a, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

/* Un bouton de la barre d'outils : icône et libellé SUR LA MÊME LIGNE, en
   11 px. L'ancien bouton empilait l'icône au-dessus du mot et imposait
   50 px de hauteur à toute la barre ; une régie met ses outils sur une ligne
   et garde la hauteur pour les formes d'onde. */
.tool {
  display: flex; align-items: center; gap: 4px;
  flex: none;
  font: inherit; font-size: var(--t-small);
  color: var(--text);
  background: transparent; border: 1px solid transparent;
  border-radius: var(--radius-btn);
  padding: 3px 8px;
  cursor: pointer;
  white-space: nowrap;
  /* Pas de transition : règle absolue 4 de la référence — rien ne s'anime dans
     la zone temps réel, et la barre d'outils porte le transport. Un fondu de
     100 ms sur un bouton qu'on frappe en parlant retarde le retour visuel. */
}
.tool__ico { display: block; width: 13px; height: 13px; color: currentColor; }
.tool:hover:not(:disabled) { background: var(--panel-2); border-color: var(--line); }
.tool:disabled { opacity: .34; cursor: not-allowed; }
/* Un outil enclenché : le violet au filet, le jaune au texte. C'est l'état
   « actif » du système, et il ne remplit pas le fond — un bouton plein se lit
   comme un voyant d'antenne, qui est rouge et réservé. */
.tool.is-on { background: transparent; border-color: var(--accent); color: var(--warn); }
/* Sauf la prise en cours : là, c'est bien l'antenne. */
.tool.is-recording { color: var(--rec); border-color: var(--rec); }

/* ------------------------------------------------------- Champs de saisie - */

.field { display: block; margin-bottom: 12px; }
.field > span {
  display: block; font-size: var(--t-small); color: var(--text-dim);
  margin-bottom: 4px; letter-spacing: var(--tr-wide);
}
/* Le champ de la référence : il est posé sur la surface SURÉLEVÉE et non sur
   le panneau. Un champ de la même valeur que le panneau qui le porte n'existe
   que par son contour ; un cran au-dessus, il se voit comme un creux où l'on
   écrit, et son contour n'a plus qu'à préciser le bord. */
.field input:not([type="range"]), .field select {
  width: 100%; font: inherit; font-size: var(--t-small); color: var(--text);
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-s);
  padding: 4px 8px;
  transition: border-color .12s cubic-bezier(.2, 0, .2, 1);
}
/* Le survol annonce le champ AVANT le clic (`hover:border-accent/60` de la
   référence) : sur une fiche de trente réglages, c'est ce qui dit lequel on
   est en train de viser. */
.field input:not([type="range"]):hover, .field select:hover {
  border-color: var(--accent-line);
}
.field input:not([type="range"]):focus, .field select:focus {
  outline: 0; border-color: var(--accent);
}
.field input[type="range"] { width: 100%; }
.field select option { background: var(--panel); color: var(--text); }
input[type="range"] { accent-color: var(--accent); }

.check {
  display: inline-flex; align-items: center; gap: 7px;
  color: var(--text-dim); font-size: var(--t-base); cursor: pointer;
}
.check input { accent-color: var(--accent); }
.check:hover { color: var(--text); }

.hint { color: var(--text-dim); font-size: var(--t-base); margin: 6px 0 0; }
.empty { color: var(--text-faint); padding: 22px 14px; text-align: center; font-size: var(--t-base); }
.spacer { flex: 1; }

.pill {
  font-size: var(--t-micro); font-family: var(--mono);
  color: var(--text-dim);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-btn);
  padding: 2px 8px;
}
.pill:empty { display: none; }

/* ============================================================== CONNEXION ==
   Inchangée : c'est la seule surface de marque de l'application, et elle
   fonctionne. Elle porte ses propres violets en dur, de sorte que le
   changement de DA du reste ne la touche pas.
   =========================================================================== */

/* Y compris la police : on REDÉFINIT les deux jetons ici plutôt que de poser
   un `font-family` :
   ainsi les descendants qui demandent explicitement var(--sans) ou var(--mono)
   — la pastille numérotée des étapes, par exemple — reçoivent eux aussi
   l'ancienne pile, sans qu'aucun d'eux ait à le savoir. */
.login {
  --sans: var(--sans-legacy);
  --mono: ui-monospace, "SF Mono", "Cascadia Mono", "JetBrains Mono", Menlo, monospace;
  font-family: var(--sans);
  /* Et l'ambre de ses messages d'erreur : le reste de l'interface est passé au
     jaune du système, cette page garde ce qu'elle avait. */
  --warn: #f0a93b;
  --warn-soft: #f0a93b1c;
}

.login {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  height: 100%;
  background: #0a0810;
}

.login__brand {
  display: flex; flex-direction: column; justify-content: center;
  gap: 18px;
  padding: 48px clamp(28px, 5vw, 72px);
  color: #f3e9fb;
  background:
    radial-gradient(900px 480px at 15% 0%, #c66ce455, transparent 65%),
    radial-gradient(700px 520px at 85% 100%, #8b3fd055, transparent 60%),
    linear-gradient(150deg, #1a0d28, #0a0810 65%);
  border-right: 1px solid #332b44;
  position: relative;
  overflow: hidden;
}
.login__brand::after {
  content: ""; position: absolute; inset: auto -10% -35% -10%;
  height: 60%;
  background: radial-gradient(60% 100% at 50% 100%, #c66ce42e, transparent 70%);
  pointer-events: none;
}
.login__logo {
  width: min(340px, 72%); height: auto; display: block;
  filter: drop-shadow(0 10px 30px #00000080);
}
.login__claim {
  margin: 0; font-size: var(--t-lead); font-weight: 600; letter-spacing: var(--tr-wide);
  color: #c66ce4;
}
.login__pitch { margin: 0; max-width: 42ch; font-size: var(--t-lead); line-height: 1.65; color: #d7c9e4; }
.login__steps {
  margin: 6px 0 0; padding: 0; list-style: none;
  display: flex; flex-direction: column; gap: 9px;
  counter-reset: step;
}
.login__steps li {
  display: flex; align-items: center; gap: 11px;
  font-size: var(--t-read); color: #c9b9d9;
  counter-increment: step;
}
.login__steps li::before {
  content: counter(step);
  flex: none;
  width: 24px; height: 24px; border-radius: 50%;
  display: grid; place-items: center;
  font-family: var(--mono); font-size: var(--t-small); font-weight: 700;
  color: #c66ce4;
  background: #c66ce41f;
  border: 1px solid #c66ce455;
}

/* Variante sans volet de marque (supervision) : une seule colonne. */
.login--solo { grid-template-columns: 1fr; background: var(--bg); }
.login--solo .login__panel {
  background: radial-gradient(900px 520px at 50% -120px, #c66ce414, transparent 70%);
}
.login__mark {
  height: 34px; width: auto; display: block; margin-bottom: 22px;
  filter: drop-shadow(0 6px 18px #00000066);
}
:root[data-theme="light"] .login__mark {
  filter: invert(1) hue-rotate(180deg) saturate(1.3) brightness(.75);
}

.login__panel { display: grid; place-items: center; padding: 32px 20px; overflow: auto; }
.login__box { width: 100%; max-width: 380px; }
.login__title { margin: 0; font-size: 27px; font-weight: 600; letter-spacing: -.6px; color: #f1ecf6; }
.login--solo .login__title { color: var(--text); }
.login__sub {
  margin: 5px 0 26px; color: #a99db8; font-size: var(--t-base);
  text-transform: uppercase; letter-spacing: var(--tr-caps); font-weight: 600;
}
.login--solo .login__sub { color: var(--text-dim); }
.login .field > span { color: #a99db8; font-size: var(--t-base); }
.login--solo .field > span { color: var(--text-dim); }
.login .field input {
  background: #06050a; border-color: #332b44; color: #f1ecf6;
  border-radius: 9px; padding: 9px 11px; font-size: var(--t-read);
}
.login--solo .field input {
  background: var(--bg-sunken); border-color: var(--line); color: var(--text);
}
.login .field input:focus { border-color: #c66ce4; box-shadow: 0 0 0 3px #c66ce422; }
.login .btn--primary {
  background: linear-gradient(135deg, #c66ce4, #8b3fd0);
  border-color: transparent; color: #150a1c; border-radius: 9px;
  box-shadow: 0 8px 22px -10px #c66ce4;
}
.login--solo .btn--primary {
  background: var(--accent); color: var(--accent-text); box-shadow: none;
}
/* « Se souvenir de moi ». Volontairement pas un `.field` : celui-ci étire ses
   champs sur toute la largeur, ce qui donnerait une case à cocher de trois
   cents pixels de large. Le libellé dit « sur ce poste » et non « se souvenir
   de moi » tout court : en régie partagée, c'est la machine qu'on engage, pas
   soi-même, et c'est ce qu'il faut lire avant de cocher. */
.login__remember {
  display: flex; align-items: center; gap: 8px;
  margin: -2px 0 14px; font-size: var(--t-small); color: var(--text-dim);
  cursor: pointer; user-select: none;
}
.login__remember input {
  width: 14px; height: 14px; margin: 0; accent-color: var(--accent);
}
.login__remember:hover { color: var(--text); }
.login__error {
  color: var(--warn); font-size: var(--t-base); margin: 14px 0 0;
  padding: 9px 12px;
  background: var(--warn-soft);
  border-radius: 9px;
  box-shadow: inset 3px 0 0 var(--warn);
}
.login__foot {
  margin: 26px 0 0; padding-top: 18px;
  border-top: 1px solid #221c2e;
  font-size: var(--t-base); color: #776b88;
}
.login--solo .login__foot { border-top-color: var(--line-soft); color: var(--text-faint); }

@media (max-width: 860px) {
  .login { grid-template-columns: 1fr; grid-template-rows: auto 1fr; }
  .login__brand {
    padding: 24px 20px; gap: 0; align-items: center;
    border-right: 0; border-bottom: 1px solid #332b44;
  }
  .login__claim, .login__pitch, .login__steps { display: none; }
  .login__logo { width: 180px; }
}

/* ================================================================ SHELL ====
   La disposition de la planche de référence, de haut en bas : une barre de
   menus, une barre d'outils d'une seule ligne, le plan de travail, et une
   barre d'état. Le gros ruban à deux étages a disparu : il mangeait 50 px de
   haut pour dire le nom des familles de boutons, alors qu'une régie met tout
   sur une ligne de 26 px et garde la hauteur pour les formes d'onde.
   =========================================================================== */

/* Colonne en flex, et non une grille à rangées nommées par leur ordre : le
   bandeau casque disparaît dès qu'on l'acquitte, et une grille rejouerait
   alors ses rangées d'un cran — le plan de travail héritait de la rangée
   `auto` et se refermait à zéro, écran vide. Ici chaque bloc porte sa propre
   hauteur, l'ordre ne décide plus de rien. */
.app { display: flex; flex-direction: column; height: 100%; min-height: 0; }
.app > :not(.desk) { flex: none; }

/* Un plan de travail par panel, et un seul visible. Il reprend le rôle que
   `.desk` tenait seul : c'est lui qui prend toute la hauteur restante sous la
   barre de menus, et c'est en lui que la barre d'outils, le plan et la barre
   d'état se répartissent.

   La règle vient APRÈS `.app > :not(.desk)` et non avant : les deux ont la
   même spécificité, donc c'est l'ordre du fichier qui tranche. Placée plus
   haut, elle se ferait écraser et le plan de travail se refermerait à zéro. */
.workspace {
  display: flex; flex-direction: column;
  min-height: 0; min-width: 0;
}
.app > .workspace { flex: 1 1 auto; }
/* Reprise de la règle ci-dessus, pour les blocs qui sont maintenant enfants du
   plan de travail et non plus de `.app` : barre d'outils, bandeau casque,
   barre d'état, onglets mobiles. Sans elle, ils se partageraient la hauteur
   avec le plan au lieu de garder la leur. */
.workspace > :not(.desk) { flex: none; }

/* ------------------------------------------- La coque à barre latérale ----
   LA VARIANTE DE COQUE DE LA RÉFÉRENCE (`AppLayout.tsx`) : une colonne de
   navigation fixe à gauche, et à sa droite l'en-tête et le plan de travail
   empilés. C'est ce que l'exploitant demande — « le menu sur le côté ».

   ELLE EST UNE VARIANTE (`.app--rail`) ET NON LA COQUE PAR DÉFAUT, et ce n'est
   pas de la prudence mal placée : `admin.html` navigue entre DIX vues nommées
   en clair, qui débordaient de la barre de menus sur un portable ; le studio
   (`index.html`) n'en a que deux, et une colonne de 240 px pour deux entrées
   prendrait au voice tracking la largeur d'une forme d'onde. La même feuille
   porte les deux pages ; une classe les distingue, et aucune des deux n'a de
   règle à elle.

   UNE GRILLE, ALORS QUE LA COQUE PAR DÉFAUT EST UNE COLONNE EN FLEX. Le
   commentaire ci-dessus explique pourquoi la colonne : une grille à rangées
   implicites rejouait ses rangées d'un cran quand le bandeau casque
   disparaissait, et le plan de travail se refermait à zéro. Ici chaque bloc
   déclare SA cellule nommément (`grid-row`, `grid-column`), donc l'ordre du
   balisage ne décide plus de rien et un bloc qui s'en va ne décale personne —
   c'est exactement la propriété qui manquait à l'ancienne grille.

   Les `.workspace` restent des enfants DIRECTS de `.app` : c'est ce que
   `js/bureaux.js` documente comme la règle de montage d'un bureau, et les
   envelopper dans une coque intermédiaire les aurait fait tomber sous
   `.workspace > :not(.desk) { flex: none }` — tableaux écrasés, bureaux
   invisibles. La grille permet de poser la barre latérale à côté d'eux sans
   toucher à leur parenté. */
.app--rail {
  display: grid;
  grid-template-columns: var(--rail-w) minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr);
}
.app--rail > .rail { grid-column: 1; grid-row: 1 / span 2; }
.app--rail > .menubar { grid-column: 2; grid-row: 1; }
/* `min-height: 0` et non la seule rangée `minmax(0, 1fr)` : un élément de
   grille a `min-height: auto` par défaut, donc un plan de travail dont le
   contenu dépasse POUSSE sa rangée au lieu de défiler dedans. C'est la même
   précaution que le `min-height: 0` de `.workspace`, mais elle doit être
   reprise ici : celle-là s'applique au conteneur en flex, pas à la cellule. */
.app--rail > .workspace { grid-column: 2; grid-row: 2; min-height: 0; }

.rail {
  display: flex; flex-direction: column;
  min-height: 0; min-width: 0;
  overflow-y: auto;
  background: var(--panel);
  /* « Un filet à droite, le seul de l'ossature : il sépare deux zones de rôle
     différent, là où une bordure par bloc hacherait l'écran. » La barre est à
     la valeur du panneau, le plan de travail au fond général : le filet ne fait
     que préciser une limite que la valeur dit déjà. */
  border-right: 1px solid var(--line-soft);
  z-index: 40;
}

/* Le haut de la colonne : la marque, et de quoi refermer au doigt.
   `px-4 py-4` de la référence, soit 16 px partout — contre 12 px avant, et ce
   cran manquait : le logo touchait le bord de la colonne et la première
   rubrique commençait trop haut. */
.rail__brand {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  flex: none;
  padding: 16px;
}

/* LA MARQUE SE DIT MAINTENANT EN PICTOGRAMME ET EN MOTS, et c'est le premier
   des changements demandés sur cette colonne. Elle ne portait que le logo de la
   station sur sa pastille sombre : une image de 17 px de haut, posée seule en
   tête d'une colonne de 240 px, qui ne disait pas ce qu'on avait sous les yeux.
   La référence écrit un pictogramme à l'accent SUIVI DU NOM DU PRODUIT
   (`Sidebar.tsx`, `text-sm font-semibold tracking-tight`), et c'est ce qu'on
   reprend, mesure pour mesure : 18 px d'icône, 10 px d'écart, le nom en demi-
   gras.

   L'ANTENNE PLUTÔT QUE L'ACCENT PUR pour le pictogramme : `--accent-ink` et
   non `--accent`. C'est la règle du jeton — l'accent est une SURFACE qui se
   remplit, `--accent-ink` est sa version qui s'écrit — et un trait de 1,75 px
   tracé dans la valeur de surface perd un point de contraste sur le panneau.

   Le logo de la station n'est plus ici. Il reste à sa place sur la page de
   connexion, qui est le seul endroit où la marque a quelque chose à dire — la
   même décision que celle prise pour le violet lors du changement d'accent. */
.rail__mark {
  display: flex; align-items: center; gap: 10px;
  font-size: var(--t-base); font-weight: 600; letter-spacing: var(--tr-tight);
  color: var(--text-hi); text-decoration: none;
  min-width: 0;
}
.rail__mark .ico { color: var(--accent-ink); }
.rail__mark span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* LE LOGO DE LA STATION, en tête de colonne, à la place du pictogramme.

   SANS FOND, et c'est le fichier qui le permet : il porte un canal alpha
   (1080×1080, RVB+alpha). La pastille sombre d'une première version était un
   réflexe, pas un besoin — elle enfermait le logo dans un rectangle plus
   voyant que lui, et à 22 px avec 2 px de marge intérieure il ne restait que
   18 px de dessin, donc de la bouillie.

   26 px et aucune marge : le logo occupe toute la place qu'on lui donne, et
   la ligne reste à la hauteur du nom qu'il accompagne. `object-fit: contain`
   plutôt qu'une hauteur seule, pour qu'un remplacement de fichier dans un
   autre rapport ne fasse pas sauter la colonne. */
.rail__logo {
  height: 26px; width: 26px; display: block; flex: none;
  object-fit: contain;
}

/* EN THÈME CLAIR SEULEMENT, le logo retrouve un fond sombre. Ce n'est pas une
   hésitation : le dessin est blanc et violet, donc sur une colonne blanche sa
   moitié blanche disparaît purement. Un carré sombre discret le rend lisible
   sans rien changer au thème sombre, où il n'a besoin de rien. */
:root[data-theme="light"] .rail__logo {
  padding: 3px;
  background: #0f0a14;
  border-radius: var(--radius-xs);
}
/* Le bouton de fermeture n'a de sens qu'en superposition : au large, la barre
   ne se referme pas, et un bouton qui ne fait rien se prend pour une panne. */
.rail__close { display: none; }

/* `px-2 pb-6` de la référence : 8 px de côté, 24 px en bas. Les 24 px ne sont
   pas de la décoration — c'est ce qui empêche la dernière entrée de se coller
   au bord de la fenêtre, où elle se lit comme une barre d'état. */
.rail__body { display: flex; flex-direction: column; gap: 24px; padding: 0 8px 24px; }

/* Le titre d'un groupe d'entrées : petites capitales espacées, au gris effacé.
   Il n'est pas cliquable et ne doit pas en avoir l'air — c'est une étiquette
   de rangement, pas une destination.

   `px-3 pb-2` de la référence : 12 px de côté — le MÊME retrait que les
   entrées, pour que le titre et ses entrées s'alignent sur la même verticale —
   et 8 px dessous. */
.rail__groupe {
  margin: 0; padding: 0 12px 8px;
  font-size: var(--t-micro); font-weight: 600;
  text-transform: uppercase; letter-spacing: var(--tr-group);
  color: var(--text-faint);
}
.rail__liste { display: flex; flex-direction: column; gap: 2px; }

/* --------- « Mes affaires », en bas de la colonne ---------
   La seule rubrique écrite en dur dans les deux pages, et la seule POUSSÉE EN
   BAS. `margin-top: auto` dans la colonne flex : elle se colle sous la
   dernière rubrique construite sur une barre courte, et au bas de la fenêtre
   dès qu'il reste de la place.

   Pourquoi là. La pente de cette barre va du quotidien vers l'exceptionnel —
   ce qu'on surveille, ce qu'on fabrique, ce qui fait tourner la machine — et
   « mes affaires » n'est dans aucun de ces trois registres : c'est le compte
   de celui qui regarde. C'est la place du menu de compte dans la référence, et
   c'est là qu'on la cherche.

   Le trait du dessus la DÉTACHE sans en faire une rubrique de plus : sans lui,
   deux entrées de même dessin collées sous la dernière vue se lisent comme la
   suite de la liste. 16 px au-dessus du trait, 16 en dessous — la même
   respiration que le `gap` des rubriques, pour que l'œil ne voie pas un
   rattrapage. */
.rail__compte {
  margin-top: auto;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}

/* QUI EST CONNECTÉ, en tête du bloc du bas. Il vivait dans la barre du haut,
   où l'exploitant a demandé à ne plus rien garder : « la barre en haut, je la
   veux dans le menu latéral ». Le nom est une information, pas une commande —
   il ne prend donc pas l'allure d'une entrée cliquable, mais celle d'un
   libellé de rubrique, posé au-dessus des gestes qui le concernent. */
.rail__ident {
  padding: 0 12px 8px;
  overflow: hidden;
}
.rail__ident .who {
  display: block;
  font-size: var(--t-small);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Les entrées du bas portent `.panelpick__tab` comme celles du haut, mais le
   dessin de la colonne était attaché à `.panelpick--rail` — un conteneur dont
   ce bloc ne fait pas partie. Elles héritaient donc du style des ONGLETS
   HORIZONTAUX : centrées au lieu d'être alignées à gauche, icônes jamais en
   colonne, et les deux liens (« Console », « Studio ») soulignés parce que
   rien ne remettait leur décoration. Une rubrique qui ne ressemble pas au
   reste de la barre se lit comme un morceau rapporté — et c'en était un.

   Les sélecteurs du dessin sont donc ÉTENDUS à ce bloc plutôt que recopiés :
   deux jeux de règles pour la même entrée finiraient par diverger à la
   première retouche, et c'est exactement la dérive que ce fichier décrit
   ailleurs à propos de cette même barre.

   DEUX DES ENTRÉES DE CE BLOC REÇOIVENT `.is-active`, et il a fallu le leur
   accorder : « Mon compte » et « Réglages » ouvrent désormais un PLAN DE
   TRAVAIL, comme le voice tracking ou la supervision — ce sont de vraies vues
   et non plus des dialogues. L'entrée doit donc s'allumer tant que son écran
   est ouvert, sans quoi on ne saurait pas, en lisant la colonne, où l'on se
   trouve. Les autres n'en reçoivent jamais, et c'est voulu : elles déclenchent
   un geste, et un « Quitter » qui resterait allumé après le clic dirait qu'un
   écran est ouvert. C'est `data-ws` / `data-panel` qui fait la différence dans
   le balisage, et c'est le navigateur de vues de chaque page qui pose la
   classe. */
.rail__compte .panelpick__tab { width: 100%; text-decoration: none; }

/* --------- L'entrée de navigation ---------
   C'est le `#panelpick` de la barre de menus, reposé dans la colonne : MÊME
   identifiant, MÊMES classes d'entrée (`.panelpick__tab`), parce que
   `js/admin.js` et `js/panels.js` remplissent ce nœud et basculent cette
   classe. Seul un modificateur sur le conteneur (`.panelpick--rail`) change le
   dessin. Deux jeux de classes pour la même navigation, c'était la dérive
   décrite plus bas au § « La barre de la supervision », et elle a déjà coûté
   un bandeau qui débordait sur téléphone. */
/* `align-items: stretch` est REPRIS explicitement : la règle `.panelpick` de
   la barre de menus centre ses enfants, ce qui est juste sur une ligne et
   désastreux en colonne — chaque entrée se réduirait à la largeur de son
   libellé, et dix rectangles de largeurs différentes empilés au milieu d'une
   colonne ne font pas une liste. */
/* `space-y-6` de la référence entre deux rubriques : 24 px, et non les 18 px
   posés à la première reprise. Trois rubriques de trois entrées empilées avec
   le même écart qu'entre deux entrées ne font pas trois rubriques — on lit une
   seule liste de dix, et le rangement par nature ne sert à rien. */
.panelpick--rail {
  display: flex; flex-direction: column; align-items: stretch;
  gap: 24px; margin: 0;
}
.panelpick--rail .panelpick__tab,
.rail__compte .panelpick__tab {
  position: relative;
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  font: inherit; font-size: var(--t-base);
  text-align: left;
  color: var(--text-dim);
  background: transparent;
  border: 0; border-radius: var(--radius-s);
  /* `px-3 py-1.5` : 12 px de côté, 6 px de haut et de bas. */
  padding: 6px 12px;
  cursor: pointer; user-select: none;
  transition: background .12s cubic-bezier(.2, 0, .2, 1),
              color .12s cubic-bezier(.2, 0, .2, 1);
}
/* L'ICÔNE DE L'ENTRÉE, et c'est le second changement demandé sur cette
   colonne. Dix libellés nus empilés se relisent de haut en bas à chaque fois ;
   un pictogramme donne à chaque entrée une silhouette qu'on retrouve sans
   lire. `Sidebar.tsx` n'a aucune entrée sans icône, et c'était la bonne raison
   de ne pas en mettre à moitié : les trois qui manquaient au jeu ont été
   dessinées (`chart`, `users`, `file` dans js/icons.js).

   `h-4 w-4 opacity-80` de la référence : 16 px, et un cran de transparence.
   L'icône est un repère, pas une information — à pleine encre, dix
   pictogrammes alignés tirent l'œil plus que les mots qu'ils accompagnent. */
.panelpick--rail .panelpick__tab .ico,
.rail__compte .panelpick__tab .ico { opacity: .8; }
/* `truncate` : un libellé long se coupe au lieu d'élargir la colonne. Les
   libellés viennent du serveur (`/api/admin/panels`) et rien ne borne leur
   longueur. */
.panelpick--rail .panelpick__tab span,
.rail__compte .panelpick__tab span {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.panelpick--rail .panelpick__tab:hover,
.rail__compte .panelpick__tab:hover {
  background: var(--panel-2); color: var(--text-hi);
}
/* L'ENTRÉE COURANTE SE DIT DEUX FOIS : un fond relevé d'un cran ET un liseré
   d'accent sur le bord gauche, plus la graisse et la valeur du texte. C'est la
   règle de la référence, écrite au-dessus de sa fonction `linkClass` : « la
   couleur ne porte donc pas seule l'information ». Sur dix entrées dont une
   est ouverte, c'est aussi ce qui permet de la retrouver sans la chercher,
   quelle que soit la dalle et quel que soit le thème. */
/* Étendu au bloc « Mes affaires » pour la raison dite plus haut : « Mon
   compte » et « Réglages » y ouvrent un plan de travail, et une vue ouverte se
   dit de la même façon où que son entrée soit rangée. */
.panelpick--rail .panelpick__tab.is-active,
.rail__compte .panelpick__tab.is-active {
  background: var(--panel-2);
  color: var(--text-hi); font-weight: 500;
  border-bottom-color: transparent;
}
.panelpick--rail .panelpick__tab.is-active::before,
.rail__compte .panelpick__tab.is-active::before {
  content: ""; position: absolute;
  inset: 4px auto 4px 0; width: 2px;
  background: var(--accent);
  border-radius: 999px;
}

/* --------- Au doigt : la colonne passe en superposition ---------
   Sous 900 px, la barre sort du flux et glisse par-dessus le plan de travail,
   avec un voile derrière elle. Un écran de téléphone ne peut pas payer 240 px
   de navigation en permanence, et une navigation qu'on replie en haut de page
   demanderait de remonter avant chaque changement de vue.

   900 px et non 1024 comme la référence : c'est le point de bascule que toute
   cette feuille emploie déjà (la disposition libre, les colonnes empilées, la
   barre du bas), et en ajouter un deuxième ferait un écran où la navigation
   est en superposition pendant que les panneaux sont encore côte à côte. */
@media (max-width: 900px) {
  .app--rail { grid-template-columns: minmax(0, 1fr); }
  .app--rail > .menubar,
  .app--rail > .workspace { grid-column: 1; }

  .app--rail > .rail {
    position: fixed; inset: 0 auto 0 0;
    width: var(--rail-w);
    transform: translateX(-100%);
    transition: transform .2s cubic-bezier(.2, 0, .2, 1);
    box-shadow: var(--shadow-overlay);
  }
  .app--rail.is-nav-open > .rail { transform: translateX(0); }
  .rail__close { display: inline-flex; }

  /* Le voile : il assombrit le plan de travail et il le REND INERTE — on le
     touche pour refermer. Sans lui, un doigt posé à droite de la colonne
     changerait d'heure dans un conducteur qu'on ne voit qu'à moitié. */
  .rail__scrim {
    position: fixed; inset: 0;
    background: var(--bg-sunken);
    opacity: .7;
    z-index: 39;
  }
}
/* Au large, le voile n'existe pas : la barre est dans le flux, rien ne flotte
   au-dessus de rien. La règle est HORS de la requête de média pour couvrir le
   cas où le script l'aurait laissé ouvert avant un agrandissement de fenêtre. */
@media (min-width: 901px) {
  .rail__scrim { display: none; }
}

/* Le bouton qui ouvre la colonne, dans la barre de menus. L'inverse du bouton
   de fermeture : il ne sert qu'en superposition. */
.menubar__burger { display: none; }
@media (max-width: 900px) {
  .menubar__burger { display: inline-flex; }
}

/* Un panel pas encore construit le dit, au lieu d'ouvrir sur un écran vide
   qu'on prendrait pour une panne. */
.holding {
  display: flex; flex-direction: column; justify-content: center;
  gap: 10px;
  flex: 1 1 auto; min-height: 0;
  max-width: 54ch;
  margin: 0 auto; padding: 24px;
  background: var(--bg-sunken);
}
.holding h2 { margin: 0; font-size: var(--t-lead); color: var(--text-hi); }
.holding p { margin: 0; font-size: var(--t-base); line-height: 1.55; color: var(--text); }
.holding__state {
  font-family: var(--mono); font-size: var(--t-small);
  color: var(--text-faint); text-transform: uppercase; letter-spacing: var(--tr-caps);
}

/* ---------------------------------------------------- Barre de menus ------ */

/* Le gris haut du système, le même que les en-têtes de panneau et de grille :
   tout ce qui surplombe a cette valeur. Au centre l'antenne alimentée, à
   droite la session — c'est l'ordre de la planche. */
/* L'EN-TÊTE COLLANT DE LA RÉFÉRENCE (`Header.tsx`), et il se lit sur DEUX
   RANGÉES. Celle-ci porte ce qui ne dépend d'aucune vue — la marque, le nom de
   la vue ouverte, la session —, celle d'en dessous (`.toolbar`) porte les
   outils de la vue. C'est la demande telle qu'elle a été formulée : « titre de
   la vue à gauche, outils de la vue à droite », à ceci près que les deux ne
   tiennent pas sur une ligne quand la vue a douze boutons. La référence
   l'admet d'ailleurs elle-même — son en-tête est `flex-wrap`, et il passe à
   deux rangées dès que la fenêtre se resserre.

   LES MESURES SONT CELLES DE LA RÉFÉRENCE : `px-5 py-2.5` et `gap-x-5`, soit
   20 px de côté, 10 px de haut et de bas, 20 px entre deux grappes. Le bandeau
   passe donc de 4 à 10 px de respiration verticale : c'est l'écart le plus
   visible de cette refonte sur la barre du haut, et c'est lui qui fait que
   l'en-tête ne se lit plus comme une barre de menus de système d'exploitation.

   LE FOND PASSE DU GRIS DU CHROME AU FOND GÉNÉRAL (`bg-bg` de la référence) :
   l'en-tête ne se distingue plus par sa valeur mais par son filet, exactement
   comme une barre de titre de panneau. OPAQUE et sans flou, là où la référence
   met `bg-bg/90 backdrop-blur-sm` : une forme d'onde qui défilerait en
   transparence sous les boutons de transport est une lecture qu'on ne peut pas
   se permettre en cabine, et sur un fond opaque le flou ne dessine rien. */
.menubar {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; justify-content: space-between;
  gap: 20px;
  padding: 10px 20px;
  font-size: var(--t-base);
  background: var(--bg);
  border-bottom: 1px solid var(--line-chrome);
  min-width: 0;
}
.menubar__left, .menubar__right {
  display: flex; align-items: center; gap: 10px; flex: none; min-width: 0;
}

/* Le nom de la vue ouverte, à GAUCHE et non plus au centre.
   `#panel-title` est écrit par `js/panels.js` (studio) et `js/admin.js`
   (console) — un seul nœud, un seul identifiant, et c'est ce qui interdisait
   de recopier le nom de la vue dans chacun des sept rubans. Le déplacer était
   donc le seul moyen de mettre le titre là où la référence le met.

   Il reste visible sur téléphone, contrairement à l'ancien `menubar__center`
   que la requête de média effaçait : sur un écran où la barre latérale est
   refermée, le nom de la vue ouverte est la seule chose qui dise où l'on est. */
.menubar__vue {
  display: flex; align-items: center; gap: 6px;
  color: var(--text-hi); font-size: var(--t-base); letter-spacing: var(--tr-wide);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  min-width: 0;
}
.menubar__vue b { font-weight: 600; }

.menubar__center {
  color: var(--text-hi); font-size: var(--t-base); letter-spacing: var(--tr-wide);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.menubar__center b { font-weight: 600; }
.menubar__sep { color: var(--text-faint); }

/* Un item de menu : rien jusqu'au survol, et le survol le prend en violet.
   C'est le comportement d'une barre de menus, pas celui d'un bouton. */
.menu-item {
  font: inherit; font-size: var(--t-base);
  color: var(--text);
  background: transparent; border: 0; border-radius: 0;
  padding: 2px 6px;
  cursor: pointer;
  white-space: nowrap;
  user-select: none;
}
/* `var(--accent-text)` et non le blanc qui était écrit là en dur. L'accent
   était un violet SOMBRE, et du blanc dessus tenait 8:1 ; le bleu de la
   référence est CLAIR, et le même blanc y tombe à 2,9:1 — un item de menu
   devenait illisible au moment précis où on le désigne. C'est le genre d'écart
   qu'un changement de jeton ne peut pas rattraper tout seul, et c'est pour ça
   que l'encre de l'accent a son propre jeton. */
.menu-item:hover { background: var(--accent); color: var(--accent-text); }
/* La supervision est un vrai lien (`<a href>`) pour s'ouvrir dans un onglet au
   clic du milieu. Il doit pour autant se fondre dans la rangée : ni
   soulignement, ni couleur de lien, et la même hauteur que ses voisins. */
a.menu-item { text-decoration: none; display: inline-flex; align-items: center; }
a.menu-item:hover { color: var(--accent-text); }

/* Le choix du panel : des onglets dans la barre de menus, pas un menu
   déroulant. On change d'outil plusieurs fois par séance, et un déroulant
   demanderait deux gestes là où un suffit — en plus de cacher ce qui existe à
   celui qui vient d'obtenir un accès. */
.panelpick { display: flex; align-items: center; gap: 2px; margin-left: 6px; }
.panelpick__tab {
  font: inherit; font-size: var(--t-base);
  /* Posé sur le gris du chrome : le gris de l'en-tête, pas l'effacé. */
  color: var(--text-dim);
  background: transparent; border: 0;
  border-bottom: 2px solid transparent;
  padding: 2px 8px 1px;
  cursor: pointer; white-space: nowrap; user-select: none;
}
.panelpick__tab:hover { color: var(--text-hi); }
/* L'onglet ouvert est souligné et non rempli : rempli, il se confondrait avec
   le survol violet des items de menu juste à côté. */
.panelpick__tab.is-active {
  color: var(--text-hi); font-weight: 600;
  border-bottom-color: var(--accent);
}

/* Le logo, posé sur une pastille sombre : il est blanc et violet, dessiné
   pour un fond sombre, et disparaîtrait tel quel en thème clair. */
.menubar__brand { display: flex; align-items: center; flex: none; }
.menubar__logo {
  height: 15px; width: auto; display: block;
  padding: 2px 6px;
  background: #0f0a14;
  border: 1px solid var(--line);
}

/* ---------------------------------------------------- Barre d'outils ------ */

/* La référence distingue ses deux barres du haut : la barre de MENUS est au
   gris du chrome (--surf-3), la barre d'OUTILS est au gris du panneau
   (--surf-1) et c'est le filet qui les sépare. Deux bandeaux de la même valeur
   n'en faisaient qu'un, de 50 px de haut. */
/* LA SECONDE RANGÉE DE L'EN-TÊTE COLLANT, et c'est là que les outils de la vue
   ont désormais leur place. Ils vivaient dans un ruban posé au-dessus du plan
   de travail, qui descendait avec lui ; ils sont maintenant dans le bandeau
   fixe de la page, sous le nom de la vue — « titre de la vue à gauche, outils
   de la vue à droite », à une rangée près.

   LA MÉCANIQUE D'AFFICHAGE N'A PAS BOUGÉ D'UN POUCE : un ruban par plan de
   travail, montré avec lui, et pour le panel IA comme pour l'automatisation,
   `renderOutils()` qui cache les boutons des autres modules. Ce qui change est
   l'endroit et le dessin, jamais qui décide quel bouton est là.

   TROIS DÉCISIONS DE DESSIN :

   — LES OUTILS SONT POUSSÉS À DROITE (`flex-end`), parce que c'est la demande
     et parce que la rangée du dessus porte déjà le titre à gauche. Les
     `.spacer` des rubans cessent donc de s'étirer : ils séparaient les outils
     de l'horloge en occupant tout le vide, et ce vide est maintenant à gauche.
     L'horloge et le voyant d'antenne restent au BOUT de la rangée, donc tout à
     droite — « à droite comme sur la planche » tient toujours, et mieux.

   — ELLE SE REPLIE (`flex-wrap`) au lieu de défiler horizontalement. Un ruban
     qui défile cache ses derniers boutons sans le dire ; un ruban qui se
     replie montre tout, et c'est ce que fait la référence.

   — MÊME FOND QUE LA RANGÉE DU DESSUS, et seul le filet les sépare. Elles
     étaient de deux valeurs différentes — c'était la distinction décrite
     ci-dessus, du temps où la barre de menus était au gris du chrome. Les deux
     rangées étant maintenant un seul en-tête, deux valeurs les auraient
     recollées en deux barres. */
.toolbar {
  display: flex; align-items: center; justify-content: flex-end;
  flex-wrap: wrap;
  /* 6 px entre deux boutons, 8 px entre deux rangées repliées. Le `gap-x-5` de
     la référence sépare des GRAPPES, pas des boutons voisins : ici c'est le
     `.toolbar__sep` qui porte cet écart, et 20 px entre chaque bouton
     désolidariseraient « Enregistrer » de « Next ». */
  gap: 8px 6px;
  padding: 8px 20px;
  font-size: var(--t-small);
  color: var(--text-dim);
  background: var(--bg);
  border-bottom: 1px solid var(--line-chrome);
  min-width: 0;
}
/* Voir ci-dessus : le vide est passé à gauche de la rangée, le séparateur
   n'est plus qu'un cran d'air entre deux grappes de boutons. */
.toolbar > .spacer { flex: 0 0 auto; width: 0; }
.toolbar__sep {
  flex: none; width: 1px; height: 18px;
  background: var(--line);
  /* L'écart entre deux grappes, celui que la référence écrit `gap-x-5` : il
     est porté par le séparateur et non par le `gap` de la rangée, pour que
     deux boutons du même geste restent collés. */
  margin: 0 9px;
}

/* Le voyant d'antenne : le seul aplat rouge de l'interface, et il ne s'allume
   que pendant la prise. Hors prise il ne s'éteint pas en gris — il
   disparaît : un voyant éteint se confond avec un bouton.

   L'ENCRE PASSE DU BLANC À `--accent-text`, et c'est une correction, pas un
   choix de style. Du blanc sur ce rouge tenait 3,6:1 — déjà sous le seuil de
   4,5:1 pour un texte de 12 px — et le rouge de la référence, moins saturé,
   l'aurait fait descendre à 3,2:1. L'encre sombre y tient 6,1:1. C'est le même
   raisonnement que l'accent : une surface CLAIRE porte une encre sombre, et un
   voyant d'antenne est la dernière chose de l'écran qu'on peut se permettre de
   ne pas lire. En thème clair, où `--rec` s'assombrit, le même jeton redonne
   du blanc — il n'y a donc qu'une règle, et elle est juste dans les deux. */
.onair {
  flex: none;
  font-size: var(--t-small); font-weight: 700; letter-spacing: var(--tr-caps);
  color: var(--accent-text); background: var(--rec);
  border: 0; border-radius: var(--radius-btn);
  padding: 2px 10px;
  user-select: none;
}
.onair[hidden] { display: none; }

/* Horloge et créneau : le bloc que l'animateur regarde le plus souvent. */
.slotpick {
  display: flex; align-items: center; gap: 3px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-s);
  padding: 2px;
  flex: none;
}
.slotpick__date, .slotpick__hour {
  font: inherit; font-size: var(--t-small); color: var(--text-hi);
  background: transparent; border: 0; border-radius: var(--radius-xs);
  padding: 4px 6px;
}
.slotpick__date { font-family: var(--mono); }
.slotpick__hour { cursor: pointer; font-family: var(--mono); font-weight: 600; }
.slotpick__date:focus, .slotpick__hour:focus { outline: 0; background: var(--panel-2); }
.slotpick__hour option { background: var(--panel); color: var(--text); }
.slotpick .btn--icon { padding: 3px 6px; }

/* L'horloge tient sur la ligne d'outils : l'heure en clair, la date en gris
   juste à côté. Elle ne se met plus sur deux étages — c'est elle qui imposait
   la hauteur du ruban. */
.clock {
  display: flex; align-items: baseline; gap: 7px;
  padding: 0 6px; flex: none;
}
.clock__time {
  font-family: var(--mono); font-size: var(--t-lead); font-weight: 400; line-height: 1;
  color: var(--text-hi);
  font-variant-numeric: tabular-nums;
}
.clock__date {
  font-family: var(--mono); font-size: var(--t-micro); color: var(--text-faint);
  text-transform: uppercase; letter-spacing: var(--tr-caps);
  font-variant-numeric: tabular-nums;
}

.who { color: var(--text-dim); font-size: var(--t-base); white-space: nowrap; }

/* Bandeau d'information (casque, avertissements). */
.notice {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 7px 12px;
  font-size: var(--t-base);
  color: var(--text);
  background: var(--panel-2);
  border-bottom: 1px solid var(--line-soft);
  box-shadow: inset 3px 0 0 var(--accent);
}
.notice--headphones strong { color: var(--accent-ink); }
.notice .btn { margin-left: auto; }
.notice--warn { box-shadow: inset 3px 0 0 var(--warn); background: var(--warn-soft); }

/* ---------------------------------------------------- Barre d'état -------- */

/* Trois lignes de bas d'écran, en chiffres tabulaires. Elle ne porte QUE des
   valeurs que l'application connaît : l'emplacement choisi, l'avancement de
   l'heure, la latence mesurée. La planche y affichait un « ETM / Gap Time »
   venu du playout, qu'on n'a pas — on ne met pas de chiffre inventé sous les
   yeux de quelqu'un qui est à l'antenne. */
.statusbar {
  display: flex; align-items: center; gap: 16px;
  padding: 3px 12px;
  font-family: var(--mono); font-size: var(--t-micro);
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
  /* --surf-1 et non le gris du chrome : la référence pose sa barre d'état sur
     le gris du panneau, et c'est le filet du haut qui la détache. */
  background: var(--panel);
  border-top: 1px solid var(--line-soft);
  min-width: 0;
  overflow: hidden;
}
/* `.status-bar span strong { color: var(--col-tag) }` dans la référence : le
   chiffre qu'on vient lire est jaune, le libellé reste gris. */
.statusbar b { font-weight: 400; color: var(--col-tag); }
.statusbar__item {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
.statusbar__item:empty { display: none; }
.statusbar__item--wide { flex: 1; }

/* ====================================================== DISPOSITION ========
   Trois colonnes de panneaux, redimensionnables, et chaque panneau se glisse
   d'une colonne à l'autre par sa barre de titre. La disposition est gardée
   dans le navigateur (voir js/layout.js) : on règle son écran une fois.
   =========================================================================== */

/* LE VIDE REVIENT ENTRE LES PANNEAUX, et c'est la contrepartie obligée de
   l'arrondi. Cette feuille accolait ses blocs et les séparait d'un filet d'un
   pixel — « une régie n'a pas de plan de travail, elle a une face avant ».
   Deux panneaux arrondis accolés, eux, laissent voir quatre coins qui se
   touchent sans se rejoindre : ça ne se lit ni comme un bloc ni comme deux.
   La référence pose ses panneaux dans une grille à gouttières, et c'est ce
   qu'on reprend : le fond du plan de travail passe entre eux, et c'est lui qui
   dit où chaque panneau commence — exactement le rôle que tenait le filet.

   La gouttière est portée par les POIGNÉES de redimensionnement et non par un
   `gap`, parce que `js/layout.js` intercale déjà un `.split` entre deux
   panneaux et que le `gap` d'une colonne en flex s'appliquerait aussi de part
   et d'autre de ces poignées — on obtiendrait un vide de 24 px là où on en
   veut 8. */
.desk {
  display: flex; align-items: stretch;
  flex: 1 1 auto;
  min-height: 0; min-width: 0;
  /* LE FOND GÉNÉRAL, et non plus le cran du dessous. La référence met son
     `<main>` sur `bg-bg`, la même valeur que son en-tête : c'est ce qui fait
     que les panneaux ont l'air POSÉS sur la page et non enfoncés dedans, et
     c'est l'état que l'exploitant a validé en voyant la pile de cartes.

     Le raisonnement de la gouttière tient toujours — elle doit être plus
     sombre que ce qu'elle sépare —, puisque le fond général (#090b0d) reste
     deux crans sous la surface d'un panneau (#12151a). C'est l'élévation du
     panneau qui porte la séparation, pas l'enfoncement du plan de travail. */
  background: var(--bg);
  padding: var(--gap);
  overflow: hidden;
}

.dock {
  display: flex; flex-direction: column;
  min-width: 0; min-height: 0;
  overflow: hidden;
}
/* Une colonne vide ne disparaît pas : elle reste comme cible de dépôt,
   sinon un panneau sorti de là ne pourrait jamais y revenir. */
.dock:empty { min-width: 46px; }
.dock.is-drop { background: var(--accent-soft); box-shadow: inset 0 0 0 1px var(--accent-line); }

/* Poignée de redimensionnement : la GOUTTIÈRE elle-même, transparente, qui
   laisse voir le fond du plan de travail. Elle faisait un filet d'un pixel et
   sa zone de saisie dépassait de 4 px de chaque côté pour être visable ; elle
   mesure maintenant les 8 px du vide entre deux panneaux, donc la zone de
   saisie EST la poignée. Le `::after` qui l'élargissait reste en place, il ne
   coûte rien et il rattrape le pixel au bord.

   On ne la voit donc toujours pas en tant que poignée — jusqu'au survol, où
   un trait d'accent apparaît en son milieu. C'est la seule façon de dire
   qu'un vide se tire, sans dessiner en permanence une barre qui recollerait
   les deux panneaux. */
.split {
  flex: none; position: relative;
  background: transparent;
  z-index: 3;
}
.split--v { width: var(--gap); cursor: col-resize; }
.split--h { height: var(--gap); cursor: row-resize; }
.split::after {
  content: ""; position: absolute; inset: -4px;
  background: transparent;
}
.split--v::after { inset: 0 -4px; }
.split--h::after { inset: -4px 0; }
/* On ne colore que la poignée survolée, et sur son axe seulement : un trait
   de 2 px au MILIEU de la gouttière, et non le vide entier rempli d'accent —
   un aplat de 8 px entre deux panneaux les recollerait visuellement, ce qui
   est exactement ce qu'on vient d'éviter. */
.split:hover::before, .split.is-active::before {
  content: ""; position: absolute;
  background: var(--accent);
  border-radius: var(--radius-xs);
}
.split--v:hover::before, .split--v.is-active::before { inset: 0 calc(50% - 1px); }
.split--h:hover::before, .split--h.is-active::before { inset: calc(50% - 1px) 0; }

/* « Un panneau se détache par la valeur de son fond, pas par un contour : une
   bordure de 1 px sur chaque bloc hache l'écran et nuit à la lecture de loin. »
   C'est le commentaire de la référence au-dessus de sa classe `.panel`, et
   c'est le raisonnement qu'on adopte : le panneau est la surface la plus
   claire du thème sombre, il n'a pas besoin qu'on lui dessine un bord pour
   qu'on voie où il commence.

   Le contour est donc RETIRÉ et remplacé par un pixel d'ombre. Il reste
   déclaré, transparent : `.panel.is-collapsed`, le cartouchier et la page
   d'admin posent des `border-color` sur des panneaux, et une bordure absente
   ne se colore pas. */
.panel {
  display: grid; grid-template-rows: var(--panel-head-h) 1fr;
  min-height: 0; min-width: 0;
  background: var(--panel);
  border: 1px solid transparent;
  border-radius: var(--radius-panel);
  box-shadow: var(--shadow-raised);
  overflow: hidden;
}
.panel.is-dragging { opacity: .5; }
.panel.is-collapsed { grid-template-rows: var(--panel-head-h); flex: none !important; }
.panel.is-collapsed .panel__body { display: none; }

/* La barre de titre d'un panneau : AUCUN APLAT PROPRE, et un filet en bas.
   C'est le `.panel-head` de la référence — « le seul filet admis, celui qui
   sépare deux zones » — et c'est aussi ce qui permet d'arrondir un panneau :
   un bandeau d'une autre valeur viendrait couper ses deux coins du haut, et il
   faudrait lui redonner le rayon à la main, en deux valeurs qui divergeraient
   à la première retouche.

   Le fond reste écrit `var(--head)`, qui vaut maintenant `--panel` dans les
   deux thèmes : la règle ne change pas, c'est le jeton qui a bougé, et un
   poste qui voudrait retrouver un chrome contrasté n'a qu'une valeur à
   reprendre. */
.panel__head {
  display: flex; align-items: center; gap: 8px;
  padding: 0 6px 0 12px;
  background: var(--head);
  border-bottom: 1px solid var(--line-soft);
  cursor: grab;
  user-select: none;
  min-width: 0;
}
.panel__head:active { cursor: grabbing; }
.panel__title {
  margin: 0; font-size: var(--t-micro); font-weight: 600;
  text-transform: uppercase; letter-spacing: var(--tr-caps);
  /* Le gris effacé, et non le secondaire : « un titre court, en capitales
     discrètes : il doit s'effacer devant les valeurs » (référence, `Panel.tsx`).
     Il prenait le gris du dessus parce que l'en-tête était un aplat clair où
     l'effacé tombait à 2:1 ; sur le panneau, il tient 5,2:1. */
  color: var(--text-faint);
  white-space: nowrap;
}
/* Tout ce qui est posé SUR l'en-tête de panneau garde le gris secondaire : la
   méta d'un panneau est une VALEUR (un compte, une durée, un nom de dossier),
   pas une étiquette, et elle se lit. C'est la seule chose qui distingue encore
   un en-tête de son titre. */
.panel__meta {
  margin-left: auto; font-size: var(--t-micro); color: var(--text-dim);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--mono);
}
.panel__acts { display: flex; align-items: center; gap: 1px; flex: none; }
.panel__act {
  font: inherit; font-size: var(--t-base); line-height: 1;
  background: transparent; border: 0; color: var(--text-dim);
  padding: 4px 5px; cursor: pointer; border-radius: var(--radius-xs);
}
.panel__act:hover { background: var(--panel-3); color: var(--text); }
.panel__body { overflow: auto; min-height: 0; min-width: 0; }
.panel__body--pad { padding: 10px; }

/* Fantôme affiché pendant qu'on déplace un panneau : la barre d'insertion
   entre deux panneaux de la colonne visée. */
.dropline {
  height: 2px; background: var(--accent); flex: none;
  box-shadow: 0 0 6px var(--accent);
}

/* ------------------------------------------------ La pile de cartes -------
   ┌──────────────────────────────────────────────────────────────────────────┐
   │ CES RÈGLES SONT HORS DE LA BOUCLE. Rien ne pose plus `.desk--pile`.      │
   └──────────────────────────────────────────────────────────────────────────┘

   Elles habillent `js/pile.js`, qui a remplacé un temps le gestionnaire de
   bureau par le `<main>` de la référence (`AppLayout.tsx`) : une colonne
   centrée à largeur maximale, des cartes à la suite. L'exploitant l'a vue et a
   tranché autrement — il garde le dessin de `radiodj-web` et reprend le geste
   de poser ses fenêtres. Le bureau est donc de nouveau ce qui monte les sept
   plans de travail, et toutes les règles ci-dessus ont retrouvé leur emploi :
   poignées, lignes de dépôt, panneau en cours de déplacement, colonne vide.

   TOUT CE BLOC EST SOUS UN SEUL MODIFICATEUR, `.desk--pile`, et c'est ce qui a
   permis le retour en arrière sans toucher à une règle du bureau : la classe
   n'est posée que par `js/pile.js`, qui ne tourne plus. On les garde pour la
   même raison que le module — c'est un MODE D'AFFICHAGE qui peut redevenir un
   choix, et il marche.

   TROIS PROPRIÉTÉS CHANGENT DE MAIN quand la pile est en service, et les trois
   expliquent pourquoi elle ne se résume pas à un `flex-direction` :

   1. C'EST LE PLAN DE TRAVAIL QUI DÉFILE, et non chaque panneau dans son
      cadre. Deux colonnes bornées à la hauteur de l'écran n'ont rien à faire
      défiler ; une pile, si.

   2. UNE CARTE A LA HAUTEUR DE SON CONTENU. Sa rangée de corps passe de `1fr`
      — une part de la hauteur que la colonne lui donne — à `auto`. C'est ce
      qui fait qu'un panneau de six lignes occupe six lignes, au lieu du tiers
      de l'écran qu'une part lui aurait réservé.

   3. ... SAUF CE QUI SE MESURE EN PIXELS. Une forme d'onde n'a pas de hauteur
      naturelle : dans un bureau elle prend celle de son cadre, qui suit la
      hauteur de l'écran — c'est l'élasticité à laquelle l'exploitant tient, et
      c'est celle qui s'applique aujourd'hui. Dans une carte à hauteur de
      contenu, ce cadre vaut `auto` et l'onde tombe à zéro : elle disparaît
      sans erreur. D'où les hauteurs déclarées plus bas (`--pile-wave-h`),
      qui ne valent QUE sous `.desk--pile` et n'ont donc aucun effet sur le
      bureau. */

.desk--pile {
  /* En bloc et non en flex : il n'y a plus de colonnes à répartir, et le bloc
     est ce qui permet de centrer la pile par une simple marge automatique. */
  display: block;
  overflow: auto;
  padding: var(--pile-pad-y) var(--pile-pad-x);
  /* LE FOND GÉNÉRAL ET NON LE CRAN D'EN DESSOUS. `.desk` posait `--bg-sunken`
     parce que deux colonnes pleines de panneaux ne laissaient voir du fond que
     la gouttière, et qu'une gouttière doit être plus sombre que ce qu'elle
     sépare. Une pile, elle, laisse voir le fond sur toute la largeur restante,
     et de part et d'autre de la colonne centrale : à ce format, le cran du
     dessous fait un cadre noir autour de l'écran. La référence met son `<main>`
     sur `bg-bg`, la même valeur que son en-tête — c'est ce qui fait que la
     colonne de cartes a l'air posée sur la page et non enfoncée dedans. */
  background: var(--bg);
}

/* La colonne centrale : la largeur maximale de la référence, centrée. Au-delà,
   une ligne de conducteur devient un ruban de deux mètres qu'on lit en
   tournant la tête. */
.desk--pile > .dock--pile {
  display: flex; flex-direction: column;
  gap: var(--pile-gap);
  width: 100%; max-width: var(--pile-max);
  margin: 0 auto;
  overflow: visible;
}
/* La seconde colonne du balisage n'a plus de rôle. `js/pile.js` la masque déjà
   par `hidden` ; la règle est ici pour que la mise en page ne dépende pas de
   l'ordre d'exécution du script. */
.desk--pile > .dock:not(.dock--pile) { display: none; }

.desk--pile .panel {
  /* Plus de part à prendre dans une colonne : une carte à la suite des
     autres. `!important` parce que la règle du petit écran, écrite plus bas
     dans le fichier, impose encore `flex: 1` à tous les panneaux. */
  flex: none !important;
  grid-template-rows: var(--panel-head-h) auto;
}
.desk--pile .panel.is-collapsed { grid-template-rows: var(--panel-head-h); }
/* La barre de titre ne se saisit plus : il n'y a nulle part où emmener la
   carte. Un curseur de préhension sur un élément qui ne bouge pas se prend
   pour une panne — on essaie, et rien ne se passe. */
.desk--pile .panel__head { cursor: default; }
/* Voir `--pile-body-max` : sans cette borne, une carte de catalogue repousse
   les suivantes hors de portée. */
.desk--pile .panel__body { max-height: var(--pile-body-max); }

/* Deux cartes sont bornées PLUS court que les autres, et pour la même raison :
   une liste les précède immédiatement dans la pile, et c'est elle qui décide
   de ce qu'on va régler en dessous. Le conducteur précède l'enregistreur et
   l'éditeur de transition ; le catalogue précède les points de montage. À 68 vh
   chacune, on choisissait une ligne puis on perdait de vue l'outil qu'on vient
   d'armer — ce qui, en cabine, veut dire chercher le bouton d'enregistrement
   à la molette. */
.desk--pile [data-panel="playlist"] .panel__body,
.desk--pile [data-panel="search"] .panel__body { max-height: 40vh; }

/* --- Ce qui se mesure en pixels et non en parts ---
   L'éditeur de transition et les points de montage. Leurs rangées `1fr`
   valaient la hauteur que la colonne leur donnait ; dans une carte à hauteur
   naturelle elles valent zéro, et la forme d'onde disparaît. On les déclare. */
.desk--pile .segue {
  height: auto;
  grid-template-rows: auto auto var(--pile-wave-h) auto;
}
.desk--pile .base__cuebody {
  max-height: none;
  /* Cinq rangées, comme en bureau — scène, défilement, transport,
     enchaînement, repères. La dernière reste `auto` ici : dans une carte à
     hauteur naturelle, c'est `--pile-list-max` juste en dessous qui borne la
     liste, et une rangée en `fr` n'aurait rien à partager. */
  grid-template-rows: var(--pile-wave-h) auto auto auto auto;
}
/* Les repères sous l'onde : leur `max-height: 46%` se référait à une carte de
   hauteur connue. Dans la pile, la carte n'a plus de hauteur de référence —
   un pourcentage y vaut « pas de limite », et trente repères poussaient
   l'onde hors de l'écran. */
.desk--pile .base__points { max-height: var(--pile-list-max); }

/* ============================================================== PLAYLIST ===
   Densité de conducteur : une ligne par élément, hauteur fixe, heure de
   passage à gauche, intro et durée à droite. C'est la grille de Myriad, et
   c'est elle que l'animateur lit en diagonale pour trouver où parler.
   =========================================================================== */

/* L'en-tête de colonnes, collé en haut du défilement : sans lui, « :20 » et
   « 3:42 » en bout de ligne sont deux nombres qu'il faut deviner. Il porte la
   même valeur de gris que les barres de titre — tout ce qui surplombe est à
   #383838 — et il reste visible pendant qu'on fait défiler l'heure. */
.pl-head {
  position: sticky; top: 0; z-index: 2;
  display: grid;
  grid-template-columns: 50px 14px 1fr auto auto;
  align-items: center;
  gap: 6px;
  height: 18px;
  padding: 0 6px;
  background: var(--head);
  border-bottom: 1px solid var(--line-soft);
  font-size: var(--t-micro); font-weight: 600;
  text-transform: uppercase; letter-spacing: var(--tr-caps);
  color: var(--text-dim);
  user-select: none;
}
.pl-head span:nth-child(4), .pl-head span:nth-child(5) { text-align: right; }

/* Programmation non déclarée terminée : le conducteur reste lisible en entier
   — on consulte une heure en cours de retouche sans y toucher — mais on ne
   peut pas y enregistrer, et il faut que ça se voie avant de cliquer. En
   jaune d'avertissement, au-dessus de l'en-tête, non collant : c'est une
   information d'heure, pas une colonne, et elle n'a pas à suivre le
   défilement une fois lue. */
.pl-locked {
  margin: 0;
  padding: 9px 12px;
  background: var(--warn-soft);
  box-shadow: inset 3px 0 0 var(--warn);
  color: var(--warn);
  font-size: var(--t-base);
  line-height: 1.45;
}
.pl-locked b { font-weight: 600; text-transform: uppercase; letter-spacing: var(--tr-wide); }

/* LA COULEUR DIT LA NATURE DE LA LIGNE, et c'est la seule chose qu'elle dit :
   cyan la musique, jaune l'habillage, rose la voix, blanc la tête d'heure,
   gris le reste. Un conducteur se lit en diagonale — on repère un jingle à sa
   teinte, pas en lisant son titre. */
.pl-item {
  display: grid;
  grid-template-columns: 50px 14px 1fr auto auto;
  align-items: center;
  gap: 6px;
  height: var(--row-h);
  padding: 0 6px;
  border-bottom: 1px solid var(--line-soft);
  cursor: pointer;
  position: relative;
  font-family: var(--mono);
  font-size: var(--t-small);
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
}
/* LE RAIL DE NATURE, au bord gauche de chaque ligne. C'est la convention des
   conducteurs de régie — Myriad et mAirList posent tous deux un filet de
   couleur du même côté — et elle vaut parce qu'elle survit au balayage du
   regard : on descend une heure en suivant une colonne de couleur, sans lire
   un seul titre. 3 px et non les 8 px du logiciel de bureau : là-bas la ligne
   fait 40 px de haut, ici elle en fait 22, et un rail se mesure par rapport à
   la ligne qu'il borde, pas dans l'absolu. */
.pl-item::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--nature, transparent);
}

/* LA TEINTE DIT LA NATURE, ET SA CLARTÉ DIT L'ÉTAT. Deux niveaux d'une même
   couleur, comme en régie : au repos la ligne est rabattue vers le fond du
   panneau — un conducteur entier à pleine saturation grésille et plus rien
   n'en ressort —, et elle remonte à sa teinte pleine dès qu'on la survole ou
   qu'on la choisit. C'est ce qui permet de garder quarante lignes colorées
   sans que l'écran devienne un sapin. */
.pl-item {
  --nature-dim: color-mix(in srgb, var(--nature, var(--text-dim))
                          var(--nature-mix), var(--nature-floor));
  color: var(--nature-dim);
}
.pl-item:hover { color: var(--nature, var(--text-dim)); }
.pl-item:hover { background: var(--line-lit); }

.pl-item.k-music  { --nature: var(--clip-music); }
.pl-item.k-jingle { --nature: var(--clip-jingle); }
.pl-item.k-voice  { --nature: var(--clip-voice); }
/* La pub et le reste n'ont pas de teinte propre (règle 2) : ils restent gris,
   et leur rail reste donc le gris du filet — présent, pour que la colonne de
   gauche ne soit pas trouée, mais muet. */
.pl-item.k-ad     { --nature: var(--text-dim); }
.pl-item.k-other  { --nature: var(--text-dim); }

.pl-item__at {
  font-family: var(--mono); font-size: var(--t-small); color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
/* La pastille de type, comme la colonne d'icônes du conducteur : on
   distingue une musique d'un jingle sans lire le titre. */
/* Les lettres J V P ♪ sont devenues des dessins. 14 px ne suffisaient pas :
   un trait de 1,75 réduit à cette taille se referme, là où une capitale
   grasse tenait. La pastille passe donc à 16 px et son trait s'épaissit —
   c'est le seul endroit de l'interface où les icônes sont aussi petites, donc
   le seul qui le demande. */
/* La pastille n'est plus une étiquette colorée mais un dessin de la couleur de
   la ligne : la teinte est déjà portée par le texte, la remplir ferait deux
   fois la même chose en plus gros. */
.pl-item__kind {
  width: 14px; height: 14px;
  display: grid; place-items: center;
  color: currentColor; background: transparent;
}
.pl-item__kind .ico { stroke-width: 2.2; vertical-align: 0; }

/* LE TITRE N'EST PAS UN NOMBRE : il est écrit dans la police de l'interface,
   pas en Consolas. Le monospace sert à ce qui s'aligne en colonne — l'heure,
   l'intro, la durée — et il le sert bien ; appliqué à « Rockabye | Clean
   Bandit » il écarte les lettres, hache les mots et fait lire un titre comme
   une référence de fichier. Le gain de place n'est pas rien non plus : à
   largeur égale, un titre en Segoe tient deux ou trois mots de plus avant de
   se faire couper. */
.pl-item__body {
  min-width: 0; display: flex; align-items: baseline; gap: 6px;
  font-family: var(--sans);
  font-size: var(--t-base);
  letter-spacing: var(--tr-none);
}
.pl-item__title {
  font-weight: 400; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  color: var(--nature, inherit);
}
.pl-item__artist {
  font-size: var(--t-small); color: var(--text-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  flex: none; max-width: 45%;
}
/* Rampe d'intro, en vert : le temps dont l'animateur dispose pour parler sur
   ce titre. Règle 2 — c'est le seul vert de la liste. */
.pl-item__intro {
  font-family: var(--mono); font-size: var(--t-micro); font-weight: 400;
  color: var(--intro);
  background: transparent;
  padding: 0 2px;
  min-width: 32px; text-align: right;
}
.pl-item__intro.is-none { color: var(--text-faint); background: transparent; }
.pl-item__dur {
  font-family: var(--mono); font-size: var(--t-micro); color: inherit; opacity: .8;
  font-variant-numeric: tabular-nums;
}
/* La lettre de fin : F pour un fondu, E pour une fin nette. */
.pl-item__dur b { font-weight: 700; color: var(--text-faint); }

/* « retirer » sur la ligne d'un VT livré : le seul geste qu'on puisse faire
   depuis le conducteur sur un élément déjà à l'antenne. Il est en rouge
   d'antenne, discret au repos — c'est une porte de secours, pas une action
   courante —, et il ne se remplit qu'au survol. */
.pl-item__pull {
  flex: none;
  margin-left: auto;
  padding: 0 5px;
  border: 1px solid transparent;
  border-radius: var(--radius-btn);
  background: transparent;
  color: var(--rec);
  font-family: var(--sans);
  font-size: var(--t-micro);
  line-height: 15px;
  cursor: pointer;
  opacity: .55;
}
.pl-item:hover .pl-item__pull { opacity: 1; border-color: var(--rec); }
/* Même correction que le voyant d'antenne : encre sombre sur rouge clair. */
.pl-item__pull:hover { background: var(--rec); color: var(--accent-text); }
.pl-item__pull:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.pl-item--fixed { cursor: default; }
.pl-item--fixed:hover { background: transparent; }
/* La ligne choisie : le violet de sélection au fond, le jaune au texte. C'est
   le seul endroit de l'interface où le violet remplit quelque chose — et le
   rail de nature cède alors la place à l'accent, parce qu'à cet instant la
   question n'est plus « qu'est-ce que c'est » mais « où suis-je ». */
.pl-item.is-selected { background: var(--accent-soft); color: var(--warn); }
.pl-item.is-selected::before { background: var(--accent-line); }
.pl-item.is-selected .pl-item__title,
.pl-item.is-selected .pl-item__artist { color: var(--warn); }

/* L'intervalle entre deux titres : c'est LÀ qu'on parle.
   Trois états, et un seul attire l'œil à la fois :
     — emplacement prévu par le conducteur : affiché en permanence ;
     — VT déjà posé : affiché, avec son statut ;
     — n'importe où ailleurs : muet, révélé au survol.
   Depuis qu'on peut parler partout, l'intervalle muet affiche quand même un
   « + » discret au survol : sans lui, rien ne disait que c'était cliquable. */
.pl-mark {
  display: flex; align-items: center; gap: 6px;
  height: 11px; padding: 0 8px 0 62px;
  cursor: pointer;
  position: relative;
}
.pl-mark__line {
  flex: 1; height: 1px;
  background: repeating-linear-gradient(90deg, var(--text-faint) 0 3px, transparent 3px 7px);
  opacity: 0;
}
.pl-mark__chip {
  font-family: var(--sans);
  font-size: var(--t-micro); font-weight: 700; line-height: 1.3;
  text-transform: uppercase; letter-spacing: var(--tr-caps);
  color: var(--accent-ink);
  padding: 0 6px; border-radius: 2px;
  border: 1px solid currentColor;
  opacity: 0;
  white-space: nowrap;
}
.pl-item:hover + .pl-mark .pl-mark__line,
.pl-mark:hover .pl-mark__line { opacity: .45; }
.pl-item:hover + .pl-mark .pl-mark__chip,
.pl-mark:hover .pl-mark__chip { opacity: 1; }

.pl-mark.is-planned {
  height: var(--row-h);
  background: var(--accent-soft);
  box-shadow: inset 2px 0 0 var(--accent);
}
.pl-mark.is-planned .pl-mark__line { opacity: .4; }
.pl-mark.is-planned .pl-mark__chip {
  opacity: 1; font-size: var(--t-micro); padding: 2px 8px;
  color: var(--accent-text); background: var(--accent); border-color: var(--accent);
}
.pl-mark.is-posed { height: var(--row-h); background: var(--bg-sunken); }
.pl-mark.is-posed .pl-mark__line { opacity: .3; }
.pl-mark.is-posed .pl-mark__chip {
  opacity: 1; font-size: var(--t-micro); padding: 2px 8px;
  color: inherit; background: var(--panel); border-color: currentColor;
}
.pl-mark.is-selected .pl-mark__chip { opacity: 1; }
.pl-mark.is-selected .pl-mark__line { opacity: .5; }

/* Statuts — même convention sur le studio et la supervision. */
.st-libre, .st-a_faire     { color: var(--text-faint); }
.st-recu, .st-traite       { color: var(--accent-ink); }
.st-livre, .st-insere      { color: var(--ok); }
.st-rejete, .st-trop_tard  { color: var(--warn); }

/* ============================================================ ENREGISTREUR = */

.recorder { display: flex; flex-direction: column; gap: 8px; padding: 10px; }
.recorder__main { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }

.rec {
  display: flex; align-items: center; gap: 9px;
  font: inherit; font-size: var(--t-read); font-weight: 600;
  color: var(--text);
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-btn);
  padding: 9px 16px;
  cursor: pointer;
  /* Règle absolue 4 : pas de transition sur le bouton de prise. */
}
.rec:hover:not(:disabled) { border-color: var(--text-faint); background: var(--panel-3); }
.rec:disabled { opacity: .38; cursor: not-allowed; }
.rec__dot { width: 11px; height: 11px; border-radius: 50%; background: var(--text-faint); }
.rec:not(:disabled) .rec__dot { background: var(--rec); }
.rec.is-recording {
  background: var(--rec); border-color: var(--rec); color: var(--accent-text);
  box-shadow: 0 0 0 3px var(--rec-glow);
}
.rec.is-recording .rec__dot {
  background: #fff;
  animation: pulse 1s steps(2, end) infinite;
}
@keyframes pulse { 50% { opacity: .15; } }
@media (prefers-reduced-motion: reduce) { .rec.is-recording .rec__dot { animation: none; } }

/* NEXT : lancer le titre suivant pendant qu'on parle. Vert, parce qu'il
   déclenche l'intro — c'est le même geste et le même repère (règle 2). */
.nextbtn {
  display: flex; align-items: center; gap: 9px;
  font: inherit; font-size: var(--t-read); font-weight: 600;
  color: var(--intro);
  background: var(--intro-soft);
  border: 1px solid var(--intro-line);
  border-radius: var(--radius-btn);
  padding: 9px 16px;
  cursor: pointer;
  transition: background .1s linear, color .1s linear;
}
.nextbtn:hover:not(:disabled) { background: var(--intro); color: var(--bg); }
.nextbtn:disabled { opacity: .3; cursor: not-allowed; }
.nextbtn.is-fired {
  background: var(--intro); color: var(--bg); border-color: var(--intro);
}
.nextbtn__ico { width: 17px; height: 17px; }
.nextbtn__sub { font-size: var(--t-micro); font-weight: 500; opacity: .85; display: block; }

.recorder__time { display: flex; flex-direction: column; min-width: 0; gap: 1px; }
.recorder__hint { font-size: var(--t-small); color: var(--text-dim); }
.recorder__row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

.timer {
  font-family: var(--mono);
  font-size: var(--t-display); font-weight: 300; letter-spacing: var(--tr-none); line-height: 1;
  color: var(--text-hi);
  font-variant-numeric: tabular-nums;
}
.timer--small { font-size: var(--t-lead); font-weight: 400; letter-spacing: var(--tr-none); color: var(--text-dim); }
.is-recording-session .timer { color: var(--rec); }

/* Le compte à rebours d'intro : une fois NEXT appuyé, il dit combien de
   secondes de chant restent avant qu'on parle dessus. C'est l'information
   que l'animateur cherche, et elle vire à l'ambre quand il la dépasse. */
.introbar {
  display: flex; align-items: center; gap: 9px;
  padding: 5px 9px;
  background: var(--intro-soft);
  border: 1px solid var(--intro-line);
  border-radius: var(--radius-s);
  font-size: var(--t-base);
  color: var(--intro);
}
.introbar__left {
  font-family: var(--mono); font-size: var(--t-big); font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.introbar__gauge {
  flex: 1; height: 4px; border-radius: 999px;
  background: #00000040; overflow: hidden; position: relative;
}
.introbar__fill { position: absolute; inset: 0 100% 0 0; background: var(--intro); }
.introbar.is-over {
  background: var(--warn-soft); border-color: var(--warn); color: var(--warn);
}
.introbar.is-over .introbar__fill { background: var(--warn); }

/* Le vumètre. Échelle de 60 dB : -60 dBFS à gauche, 0 à droite.

   Le dégradé couvre TOUTE la largeur et c'est un découpage qui le révèle.
   Auparavant il était peint sur le remplissage lui-même, donc ses bornes —
   60 %, 85 % — se rapportaient à la largeur courante de la barre et non à
   l'échelle : à n'importe quel niveau la barre était 60 % verte, 25 % ambre
   et 15 % rouge, avec du rouge au bout en permanence. Un vumètre dont les
   couleurs suivent l'aiguille ne dit rien du niveau.

   Les bornes sont maintenant des valeurs : -12 dBFS (80 %) et -6 dBFS (90 %).
   Une voix bien réglée vit dans le vert et tape l'ambre sur les attaques. */
.meter {
  position: relative; height: 8px;
  background: var(--bg-sunken);
  border: 1px solid var(--line-soft);
  border-radius: 2px;
  overflow: hidden;
}
.meter__fill {
  position: absolute; inset: 0;
  background: linear-gradient(
    90deg,
    var(--ok) 0 80%,
    var(--cat-jingle) 80% 90%,
    var(--rec) 90%
  );
  /* `clip-path` plutôt que `width` ou `right` : le découpage se fait à la
     composition, sans refaire la mise en page soixante fois par seconde. */
  clip-path: inset(0 100% 0 0);
}
/* Les deux graduations, visibles même quand la barre est basse : on doit
   savoir où est la zone rouge AVANT d'y entrer. */
.meter::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(90deg, transparent 80%, var(--line) 80% calc(80% + 1px), transparent calc(80% + 1px)),
    linear-gradient(90deg, transparent 90%, var(--line) 90% calc(90% + 1px), transparent calc(90% + 1px));
  opacity: .55;
}
/* La crête, retenue puis relâchée : c'est elle qui dit qu'on a saturé il y a
   une seconde, quand l'aiguille est déjà redescendue. */
.meter__peak {
  position: absolute; top: 0; bottom: 0; left: 0; width: 2px;
  margin-left: -1px;
  background: var(--text); opacity: .85;
}
.meter__peak.is-hot { background: var(--rec); opacity: 1; }

/* =========================================================== SEGUE EDITOR ==
   L'éditeur de transition, refait sur le modèle du Segue Editor de Myriad :
   les éléments sont empilés en cascade sur une seule échelle de temps, chacun
   avec son bandeau de titre et sa forme d'onde, et la courbe de volume se
   tire à la main par ses nœuds.

   Tout le dessin est fait au canvas (js/segue.js) : à ce niveau de densité —
   quatre formes d'onde, une grille, une enveloppe, un curseur à 60 images par
   seconde — le DOM ne suit pas. Le CSS ne pose donc que le cadre.
   =========================================================================== */

.segue {
  position: relative;
  display: grid; grid-template-rows: auto auto 1fr auto;
  height: 100%; min-height: 0;
}

.segue__bar {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 4px 10px;
  background: var(--panel);
  border-bottom: 1px solid var(--line-soft);
}

/* Le gros bouton rond de lecture, comme au centre de l'éditeur de Myriad. */
.playbtn {
  width: 32px; height: 28px; border-radius: var(--radius-btn);
  display: grid; place-items: center;
  color: var(--ok); background: var(--panel-2);
  border: 1px solid var(--line);
  cursor: pointer; flex: none;
  /* Plus de décalage optique : le triangle était un glyphe, posé de travers
     dans sa case, qu'il fallait repousser de 2 px pour qu'il ait l'air
     centré. Le dessin SVG est centré dans sa grille, lui. */
}
.playbtn:hover:not(:disabled) { background: var(--panel-3); }
.playbtn:disabled { opacity: .35; cursor: not-allowed; }
.playbtn.is-playing { color: var(--warn); border-color: var(--accent); }

.knob {
  display: flex; align-items: center; gap: 6px;
  font-size: var(--t-micro); text-transform: uppercase; letter-spacing: var(--tr-caps);
  color: var(--text-dim);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-s);
  padding: 3px 8px;
}
.knob input[type="range"] { width: 84px; }
.knob output {
  min-width: 50px; color: var(--text-hi); font-size: var(--t-small); font-weight: 400;
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  text-transform: none; letter-spacing: var(--tr-none);
}
/* Le libellé du niveau porte le nom de l'élément visé : un titre de cartouche
   entier pousserait tout le reste de la barre hors de l'écran. */
#knob-gain > span {
  max-width: 94px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.knob.is-overridden { opacity: .45; }
.knob.is-overridden input { pointer-events: none; }

/* La bande d'aperçu : toute la transition en miniature, avec le rectangle de
   la portion affichée en dessous — on le glisse pour se déplacer, on tire ses
   bords pour zoomer. C'est la barre du haut de l'éditeur de Myriad. */
.segue__overview {
  position: relative; height: 26px;
  background: var(--wave-bg);
  border-bottom: 1px solid var(--line-soft);
  cursor: grab;
  touch-action: none;
}
.segue__overview:active { cursor: grabbing; }
.segue__overview canvas { display: block; width: 100%; height: 100%; }
.segue__view {
  position: absolute; top: 0; bottom: 0;
  border: 1px solid var(--accent);
  background: var(--accent-soft);
  pointer-events: none;
}

/* Fond de forme d'onde isolé du fond global : c'est la règle qui fait
   ressortir les ondes, et le canvas lit le même jeton (js/segue.js). */
.segue__stage {
  position: relative; min-height: 0;
  background: var(--wave-bg);
  overflow: hidden;
  touch-action: none;
}
.segue__stage canvas { display: block; width: 100%; height: 100%; }
.segue__stage.is-dragover { box-shadow: inset 0 0 0 2px var(--accent); }
/* Les curseurs changent selon ce qui est sous le pointeur : le canvas ne
   peut pas le dire tout seul, js/segue.js pose la classe. */
.segue__stage[data-cursor="move"]   { cursor: ew-resize; }
.segue__stage[data-cursor="node"]   { cursor: grab; }
.segue__stage[data-cursor="drag"]   { cursor: grabbing; }
.segue__stage[data-cursor="line"]   { cursor: crosshair; }
.segue__stage[data-cursor="pan"]    { cursor: default; }

.segue__foot {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 3px 10px;
  font-size: var(--t-micro); color: var(--text-dim);
  background: var(--panel);
  border-top: 1px solid var(--line-soft);
  min-height: 22px;
}
.segue__legend { display: flex; align-items: center; gap: 4px; }
.segue__legend i {
  width: 9px; height: 9px; display: block;
}
.segue__hint { margin-left: auto; color: var(--text-faint); }

/* Le message d'attente se POSE sur la scène, il ne s'ajoute pas dessous : en
   enfant de plus de la grille, il ajoutait une rangée pleine hauteur et
   faisait défiler tout le panneau. La barre d'outils reste donc atteignable,
   notamment le bouton d'écoute. */
.segue__empty {
  position: absolute; inset: 0; z-index: 2;
  display: grid; place-content: center; gap: 6px; align-content: center;
  background: var(--wave-bg);
  color: var(--text-faint); font-size: var(--t-base); text-align: center; padding: 20px;
}
.segue__empty small { color: var(--text-faint); opacity: .75; font-size: var(--t-small); }

/* =========================================================== CARTOUCHIER === */

/* Le cartouchier : SEIZE LECTEURS, pas seize vignettes.
   Chacun a l'anatomie d'un cartoucheur de régie (FreQQ, MediaWall) : plaque de
   titre, barre de défilement, gros compteur monospace, commandes. Le compteur
   est l'élément le plus gros du panneau, devant le titre — en parlant on ne
   relit pas le nom du jingle qu'on vient de lancer, on regarde le temps qui
   reste avant de reprendre.
   Deux écarts assumés avec FreQQ : son PLAY vert et son STOP rouge sont ici
   impossibles (règles 1 et 2 de cette feuille), donc le départ prend l'accent
   et l'arrêt reste neutre. On ne perd rien : le lecteur en l'air s'allume
   entièrement, ce qui se voit de plus loin qu'un bouton coloré.
   Les cases vides restent dessinées : un mur à trous se mémorise par ses
   positions, un mur qui se referme sur lui-même se réapprend à chaque fois. */

/* Les pages vivent dans la barre de titre du panneau, à côté du nom : c'est la
   place qu'elles ont sur une console, et ça rend toute la hauteur au mur. */
/* Sur une seule ligne, et rognées plutôt qu'enroulées : la barre de titre fait
   26 px de haut, un retour à la ligne y serait coupé en deux. */
.wall__tabs {
  display: flex; flex-wrap: nowrap; gap: 1px;
  margin-left: 6px; margin-right: auto;
  min-width: 0; overflow: hidden;
}
.wall__tab {
  font: inherit; font-size: var(--t-micro); font-weight: 600;
  letter-spacing: var(--tr-caps); text-transform: uppercase;
  padding: 2px 8px;
  /* Les pages du cartouchier vivent dans l'en-tête du panneau : gris
     d'en-tête (#959595), sinon elles s'effacent sur le #383838. */
  color: var(--text-dim);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-btn);
  cursor: pointer;
}
.wall__tab:hover { color: var(--text); background: var(--panel-2); border-color: var(--line); }
.wall__tab.is-active {
  color: var(--warn); background: transparent; border-color: var(--accent);
}
.wall__tab--add { letter-spacing: var(--tr-none); font-size: var(--t-base); padding: 1px 7px; }

/* Deux murs dans la même barre : « Antenne » puis ses numéros, « Perso » puis
   les siens. L'étiquette de groupe n'est pas un onglet — elle ne se clique
   pas, elle est en retrait, et c'est ce qui permet aux onglets de n'afficher
   qu'un chiffre sans qu'on se demande à quoi il appartient. */
.wall__group {
  align-self: center;
  font-size: var(--t-micro); font-weight: 700;
  letter-spacing: var(--tr-caps); text-transform: uppercase;
  color: var(--text-dim); opacity: .8;
  padding: 0 5px 0 2px;
  white-space: nowrap;
}
.wall__group--mine { margin-left: 6px; border-left: 1px solid var(--line); padding-left: 8px; }

.wall {
  display: grid;
  /* 150 px : deux lecteurs tiennent dans une colonne étroite, quatre dans un
     panneau tiré en pleine largeur — la disposition à quatre colonnes d'un
     cartoucheur se retrouve dès qu'on lui en laisse la place. */
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  align-content: start;
  /* Un filet d'un pixel entre les cases, et non un écart : c'est la grille de
     cartouches du système — les lecteurs se touchent, séparés par le trait. */
  gap: 1px;
  padding: 1px;
  background: var(--line-soft);
}
.wall .empty { grid-column: 1 / -1; }

.cp {
  position: relative;
  display: flex; flex-direction: column;
  min-height: 120px;
  color: var(--text);
  background: var(--ground);
  border: 0;
  border-left: 2px solid var(--cat-autre);
  border-radius: var(--radius-xs);
  overflow: hidden;
  cursor: pointer;
}
.cp:hover { background: var(--panel); }
.cp.is-playing {
  background: var(--accent-soft);
}
.cp.is-dragging { opacity: .45; }

/* La plaque de titre : fond enfoncé, titre sur une ligne. Deux lignes de titre
   feraient danser le compteur d'un lecteur à l'autre, et c'est le compteur
   qu'on vise. */
.cp__plate {
  display: flex; align-items: center; gap: 6px;
  padding: 5px 8px 4px;
  background: transparent;
  border-bottom: 1px solid var(--line-soft);
}
.cp__num {
  font-family: var(--mono); font-size: var(--t-micro);
  color: var(--text-faint); flex: none;
}
.cp__title {
  flex: 1; min-width: 0;
  font-size: var(--t-small); font-weight: 400; line-height: 1.3;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* La touche du clavier : c'est elle qu'on cherche des yeux en parlant. */
.cp__key {
  flex: none;
  font-family: var(--mono); font-size: var(--t-small); font-weight: 400; line-height: 1;
  color: var(--text-dim);
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-btn);
  padding: 1px 4px;
}
.cp.is-playing .cp__key { color: var(--warn); border-color: var(--accent); }

/* Le défilement, en vraie position de lecture — pas une animation lancée à
   l'aveugle : l'aiguille est calculée sur l'horloge audio. */
.cp__rail {
  height: 3px; background: var(--panel);
  border-bottom: 1px solid var(--line-soft);
}
.cp__fill {
  display: block; height: 100%;
  background: var(--accent);
  transform: scaleX(0); transform-origin: left;
}

.cp__body {
  flex: 1;
  display: flex; flex-direction: column; justify-content: center;
  gap: 2px; padding: 5px 7px 3px;
}
.cp__count {
  font-family: var(--mono); font-size: var(--t-big); font-weight: 300;
  line-height: 1; letter-spacing: var(--tr-none);
  font-variant-numeric: tabular-nums;
  color: var(--text-hi);
}
.cp.is-playing .cp__count { color: var(--warn); }
.cp__meta {
  display: flex; justify-content: space-between; gap: 6px;
  font-family: var(--mono); font-size: var(--t-micro);
  color: var(--text-faint);
}
.cp__loopmark {
  display: inline-flex; align-items: center; gap: 3px;
  color: var(--text-dim);
}

/* Le niveau du lecteur, comme le VOL d'une colonne de cartoucheur. Discret au
   repos, il s'éclaire au survol : on le règle une fois par cartouche, pas à
   chaque lancement. */
.cp__vol {
  display: flex; align-items: center; gap: 5px;
  padding: 0 7px 3px;
  cursor: pointer;
}
.cp__voltag {
  font-size: var(--t-micro); font-weight: 700; letter-spacing: var(--tr-caps);
  color: var(--text-faint);
}
.cp__vol input[type="range"] {
  flex: 1; min-width: 0; height: 12px; margin: 0;
  accent-color: var(--text-faint);
  cursor: pointer;
}
.cp:hover .cp__vol input[type="range"],
.cp.is-playing .cp__vol input[type="range"] { accent-color: var(--accent); }

/* Les commandes. Toujours visibles : un bouton qui n'apparaît qu'au survol
   oblige à chercher à la souris ce qu'on devrait frapper du doigt. */
.cp__acts {
  display: flex; gap: 2px;
  padding: 4px 5px 5px;
  border-top: 1px solid var(--line-soft);
}
.cpb {
  font: inherit; font-size: var(--t-micro); font-weight: 600;
  text-transform: uppercase; letter-spacing: var(--tr-caps);
  color: var(--text-dim);
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-btn);
  padding: 3px 0;
  cursor: pointer;
  flex: 1;
}
.cpb:hover { color: var(--text); background: var(--panel-3); }
.cpb--play { color: var(--ok); }
.cpb--play:hover { color: var(--ok); background: var(--panel-3); }
.cpb--icon { flex: 0 0 24px; font-size: var(--t-small); letter-spacing: var(--tr-none); }
.cpb--icon.is-on {
  color: var(--warn); border-color: var(--accent); background: transparent;
}
.cpb--del:hover { color: var(--rec); border-color: var(--rec); }

/* Une case libre reste une case : son numéro est écrit, sa place est tenue. */
.cp--empty {
  justify-content: flex-start;
  background: transparent;
  border: 1px dashed var(--line);
  border-left: 3px dashed var(--line);
  color: var(--text-faint);
  cursor: default;
}
.cp--empty .cp__plate { background: transparent; border-bottom-color: transparent; }
.cp--empty .cp__key { opacity: .35; }
.cp__hint {
  flex: 1; display: grid; place-items: center;
  font-size: var(--t-micro); text-transform: uppercase; letter-spacing: var(--tr-caps);
  opacity: .7; text-align: center; padding: 0 6px 8px;
}
.cp.is-target {
  border-style: solid; border-color: var(--accent); background: var(--accent-soft);
}

.cat-jingle   { border-left-color: var(--cat-jingle); }
.cat-sweeper  { border-left-color: var(--cat-sweeper); }
.cat-liner    { border-left-color: var(--cat-liner); }
.cat-bed      { border-left-color: var(--cat-bed); }
.cat-bruitage { border-left-color: var(--cat-bruitage); }
.cat-musique  { border-left-color: var(--cat-musique); }
.cat-pub      { border-left-color: var(--cat-pub); }
.cat-voix     { border-left-color: var(--cat-voix); }

.segmented {
  display: flex; margin-left: auto; flex: none;
  background: var(--bg-sunken);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-xs); padding: 1px;
}
.seg {
  font: inherit; font-size: var(--t-micro); font-weight: 600;
  text-transform: uppercase; letter-spacing: var(--tr-wide);
  color: var(--text-faint);
  background: transparent; border: 0; border-radius: 2px;
  padding: 2px 8px; cursor: pointer;
}
.seg:hover { color: var(--text); }
.seg.is-active { background: var(--panel-3); color: var(--text); }

/* La recherche reste en haut quand la liste défile : c'est un panneau à part
   maintenant, on y descend loin, et remonter chercher le champ à chaque
   requête serait une corvée. */
.library__search {
  position: sticky; top: 0; z-index: 2;
  display: flex; gap: 5px; padding: 7px;
  background: var(--panel);
  border-bottom: 1px solid var(--line-soft);
}
.library__search input, .library__search select {
  font: inherit; font-size: var(--t-small); color: var(--text);
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius-s); padding: 4px 8px; min-width: 0;
}
.library__search input { flex: 1; }
.library__search select { max-width: 42%; }
.library__search option { background: var(--panel); color: var(--text); }
.library__search input:focus, .library__search select:focus {
  outline: 0; border-color: var(--accent);
}

.breadcrumb {
  display: flex; flex-wrap: wrap; align-items: center; gap: 1px;
  padding: 5px 8px; font-size: var(--t-small);
  border-bottom: 1px solid var(--line-soft);
}
.breadcrumb button {
  font: inherit; font-size: var(--t-small); background: transparent; border: 0;
  color: var(--accent-ink); cursor: pointer; padding: 1px 4px; border-radius: 2px;
}
.breadcrumb button:hover { background: var(--accent-soft); }
.breadcrumb span { color: var(--text-faint); }

/* Même règle que le conducteur : la police de l'interface pour les noms, le
   monospace réservé à la durée, qui s'aligne. */
.lib-row {
  display: grid; grid-template-columns: 1fr auto auto;
  align-items: center; gap: 7px;
  padding: 3px 8px;
  border-bottom: 1px solid var(--line-soft);
  font-family: var(--sans); font-size: var(--t-base);
}
.lib-row--folder { cursor: pointer; }
.lib-row--item { cursor: grab; }
.lib-row:hover { background: #ffffff08; }
.lib-row__title { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* La ligne de dossier porte une icône devant son nom. `gap` plutôt qu'une
   espace écrite dans le texte : l'espace ferait partie du nom, donc du
   découpage par points de suspension. */
.lib-row--folder .lib-row__title {
  display: flex; align-items: center; gap: 5px;
  color: var(--text-dim);
}
/* La ligne qu'on écoute se tient allumée : le bouton passe à STOP, et la
   ligne se marque — on la retrouve après avoir fait défiler la liste. */
.lib-row--item.is-playing { background: var(--accent-soft); }
.lib-row--item.is-playing .lib-row__title { color: var(--warn); }
.lib-row__artist { font-size: var(--t-small); color: var(--text-dim); }
.lib-row__dur { font-family: var(--mono); font-size: var(--t-small); color: var(--text-faint); }

/* =============================================================== DIALOGUE == */

.dialog { border: 0; padding: 0; background: transparent; max-width: 470px; width: calc(100% - 32px); }
.dialog::backdrop { background: #04060abb; }
.dialog__box {
  background: var(--panel); color: var(--text);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 18px;
  box-shadow: var(--shadow-l);
  max-height: 86vh; overflow: auto;
}
.dialog__box h2 { margin: 0 0 14px; font-size: var(--t-lead); font-weight: 600; }
.dialog fieldset {
  border: 1px solid var(--line-soft); border-radius: var(--radius-s);
  padding: 12px; margin: 0 0 12px;
  background: var(--panel-2);
}
.dialog legend {
  padding: 0 6px; font-size: var(--t-micro); font-weight: 700;
  text-transform: uppercase; letter-spacing: var(--tr-caps); color: var(--text-dim);
}
.dialog select option { background: var(--panel); color: var(--text); }
.dialog__actions { display: flex; justify-content: flex-end; gap: 7px; margin: 0; padding: 0; }

/* Une question, pas un formulaire : plus étroite, et la réponse se lit d'un
 * coup d'œil depuis la régie. */
/* Le catalogue, ouvert pour choisir un élément ou un dossier. Plus large
 * qu'une question et de hauteur fixe : la liste défile à l'intérieur, et une
 * fenêtre qui grandit avec le nombre de résultats ferait sauter les boutons
 * hors de l'écran dès qu'un dossier contient quatre cents titres. */
.dialog--pick { max-width: 560px; }
.pick__q {
  width: 100%; box-sizing: border-box; margin-bottom: 8px;
  padding: 6px 9px; font: inherit; font-size: var(--t-base);
  color: var(--text); background: var(--panel-2);
  border: 1px solid var(--line); border-radius: var(--radius-s);
}
.pick__list {
  height: 46vh; min-height: 200px; overflow: auto;
  border: 1px solid var(--line); border-radius: var(--radius-s);
  background: var(--bg-sunken);
}
.dialog--pick .breadcrumb { margin-bottom: 6px; }
.dialog--pick .dialog__actions { margin-top: 12px; }
.dialog--pick .hint { font-size: var(--t-small); }

.dialog--ask { max-width: 400px; }
.dialog--ask .dialog__box { padding: 20px; }
.dialog--ask .dialog__actions { margin-top: 18px; }
.ask__text { margin: 0 0 8px; font-size: var(--t-base); line-height: 1.5; color: var(--text-dim); }
.ask__text:last-of-type { margin-bottom: 0; }
.keys { margin: 0; padding-left: 16px; font-size: var(--t-base); color: var(--text-dim); }
.keys li { margin: 3px 0; }
kbd {
  font-family: var(--mono); font-size: var(--t-small);
  background: var(--bg-sunken); border: 1px solid var(--line);
  border-bottom-width: 2px;
  border-radius: 2px; padding: 1px 5px; color: var(--text);
}

/* --- Le clavier qu'on se règle ----------------------------------------
   Une ligne par geste : à gauche ce que ça fait, à droite la touche, qui est
   un bouton. On clique la touche et on appuie sur celle qu'on veut — c'est le
   geste des consoles de jeu, et c'est le seul qui n'oblige pas à connaître le
   nom que le navigateur donne aux touches. */

.keymap { display: grid; gap: 4px; margin: 0 0 8px; }
.keymap__row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  align-items: center; gap: 10px;
  padding: 5px 0;
  border-bottom: 1px solid var(--line-soft);
}
.keymap__what { display: grid; gap: 1px; min-width: 0; }
.keymap__what b { font-size: var(--t-base); font-weight: 600; }
.keymap__what .hint { margin: 0; font-size: var(--t-small); }
.keymap__key {
  font-family: var(--mono); font-size: var(--t-small); color: var(--text);
  min-width: 76px; padding: 4px 8px;
  background: var(--bg-sunken);
  border: 1px solid var(--line); border-bottom-width: 2px;
  border-radius: 2px; cursor: pointer;
}
.keymap__key:hover { border-color: var(--accent); }
/* En attente d'une frappe : la touche s'allume comme une sélection, parce que
   tout le clavier est à elle tant qu'on n'a pas appuyé. */
.keymap__key.is-waiting {
  background: var(--accent); border-color: var(--accent); color: var(--accent-text);
}

/* --- Les couleurs qu'on se règle -------------------------------------- */

.palette { display: grid; gap: 2px; margin: 0 0 8px; }
.palette__group {
  margin: 10px 0 0; font-size: var(--t-small); font-weight: 600;
  text-transform: uppercase; letter-spacing: var(--tr-wide); color: var(--text-dim);
}
.palette__group:first-child { margin-top: 0; }
.palette .hint { margin: 0 0 4px; }
.palette__row {
  display: grid; grid-template-columns: 30px minmax(0, 1fr) auto;
  align-items: center; gap: 9px; padding: 2px 0;
}
.palette__row input[type="color"] {
  width: 30px; height: 20px; padding: 0;
  background: transparent; border: 1px solid var(--line);
  border-radius: var(--radius-btn); cursor: pointer;
}
.palette__name { font-size: var(--t-base); }
.palette__undo {
  font: inherit; font-size: var(--t-small);
  background: transparent; color: var(--text-dim);
  border: 1px solid var(--line); border-radius: var(--radius-btn);
  padding: 1px 6px; cursor: pointer;
}
.palette__undo:hover { border-color: var(--accent); color: var(--text); }

/* ================================================================ TOASTS === */

.toasts {
  position: fixed; bottom: 14px; left: 50%; transform: translateX(-50%);
  display: flex; flex-direction: column; gap: 6px; z-index: 50;
  pointer-events: none;
}
.toast {
  background: var(--panel); color: var(--text);
  border: 1px solid var(--line);
  border-radius: var(--radius-s); padding: 8px 13px;
  font-size: var(--t-base);
  box-shadow: var(--shadow-l), inset 3px 0 0 var(--accent);
  max-width: min(520px, calc(100vw - 32px));
  animation: toast-in .14s ease-out;
}
.toast--warn { box-shadow: var(--shadow-l), inset 3px 0 0 var(--warn); }
.toast--ok   { box-shadow: var(--shadow-l), inset 3px 0 0 var(--ok); }
@keyframes toast-in { from { opacity: 0; transform: translateY(6px); } }
@media (prefers-reduced-motion: reduce) { .toast { animation: none; } }

/* ================================================================ MOBILE === */

.mobilebar { display: none; }
/* Garde-fou pour le mode pile (`js/pile.js`, hors de la boucle) : une pile n'a
   qu'une colonne, donc aucun volet à choisir, et elle masque ces barres par
   `hidden`. Or la règle du petit écran, en bas de ce fichier, les rallume par
   une CLASSE — qui l'emporterait sur l'attribut. Écrite ici, à spécificité
   égale mais avec `!important`, cette ligne garantit qu'on ne rallume pas ce
   qu'un script a éteint.

   Sans effet aujourd'hui : plus personne ne pose `hidden` sur une `.mobilebar`,
   et les barres d'onglets sont en service. */
.mobilebar[hidden] { display: none !important; }

@media (max-width: 900px) {
  /* Au doigt, la disposition libre n'a pas de sens : pas de souris pour
     tirer une poignée de 5 px, et un écran qui ne tient qu'un panneau. On
     repasse donc en empilement, un panneau à la fois, choisi en bas. */
  body { overflow: auto; }
  /* CETTE LIGNE ÉTAIT MORTE ET ELLE EST REDEVENUE VIVANTE. Elle décrivait
     quatre rangées d'une époque où la coque était une grille ; depuis qu'elle
     est une colonne en flex, `grid-template-rows` n'y a aucun effet, et c'est
     pour ça qu'elle avait survécu à la refonte sans que personne la voie.
     `.app--rail` ramène une grille — et comme cette règle est ÉCRITE PLUS BAS
     dans le fichier à spécificité égale, c'est elle qui tranchait : le plan de
     travail se retrouvait dans une rangée `auto` avec un `1fr` vide dessous,
     donc tableaux refermés et moitié d'écran perdue, sur téléphone seulement.
     On lui donne les deux rangées de la coque à barre latérale : juste pour
     elle, et toujours sans effet sur la coque en colonne. */
  .app { grid-template-rows: auto minmax(0, 1fr); }
  .menubar__center { display: none; }
  /* La rangée de gauche défile au doigt au lieu de pousser la session hors de
     l'écran. `flex: none` la rendait incompressible : au large c'est ce qu'on
     veut, mais sur un téléphone le choix de panel — quatre vues nommées en
     clair sur la supervision — débordait du bandeau et emportait le nom du
     compte avec lui. */
  .menubar__left { flex: 1 1 auto; overflow-x: auto; }
  /* `nowrap` était la contrepartie du défilement horizontal du ruban. Il se
     replie maintenant partout, et sur un téléphone c'est là que ça compte le
     plus : un ruban qui défile y cache la moitié de ses boutons. Les marges
     reprennent la valeur étroite de la référence (`px-3`). */
  .toolbar { flex-wrap: wrap; padding: 8px 12px; }
  .menubar { padding: 8px 12px; gap: 12px; }
  .clock { display: none; }
  .statusbar { display: none; }

  .desk { flex-direction: column; }
  .split { display: none; }
  .dock { flex: 1 !important; width: auto !important; }
  .dock:not([data-pane-active]) { display: none; }
  .panel { flex: 1 !important; }
  .panel__head { cursor: default; }

  /* --- La pile au doigt ---
     Elle n'a pas de variante : c'est le même écran, plus étroit. C'est tout
     l'intérêt de la pile — l'empilement un-panneau-à-la-fois que ces règles
     fabriquaient sur téléphone est devenu la disposition de tout le monde, et
     il n'y a plus deux dessins à tenir d'accord.

     Reste à défaire ce que les règles ci-dessus imposent encore à tous les
     panneaux : une part égale de la hauteur (`flex: 1`), qui étirerait chaque
     carte au lieu de la laisser à son contenu. La marge latérale reprend la
     valeur étroite de la référence (`px-4`). */
  .desk--pile { padding: 20px 16px; }
  .desk--pile .panel { flex: none !important; }
  /* Une carte ne peut pas prendre les deux tiers d'un écran de téléphone : il
     en resterait une seule à l'écran, et la pile cesserait de se parcourir. */
  .desk--pile .panel__body { max-height: 60vh; }
  /* L'onde se resserre : 320 px sur un écran de 640 px de haut, c'est la
     moitié de l'écran pour un seul panneau. */
  .desk--pile .segue { grid-template-rows: auto auto 200px auto; }
  .desk--pile .base__cuebody { grid-template-rows: 200px auto auto auto; }

  .mobilebar {
    display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
    background: var(--panel); border-top: 1px solid var(--line);
    padding-bottom: env(safe-area-inset-bottom);
  }
  .mb {
    font: inherit; font-size: var(--t-small); color: var(--text-dim);
    background: transparent; border: 0; padding: 10px 4px; cursor: pointer;
  }
  .mb.is-active { color: var(--accent-ink); box-shadow: inset 0 2px 0 var(--accent); }

  /* Sans survol, les intervalles muets disparaissent plutôt que d'afficher
     vingt « parler ici » par heure : on touche la LIGNE, qui porte le même
     ancrage. Les emplacements prévus, eux, restent visibles. */
  .pl-mark { height: 6px; }
  .pl-mark.is-planned, .pl-mark.is-posed { height: 26px; }
  .pl-item { height: 32px; }
}

/* ===========================================================================
   Supervision (admin.html)

   Mêmes jetons et même convention de statut (`st-*`) que le studio. C'est la
   seule page où l'échéance de livraison est encore affichée : elle n'interdit
   plus rien, mais un responsable d'antenne doit pouvoir la voir.
   =========================================================================== */

/* --- La barre de la supervision --------------------------------------------
   Il n'y en a plus. La page portait `.topbar`, `.tabs` et `.tab`, un jeu de
   classes à elle seule, hérité du studio d'avant : la refonte de direction
   artistique a renommé le shell en `.menubar` / `.toolbar` / `.workspace` et
   n'a pas emmené la supervision avec elle. On lui a d'abord redessiné sa barre
   à part — d'où ces règles, maintenant retirées — et elle a redérivé aussitôt,
   parce qu'une barre à part ne reçoit pas les règles d'étroitesse écrites pour
   `.menubar` : sur un téléphone, quatre onglets, un calendrier, un menu de
   statuts et quatre boutons tenaient sur une ligne qui ne se replie pas, et
   le bandeau débordait de l'écran.

   `admin.html` emploie donc le shell du studio tel quel. Il n'y a plus rien à
   tenir en double, et la page suivra la prochaine refonte sans qu'on y
   pense. */

.admin {
  display: grid; grid-template-rows: auto 1fr;
  gap: var(--gap); padding: var(--gap);
  min-height: 0; overflow: hidden;
}
/* Et la vue prend la hauteur restante. Sans cette ligne, le `flex: none` du
   shell — écrit pour le studio, où seul `.desk` grandit — figeait la vue à sa
   hauteur naturelle : la rangée `1fr` tombait à zéro, les tableaux se
   refermaient, et la page se réduisait à son bandeau. C'est ce qui faisait
   qu'on ne pouvait rien faire une fois connecté.

   Les deux parentés sont couvertes, et non la seule qui sert aujourd'hui : la
   vue est posée dans `.workspace`, mais `.app` reste le parent si on la sort
   un jour du plan de travail, et la ligne ne coûte rien. */
.app > .admin,
.workspace > .admin { flex: 1 1 auto; min-height: 0; }
.admin--single { grid-template-rows: 1fr; }
.admin .panel {
  border: 1px solid transparent;
  border-radius: var(--radius-panel);
  box-shadow: var(--shadow-raised);
}

/* Les blocs de la page d'admin. Ce sont les panneaux du studio sans le
   glisser-déposer : on ne range pas une page de surveillance, on la lit. Le
   titre y est donc lisible et non une étiquette minuscule — il n'y a pas
   trente panneaux à distinguer d'un coup d'œil, il y en a deux. */
.zone {
  display: grid; grid-template-rows: auto 1fr;
  min-height: 0; min-width: 0;
  background: var(--panel);
  /* Même raisonnement que `.panel` : l'ombre à la place du contour. La
     bordure reste déclarée transparente pour les règles qui la colorent. */
  border: 1px solid transparent;
  border-radius: var(--radius-panel);
  box-shadow: var(--shadow-raised);
  overflow: hidden;
}
.zone__head {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 12px;
  background: var(--head);
  border-bottom: 1px solid var(--line-soft);
}
.zone__head h2 {
  margin: 0; font-size: var(--t-base); font-weight: 600;
  letter-spacing: var(--tr-none); color: var(--text);
}
/* `margin-left: auto` sur le premier élément qui suit le titre : le méta
   comme le bouton se posent à droite, quel que soit celui des deux qui est
   là. Un sélecteur par cas aurait laissé le bouton collé au titre sur la
   zone qui n'a pas de méta. */
.zone__head > :nth-child(2) { margin-left: auto; }
.zone__meta { font-size: var(--t-small); color: var(--text-dim); font-family: var(--mono); }
.zone__body { overflow: auto; min-height: 0; min-width: 0; }
.zone__body--pad { padding: 11px; }

.statline { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.stat {
  display: flex; align-items: baseline; gap: 6px;
  padding: 4px 10px;
  background: var(--panel-2);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-s);
  font-size: var(--t-small); color: var(--text-dim);
}
.stat b { font-family: var(--mono); font-size: var(--t-lead); font-weight: 700; }
.stat--total { background: transparent; border-style: dashed; }

/* La liste de ce qui risque de manquer à l'antenne. C'est la seule chose
   qu'un responsable vient lire ici, et il la lit en entier : elle doit donc
   tenir à l'écran, pas se dérouler sur trois hauteurs de fenêtre.

   Une GRILLE et non une rangée souple. En `flex`, chaque ligne plaçait ses
   mots où la largeur du texte précédent le voulait : trente lignes dont
   aucune colonne ne tombait au même endroit, qu'on était obligé de lire une
   par une. Quatre colonnes fixes et l'œil descend d'un seul mouvement — le
   quand, l'état, le pourquoi, et le bouton tout au bout.

   Chaque ligne était aussi une CARTE : un cadre, un fond, un arrondi et six
   pixels de marge, soit 48 px de haut pour trois mots. On revient à ce qu'est
   une ligne de liste — un filet en bas, rien d'autre —, le rail d'alerte
   reste, et la page passe de dix-huit lignes visibles à plus du double. */
.risk {
  display: grid;
  grid-template-columns: 14ch 11ch 18ch minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 10px;
  padding: 3px 10px 3px 12px;
  border-bottom: 1px solid var(--line-soft);
  box-shadow: inset 3px 0 0 var(--warn);
  font-size: var(--t-base);
}
.risk:hover { background: var(--line-lit); }
.risk__when {
  font-family: var(--mono); font-size: var(--t-small); color: var(--text-dim);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.risk__left { color: var(--warn); font-weight: 600; white-space: nowrap; }
/* Le motif est la seule colonne qui puisse être longue : c'est elle qui se
   coupe, et jamais l'état ni l'heure, qui sont ce qu'on balaie. */
.risk__what {
  color: var(--text-faint); font-size: var(--t-small);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Le bouton de journal se répète sur chaque ligne. Encadré, il dessinait une
   colonne de trente boîtes au bord droit de l'écran, plus visible que les
   alertes qu'elle accompagne : il ne se cerne qu'au survol de sa ligne. */
.risk .btn {
  background: transparent; border-color: transparent; color: var(--text-faint);
  padding: 0 6px;
}
.risk:hover .btn { border-color: var(--line); color: var(--text); }

/* La grille de supervision suit la grille du système : en-tête au gris haut,
   chiffres en Consolas tabulaire, lignes séparées d'un filet. */
/* Les vingt-quatre heures d'une journée, en cases à cocher.
   Une grille fixe de douze colonnes et non un `auto-fill` : on lit « 14h » en
   comptant les cases, et des colonnes qui changent de nombre avec la largeur
   de la fenêtre feraient compter faux. Deux rangées de douze, c'est aussi la
   matinée au-dessus de l'après-midi. */
.prgrid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 5px;
  margin-top: 12px;
}
.prcell {
  display: flex; align-items: center; justify-content: center; gap: 5px;
  padding: 7px 4px;
  background: var(--panel-2);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-s);
  font-family: var(--mono); font-size: var(--t-small);
  color: var(--text-faint);
  cursor: pointer;
  user-select: none;
}
.prcell:hover { border-color: var(--line); }
/* Ouverte = verte, et pas simplement cochée : on vient ici pour voir d'un
   coup d'œil ce qui est ouvert, et une coche se cherche. */
.prcell.is-ready { background: var(--ok-soft); border-color: var(--ok); color: var(--ok); }
.prcell input { accent-color: var(--ok); margin: 0; cursor: pointer; }
@media (max-width: 760px) {
  .prgrid { grid-template-columns: repeat(6, 1fr); }
}

.grid {
  width: 100%; border-collapse: collapse;
  font-family: var(--mono); font-size: var(--t-small);
  font-variant-numeric: tabular-nums;
}
/* LES FILETS VERTICAUX SONT PARTIS. Un tableau quadrillé se lit comme une
   feuille de calcul : l'œil doit traverser un trait à chaque colonne, et sur
   huit colonnes de supervision ça fait sept obstacles par ligne. La référence
   ne sépare que les RANGÉES, et l'alignement suffit à tenir les colonnes —
   c'est d'ailleurs à ça que servent les chiffres tabulaires. Le filet
   horizontal, lui, reste : c'est lui qui permet de suivre une ligne du regard
   jusqu'au bout sur un écran large.

   L'en-tête garde `var(--head)`, qui vaut maintenant `--panel` : il doit
   rester OPAQUE puisqu'il est collant, sinon les lignes défileraient sous son
   texte. C'est le seul endroit où ce jeton compte encore vraiment. */
.grid th {
  position: sticky; top: 0; z-index: 1;
  background: var(--head);
  text-align: left;
  font-family: var(--sans);
  font-size: var(--t-micro); text-transform: uppercase; letter-spacing: var(--tr-caps);
  color: var(--text-faint); font-weight: 600;
  padding: 5px 8px;
  border-bottom: 1px solid var(--line-soft);
  border-right: 0;
}
.grid td {
  padding: 4px 8px;
  border-bottom: 1px solid var(--line-soft);
  border-right: 0;
  vertical-align: top;
}
.grid .num, .grid th.num { text-align: right; }
/* `var(--line-lit)` et non le blanc à 3 % qui était écrit là en dur : du blanc
   dilué sur un panneau blanc n'éclaircit rien, et la ligne survolée ne bougeait
   pas d'un cheveu en thème clair. Le jeton, lui, est une encre en clair. */
.grid tbody tr:hover { background: var(--line-lit); }
.grid tr.is-faulty { box-shadow: inset 3px 0 0 var(--warn); background: var(--warn-soft); }
.cell-note { font-size: var(--t-small); color: var(--warn); margin-top: 3px; max-width: 42ch; }
.tag { font-size: var(--t-small); font-weight: 600; }
.mono-soft { font-family: var(--mono); font-size: var(--t-small); color: var(--text-faint); }
.grid td .btn { margin-left: 6px; }
/* Note neutre, par opposition à `.cell-note` qui est une alerte : un
   identifiant ou un rappel de règle n'est pas un problème, et le mettre en
   orange ferait croire qu'il y en a un sur chaque ligne. */
.cell-sub { font-size: var(--t-small); color: var(--text-faint); margin-top: 3px; max-width: 46ch; }

/* ---------- Comptes et droits ---------- */

.who-name { font-family: var(--sans); font-size: var(--t-base); font-weight: 600; }
/* Un compte désactivé reste lisible mais se retire : il est dans la liste
   pour qu'on puisse le rouvrir, pas pour qu'on le compte parmi les autres. */
.grid tr.is-off { opacity: .55; }
.grid tr.is-off .who-name { text-decoration: line-through; }

.rights { display: flex; flex-wrap: wrap; gap: 4px 6px; }
.right {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 8px 3px 6px;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-s);
  background: var(--panel-2);
  font-family: var(--sans); font-size: var(--t-small); color: var(--text-dim);
  cursor: pointer; user-select: none;
}
.right:hover { border-color: var(--line); color: var(--text); }
/* La case cochée tient sur `:has()` et non sur une classe posée par le JS :
   l'état vrai est celui de la case, et deux représentations du même état se
   désynchronisent toujours un jour. */
.right:has(input:checked) {
  border-color: var(--accent);
  color: var(--text);
  background: var(--accent-soft);
}
.right input { accent-color: var(--accent); margin: 0; }

.row-actions { white-space: nowrap; }
/* Première action de la cellule : pas de marge à gauche, elle ne suit rien. */
.row-actions .btn:first-child { margin-left: 0; }

.form-grid { display: grid; gap: 11px; margin-top: 4px; }
.dialog--form { max-width: 420px; }
.dialog--form .dialog__box { padding: 20px; }

.logs {
  margin: 0; padding: 11px;
  font-family: var(--mono); font-size: var(--t-small); line-height: 1.55;
  color: var(--text-dim);
  background: var(--bg-sunken);
  white-space: pre-wrap; word-break: break-word;
  max-height: 100%; overflow: auto;
}

.trail { list-style: none; margin: 0; padding: 0; }
.trail__step {
  display: grid; grid-template-columns: 136px 92px 1fr;
  gap: 9px;
  padding: 7px 0 7px 11px;
  border-left: 2px solid var(--line);
  font-size: var(--t-base);
}
.trail__step--warn  { border-left-color: var(--warn); }
.trail__step--error { border-left-color: var(--warn); background: var(--warn-soft); }
.trail__at { color: var(--text-faint); font-size: var(--t-small); font-family: var(--mono); }
.trail__event { font-weight: 600; color: var(--accent-ink); }
.trail__detail { color: var(--text-dim); word-break: break-word; }

.facts { display: grid; grid-template-columns: max-content 1fr; gap: 7px 16px; margin: 0; font-size: var(--t-base); }
.facts dt { color: var(--text-dim); font-size: var(--t-small); }
.facts dd { margin: 0; word-break: break-word; }
.facts dd.yes { color: var(--ok); }
.facts dd.no  { color: var(--warn); font-weight: 600; }

/* ---------- « Mon compte » : le second facteur ----------
   Deux choses se lisent ici dans des conditions hostiles — un secret qu'on
   recopie à la main dans une application, et des codes de secours qu'on
   recopie sur du papier à six heures du matin. Les deux sont donc en
   monospace, assez grands pour qu'un O et un 0 ne se confondent pas (c'est
   aussi pourquoi l'alphabet des codes n'en contient ni l'un ni l'autre, voir
   backend/app/totp.py), et espacés : une suite de caractères sans chasse
   fixe ni interlettrage se recopie de travers une fois sur trois. */
.a2f__secret {
  display: block; padding: 8px 10px;
  font-family: var(--mono); font-size: var(--t-read);
  letter-spacing: .12em; word-break: break-all;
  color: var(--text); background: var(--bg-sunken);
  border: 1px solid var(--line); border-radius: var(--radius-s);
}

/* Le bloc des codes de secours porte le liséré d'avertissement : c'est le seul
   moment où ils existent en clair, et l'écran doit le dire par son dessin
   autant que par son texte.

   LE CADRE EST ÉCRIT ICI EN ENTIER, et il ne l'était pas : la règle ne posait
   qu'une couleur de bordure, et c'est `.dialog fieldset` qui donnait le trait,
   le rayon et le retrait. « Mon compte » est devenu une vue faite de panneaux
   et ce bloc n'est plus dans un dialogue ; la couleur seule aurait teinté une
   bordure large de zéro, c'est-à-dire rien du tout — et c'est le seul dessin de
   cet écran qui dise « attention, ce qui suit ne reviendra pas ». */
.a2f__secours {
  border: 1px solid var(--warn); border-radius: var(--radius-s);
  padding: 10px;
}

/* Deux colonnes et non une : huit codes à la file font une liste qu'on perd
   des yeux en recopiant. En dessous de 420 px, une seule — mieux vaut
   dérouler que couper un code en deux. */
.a2f__codes {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px 12px; margin: 10px 0;
}
.a2f__code {
  padding: 6px 8px;
  font-family: var(--mono); font-size: var(--t-read);
  letter-spacing: .08em; text-align: center;
  background: var(--bg-sunken);
  border: 1px solid var(--line); border-radius: var(--radius-s);
}

/* ---------- Le texte d'un signalement ----------
   `pre-wrap` et non `normal` : on y colle une trace d'erreur de la console du
   navigateur, qui est souvent ce qu'il y a de plus utile, et une trace repliée
   sur une seule ligne ne se lit pas. `break-word` par-dessus, parce qu'une
   trace porte des chemins de fichier sans espace qui déborderaient la carte. */
.bug__texte {
  margin: 0 0 12px;
  white-space: pre-wrap; word-break: break-word;
  font-size: var(--t-base); line-height: 1.5;
}

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

@media (max-width: 760px) {
  .admin { grid-template-rows: auto auto; overflow: auto; }
  .trail__step { grid-template-columns: 1fr; gap: 2px; }
  .panel__body { overflow-x: auto; }
  .grid { min-width: 620px; }
}

#events.dialog { max-width: 780px; }
#events .dialog__box { max-height: min(82vh, 760px); overflow: auto; }

/* ======================================================= Import FTP ====== */

/* La liste des serveurs. Une ligne dit quatre choses — un voyant, un nom, un
   hôte, l'état de la dernière relève — et c'est le voyant qu'on lit en
   premier : on ouvre ce panel parce qu'on se demande si tout rentre. */
.ftprow {
  display: grid;
  grid-template-columns: 10px 1fr auto;
  grid-template-areas: "dot name host" ". state state";
  align-items: center;
  gap: 2px 9px;
  width: 100%;
  padding: 8px 11px;
  font: inherit; text-align: left;
  color: var(--text);
  background: transparent;
  border: 0; border-bottom: 1px solid var(--line);
  cursor: pointer;
}
.ftprow:hover { background: var(--accent-soft); }
.ftprow.is-active { background: var(--accent-soft); box-shadow: inset 2px 0 0 var(--accent); }
.ftprow__dot {
  grid-area: dot;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--ok);
}
/* Inactif : le serveur est déclaré mais personne ne le relève. Ce n'est pas
   une panne, donc ce n'est pas la couleur d'une panne — c'est une absence. */
.ftprow.is-off { color: var(--text-dim); }
.ftprow.is-off .ftprow__dot { background: var(--line); }
.ftprow.is-bad .ftprow__dot { background: var(--warn); }
.ftprow__name { grid-area: name; font-weight: 600; font-size: var(--t-base); }
.ftprow__host { grid-area: host; font-family: var(--mono); font-size: var(--t-small); color: var(--text-faint); }
.ftprow__state { grid-area: state; font-size: var(--t-small); color: var(--text-dim); }

/* La fiche. Deux colonnes dès qu'il y a la place : trente-quatre réglages
   empilés sur une seule font un rouleau que personne ne lit jusqu'en bas. */
.ftpform { padding: 12px 14px 18px; }
.ftpform__group {
  margin: 0 0 16px; padding: 12px 13px 4px;
  border: 1px solid var(--line); border-radius: var(--radius-s);
  background: var(--bg-sunken);
}
.ftpform__group legend {
  padding: 0 6px; font-size: var(--t-small); letter-spacing: var(--tr-wide);
  text-transform: uppercase; color: var(--accent-ink);
}
.ftpform__check { display: flex; flex-wrap: wrap; margin-bottom: 12px; }
/* L'explication passe à la ligne sous la case, pas à côté : à côté, elle
   repousse le libellé et deux cases voisines ne s'alignent plus. */
.ftpform__check .ftpform__hint { flex: 1 0 100%; margin-left: 24px; }
.ftpform__hint {
  margin: 4px 0 0; font-size: var(--t-small); line-height: 1.45;
  color: var(--text-faint);
}
.ftpform__acts {
  display: flex; gap: 9px; justify-content: flex-end;
  position: sticky; bottom: 0;
  padding: 11px 0 2px;
  background: linear-gradient(to top, var(--panel) 72%, transparent);
}

@media (min-width: 1100px) {
  .ftpform__group {
    display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px;
    align-items: start;
  }
  .ftpform__group legend { grid-column: 1 / -1; }
  /* Les cases à cocher et leurs explications prennent la largeur : une
     explication de deux lignes dans une demi-colonne décale tout ce qui
     suit. */
  .ftpform__check { grid-column: 1 / -1; }
}

/* Le compte rendu de relève. */
.ftprep { padding: 12px 14px; }
.ftprep__head {
  margin: 0 0 10px; font-weight: 600; font-size: var(--t-read);
  color: var(--ok);
}
.ftprep--bad .ftprep__head { color: var(--warn); }
.ftprep__list { padding: 10px 14px; border-top: 1px solid var(--line); }
.ftprep__list h4 {
  margin: 0 0 6px; font-size: var(--t-small); letter-spacing: var(--tr-wide);
  text-transform: uppercase; color: var(--text-dim);
}
.ftprep__list ul {
  margin: 0; padding: 0; list-style: none;
  font-family: var(--mono); font-size: var(--t-small); line-height: 1.6;
  color: var(--text-dim);
  word-break: break-all;
}
.ftprep__ok li { color: var(--text); }
.ftprep__err li { color: var(--warn); }
.ftprep__skip li { color: var(--text-faint); }
.ftprep__hist li {
  display: grid; grid-template-columns: 132px 1fr auto;
  gap: 9px; align-items: baseline;
  padding: 2px 0;
}
.ftprep__when { color: var(--text-faint); }
.ftprep__path { color: var(--text); word-break: break-all; }
.ftprep__id { color: var(--accent-ink); white-space: nowrap; }

@media (max-width: 760px) {
  .ftprep__hist li { grid-template-columns: 1fr; gap: 0; }
}

/* ===========================================================================
   Panel « Programmation ».

   Un parti pris de lisibilité traverse tout ce bloc : rien ne se jauge à une
   couleur seule. La grille marque une heure programmée par sa teinte ET par
   le nom de l'horloge ; une ligne de conducteur repêchée porte une étiquette
   écrite, pas un fond orange ; une règle dure se signale par un filet ET par
   sa case cochée. Un conducteur se relit à 6 h du matin sur l'écran mal réglé
   d'un studio, et il faut qu'il s'y relise.
   ======================================================================== */

/* --- la grille des 168 heures --- */
.pgrid__bar {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 12px; border-bottom: 1px solid var(--line);
  background: var(--bg-sunken);
}
.pgrid__count { flex: 1; font-size: var(--t-small); color: var(--text-dim); }
.pgrid__bar select {
  min-width: 0; max-width: 190px;
  padding: 4px 7px; font: inherit; font-size: var(--t-base);
  color: var(--text); background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--radius-s);
}
.pgrid__bar select option { background: var(--panel); color: var(--text); }
.pgrid__scroll { overflow: auto; }
.pgrid { width: 100%; border-collapse: collapse; table-layout: fixed; }
/* L'en-tête des jours reste visible pendant qu'on descend les vingt-quatre
   heures : sans cela, arrivé à 19 h on ne sait plus quelle colonne est
   samedi. */
.pgrid thead th {
  position: sticky; top: 0; z-index: 2;
  padding: 5px 2px;
  font-size: var(--t-small); letter-spacing: var(--tr-wide); text-transform: uppercase;
  color: var(--text-dim); background: var(--panel);
  border-bottom: 1px solid var(--line);
  cursor: pointer; user-select: none;
}
.pgrid thead th:hover { color: var(--accent-ink); }
.pgrid__hour {
  position: sticky; left: 0; z-index: 1;
  width: 30px; padding: 0 5px;
  font-size: var(--t-small); font-weight: 400; text-align: right;
  color: var(--text-faint); background: var(--panel);
}
.pgrid__cell {
  height: 20px; padding: 0 4px;
  overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
  font-size: var(--t-small); color: var(--text-faint);
  border: 1px solid var(--line);
  cursor: pointer;
}
.pgrid__cell:hover { background: var(--accent-soft); }
.pgrid__cell.is-set { color: var(--text); background: #ffffff0a; }
/* Choisie : le filet, et pas seulement le fond. Une case choisie au milieu de
   quarante cases remplies doit se repérer d'un coup d'oeil. */
.pgrid__cell.is-picked {
  color: var(--accent-ink);
  background: var(--accent-soft);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.pgrid__hint { padding: 9px 12px 12px; margin: 0; }

/* --- le calendrier d'habillage : la palette et les cases peintes ---
   La grille elle-même réutilise `.pgrid`, et ce n'est pas une économie de
   feuille de style : la régie peint déjà ses horloges sur ce damier-là, et un
   second damier d'aspect différent pour le même geste serait une seconde
   habitude à prendre. Ne vit ici que ce que celui-ci a de plus — la teinte par
   dossier, qui ne dit PAS la nature (voir le commentaire de `TEINTES` dans
   auto.js) mais seulement « celui-ci plutôt que celui-là ». */
.dcal__packs {
  margin: 0; padding: 6px 10px; list-style: none;
  border-bottom: 1px solid var(--line);
}
.dcal__pack {
  --teinte: var(--text-faint);
  display: flex; align-items: center; gap: 6px;
  padding: 3px 0;
}
/* Hors service : la fiche reste LISIBLE et visiblement éteinte. La masquer
   ferait chercher un dossier disparu, et c'est la case à cocher qu'il faut
   retrouver, pas la fiche. */
.dcal__pack.is-off { opacity: .5; }
.dcal__chip {
  flex: none; width: 20px; height: 20px;
  display: flex; align-items: center; justify-content: center;
  font-size: var(--t-small); font-weight: 600; line-height: 1;
  color: #000; background: var(--teinte);
  border: 1px solid var(--teinte); border-radius: var(--radius-s);
  cursor: pointer;
}
/* Le pinceau courant porte un halo, et pas seulement une bordure : c'est
   l'information la plus utile de l'écran — « qu'est-ce que je peins » — et
   elle doit se voir sans chercher. */
.dcal__pack.is-brush .dcal__chip {
  box-shadow: 0 0 0 2px var(--panel), 0 0 0 3px var(--accent);
}
.dcal__pack--gomme .dcal__chip { color: var(--text-dim); background: transparent; }
.dcal__name {
  flex: 1; min-width: 60px;
  padding: 2px 5px; font: inherit; font-size: var(--t-small);
  color: var(--text); background: transparent;
  border: 1px solid transparent; border-radius: var(--radius-s);
}
.dcal__name:hover, .dcal__name:focus {
  border-color: var(--line); background: var(--panel);
}
.dcal__name--mute { color: var(--text-faint); border-color: transparent; }
.dcal__folder {
  flex: none; max-width: 120px; overflow: hidden;
  white-space: nowrap; text-overflow: ellipsis;
  padding: 2px 6px; font-size: var(--t-small);
  color: var(--text-dim); background: transparent;
  border: 1px solid var(--line); border-radius: var(--radius-s);
  cursor: pointer;
}
.dcal__folder:hover { color: var(--text); border-color: var(--accent); }
.dcal__folder.is-empty { color: var(--warn); border-color: var(--warn); }
.dcal__on { flex: none; display: flex; }
.dcal__del {
  flex: none; width: 18px; height: 18px;
  font-size: var(--t-read); line-height: 1;
  color: var(--text-faint); background: transparent;
  border: 0; cursor: pointer;
}
.dcal__del:hover { color: var(--warn); }
/* La case peinte prend la teinte de sa fiche, en fond ET en lettre. Le fond
   seul suffirait à l'œil, la lettre seule suffirait à qui lit mal les
   couleurs : les deux ensemble se lisent dans les deux cas. */
.dcal__cell { user-select: none; }
.dcal__cell.is-set {
  color: var(--teinte);
  background: color-mix(in srgb, var(--teinte) 18%, transparent);
}
.dcal__cell.is-set:hover {
  background: color-mix(in srgb, var(--teinte) 34%, transparent);
}

/* --- les éditeurs d'horloge et de rotation --- */
.pedit__bar {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 12px; border-bottom: 1px solid var(--line);
  background: var(--bg-sunken);
}
.pedit__pick {
  flex: 1; min-width: 0;
  padding: 4px 7px; font: inherit; font-size: var(--t-base); font-weight: 600;
  color: var(--text); background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--radius-s);
}
.pedit__pick option { background: var(--panel); color: var(--text); font-weight: 400; }
.pedit__fields { padding: 12px 14px 0; }
.pedit__fields .field { margin-bottom: 10px; }
.pedit__h {
  margin: 14px 0 7px; padding: 0 14px;
  font-size: var(--t-small); letter-spacing: var(--tr-wide); text-transform: uppercase;
  color: var(--accent-ink);
}
.pedit__sum {
  margin: 0; padding: 7px 14px;
  font-size: var(--t-small); color: var(--text-dim);
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  background: var(--bg-sunken);
}
.pedit__fields + .pedit__h { margin-top: 4px; }

@media (min-width: 1100px) {
  .pedit__fields { display: grid; grid-template-columns: 1fr 1fr; gap: 0 14px; }
}

/* --- la bibliothèque qu'on glisse dans l'horloge ---
   Les lignes sont celles du sélecteur de catalogue (`.lib-row`), au pixel près
   et exprès : c'est le même catalogue, et deux aspects pour la même liste
   feraient douter qu'il s'agisse du même contenu. Ne vit ici que ce qui tient
   au panneau — une liste qui occupe la hauteur disponible au lieu des 46 vh du
   dialogue, et les pastilles de nature. */
.pbib { display: flex; flex-direction: column; gap: 8px; padding: 10px 12px 12px; }
.pbib__list { height: auto; flex: 1; min-height: 140px; }
.pbib__rots {
  max-height: 120px; overflow: auto;
  border: 1px solid var(--line); border-radius: var(--radius-s);
  background: var(--bg-sunken);
}
.lib-row--rot { cursor: grab; }
.pbib__chips { display: flex; flex-wrap: wrap; gap: 5px; }
/* La pastille porte la teinte de sa nature, comme l'emplacement qu'elle crée :
   on glisse du jaune vers une liste où le jingle est jaune. La pastille reçoit
   la classe `.pslot--*` pour cela et n'en lit que le jeton `--nature` ; le
   filet hachuré du dossier et du comblement la suit au passage, et c'est tant
   mieux — c'est la même chose qu'il dit là-bas, « ce qui sortira n'est pas
   encore nommé ». D'où `background-color` et non le raccourci, qui effacerait
   ce filet. */
.pbib__chip {
  padding: 3px 9px 3px 12px; font: inherit; font-size: var(--t-small);
  color: var(--nature, var(--text-dim));
  background-color: color-mix(in srgb, var(--nature, var(--line)) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--nature, var(--line)) 45%, transparent);
  border-radius: var(--radius-btn); cursor: grab;
}
.pbib__chip:hover {
  background-color: color-mix(in srgb, var(--nature, var(--line)) 28%, transparent);
}
.lib-row.is-dragging, .pbib__chip.is-dragging { opacity: .45; }

/* --- les emplacements d'une horloge --- */
.pslots, .psrcs, .prules { margin: 0; padding: 0; list-style: none; }
/* La cible de fin de liste. Visible en permanence et non seulement pendant un
   glisser : c'est elle qui APPREND le geste à qui ouvre l'écran, et une cible
   qui n'apparaît qu'une fois qu'on a commencé à glisser arrive trop tard. */
.pslots__drop {
  margin: 8px 14px 0; padding: 10px 12px;
  font-size: var(--t-small); color: var(--text-faint); text-align: center;
  border: 1px dashed var(--line); border-radius: var(--radius-s);
}
.pslots__drop.is-over {
  color: var(--accent-ink); border-color: var(--accent);
  background: var(--accent-soft);
}
.pslot {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 5px 14px;
  border-bottom: 1px solid var(--line);
}
/* `background-color` et non `background` : la ligne d'un dossier porte son
   filet en IMAGE de fond (voir plus bas), et le raccourci l'effacerait au
   survol — la ligne perdrait sa teinte à l'instant où le curseur la désigne. */
/* Au survol, la teinte de la ligne MONTE au lieu d'être remplacée par un gris :
   la ligne désignée ne doit pas perdre sa nature à l'instant où on la vise. */
.pslot:hover { background-color: color-mix(in srgb, var(--nature) 22%, transparent); }

/* LA COULEUR DIT LA NATURE, ici comme dans le conducteur du studio : un
   emplacement de voix est rose là comme ici. Une horloge se relit en
   diagonale — « il manque un jingle avant la pub » se voit à la suite des
   teintes, pas en lisant six sélecteurs.

   Chaque nature pose sa teinte dans UN jeton de ligne, `--nature`, et le
   filet de gauche, le rang et le sélecteur de nature y puisent tous les
   trois. Un seul endroit à changer par nature, et aucune chance qu'un filet
   cyan surmonte un libellé jaune. */
.pslot {
  --nature: var(--line);
  box-shadow: inset 3px 0 0 var(--nature);
  /* TOUTE LA LIGNE est teintée, et pas seulement le sélecteur de nature.
     Demande de la régie, et elle est juste : un filet de 3 px au bord gauche
     se perd au bout d'une liste de vingt emplacements, et c'est la ligne
     entière qu'on balaie des yeux quand on cherche « où est le jingle avant
     la pub ». Un lavis à 10 % : assez pour nommer la nature d'un coup d'œil,
     assez peu pour que le texte garde son contraste et que six lignes de
     natures différentes ne fassent pas un arc-en-ciel. */
  background-color: color-mix(in srgb, var(--nature) 10%, transparent);
}
.pslot--rotation { --nature: var(--clip-music); }
/* Élément fixe et dossier partagent le jaune de l'habillage : ils posent la
   même nature à l'antenne. Ce qui les sépare, c'est de choisir ou non — et ça
   se dit par le TRAIT, pas par une sixième teinte. */
.pslot--element, .pslot--dossier { --nature: var(--clip-jingle); }
.pslot--vt { --nature: var(--clip-voice); }
/* Un marqueur ne sonne pas : il n'a droit à aucune des quatre teintes, et son
   filet est celui des séparations. */
.pslot--marqueur { --nature: var(--text-faint); }
/* Un comblement se distingue des autres : il ne produit rien à la génération
   quand l'heure tombe juste, et le confondre avec un emplacement qui pose
   toujours ferait compter une promo dans chaque heure. */
.pslot--comblement { --nature: var(--accent-ink); }
/* Une page de pub prend le gris de la catégorie publicité, celui du
   cartouchier et de la bibliothèque : c'est la même chose à l'antenne, et lui
   donner une teinte musicale ferait lire une page vendue comme un emplacement
   que les règles peuvent déplacer. */
.pslot--pub { --nature: var(--cat-pub); }
/* Le filet HACHURÉ, pour les deux natures qui ne nomment pas ce qu'elles
   posent : un tiret plein, un tiret vide, comme le pointillé d'un contour
   qu'on n'a pas encore rempli. Le dossier est désigné, le titre qui en sortira
   ne l'est pas ; le comblement est déclaré, et certaines heures ne lui feront
   rien poser du tout. Le filet passe en image de fond, un `box-shadow` étant
   continu par nature. */
.pslot--dossier, .pslot--comblement {
  box-shadow: none;
  background-image: repeating-linear-gradient(
    var(--nature) 0 5px, transparent 5px 10px
  );
  background-size: 3px 100%;
  background-repeat: no-repeat;
}
/* Le rang prend la teinte de sa nature : c'est le bord gauche de la ligne, et
   le filet seul fait 3 px — trop peu pour se voir au bout d'une liste de
   vingt emplacements. */
.pslot__pos {
  width: 18px; font-size: var(--t-small); text-align: right; color: var(--nature);
}
/* Le sélecteur de nature est le seul champ TEINTÉ de la ligne. Les autres
   restent neutres : un identifiant de catalogue en cyan se lirait comme une
   information sur le titre, alors que ce n'en est pas une.
   `select.pslot__kind` et non `.pslot__kind` seul : la règle commune des
   champs, plus bas, porte un sélecteur de type et gagnerait sinon la
   couleur. */
.pslot select.pslot__kind {
  color: var(--nature);
  border-color: var(--nature);
}
/* Le rang fait poignée : c'est le chiffre qui dit la place, le glisser est
   donc le geste de la changer. Il reste focalisable au clavier, les flèches
   haut/bas y gardent la fonction qu'avaient les deux boutons d'avant. */
.pslot__grip { cursor: grab; user-select: none; }
.pslot__grip:focus-visible {
  outline: 1px solid var(--accent); outline-offset: 2px; border-radius: 2px;
}
.pslot.is-dragging { opacity: .45; }
.pslot.is-dragging .pslot__grip { cursor: grabbing; }
/* Le trait d'arrivée, en bordure et non en `box-shadow` : celui-ci porte déjà
   le filet de nature à gauche, et l'écraser ferait perdre la couleur de
   l'emplacement à l'instant où on cherche à savoir ce qu'on déplace. Les
   marges négatives compensent l'épaisseur gagnée, sans quoi la liste
   sursauterait sous le curseur à chaque ligne survolée. */
.pslot.is-over-top { border-top: 2px solid var(--accent); margin-top: -2px; }
.pslot.is-over-bot {
  border-bottom: 2px solid var(--accent); margin-bottom: -1px;
}
.pslot select, .pslot input {
  padding: 3px 6px; font: inherit; font-size: var(--t-small);
  color: var(--text); background: var(--panel-2);
  border: 1px solid var(--line); border-radius: var(--radius-s);
}
.pslot select option { background: var(--panel); color: var(--text); }
.pslot__kind { width: 104px; }
.pslot__rot { width: 130px; }
.pslot__id, .pslot__label { flex: 1; min-width: 80px; }
.pslot__sec, .pslot__fix { width: 58px; }
.pslot__acts { display: flex; gap: 2px; margin-left: auto; }
/* La deuxième ligne d'un emplacement : la substitution, et le nom de ce qui a
   été choisi au catalogue. `1 0 100%` dans un conteneur qui enveloppe fait
   une ligne à part — même procédé que l'explication d'une règle. Posée après
   les actions pour que les flèches de déplacement ne bougent jamais. */
.pslot__sub, .pslot__name {
  flex: 1 0 100%;
  display: flex; align-items: center; gap: 5px;
  padding-left: 24px;
}
.pslot__subl { font-size: var(--t-small); color: var(--text-faint); }
.pslot__name { font-size: var(--t-small); color: var(--text-dim); }
.pslot__name .ico { color: var(--text-faint); flex: none; }
.pslot__name span { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Les réglages musicaux, repliés. Ils se touchent une fois par horloge ; les
   emplacements se touchent à chaque visite, et doivent donc être devant. */
.pedit__fold { border-bottom: 1px solid var(--line); }
.pedit__fold > summary {
  padding: 8px 14px; cursor: pointer;
  font-size: var(--t-small); font-weight: 700;
  text-transform: uppercase; letter-spacing: var(--tr-caps); color: var(--text-dim);
}
.pedit__fold > summary:hover { color: var(--text); background: #ffffff06; }
.pedit__fold[open] > summary { color: var(--text); }

/* Les parts visées par style : une colonne de noms, une de pour-cent. */
.pstyles { display: grid; gap: 3px; margin-top: 4px; }
.pstyles__row {
  display: grid; grid-template-columns: 1fr 58px auto;
  align-items: center; gap: 6px;
  font-size: var(--t-small); color: var(--text-dim);
}
.pstyles__pc { font-size: var(--t-small); color: var(--text-faint); }

/* --- les rotations, leurs sources et leurs règles --- */
.prots { display: flex; flex-wrap: wrap; gap: 5px; padding: 10px 14px 0; }
.prot {
  display: flex; flex-direction: column; gap: 1px;
  padding: 5px 9px;
  font: inherit; text-align: left;
  color: var(--text); background: var(--panel-2);
  border: 1px solid var(--line); border-radius: var(--radius-s);
  cursor: pointer;
}
.prot:hover { border-color: var(--accent); }
.prot.is-active {
  color: var(--accent-ink); border-color: var(--accent); background: var(--accent-soft);
}
/* Inactive : une rotation déclarée, que les horloges peuvent encore nommer,
   mais qui ne tire plus. Mise en retrait, pas en alerte. */
.prot.is-off { color: var(--text-faint); }
.prot.is-off .prot__name { text-decoration: line-through; }
.prot__name { font-size: var(--t-base); font-weight: 600; }
.prot__meta { font-size: var(--t-small); color: var(--text-faint); }

.psrc, .prule {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  padding: 6px 14px;
  border-bottom: 1px solid var(--line);
}
.psrc select, .psrc input[type="text"],
.prule select, .prule input[type="text"], .prule input[type="number"] {
  padding: 3px 6px; font: inherit; font-size: var(--t-small);
  color: var(--text); background: var(--panel-2);
  border: 1px solid var(--line); border-radius: var(--radius-s);
}
.psrc select option, .prule select option { background: var(--panel); color: var(--text); }
.psrc__value { flex: 1; min-width: 110px; }
/* Une source d'exclusion se lit à l'envers des autres : elle retire. Le filet
   d'alerte le dit avant qu'on ne cherche pourquoi le vivier est vide. */
.psrc.is-excl { box-shadow: inset 2px 0 0 var(--warn); }
.prule__kind { width: 150px; }
.prule__val { flex: 1; min-width: 90px; }
.prule__min, .prule__w { width: 62px; }
/* La même durée, en clair, collée au champ de minutes : « 21600 » ne se lit
   pas, « 15 j » se lit. Largeur fixe pour que les lignes restent alignées
   quand une règle l'affiche et la suivante non. */
.prule__clair {
  width: 44px; font-size: var(--t-small); color: var(--text-faint);
}
.prule.is-hard { box-shadow: inset 2px 0 0 var(--warn); }
.prule.is-off { opacity: .5; }
.prule__hint {
  flex: 1 0 100%; margin: 2px 0 0;
  font-size: var(--t-small); line-height: 1.4; color: var(--text-faint);
}
/* La corbeille est rejetée au bout de la ligne, les autres boutons non : la
   loupe doit rester collée au champ qu'elle remplit. Sans ce
   `:not(.btn--danger)`, c'est le PREMIER bouton de la ligne qui prendrait
   l'espace libre — donc la loupe, qui partirait au milieu de nulle part. */
.psrc .btn--icon.btn--danger, .prule .btn--icon.btn--danger { margin-left: auto; }
/* Le choix de la règle à ajouter, et son explication dessous : on se demande
   laquelle prendre AVANT de l'ajouter, pas après. */
.prules__add {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 8px 14px;
}
.prules__add select {
  padding: 3px 6px; font: inherit; font-size: var(--t-small);
  color: var(--text); background: var(--panel-2);
  border: 1px solid var(--line); border-radius: var(--radius-s);
}
.prules__add select option { background: var(--panel); color: var(--text); }
.prules__add .ftpform__hint { flex: 1 0 100%; margin: 0; }
/* Le filtre du mur de rotations : il y en a une vingtaine, et trois lettres
   vont plus vite que l'œil. */
.prots__q {
  flex: 1; min-width: 90px;
  padding: 3px 7px; font: inherit; font-size: var(--t-small);
  color: var(--text); background: var(--panel-2);
  border: 1px solid var(--line); border-radius: var(--radius-s);
}

/* --- le vivier résolu --- */
.ppool {
  margin: 12px 0 0; padding: 10px 14px;
  border-top: 1px solid var(--line); background: var(--bg-sunken);
}
.ppool__head { margin: 0 0 6px; font-size: var(--t-small); font-weight: 600; color: var(--ok); }
.ppool ul { margin: 0; padding: 0; list-style: none; }
.ppool li {
  display: grid; grid-template-columns: 1fr auto 110px 54px;
  gap: 8px; align-items: baseline;
  padding: 2px 0; font-size: var(--t-small);
}
.ppool__t, .ppool__s {
  overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}
.ppool__t { color: var(--text); }
.ppool__d { color: var(--text-dim); font-family: var(--mono); }
.ppool__s { color: var(--text-faint); font-size: var(--t-small); }
/* Le sélecteur de langue. Discret par défaut — on vient vérifier un vivier,
   pas remplir un formulaire — mais souligné quand la valeur a été devinée par
   un import : ce sont celles-là qu'il faut relire. */
.ppool__l {
  padding: 1px 2px; font: inherit; font-size: var(--t-small);
  color: var(--text-dim); background: transparent;
  border: 1px solid transparent; border-radius: var(--radius-s);
}
.ppool__l:hover, .ppool__l:focus { color: var(--text); border-color: var(--line); }
.ppool__l option { background: var(--panel); color: var(--text); }
.ppool__l--devine { color: var(--text); border-bottom-color: var(--warn); }

/* --- le conducteur généré --- */
.pplan__head {
  padding: 10px 14px; border-bottom: 1px solid var(--line);
  background: var(--bg-sunken);
}
.pplan__clock { margin: 0 0 3px; font-size: var(--t-base); font-weight: 600; color: var(--text); }
.pplan__sum { margin: 0; font-size: var(--t-small); color: var(--ok); }
/* Hors tolérance : c'est l'écart qui change de couleur, pas toute l'heure. Un
   conducteur juste dont la durée dérive de quarante secondes reste juste. */
.pplan__head.is-off .pplan__sum { color: var(--warn); }
.pplan__warn {
  margin: 0; padding: 8px 14px 8px 30px;
  font-size: var(--t-small); line-height: 1.5; color: var(--warn);
  border-bottom: 1px solid var(--line);
}
.prows { margin: 0; padding: 0; list-style: none; }
.prow {
  display: grid;
  grid-template-columns: 42px 18px 1fr auto auto auto;
  grid-template-areas: "at ico title fix len cost" ". . why why why why";
  gap: 1px 8px; align-items: center;
  padding: 5px 12px;
  font-size: var(--t-base);
  border-bottom: 1px solid var(--line);
  /* Même jeton, même lavis que dans l'éditeur d'horloge : une heure générée
     se relit à côté de l'horloge qui l'a produite, et un emplacement de voix
     rose là doit être rose ici. L'icône garde la teinte PLEINE — c'est elle
     qui nomme, la ligne ne fait que situer. */
  --nature: var(--text-dim);
  background-color: color-mix(in srgb, var(--nature) 10%, transparent);
}
.prow:hover { background-color: color-mix(in srgb, var(--nature) 22%, transparent); }
.prow__at { grid-area: at; font-size: var(--t-small); color: var(--text-faint); }
.prow__ico { grid-area: ico; display: flex; color: var(--nature); }
.prow--rotation { --nature: var(--clip-music); }
.prow--element, .prow--dossier { --nature: var(--clip-jingle); }
.prow--vt { --nature: var(--clip-voice); }
.prow--comblement { --nature: var(--accent-ink); }
.prow--pub { --nature: var(--cat-pub); }
/* Un marqueur ne sonne pas : aucune des quatre teintes, comme dans l'horloge. */
.prow--marqueur { --nature: var(--text-faint); }
/* Les spots d'une page, dans la colonne du motif : la page garde sa ligne — un
   conteneur est une ligne dans mAirList — et son contenu se lit dessous, en
   retrait et en teinte faible, comme un détail qu'on ne compte pas deux fois
   dans la lecture du conducteur. */
.prow__spots {
  grid-area: why;
  margin: 1px 0 0; padding: 0 0 0 10px;
  list-style: none;
  border-left: 1px solid var(--line);
  font-size: var(--t-small); color: var(--text-dim);
}
.prow__spots > li {
  display: flex; gap: 8px; justify-content: space-between;
  /* Un titre de spot peut être long ; il se coupe plutôt que de pousser la
     durée hors de la ligne. */
  min-width: 0;
}
.prow__spots > li > span:first-child {
  overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}
/* Une ligne de comblement est une permission, pas un élément à l'antenne :
   elle est écrite en teinte faible et en italique pour qu'on ne la cherche pas
   dans le conducteur posé, où elle ne descendra jamais. */
.prow--comblement .prow__title { color: var(--text-dim); font-style: italic; }
.prow__title {
  grid-area: title; min-width: 0;
  overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}
.prow__fix { grid-area: fix; font-size: var(--t-small); color: var(--accent-ink); }
.prow__len { grid-area: len; font-size: var(--t-small); color: var(--text-dim); }
/* Le coût reste pâle : il sert à régler les poids, pas à se lire en conduite.
   En évidence, il ferait croire à une anomalie à chaque ligne. */
.prow__cost {
  grid-area: cost; width: 30px; text-align: right;
  font-size: var(--t-small); color: var(--text-faint);
}
.prow__why { grid-area: why; font-size: var(--t-small); line-height: 1.4; color: var(--text-faint); }
.prow__tag {
  grid-area: cost; justify-self: end;
  padding: 0 4px; font-size: var(--t-micro); letter-spacing: var(--tr-wide); text-transform: uppercase;
  color: var(--warn); border: 1px solid var(--warn);
}
.prow.is-rescued .prow__cost { display: none; }
.prow.is-rescued .prow__why { color: var(--warn); }
/* Non pourvu : l'emplacement RESTE dans la liste, barré, avec son motif. Un
   trou silencieux ne se voit pas, et c'est précisément ce qu'il faut voir
   avant l'antenne. */
/* Le rouge est celui de l'antenne et de l'erreur (--col-onair), pas une
   cinquième teinte : la référence n'en a pas d'autre. */
.prow.is-missing { background: #ff33330f; }
.prow.is-missing .prow__title { color: var(--warn); text-decoration: line-through; }
.prow.is-missing .prow__why { color: var(--warn); }
/* Absorbé n'est pas manquant. Un power intro remplace à la fois le jingle et
   le titre qui suivait : la ligne du titre disparaît PARCE QUE tout s'est bien
   passé. Elle reste visible — on doit pouvoir vérifier ce qui a été repris —
   mais en retrait, sans la couleur d'alerte que porte un vrai trou. */
.prow.is-absorbed .prow__title { color: var(--text-faint); text-decoration: line-through; }
.prow.is-absorbed .prow__cost { display: none; }

@media (max-width: 760px) {
  .prow {
    grid-template-columns: 40px 18px 1fr auto;
    grid-template-areas: "at ico title len" ". . why why";
  }
  .prow__fix, .prow__cost { display: none; }
  /* En régie étroite, la langue reste : c'est la seule colonne sur laquelle
     on agit, le dossier d'origine n'est qu'un repère. */
  .ppool li { grid-template-columns: 1fr auto 54px; }
  .ppool__s { display: none; }
  .pslot__kind, .pslot__rot { width: 100%; }
}

/* ======================================================================
   Panel Automatisation — module Habillage
   ======================================================================

   Même parti pris d'accessibilité que la programmation : rien ne se juge à la
   couleur seule. Un dossier non réglé porte une teinte ET le mot « aucun
   dossier » ; une couverture d'intros faible porte une couleur ET son
   pourcentage. Un panneau de réglage se relit sur l'écran mal réglé d'un
   studio, et parfois par quelqu'un qui ne distingue pas le rouge du vert.

   Les deux états sont nommés par ce qu'ils valent et non par leur couleur —
   `is-ok`, `is-warn` — pour que changer la palette ne demande pas de relire le
   JavaScript. */

/* --- La liste des modules --------------------------------------------- */
.amod {
  display: grid; grid-template-columns: 20px 1fr auto; gap: 8px;
  align-items: start; width: 100%;
  padding: 8px 10px; margin-bottom: 4px; text-align: left;
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: var(--radius-s); color: var(--text); cursor: pointer;
}
.amod:hover { border-color: var(--accent-soft); }
.amod.is-active { border-color: var(--accent); background: var(--bg-sunken); }
.amod.is-soon { opacity: .55; cursor: default; }
.amod__ico { width: 18px; height: 18px; margin-top: 1px; color: var(--accent-ink); }
.amod.is-soon .amod__ico { color: var(--text-faint); }
.amod__body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.amod__name { font-weight: 600; }
.amod__blurb { font-size: var(--t-small); color: var(--text-dim); }
.amod__soon {
  padding: 0 5px; font-size: var(--t-micro); letter-spacing: var(--tr-wide); text-transform: uppercase;
  color: var(--text-faint); border: 1px solid var(--line);
}

/* --- Le verdict : un grand chiffre et ce qu'il veut dire --------------- */
/* Le chiffre est gros parce que c'est lui qu'on vient chercher, et la phrase
   en dessous existe parce qu'un pourcentage seul ne dit pas s'il est bon.
   Treize pour cent d'un catalogue marqué n'est pas un réglage réussi. */
.averdict {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 10px;
  align-items: center;
  padding: 9px 11px; margin-bottom: 9px;
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: var(--radius-s);
}
.averdict.is-ok   { border-color: var(--ok); }
.averdict.is-warn { border-color: var(--warn); }
.averdict__drift { font-size: var(--t-display); font-weight: 700; line-height: 1; }
.averdict.is-ok   .averdict__drift { color: var(--ok); }
.averdict.is-warn .averdict__drift { color: var(--warn); }
.averdict__body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.averdict__line { margin: 0; font-size: var(--t-base); }
.averdict__line--dim { color: var(--text-dim); font-size: var(--t-small); }

/* --- Les titres de section et les avertissements ---------------------- */
.asec {
  margin: 11px 0 5px; font-size: var(--t-small); font-weight: 600;
  letter-spacing: var(--tr-caps); text-transform: uppercase; color: var(--text-faint);
}
.awarn { margin: 0 0 5px; font-size: var(--t-base); color: var(--warn); }

/* --- Un élément d'habillage, ou une rotation mesurée ------------------- */
/* Quatre colonnes : un repère à gauche, le libellé qui s'étire, une étiquette
   de type s'il y en a une, et la durée à droite. La durée est à droite et en
   chiffres tabulaires parce qu'on la compare de ligne à ligne — c'est elle
   qui dit quelle intro il faudra pour que l'élément passe. */
/* Le cadre de la liste, et non une bordure par ligne : c'est lui qui dit où
   la liste commence et finit quand un dossier ne contient que deux éléments. */
.aitems {
  border: 1px solid var(--line); border-radius: var(--radius-s);
  background: var(--panel-2); overflow: hidden;
}
.aitem {
  display: grid; gap: 7px; align-items: center;
  grid-template-columns: 16px minmax(0, 1fr) auto auto;
  padding: 3px 8px; border-bottom: 1px solid var(--line);
  font-size: var(--t-base);
}
.aitem:last-child { border-bottom: 0; }
.aitem__at { color: var(--text-faint); font-size: var(--t-small); font-weight: 600; }
.aitem__ico { width: 14px; height: 14px; stroke-width: 2.2; }
.aitem__ico.k-jingle { color: var(--clip-jingle); }
.aitem__title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.aitem__len { justify-self: end; color: var(--text-dim); }
.aitem__tag {
  padding: 0 4px; font-size: var(--t-micro); letter-spacing: var(--tr-wide); text-transform: uppercase;
  color: var(--text-faint); border: 1px solid currentColor;
}
/* Une ligne que le serveur n'a pas pu lire reste LISIBLE et porte son
   message : la seule atténuation ferait croire à un bug d'affichage. */
.aitem.is-estimated { background: #ffffff06; color: var(--warn); }

/* --- Le choix d'un dossier -------------------------------------------- */
/* La valeur et les boutons sur une ligne : « quel dossier » et « en changer »
   sont la même question, et les séparer ferait chercher le bouton. */
.adress__pick { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.adress__val {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--t-base);
}
/* Un dossier non réglé le DIT, et n'est pas seulement grisé : c'est la
   différence entre « rien n'est réglé » et « l'écran n'a pas fini de
   charger ». */
.adress__val.is-empty { color: var(--text-faint); font-style: italic; }

/* --- La fiche de réglages --------------------------------------------- */
.azform__hint { margin: 0; font-size: var(--t-small); color: var(--text-faint); }
.azform__check { display: flex; gap: 7px; align-items: baseline; flex-wrap: wrap; }
.azform__acts { display: flex; gap: 6px; justify-content: flex-end; margin-top: 4px; }

/* --- La provenance d'une valeur mesurée ------------------------------- */
/* « 128 BPM mesuré à 0,52 de confiance » et « 128 BPM tranché par quelqu'un
   qui connaît le morceau » sont deux choses différentes, et sans ce bandeau
   elles s'afficheraient à l'identique. Le trait de gauche fait la distinction
   au coup d'œil ; le texte la fait quand même, parce qu'un écran de studio
   n'est pas un écran calibré et qu'on y travaille de nuit. */
.aprov {
  margin: 4px 0 0; padding: 3px 7px; font-size: var(--t-small); line-height: 1.35;
  border-left: 2px solid var(--line); border-radius: 0 var(--radius-s) var(--radius-s) 0;
  background: var(--panel-2); color: var(--text-dim);
}
/* Saisi : la valeur fait foi et ferme la porte au balayage. */
.aprov--saisi { border-left-color: var(--ok); color: var(--text); }
/* Mesuré : une machine l'a trouvé, et on peut le remplacer. */
.aprov--mesure { border-left-color: var(--text-faint); }
/* Mesuré de justesse : au-dessus du seuil, mais de peu. */
.aprov--mesure.is-faible { border-left-color: var(--warn); color: var(--warn); }
/* Rien : ni mesuré, ni saisi — une case vide, pas une panne. */
.aprov--vide { border-left-color: var(--line); color: var(--text-faint); font-style: italic; }
/* Le nom en clair déduit de la case, sur la ligne du champ. */
.aprov--nom {
  margin: 0; border-left: 0; border-radius: var(--radius-s); background: transparent;
  padding: 0 2px; white-space: nowrap;
}
.aprov--nom.is-vide { color: var(--text-faint); font-style: italic; }
.aprov--nom.is-faux { color: var(--warn); }
/* Ce que la requête portera, dit avant qu'on clique. */
.aenvoi { color: var(--text-dim); margin-top: 6px; }

@media (max-width: 760px) {
  .aitem { grid-template-columns: 16px minmax(0, 1fr) auto; }
  .aitem__tag { display: none; }
}

/* ======================================================================
   Panel Base de données — l'éditeur de points de montage
   ======================================================================

   Trois choses à savoir en relisant ces règles.

   La scène réutilise `.segue__stage` : c'est la même affaire — une forme
   d'onde plein cadre avec des repères posés dessus —, et deux écrans qui
   montrent la même chose ne doivent pas avoir deux fonds, deux hauteurs de
   règle et deux comportements au survol.

   Un repère se nomme par son LIBELLÉ et porte sa valeur en chiffres : la
   couleur du tiret n'est qu'un rappel. Un animateur qui ne distingue pas le
   vert du gris lit « Fin d'intro (Ramp 1) » et « 12.000 », et ça lui suffit.

   Une valeur modifiée et pas encore enregistrée se marque (`is-dirty`), parce
   qu'ici l'enregistrement part dans le catalogue et non dans une heure : il
   faut voir, avant de cliquer, tout ce qui va partir. */

/* Le corps du panneau ne défile PAS : il se partage. `.panel__body` défile
   par défaut, et ici ça donnait une scène poussée hors du cadre — on ne
   voyait plus que la règle du bas, collée à la liste des repères, et la
   moitié des lignes coupées. La scène prend la place qui reste, la liste
   défile dans son coin, et le panneau garde toujours ses deux parties. */
/* CINQ rangées, et le compte n'est pas décoratif : scène, barre de
   défilement, transport, enchaînement, repères. Il y en avait quatre, parce
   que le bloc « Tester l'enchaînement » est arrivé après et que personne n'a
   recompté. La liste tombait alors dans une rangée IMPLICITE, donc de
   hauteur indéfinie — et un `max-height` en pourcentage sur une hauteur
   indéfinie ne s'applique à rien. La liste prenait toute sa hauteur de
   contenu, le `overflow: hidden` d'ici la coupait net, et l'on voyait une
   ligne tranchée en deux avec un ascenseur qui ne servait à rien.

   La dernière rangée est donc BORNÉE ici plutôt que sur l'enfant :
   `minmax(0, 0.9fr)` lui donne au plus un peu moins de la moitié de ce qui
   reste, et le `0` la laisse se réduire quand le panneau est court. C'est la
   grille qui tient la hauteur, et la liste n'a plus qu'à défiler dedans. */
.base__cuebody {
  display: grid;
  grid-template-rows: minmax(90px, 1fr) auto auto auto minmax(0, 0.9fr);
  min-height: 0; padding: 0;
  overflow: hidden;
}
.base__stage {
  position: relative; min-height: 0;
  background: var(--wave-bg);
  overflow: hidden;
  touch-action: none;
}
.base__stage canvas { display: block; width: 100%; height: 100%; }

/* --- La barre de défilement --------------------------------------------
   Elle n'existe que zoomé : à pleine vue elle occuperait toute la largeur et
   ne dirait rien. Le curseur porte la part visible du titre, donc la barre
   sert de repère de zoom autant que de défilement — c'est pour ça qu'elle est
   collée sous l'onde et pas rangée dans le transport. */

.base__scroll {
  position: relative; height: 10px; flex: none;
  background: var(--wave-bg);
  border-top: 1px solid var(--line-soft);
  cursor: grab;
  touch-action: none;
}
.base__scroll[hidden] { display: none; }
.base__scroll:active { cursor: grabbing; }
.base__thumb {
  position: absolute; top: 2px; bottom: 2px;
  border-radius: var(--radius-btn);
  background: var(--accent);
  opacity: .7;
  pointer-events: none;
}
.base__scroll:hover .base__thumb { opacity: 1; }

/* --- Le transport ------------------------------------------------------
   Collé sous la forme d'onde, et non dans le ruban du haut : on écoute
   l'endroit qu'on vient de régler, l'œil ne doit pas traverser l'écran pour
   trouver le bouton. Un seul bouton, qui change de dessin — lecture et pause
   sont le même geste dans les deux sens. */

.base__transport {
  display: flex; align-items: center; gap: 8px;
  padding: 3px 8px;
  background: var(--panel);
  border-top: 1px solid var(--line-soft);
}
.base__play {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 22px; flex: none;
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--line); border-radius: var(--radius-btn);
  cursor: pointer;
}
.base__play:hover:not(:disabled) { border-color: var(--accent); }
.base__play:disabled { opacity: .35; cursor: not-allowed; }
/* En lecture, le bouton se remplit : c'est le seul endroit du panneau qui dit
   que quelque chose sort dans le casque. */
.base__play.is-on {
  background: var(--accent); border-color: var(--accent); color: var(--accent-text);
}
.base__clock { font-size: var(--t-base); color: var(--text); }
.base__of { font-size: var(--t-small); color: var(--text-faint); }
.base__hint { font-size: var(--t-small); color: var(--text-faint); }

/* Les trois boutons de zoom. Collés les uns aux autres : c'est un seul
   réglage à trois crans, pas trois outils. */
.base__zoom { display: inline-flex; gap: -1px; }
.base__zb {
  font: inherit; font-size: var(--t-base); line-height: 1;
  min-width: 22px; height: 20px; padding: 0 5px;
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--line); border-radius: 0;
  cursor: pointer;
}
.base__zb + .base__zb { margin-left: -1px; }
.base__zb:first-child { border-radius: var(--radius-btn) 0 0 var(--radius-btn); }
.base__zb:last-child { border-radius: 0 var(--radius-btn) var(--radius-btn) 0; }
.base__zb--wide { font-size: var(--t-small); }
.base__zb:hover:not(:disabled) { border-color: var(--accent); color: var(--text-hi); }
.base__zb:disabled { opacity: .35; cursor: not-allowed; }

/* --- L'essai d'enchaînement ------------------------------------------- */

/* Sous le transport, au-dessus des repères : on règle un point, on l'écoute
   seul avec le bouton du dessus, puis on l'écoute EN SITUATION ici. L'ordre
   des trois blocs est l'ordre des trois gestes. */
.base__chain {
  border-top: 1px solid var(--line-soft);
  background: var(--panel);
  padding: 6px 8px;
  display: grid; gap: 5px;
  font-size: var(--t-small);
}
.base__chain-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.base__chain-head strong { font-size: var(--t-small); color: var(--text); }
.base__chain-say { font-size: var(--t-small); color: var(--text-faint); }
.base__chain-row {
  position: relative;
  display: grid; grid-template-columns: 58px minmax(0, 1fr) auto;
  align-items: center; gap: 7px;
}
.base__chain-tag { color: var(--text-dim); }
.base__chain-q {
  font: inherit; font-size: var(--t-small); color: var(--text);
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: var(--radius-s); padding: 2px 6px; width: 100%;
}
.base__chain-q:focus { outline: 0; border-color: var(--accent); }
/* Choisi : le champ porte le nom de l'élément, et il s'allume pour qu'on ne
   le confonde pas avec une recherche en cours. */
.base__chain-row.is-set .base__chain-q { border-color: var(--accent); color: var(--text-hi); }
/* La liste flotte AU-DESSUS des repères : poussée dans le flux, elle
   déplacerait la forme d'onde à chaque frappe. */
.base__chain-list {
  position: absolute; z-index: 5; top: 100%; left: 58px; right: 0;
  max-height: 168px; overflow: auto;
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: var(--radius-s);
  box-shadow: var(--shadow, 0 6px 18px #0006);
}
.base__chain-opt {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: 7px; align-items: center;
  padding: 3px 7px; cursor: pointer;
  border-bottom: 1px solid var(--line-soft);
}
.base__chain-opt:last-child { border-bottom: 0; }
.base__chain-opt:hover { background: var(--accent-soft); }
.base__chain-opt span:first-child {
  min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.base__chain-opt small { color: var(--text-faint); font-family: var(--mono); }
.base__chain-foot { display: flex; align-items: center; gap: 8px; }
.base__chain-state { font-size: var(--t-small); color: var(--text-faint); }
#base-chain-play.is-on { border-color: var(--rec); color: var(--rec); }

/* --- La liste des repères -------------------------------------------- */

.base__points {
  border-top: 1px solid var(--line-soft);
  background: var(--panel);
  /* Pas de `max-height` ici : c'est la rangée de `.base__cuebody` qui borne,
     et un pourcentage posé sur l'enfant ne tenait que tant qu'il tombait dans
     une rangée de hauteur définie — ce qui a cessé d'être vrai le jour où une
     cinquième rangée est apparue sans être déclarée. */
  min-height: 0; overflow: auto;
}
.base__pt {
  display: grid; grid-template-columns: 4px minmax(0, 1fr) 78px auto auto;
  align-items: center; gap: 7px;
  padding: 2px 8px;
  border-bottom: 1px solid var(--line-soft);
  font-size: var(--t-small);
}
.base__pt:hover { background: #ffffff08; }
.base__pt.is-sel { background: var(--accent-soft); }
.base__pt-dash { height: 13px; border-radius: 0; }
.base__pt-name {
  min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.base__pt-val {
  font-family: var(--mono); font-size: var(--t-small); color: var(--text);
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: var(--radius-s); padding: 2px 5px; width: 100%;
  text-align: right;
}
.base__pt-val:focus { outline: 0; border-color: var(--accent); }
/* Modifié et pas encore parti : la valeur s'allume, et le nom avec. */
.base__pt.is-dirty .base__pt-val { border-color: var(--warn); color: var(--warn); }
.base__pt.is-dirty .base__pt-name { color: var(--warn); }
.base__pt-act {
  font: inherit; font-size: var(--t-small);
  background: transparent; color: var(--text-dim);
  border: 1px solid var(--line); border-radius: var(--radius-s);
  padding: 1px 6px; cursor: pointer;
  display: inline-flex; align-items: center;
}
.base__pt-act:hover:not(:disabled) { border-color: var(--accent); color: var(--text); }
.base__pt-act:disabled { opacity: .3; cursor: not-allowed; }
.base__pt-act--off:hover:not(:disabled) { border-color: var(--rec); color: var(--rec); }

/* --- La fiche --------------------------------------------------------- */

.base__info {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: 2px 10px; margin: 12px 0 0; font-size: var(--t-small);
}
.base__info dt { color: var(--text-faint); }
.base__info dd { margin: 0; min-width: 0; }
/* Un chemin Windows ne se coupe pas aux espaces : sans ceci il élargit le
   panneau et fait défiler toute la fiche de côté. */
.base__path { word-break: break-all; font-size: var(--t-small); color: var(--text-dim); }

/* --- La légende ------------------------------------------------------- */

.base__note {
  margin: 0; padding: 8px 10px;
  font-size: var(--t-small); color: var(--text-dim);
  border-bottom: 1px solid var(--line-soft);
}
.base__leg {
  display: grid; grid-template-columns: 4px minmax(0, 1fr); gap: 9px;
  padding: 8px 10px;
  border-bottom: 1px solid var(--line-soft);
  font-size: var(--t-small);
}
.base__leg-dash { border-radius: 0; }
.base__leg strong { font-weight: 600; }
.base__leg-name {
  font-family: var(--mono); font-size: var(--t-micro); color: var(--text-faint);
  margin-left: 6px;
}
.base__leg p { margin: 2px 0 0; color: var(--text-dim); }
.base__leg small { color: var(--text-faint); font-size: var(--t-small); }
/* L'ancre n'est pas un repère du fichier : son bloc se détache. */
.base__leg--note { background: var(--panel-2); }

/* La ligne du catalogue qu'on est en train d'éditer. Pas la même teinte que
   `is-playing` de la bibliothèque : ici on ne l'écoute pas, on la modifie. */
.lib-row--item.is-open { background: var(--accent-soft); }
.lib-row--item.is-open .lib-row__title { color: var(--accent-ink); }
.lib-row--item.is-open { cursor: default; }

/* La pastille « intro » de la troisième colonne. Discrète : c'est un repère
   de relecture — on balaie un dossier des yeux pour voir ce qui reste à
   monter —, pas une information qu'on vient lire ligne par ligne. */
.base__flag {
  font-size: var(--t-micro);
  letter-spacing: var(--tr-wide);
  color: var(--mark-sing);
  text-transform: uppercase;
}

@media (max-width: 760px) {
  .base__pt { grid-template-columns: 4px minmax(0, 1fr) 66px auto auto; }
  .base__points { max-height: 55%; }
}

/* ---------------------------------------------------------------- aide ----
 * Le panneau « Comment ça marche » de la programmation.
 *
 * De la prose dans un outil, c'est inhabituel, et c'est assumé : le réglage
 * qui demande une explication est à deux panneaux de là, et personne n'ouvre
 * un README avant de toucher à une horloge. D'où un corps un peu plus large
 * et un interlignage de lecture, mais les mêmes couleurs que le reste — ce
 * n'est pas une page à part, c'est un panneau.
 */
.aide { padding: 4px 14px 16px; font-size: var(--t-base); line-height: 1.55; }
.aide p { margin: 0 0 9px; color: var(--text-dim); }
.aide b { color: var(--text); font-weight: 600; }
.aide code {
  font-family: var(--mono); font-size: var(--t-small);
  color: var(--accent-ink); background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--radius-btn); padding: 0 3px;
}
.aide__h {
  margin: 16px 0 7px;
  font-size: var(--t-small); letter-spacing: var(--tr-wide); text-transform: uppercase;
  color: var(--accent-ink);
}
.aide__h:first-child { margin-top: 6px; }
.aide__steps, .aide__list { margin: 0 0 9px; padding-left: 20px; color: var(--text-dim); }
.aide__steps li, .aide__list li { margin-bottom: 6px; }
/* Une remarque qu'on ne doit pas rater : le filet à gauche la sort du flux de
 * lecture sans l'enfermer dans un encadré, qui ferait pavé d'avertissement. */
.aide__note {
  border-left: 2px solid var(--accent);
  padding: 2px 0 2px 10px;
  margin: 11px 0;
}
.aide__tab { width: 100%; border-collapse: collapse; margin: 0 0 11px; }
.aide__tab th, .aide__tab td {
  text-align: left; vertical-align: top;
  padding: 5px 8px 5px 0;
  border-bottom: 1px solid var(--line);
  color: var(--text-dim);
}
.aide__tab th {
  font-size: var(--t-micro); letter-spacing: var(--tr-wide); text-transform: uppercase;
  color: var(--text-faint); font-weight: 500;
}
.aide__tab td:first-child { color: var(--text); white-space: nowrap; }
.aide__tab td:nth-child(2) { color: var(--accent-ink); white-space: nowrap; }

/* ===========================================================================
   Génération en série
   ---------------------------------------------------------------------------
   Deux colonnes : à gauche ce qu'on demande, à droite ce que ça donne. La
   demande est courte et fixe, le déroulé est long et défile — d'où une
   largeur fixe à gauche et tout le reste à droite, plutôt que deux parts.

   Le déroulé est une grille et non un tableau : une ligne par heure, les
   colonnes alignées à la même largeur d'un bout à l'autre, et on peut lire
   une journée en suivant une seule colonne du regard.
   =========================================================================== */
.segmented--lead { margin-left: 0; }

/* `.workspace > :not(.desk)` donne `flex: none` à tout ce qui n'est pas le
 * bureau — barre d'outils, barre d'état, onglets. La page Série, elle, prend
 * la place du bureau : elle doit s'étirer comme lui. */
.workspace > .serie {
  flex: 1 1 auto; min-height: 0;
  display: grid; grid-template-columns: 320px 1fr;
  gap: 1px; background: var(--line);
}
.serie__form, .serie__suivi {
  background: var(--bg); min-height: 0;
  display: flex; flex-direction: column;
}
.serie__form { padding: 16px 18px; overflow: auto; }
.serie__h {
  margin: 0 0 12px;
  font-size: var(--t-small); letter-spacing: var(--tr-wide); text-transform: uppercase;
  color: var(--accent-ink); font-weight: 600;
}

.serie__presets { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 7px; }

/* Vingt-quatre cases de même taille, six par rangée : la grille a la forme
 * d'une journée, et on voit d'un coup si la nuit est dedans ou dehors. */
.serie__heures { display: grid; grid-template-columns: repeat(6, 1fr); gap: 4px; }
.serie__heure {
  font: inherit; font-size: var(--t-small);
  font-variant-numeric: tabular-nums;
  color: var(--text-faint);
  background: var(--bg-sunken);
  border: 1px solid var(--line-soft); border-radius: var(--radius-xs);
  padding: 4px 0; cursor: pointer; text-align: center;
}
.serie__heure:hover { color: var(--text); }
.serie__heure.is-on {
  color: var(--bg); background: var(--accent); border-color: var(--accent);
}

.serie__actions { display: flex; gap: 8px; margin: 14px 0 4px; }

.serie__head {
  display: flex; align-items: baseline; gap: 12px;
  padding: 14px 18px 0;
}
.serie__head .serie__h { margin: 0; }
.serie__resume { margin-left: auto; font-size: var(--t-small); color: var(--text-dim); }

/* La jauge est fine et pleine largeur, sous le titre : elle répond à « ça
 * avance ? » sans qu'on ait à lire un chiffre. */
.serie__jauge {
  height: 3px; margin: 10px 18px 0;
  background: var(--bg-sunken); border-radius: 2px; overflow: hidden;
}
.serie__jauge > i {
  display: block; height: 100%; width: 0;
  background: var(--accent); transition: width .25s ease;
}

.serie__liste { flex: 1; min-height: 0; overflow: auto; padding: 10px 18px 18px; }
.serie__ligne {
  display: grid; grid-template-columns: 96px 44px 88px 62px 1fr;
  align-items: baseline; gap: 10px;
  padding: 4px 0; border-bottom: 1px solid var(--line);
  font-size: var(--t-base); color: var(--text-dim);
}
.serie__ligne > :nth-child(-n+4) { font-variant-numeric: tabular-nums; }
.serie__ligne b { color: var(--text); font-weight: 500; }
.serie__msg {
  color: var(--text-faint); font-size: var(--t-small);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* L'état porte toute la lecture du déroulé : posée, hors grille, échec. Trois
 * couleurs seulement, et le gris pour « pas encore » — un échec doit sauter
 * aux yeux dans trois cents lignes. */
.serie__etat { font-size: var(--t-small); text-transform: uppercase; letter-spacing: var(--tr-wide); }
.serie__ligne[data-state="posee"]    .serie__etat { color: var(--ok); }
.serie__ligne[data-state="echec"]    .serie__etat { color: var(--warn); }
.serie__ligne[data-state="vide"]     .serie__etat { color: var(--text-faint); }
.serie__ligne[data-state="sautee"]   .serie__etat { color: var(--text-faint); }
.serie__ligne[data-state="en_cours"] .serie__etat { color: var(--accent-ink); }
.serie__ligne[data-state="en_cours"] { background: var(--panel-2); }
.serie__ligne[data-state="attente"]  { opacity: .45; }

/* Un jour commence : un filet et sa date, pour ne pas avoir à relire la
 * colonne de gauche à chaque ligne. */
.serie__jour {
  margin: 14px 0 4px; padding-bottom: 3px;
  border-bottom: 1px solid var(--line-soft);
  font-size: var(--t-small); text-transform: uppercase; letter-spacing: var(--tr-wide);
  color: var(--text-faint);
}
.serie__jour:first-child { margin-top: 0; }

@media (max-width: 860px) {
  .workspace > .serie { grid-template-columns: 1fr; grid-template-rows: auto 1fr; }
}

/* ==== Statistiques ====
 *
 * Huit panneaux qui ne font que compter, donc huit variations d'une seule
 * forme : un chiffre, un libellé, et une longueur qui se compare. Tout est
 * posé sur les jetons existants — aucune couleur en dur ici, sans quoi le
 * thème clair rendrait des barres invisibles et le thème sombre des chiffres
 * illisibles. L'accent porte les barres et les colonnes : c'est une mesure du
 * catalogue, pas une nature d'élément, donc ni le cyan de la musique ni le
 * jaune du jingle n'auraient de sens.
 */

/* Le choix de la fenêtre d'historique, dans le ruban. Accolé comme le
 * sélecteur de créneau de la programmation : même geste, même dessin. */
.stats__pick {
  display: flex; align-items: center; gap: 6px;
  flex: none;
  font-size: var(--t-small); color: var(--text-dim);
}
.stats__pick select {
  background: var(--panel); color: var(--text);
  border: 1px solid var(--line); border-radius: var(--radius-s);
  padding: 3px 6px; font: inherit; font-size: var(--t-small);
}

/* Les panneaux de ce panel respirent : ce sont des chiffres qu'on lit, pas une
 * liste qu'on parcourt au clavier. Le `padding` est ici et non sur
 * `.panel__body`, qui sert partout ailleurs. */
#ws-stats .panel__body { padding: 10px 12px 14px; }
#ws-stats .panel__body > .hint { margin: 10px 0 0; }

/* --- Les chiffres qu'on cite --- */
/* `auto-fit` et non un nombre fixe de colonnes : le même bloc sert dans une
 * colonne de 50 % d'écran et dans un panneau replié sur un téléphone, et six
 * chiffres sur une seule ligne y seraient six chiffres illisibles. */
.stats__sum {
  display: grid; gap: 8px;
  grid-template-columns: repeat(auto-fit, minmax(112px, 1fr));
}
.stats__fig {
  display: flex; flex-direction: column; gap: 1px;
  padding: 7px 9px;
  background: var(--bg-sunken);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-xs);
}
.stats__fignum { font-size: var(--t-big); font-weight: 600; color: var(--text-hi); }
.stats__figlab {
  font-size: var(--t-small); text-transform: uppercase; letter-spacing: var(--tr-wide);
  color: var(--text-faint);
}

.stats__h {
  margin: 14px 0 6px;
  font-size: var(--t-small); text-transform: uppercase; letter-spacing: var(--tr-wide);
  color: var(--text-faint); font-weight: 500;
}
.stats__h:first-child { margin-top: 0; }

/* --- L'avancement d'un relevé --- */
/* Pleine largeur et plate : elle n'est là que pendant le parcours, et une
 * jauge qui prendrait de la place laisserait un vide une fois le relevé fini. */
.stats__gauge {
  height: 4px; margin-bottom: 10px;
  background: var(--bg-sunken);
  border-radius: 999px; overflow: hidden;
}
.stats__gauge > i {
  display: block; height: 100%;
  background: var(--accent);
  transition: width .4s ease;
}

/* --- Les barres horizontales --- */
/* Trois colonnes : le libellé, la barre, le compte. Le libellé est borné pour
 * que les barres partent toutes du même x — sans quoi elles ne se comparent
 * plus, et c'est tout ce qu'on leur demande. */
.stats__bars { display: flex; flex-direction: column; gap: 3px; }
.stats__bar {
  display: grid; grid-template-columns: minmax(0, 11em) 1fr auto;
  align-items: center; gap: 8px;
  font-size: var(--t-base);
}
.stats__barlab {
  color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.stats__bartrack {
  height: 11px;
  background: var(--bg-sunken);
  border-radius: var(--radius-xs);
  overflow: hidden;
}
.stats__barfill {
  display: block; height: 100%;
  background: var(--accent);
  border-radius: var(--radius-xs);
}
.stats__barval { color: var(--text-faint); font-size: var(--t-small); white-space: nowrap; }

/* --- L'histogramme --- */
/* Largeur fluide, hauteur déduite du viewBox : le dessin suit sa colonne sans
 * qu'une seule mesure soit faite en JavaScript. */
.stats__diag { display: block; width: 100%; height: auto; margin-bottom: 8px; }
.stats__col { fill: var(--accent); }
.stats__colval { fill: var(--text-dim); font-size: var(--t-read); }
.stats__collab { fill: var(--text-faint); font-size: var(--t-read); }
.stats__axe { stroke: var(--line); stroke-width: 1; }

.stats__leg {
  display: flex; flex-wrap: wrap; gap: 4px 14px;
  font-size: var(--t-small); color: var(--text-faint);
}
.stats__legitem b { color: var(--text-dim); font-weight: 500; }

/* --- Les palmarès --- */
/* Deux colonnes côte à côte quand la place le permet, l'une sous l'autre
 * sinon : les plus longs et les plus courts se lisent ensemble, c'est leur
 * écart qui parle. */
.stats__deux {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
}
.stats__bloc { min-width: 0; }

.stats__rank { margin: 0; padding: 0; list-style: none; counter-reset: rang; }
.stats__rankrow {
  display: grid; grid-template-columns: 1.6rem minmax(0, 1fr) minmax(0, .9fr) auto;
  align-items: baseline; gap: 8px;
  padding: 3px 0; border-bottom: 1px solid var(--line-soft);
  font-size: var(--t-base);
}
/* Le rang est un compteur CSS et non un nombre écrit dans la page : la liste
 * vient du serveur dans l'ordre, et recopier son index en JavaScript ferait
 * deux sources pour le même chiffre. */
.stats__rankrow::before {
  counter-increment: rang; content: counter(rang) ".";
  color: var(--text-faint); font-size: var(--t-small); text-align: right;
}
.stats__ranktitle {
  color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.stats__rankartist {
  color: var(--text-faint); font-size: var(--t-small);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.stats__rankval { color: var(--text-dim); white-space: nowrap; }

/* --- Les dossiers illisibles --- */
/* Replié par défaut : un dossier verrouillé pendant le parcours est fréquent
 * et sans conséquence sur les parts — mais il doit rester consultable, sans
 * quoi un relevé incomplet passerait pour complet. */
.stats__soucis { margin-top: 10px; font-size: var(--t-small); color: var(--text-faint); }
.stats__soucis summary { cursor: pointer; }
.stats__soucis ul { margin: 6px 0 0; padding-left: 18px; }
.stats__soucis li { margin: 2px 0; }

@media (max-width: 860px) {
  /* Les libellés mangeraient la barre sur un téléphone : on leur laisse le
     tiers de la ligne, et l'ellipse fait le reste. */
  .stats__bar { grid-template-columns: minmax(0, 7.5em) 1fr auto; }
}

/* ======================================================================
   Le serveur, et la liaison
   ====================================================================== */

/* --- Le choix du serveur, sur l'écran de connexion et dans les Réglages --- */
/* Replié : une installation ordinaire n'a rien à régler ici, et un champ
 * d'adresse offert en grand sous un formulaire de connexion invite à le
 * remplir — c'est le contraire de ce qu'on veut. Voir js/serveur.js. */
.srv { margin-top: 14px; font-size: var(--t-base); }
.srv > summary {
  cursor: pointer; color: var(--text-faint); padding: 4px 0;
  list-style: none;
}
.srv > summary::before { content: "▸ "; }
.srv[open] > summary::before { content: "▾ "; }
.srv > summary:hover { color: var(--text-dim); }
.srv__body { padding: 8px 0 0; }
.srv__actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
/* Le volet vit aussi sous le formulaire de connexion, dont la typographie est
 * posée sur fond sombre : sans ceci, le résumé disparaîtrait dessus. */
.login .srv > summary { color: #a99db8; }
.login .srv > summary:hover { color: #d7c9e4; }
.login--solo .srv > summary { color: var(--text-faint); }

/* --- Les adresses du mAirListDB, dans la supervision --- */
.liaison__form { max-width: 520px; }
.liaison__form .field + .field { margin-top: 10px; }
.liaison__actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0 18px; }
.liaison__sante { border-top: 1px solid var(--line-soft); padding-top: 14px; }

/* --- La liste de travail de la Base de données --- */
/* Les mentions sont des ÉTIQUETTES et non des colonnes : une ligne porte
 * rarement les trois, et trois colonnes vides sur quatre lignes sur cinq
 * mangeraient la largeur d'un panneau qui sert d'abord à lire des titres. */
.todo__tags { display: inline-flex; flex-wrap: wrap; gap: 4px; margin-left: 6px; }
.todo__tag {
  font-size: var(--t-micro); line-height: 1.5; padding: 0 5px; border-radius: var(--radius-btn);
  color: var(--text-faint); background: #ffffff0e; white-space: nowrap;
}
/* Ce qui manque en jaune, ce qui est fait en vert : c'est la seule lecture
 * qu'on fait de cette liste en la parcourant du regard. */
.todo__tag--manque { color: var(--warn); background: var(--warn-soft); }
.todo__tag--fait { color: var(--ok); background: var(--ok-soft); }
.todo__pages {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  padding: 10px 0 4px;
}

/* --- Ce que le modèle propose (module Annotation IA) --- */
/* Trois colonnes de largeur fixe pour les deux bouts : la langue et la
 * confiance se lisent en descendant la liste, et des colonnes qui dansent au
 * gré des titres rendraient ce balayage du regard impossible. */
.ia-row {
  display: grid; grid-template-columns: 3.4em minmax(0, 1fr) 3.2em;
  align-items: center; gap: 8px;
  padding: 4px 6px; border-bottom: 1px solid var(--line-soft);
  font-size: var(--t-base);
}
.ia-row__langue {
  font-size: var(--t-small); font-weight: 600; text-align: center;
  padding: 1px 0; border-radius: var(--radius-btn);
  color: var(--ok); background: var(--ok-soft);
}
.ia-row__titre { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ia-row__conf { text-align: right; color: var(--text-faint); font-size: var(--t-small); }
/* Sous le seuil : la proposition s'affiche, mais rien n'est écrit. Elle est
 * donc montrée EN RETRAIT — on la lit, on ne la croit pas. */
.ia-row--doute { opacity: .62; }
.ia-row--doute .ia-row__langue { color: var(--warn); background: var(--warn-soft); }

/* --- Le résumé et les conseils du rapport de conformité --- */
/* Posés AVANT la liste des manquements, et visuellement détachés d'elle : la
 * liste est un constat, ceci est une décision. Les confondre, c'est ce qui
 * faisait relire deux cents lignes sans jamais trouver quoi changer.
 *
 * Pas de couleur d'alerte ici, volontairement. Un conseil n'est pas une
 * panne — il est lu posément, avant la prochaine génération, et le peindre en
 * rouge ferait de chaque rapport une urgence. */
.ia-bilan {
  margin: 0 0 10px; padding: 10px 12px;
  border-left: 3px solid var(--accent); border-radius: var(--radius-btn);
  background: var(--accent-soft); color: var(--text);
  font-size: var(--t-base); line-height: 1.5;
}
.ia-conseils { list-style: none; margin: 0 0 14px; padding: 0; }
.ia-conseil {
  padding: 8px 0; border-bottom: 1px solid var(--line-soft);
}
.ia-conseil:last-child { border-bottom: 0; }
.ia-conseil__titre { margin: 0; font-weight: 600; font-size: var(--t-base); }
.ia-conseil__constat {
  margin: 3px 0 0; color: var(--text-dim); font-size: var(--t-small);
  line-height: 1.45;
}
/* Le geste ressort du constat : c'est la seule ligne sur laquelle on agit. */
.ia-conseil__geste {
  margin: 4px 0 0; color: var(--text); font-size: var(--t-small);
  line-height: 1.45;
}
.ia-conseil__poids {
  margin: 3px 0 0; color: var(--text-faint); font-size: var(--t-micro);
}

/* --- L'arbre de progression de la veille --- */
/* L'indentation vient d'une variable et non de classes par niveau : l'arbre
 * est rendu récursivement, et un contrôle qui gagnera une étape fille demain
 * s'affichera sans qu'on ajoute une règle ici. */
.arbre__noeud {
  display: grid;
  grid-template-columns: 12px minmax(0, auto) minmax(0, 1fr) auto;
  align-items: baseline; gap: 6px;
  padding: 2px 0 2px calc(var(--prof, 0) * 14px);
  font-size: var(--t-base);
}
/* Les enfants reprennent toute la largeur sous leur parent. */
.arbre__noeud .arbre__noeud { grid-column: 1 / -1; }
.arbre__pastille {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--text-faint); justify-self: center;
}
.arbre__pastille--ok { background: var(--ok); }
.arbre__pastille--alerte { background: var(--warn); }
.arbre__pastille--echec { background: var(--rec); }
/* En cours : la pastille respire, c'est le seul mouvement de l'écran et il
 * dit la seule chose qui bouge. */
.arbre__pastille--en-cours { background: var(--accent); animation: arbre-pulse 1s infinite; }
@keyframes arbre-pulse { 50% { opacity: .3; } }
.arbre__label { color: var(--text); }
.arbre__detail { color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; }
.arbre__duree { color: var(--text-faint); font-size: var(--t-small); }

/* --- Le journal des contrôles --- */
.veille-row {
  display: grid; grid-template-columns: 3.2em minmax(0, 1fr) 4em auto;
  align-items: baseline; gap: 8px;
  padding: 4px 6px; border-bottom: 1px solid var(--line-soft);
  font-size: var(--t-base);
}
.veille-row__resume { color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.veille-row__cumul { text-align: right; font-weight: 600; }
.veille-row__quand { color: var(--text-faint); font-size: var(--t-small); white-space: nowrap; }
.veille-row--ok .veille-row__cumul { color: var(--ok); }
.veille-row--alerte .veille-row__cumul { color: var(--warn); }
.veille-row--echec .veille-row__cumul { color: var(--rec); }

/* --- Les repères proposés sur un dossier entier --- */
/* La case est en tête de ligne et la ligne entière est un `<label>` : on
 * coche en cliquant n'importe où, parce qu'on en coche trente à la suite. */
.rep-row {
  display: grid; grid-template-columns: auto minmax(0, 1fr) 4.5em 3.2em;
  align-items: center; gap: 8px;
  padding: 4px 6px; border-bottom: 1px solid var(--line-soft);
  font-size: var(--t-base); cursor: pointer;
}
.rep-row:hover { background: #ffffff08; }
.rep-row__titre { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rep-row__pos { text-align: right; color: var(--intro); }
.rep-row__conf { text-align: right; color: var(--text-faint); font-size: var(--t-small); }
/* Sous le seuil : la ligne reste cochable — c'est un choix humain — mais elle
 * s'affiche en retrait pour qu'on ne la coche pas par inadvertance. */
.rep-row--doute { opacity: .6; }
.rep-row--doute .rep-row__pos { color: var(--warn); }

/* Le choix des marqueurs cherchés, dans le module Repérage. */
.rep-choix { display: flex; flex-wrap: wrap; gap: 4px 14px; margin: 8px 0 12px; }
.rep-row { grid-template-columns: auto minmax(0, 1fr) 7em 4.5em 3.2em; }
.rep-row__marq { color: var(--text-faint); font-size: var(--t-small); white-space: nowrap; }

/* Le bloc d'attente d'une vue pas encore écrite (le panel IA). Même rôle que
 * `.workspace > .admin` : sans cette ligne, le `flex: none` du shell fige le
 * bloc à sa hauteur naturelle et sa bande de fond s'arrête au tiers de
 * l'écran. */
.workspace > .holding { flex: 1 1 auto; min-height: 0; }

/* ===========================================================================
   § HERTZIA — ce que mAirList2go n'avait pas à dessiner
   ===========================================================================

   TOUT CE QUI PRÉCÈDE EST PORTÉ TEL QUEL depuis `radiosmp-vt/frontend/app.css`,
   octet pour octet. C'est voulu, et c'est la seule façon de tenir la consigne :
   deux feuilles « presque pareilles » divergent au premier correctif appliqué
   d'un seul côté, et la différence se voit alors sur un écran de régie avant de
   se voir dans un diff. Ce qui manque à Hertzia est donc AJOUTÉ ICI, à la fin,
   et jamais glissé au milieu — on doit pouvoir recopier le fichier amont par
   dessus et ne recoller que ce bloc.

   Trois manques, et trois seulement. */

/* ---------------------------------------------------------- 1. La marque ---

   PAS DE LOGO. mAirList2go a le sien (`img/logo-smp.png`), Hertzia n'en a
   aucun, et poser celui de RadioSMP sur un autre produit serait un faux. On
   écrit donc le nom, et on l'écrit en tant que marque : c'est l'usage normal
   d'un mot-symbole, pas un pis-aller — la plupart des logos de radio sont
   exactement ça.

   Le « z » en accent ne fait pas que décorer : le mot est long, il est bas de
   casse, et sans ce point d'appui l'œil n'a rien à quoi s'accrocher dans la
   colonne de 240 px. Le dégradé est celui du bouton de connexion, pour que la
   page n'ait qu'une seule couleur de marque. */
.marque {
  font-family: var(--sans);
  font-weight: 600;
  letter-spacing: -.02em;
  line-height: 1;
}
.marque b { font-weight: 600; color: #c66ce4; }

.login__word {
  margin: 0;
  font-size: clamp(44px, 7.5vw, 76px);
  color: #f3e9fb;
  /* L'ombre portée du logo qu'il remplace, à la même profondeur : sans elle le
     mot flotte à plat sur le dégradé et se lit comme un titre de paragraphe. */
  text-shadow: 0 10px 30px #00000080;
}
.rail__word {
  font-size: var(--t-lead);
  color: var(--text-hi);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ------------------------------------------------- 2. Les natures en plus ---

   LES TROIS TEINTES NE BOUGENT PAS : cyan la musique, jaune l'habillage, rose
   la voix. Elles sont déjà posées plus haut (`.pl-item.k-music` &c.) et elles
   portent le même sens dans les deux outils — un son cyan est un son qu'on a
   le droit d'étirer, dans Hertzia comme dans mAirList2go. Les redéfinir ici,
   même à l'identique, aurait créé l'endroit où elles pourront divergir.

   Hertzia a quatre natures que mAirList2go ne nomme pas. Deux prennent une
   teinte existante parce qu'elles sont la MÊME CHOSE pour qui regarde l'écran :
   le top horaire est de l'habillage, le lit musical est de la musique. Les deux
   autres restent grises — c'est la règle de la feuille amont, qui ne donne de
   teinte qu'à ce qui se distingue d'un coup d'œil, et un écran où tout est
   coloré ne dit plus rien.

   LE TOP EST LA SEULE EXCEPTION À CETTE RÈGLE, et elle est délibérée : il ne
   s'étire jamais et ne se déplace jamais (voir `Nature` dans models.py). C'est
   la seule nature dont la méconnaissance fait rater une heure, donc la seule
   qui gagne un repère en plus de sa teinte — un filet plus large, pas une
   quatrième couleur. */
.pl-item.k-top    { --nature: var(--clip-jingle); }
.pl-item.k-bed    { --nature: var(--clip-music); }
.pl-item.k-pub    { --nature: var(--text-dim); }
.pl-item.k-top::before { width: 5px; }

/* ------------------------------------------------- 3. Le bandeau d'antenne ---

   CE QUE mAirList2go N'A PAS : il prépare des voix pour une antenne tenue
   ailleurs, Hertzia EST l'antenne. Le panneau « En ce moment » est donc neuf, et
   il est le seul endroit de cette feuille qui ne vienne pas du portage.

   Il tient sur les jetons existants, sans en ajouter un seul : `--rec` pour
   l'antenne ouverte, `--clip-*` pour la nature, `--mono` pour tout ce qui est
   un nombre qu'on compare d'un coup d'œil. */
.air {
  display: flex; flex-direction: column; gap: 10px;
  padding: 12px;
}
.air__titre {
  margin: 0;
  font-size: var(--t-lead); font-weight: 600; color: var(--text-hi);
  /* Un titre long ne doit ni passer à la ligne ni pousser la jauge hors du
     panneau : c'est la ligne qu'on lit de loin, et elle doit garder sa place. */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.air__artiste {
  margin: 0; font-size: var(--t-read); color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.air__titre:empty::after, .air__artiste:empty::after {
  content: "—"; color: var(--text-faint);
}

/* La jauge d'avancement du titre en cours.
   Pas de transition sur la largeur : elle est repoussée plusieurs fois par
   seconde, et une animation de 200 ms la laisserait toujours en retard sur ce
   qui sonne — une jauge qui ment est pire qu'une jauge absente. */
.air__jauge {
  height: 6px; border-radius: 3px; overflow: hidden;
  background: var(--bg-sunken);
  box-shadow: inset 0 0 0 1px var(--line-soft);
}
.air__barre {
  height: 100%; width: 0;
  background: var(--accent);
  border-radius: 3px;
}

.air__ligne {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  font-family: var(--mono); font-size: var(--t-small);
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.air__ligne .spacer { flex: 1; }

/* La pastille d'antenne. Elle ne clignote PAS : un voyant qui clignote attire
   l'œil en permanence, et celui-ci est allumé des heures d'affilée. Il dit un
   état, pas une alarme. */
.air__pastille {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--mono); font-size: var(--t-small);
  text-transform: uppercase; letter-spacing: var(--tr-caps);
  color: var(--text-faint);
}
.air__pastille::before {
  content: ""; width: 9px; height: 9px; border-radius: 50%;
  background: var(--text-faint);
}
.air__pastille.is-on { color: var(--rec); }
.air__pastille.is-on::before {
  background: var(--rec); box-shadow: 0 0 0 4px var(--rec-glow);
}

/* Le calage, tel que le moteur le rend : un pourcentage d'étirement.
   Zéro est l'état normal et doit se taire — on ne colore que l'écart. */
.air__calage { font-family: var(--mono); font-size: var(--t-small); color: var(--text-faint); }
.air__calage.is-tendu { color: var(--warn); }

/* Le journal d'antenne et la file à venir : des listes de lignes de conducteur,
   donc le `.pl-item` de la feuille amont. Rien à redessiner — c'est tout
   l'intérêt d'avoir porté la feuille entière. Seul le cadre change. */
.air__liste { display: flex; flex-direction: column; min-height: 0; }

/* La case à cocher posée dans un ruban, et non dans un `.field`.
 *
 * mAirList2go n'en a qu'une — « se souvenir de moi » — et elle vit sous
 * `.login__remember`, avec le dessin de sa page. Hertzia en pose une dans la
 * bibliothèque, et lui donner la classe de la page de connexion aurait été un
 * emprunt : deux endroits sans rapport accrochés au même sélecteur, que le
 * premier retouche du dessin de la connexion aurait séparés sans prévenir.
 *
 * Volontairement pas un `.field` non plus : celui-ci étire ses champs sur toute
 * la largeur, ce qui donnerait une case à cocher de trois cents pixels. */
.coche {
  display: flex; align-items: center; gap: 8px;
  font-size: var(--t-small); color: var(--text-dim);
  cursor: pointer; user-select: none;
}
.coche input {
  width: 14px; height: 14px; margin: 0; accent-color: var(--accent);
  flex: none;
}
.coche:hover { color: var(--text); }

/* ======================================================= § ÉDITEUR DE CUE ==
   Ajouté par Hertzia : mAirList2go dessine sa forme d'onde dans un canvas
   réglé en JavaScript, et n'a donc presque rien à styler. Ici on pose le
   strict nécessaire — une piste qui occupe la place qu'on lui donne, et des
   repères qui se lisent. */

.cue__barre {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px; flex-wrap: wrap;
  border-bottom: 1px solid var(--line, #1e2638);
}

/* La hauteur est en `min-height` et non fixe : le panneau se redimensionne à
   la souris (layout.js), et une onde qui ne suivrait pas laisserait une bande
   vide sous elle. */
.cue__onde {
  display: block; width: 100%; height: 100%;
  min-height: 120px;
  cursor: crosshair;
}

.cue__points { padding: 6px 10px 10px; display: grid; gap: 3px; }

.base__pt-rail {
  width: 3px; align-self: stretch; border-radius: 2px;
}

/* ================================================= § NATURES, PAR LEUR NOM ==
   Les teintes de nature existaient déjà sous le nom `--clip-*` (musique,
   habillage, voix), hérité de la timeline de mAirList2go où ce sont des
   CLIPS qu'on colore. Trois écrans de Hertzia les demandent par le nom du
   concept — `--nature-music` — parce que c'est ce qu'ils dessinent : une
   ligne de conducteur n'est pas un clip.

   Des alias, donc, et pas une seconde définition : une couleur qui porte un
   sens ne doit exister qu'à un seul endroit, sinon les deux divergent au
   premier ajustement et le même titre n'a pas la même teinte d'un écran à
   l'autre.

   Les quatre dernières n'avaient PAS de teinte à elles. Le top prend celle de
   l'habillage (c'en est), le lit celle de la musique (c'en est une), la pub
   et le reste prennent le gris de texte : elles ne nomment pas une couleur de
   station, et leur en inventer une ferait croire à un sens qui n'existe pas. */
:root {
  --nature-music:  var(--clip-music);
  --nature-jingle: var(--clip-jingle);
  --nature-voice:  var(--clip-voice);
  --nature-top:    var(--clip-jingle);
  --nature-bed:    var(--clip-music);
  --nature-pub:    var(--text-dim);
  --nature-other:  var(--text-dim);
}
