/* da-hud.animations.css — géométrie et mise en mouvement du thème DMPM HUD.
   Identique dans les deux dépôts. Aucune couleur en dur : tout passe par les jetons.
   TOUT est scopé par [data-da="dmpm-hud"], y compris les biseaux : sous une autre
   charte, aucune de ces classes ne produit le moindre effet — pas même un contexte
   de découpe. Voir README.md §Biseaux pour les rôles, les ombres et le mode clair. */

@keyframes hud-scan     { 0% { transform: translateX(-30%); }  100% { transform: translateX(1300%); } }
@keyframes hud-shimmer  { 0% { transform: translateX(-120%); } 100% { transform: translateX(520%); } }
@keyframes hud-ring     { 0% { transform: scale(1); opacity: .55; } 70%, 100% { transform: scale(3.4); opacity: 0; } }
@keyframes hud-blink    { 0%, 45% { opacity: 1; } 55%, 100% { opacity: .15; } }
@keyframes hud-flow     { 0% { background-position: 0 0; } 100% { background-position: 24px 0; } }
@keyframes hud-bracket  { 0%, 100% { opacity: .35; } 50% { opacity: 1; } }
@keyframes hud-breathe  { 0%, 100% { box-shadow: inset 2px 0 0 var(--accent); }
                          50%      { box-shadow: inset 2px 0 0 var(--accent-text), var(--glow, none); } }
@keyframes hud-fill     { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* ══ BISEAUX ═══════════════════════════════════════════════════════════════════
   Scopés par [data-da]. Un `clip-path`, même à 0px, crée un contexte de découpe qui
   rogne tout ce qui dépasse : appliquer ces classes hors du thème coupait les ombres
   débordantes de la charte DMPM (17 règles côté loxbackup). Elles ne mordent donc
   plus que sous ce thème.

   LA COUPE VAUT LE JETON SUR LES DEUX AXES — donc 45° partout, sur n'importe quel
   rapport de forme. Elle a été plafonnée par `min(jeton, 12%)` jusqu'au 2026-08-26 :
   ce filet de sécurité se résolvait PAR AXE, le pourcentage portant sur la largeur en
   x et sur la hauteur en y, et il aplatissait donc la diagonale dès que l'élément
   n'était pas carré. Mesuré dans l'application, sur 334 éléments biseautés rendus :
   55 étaient à 45°, 279 s'en écartaient, l'écart médian valait 25,2° et le plus plat
   tombait à 4,6°. Les panneaux (--bevel-lg, format médian 768×158) coupaient à 45°,
   les champs et boutons (--bevel, format médian 186×35) à 19,2° : deux angles sur le
   MÊME écran, ce qui se lit comme un défaut de rendu et non comme un parti pris.

   CE QUE LE PLAFOND PROTÉGEAIT VRAIMENT — la question a été posée dans l'autre sens,
   et c'est la bonne : sans lui, y a-t-il des éléments où la coupe retirerait plus de
   12 % d'un côté ? Oui, 279 sur 334. Mais 275 d'entre eux sont des champs et des
   boutons, que le tableau des rôles nomme explicitement sous --bevel : leur hauteur
   est simplement petite devant le jeton, ce n'est pas une violation. Les 4 autres en
   étaient une — une jauge de 8 px de haut, un point d'état de 8×8, deux pastilles
   rondes — et le plafond les rendait présentables au lieu de les signaler. Ils ont été
   retirés de la pose. Le plafond ne protégeait donc pas la géométrie : il masquait
   quatre erreurs de pose, et tordait les 330 autres éléments pour le faire.

   CE QUI LE REMPLACE EST UNE RÈGLE VÉRIFIABLE, pas un correctif silencieux : rien
   sous 24 px de haut n'est biseauté, et les familles exclues par le tableau des rôles
   — pastilles, badges, icônes, points d'état — ne le sont pas non plus. Côté Central,
   `scripts/themes.test.mjs` fait échouer la suite si une classe de biseau apparaît sur
   l'une d'elles.

   ⚠ CONSÉQUENCE À REGARDER, PAS À DÉDUIRE : la coupe d'un champ passe de 4,8 px à
   12 px de haut, soit 41 % d'un champ de 29 px de haut. La géométrie est juste ; la
   VALEUR du jeton sur les petits éléments est une question de charte, et elle revient
   à Design. */

[data-da="dmpm-hud"] .da-bevel-sm { clip-path: polygon(0 0, calc(100% - var(--bevel-sm, 0px)) 0, 100% var(--bevel-sm, 0px), 100% 100%, var(--bevel-sm, 0px) 100%, 0 calc(100% - var(--bevel-sm, 0px))); }
[data-da="dmpm-hud"] .da-bevel    { clip-path: polygon(0 0, calc(100% - var(--bevel, 0px)) 0, 100% var(--bevel, 0px), 100% 100%, var(--bevel, 0px) 100%, 0 calc(100% - var(--bevel, 0px))); }
[data-da="dmpm-hud"] .da-bevel-lg { clip-path: polygon(0 0, calc(100% - var(--bevel-lg, 0px)) 0, 100% var(--bevel-lg, 0px), 100% 100%, var(--bevel-lg, 0px) 100%, 0 calc(100% - var(--bevel-lg, 0px))); }
[data-da="dmpm-hud"] .da-bevel-xl { clip-path: polygon(0 0, calc(100% - var(--bevel-xl, 0px)) 0, 100% var(--bevel-xl, 0px), 100% 100%, var(--bevel-xl, 0px) 100%, 0 calc(100% - var(--bevel-xl, 0px))); }

/* ── Biseau, filet et ombre : la variante à face ───────────────────────────────
   `filter` s'applique AVANT `clip-path` sur un même élément : une ombre portée y est
   dessinée puis rognée par la découpe. Les deux ne peuvent pas cohabiter sur le même
   élément. Le conteneur ne porte donc AUCUNE découpe — sa silhouette est faite de deux
   pseudo-éléments biseautés, et l'ombre est portée par celui du dessous.

   Conséquence recherchée, et non heureux hasard : le conteneur n'ayant pas de
   contexte de découpe, contour de focus, infobulle et menu déroulant débordent
   normalement. Toute évolution de ces règles doit préserver cette propriété.

   ⚠ AUCUN `filter` SUR L'ÉLÉMENT QUI PORTE DU CONTENU — c'est la règle qui a changé
   le 2026-08-26, et elle a été payée. Le filet était fait de quatre `drop-shadow()`
   décalés de 1,5 px, posés sur le conteneur. Sur un élément qui porte une face opaque,
   ces quatre passes détourent la face : c'est le dessin voulu. Sur une commande de
   formulaire, qui ne génère pas toujours ses pseudo-éléments, il n'y a AUCUNE face à
   détourer — les quatre passes s'appliquent alors à ce qui reste de visible,
   c'est-à-dire AU TEXTE, dont elles tracent quatre copies décalées. Mesuré en image
   sur un champ de 200×34 en mode clair : la surface encrée passait de 8 450 à 25 977
   pixels, soit trois fois plus, et 27,3 % de la boîte changeait quand on retirait le
   filtre. Ce n'était pas un texte adouci, c'était un texte quadruplé.
   L'hypothèse d'origine — un filtre force le sous-arbre dans une couche composée et le
   texte y perd l'anticrénelage sous-pixel — n'a PAS été confirmée : le témoin non
   biseauté ne portait lui non plus aucune frange sous-pixel, donc ce rendu n'emploie
   pas cet anticrénelage du tout et l'instrument y était aveugle. On ne conclut pas
   d'une absence de signal : la cause retenue est celle qu'on a vue, le halo.

   DEUX COUCHES DÉCOUPÉES, ET AUCUN FILTRE SUR LE CONTENU :
     · `::after`  porte le FILET — le polygone EXTÉRIEUR, peint en --bevel-edge, plus
       l'ombre portée. Un `filter` y est sans danger : ce pseudo-élément ne contient
       aucun texte, et l'ombre épouse enfin exactement la silhouette ;
     · `::before` porte la FACE — le MÊME polygone rentré de 1,5 px, peint en
       --face-bg. Ce qui dépasse du premier fait le trait.
   Le polygone rentré n'est pas le polygone extérieur mis à l'échelle : les quatre
   arêtes droites rentrent de 1,5 px, et la diagonale rentre de 1,5 px SELON SA
   NORMALE, ce qui déplace son intersection avec les bords de 1,5·(2 − √2) = 0,879 px.
   Mesurée depuis la boîte extérieure, la coupe du polygone rentré tombe donc à
   1,5 px + jeton − 0,879 px, d'où le +0,621 px qu'on lit dans les règles ci-dessous.
   Un simple `inset()` ou un `scale()` donnerait un trait plus épais dans l'angle.

   POURQUOI ::after POUR LE FILET ET ::before POUR LA FACE, et pas l'inverse. Un
   élément n'a que deux pseudo-éléments, et `.da-bracket::after` en occupe un là où
   elle est posée. En cas de collision, l'élément perd le pseudo-élément le PLUS
   TARDIF : mieux vaut y perdre le filet — un trait de 1,5 px — que la face, c'est-à-dire
   le fond entier. L'arbitrage n'est pas laissé à la spécificité : les règles du filet,
   NON-PROPRIÉTAIRE de ::after, sont écrites dans :where() — spécificité (0,0,1) — donc
   .da-bracket::after gagne quel que soit le nombre de classes empilées en face (voir
   sa règle en bas de fichier, et README.md §Qui possède quel pseudo-élément).
   Central ne pose `.da-bracket` nulle part (vérifié le 2026-08-26).

   --face-bg N'EST PAS FACULTATIF sur tout élément dont le fond porte une information.
   Le repli --surface est juste pour une carte ou un panneau, destructeur sur un badge
   d'état : un badge « en erreur » repeint en --surface perd le lavis qui EST
   l'information. Voir le tableau des rôles dans README.md §Biseaux.

   ⚠ CES RÈGLES NE CONVIENNENT NI AUX COMMANDES DE FORMULAIRE NI AUX BOUTONS. Un
   `<input>` ne génère NI ::before NI ::after — mesuré au pixel le 2026-08-26 : zéro
   pixel de face, alors
   que getComputedStyle répondait content: "" et un backgroundColor résolu, exactement
   comme pour un `<div>`. Un `<select>` et un `<textarea>` les génèrent mais leur rendu
   natif passe devant (2 % et 0 % de la boîte peints). Or `.da-bevel-face` retire le
   fond (`background: none`) et neutralise la bordure : un champ qui ne peint pas sa
   face n'a donc PLUS NI FOND NI CONTOUR. Sur les écrans mesurés, 132 des 154 éléments
   biseautés rendus étaient dans ce cas. Une commande de formulaire se biseaute avec la
   classe NUE (.da-bevel*), qui découpe l'élément lui-même : elle garde son propre fond
   et sa propre bordure, qui suivent la coupe, et rien ne filtre son texte.

   UN `<button>` EST LE CAS INVERSE, ET AUSSI GRAVE. Il génère bien ses deux
   pseudo-éléments, donc il reçoit `background: none` et `border-color: transparent`,
   et sa face est repeinte avec le repli --surface. Son libellé, lui, est sauvé par les
   z-index négatifs — le défaut est donc invisible à la lecture du DOM et se voit
   seulement au rendu. Or LE FOND D'UN BOUTON PRIMAIRE EST SON AFFORDANCE, et aucun
   --face-bg n'est déclaré pour les boutons. Mesuré le 2026-08-27 : « Annuler », témoin
   non biseauté, fond #252A31 sur libellé #B6BCC3 → 7,54:1 ; « Enregistrer », biseauté,
   fond #252A31 sur libellé #091611 → 1,28:1. Le bouton d'action principal d'un
   formulaire devenait illisible. Un bouton retombe donc, lui aussi, sur la découpe nue :
   il garde son propre fond, quel qu'il soit, et la coupe le suit.

   BORDER-COLOR: TRANSPARENT, et pas « retirer la bordure du composant ». La consigne
   d'origine était de retirer `border` des éléments passés en .da-bevel-face, puisque
   le filet la remplace. Elle ne tient que dans un dépôt qui ne sert QUE ce thème :
   .da-bevel-face est une CLASSE, mais c'est la RÈGLE qui est scopée, et Central rend
   les mêmes composants sous trois chartes. Bordure gardée, elle reste rectangulaire et
   dépasse la coupe sous le HUD ; bordure retirée, la carte n'a plus AUCUN contour sous
   dmpm et sous guet. Mesuré en image le 2026-08-25. On la neutralise donc ICI, dans le
   seul endroit qui connaît le thème : les composants gardent leur `border`.

   LES DEUX COUCHES PASSENT DERRIÈRE LE CONTENU — deux propriétés, deux raisons.
   `.da-bevel-face > *` remonte les enfants ÉLÉMENTS au-dessus. Mais un NŒUD DE TEXTE
   n'est pas un élément : `> *` ne peut pas l'atteindre, et la face le recouvrait.
   Mesuré le 2026-08-26 à la première pose réelle : cinq boutons d'action avaient perdu
   leur libellé — « Ajouter la clé », « + Nouveau ticket »… Un bouton sans texte, ce
   n'est pas un défaut d'apparence.
     · z-index négatif : les deux couches passent derrière TOUT le contenu, nœuds de
       texte compris. -2 pour le filet, -1 pour la face, qui doit le recouvrir ;
     · `isolation: isolate` crée le contexte d'empilement qui empêche ces négatifs de
       filer derrière le fond d'un ancêtre — sans lui, les couches disparaîtraient sous
       la première surface opaque au-dessus d'elles.
   `> *` devient redondant et reste : le retirer serait un changement de plus, il ne
   coûte rien, et il documente l'état antérieur. À nettoyer dans un lot de forme.

   UN --face-bg TRANSLUCIDE EST COMPOSITÉ, PAS APLATI. Les jetons d'état de la charte
   — --st-ok-bg, --st-warn-bg, --st-err-bg, --st-off-bg — sont des rgba() translucides,
   par construction : sur un badge non biseauté ils se lisent sur le fond du parent.
   Peints seuls sur la couche du filet, qui est pleine, ils donnaient une couleur fausse
   — mesuré le 2026-08-27 : 3,11:1 en clair, le fond passant de #EAEFED à #B7BCBA, et
   4,47:1 en sombre. Les --face-bg: var(--st-*-bg) que la spécification range dans son
   tableau ne rendaient donc PAS un badge biseautable : la garde « fond signifiant ⇒
   --face-bg obligatoire » passait, et le résultat était faux. Une garde qui valide du
   faux est pire que pas de garde.
   Les deux couches portent donc une BASE OPAQUE en background-color, et la couleur
   demandée par-dessus en background-image : le lavis se composite sur la base, comme il
   le ferait sur le fond d'un parent. Exiger un --face-bg opaque était l'autre sortie ;
   elle aurait rendu inutilisables les quatre jetons d'état que le tableau désigne.
   --face-under nomme cette base et vaut --surface par défaut, l'hypothèse que fait déjà
   tout composant posé sur une carte. À surcharger sur un élément posé ailleurs — un
   badge dans un panneau prend --face-under: var(--surface-2). Aucun jeton nouveau. */
[data-da="dmpm-hud"] .da-bevel-face:not(:is(input, select, textarea, button, [data-da-own-bg])) {
                                                 position: relative; background: none;
                                                 border-color: transparent;
                                                 isolation: isolate; }
[data-da="dmpm-hud"] .da-bevel-face > *        { position: relative; }
[data-da="dmpm-hud"] .da-bevel-face::before    { content: ""; position: absolute; inset: 0;
                                                 pointer-events: none;
                                                 background-color: var(--face-under, var(--surface));
                                                 z-index: -1;
                                                 background-image: linear-gradient(var(--face-bg, transparent), var(--face-bg, transparent)); }
/* Le FILET est non-propriétaire de ::after : tout son sélecteur passe en :where(),
   spécificité (0,0,1), pour que le propriétaire déclaré gagne sans compter ses classes. */
:where([data-da="dmpm-hud"] .da-bevel-face)::after
                                               { content: ""; position: absolute; inset: 0;
                                                 pointer-events: none;
                                                 background-color: var(--face-under, var(--surface));
                                                 z-index: -2;
                                                 background-image: linear-gradient(var(--bevel-edge, var(--border-strong)), var(--bevel-edge, var(--border-strong)));
                                                 filter: drop-shadow(0 1px 2px rgb(0 0 0 / .18)); }
[data-da="dmpm-hud"] .da-bevel-face.da-bevel-sm,
[data-da="dmpm-hud"] .da-bevel-face.da-bevel,
[data-da="dmpm-hud"] .da-bevel-face.da-bevel-lg,
[data-da="dmpm-hud"] .da-bevel-face.da-bevel-xl { clip-path: none; }
:where([data-da="dmpm-hud"] .da-bevel-face.da-bevel-sm)::after  { clip-path: polygon(0 0, calc(100% - var(--bevel-sm, 0px)) 0, 100% var(--bevel-sm, 0px), 100% 100%, var(--bevel-sm, 0px) 100%, 0 calc(100% - var(--bevel-sm, 0px))); }
:where([data-da="dmpm-hud"] .da-bevel-face.da-bevel)::after  { clip-path: polygon(0 0, calc(100% - var(--bevel, 0px)) 0, 100% var(--bevel, 0px), 100% 100%, var(--bevel, 0px) 100%, 0 calc(100% - var(--bevel, 0px))); }
:where([data-da="dmpm-hud"] .da-bevel-face.da-bevel-lg)::after  { clip-path: polygon(0 0, calc(100% - var(--bevel-lg, 0px)) 0, 100% var(--bevel-lg, 0px), 100% 100%, var(--bevel-lg, 0px) 100%, 0 calc(100% - var(--bevel-lg, 0px))); }
:where([data-da="dmpm-hud"] .da-bevel-face.da-bevel-xl)::after  { clip-path: polygon(0 0, calc(100% - var(--bevel-xl, 0px)) 0, 100% var(--bevel-xl, 0px), 100% 100%, var(--bevel-xl, 0px) 100%, 0 calc(100% - var(--bevel-xl, 0px))); }
[data-da="dmpm-hud"] .da-bevel-face.da-bevel-sm::before { clip-path: polygon(1.5px 1.5px, calc(100% - var(--bevel-sm, 0px) - 0.621px) 1.5px, calc(100% - 1.5px) calc(var(--bevel-sm, 0px) + 0.621px), calc(100% - 1.5px) calc(100% - 1.5px), calc(var(--bevel-sm, 0px) + 0.621px) calc(100% - 1.5px), 1.5px calc(100% - var(--bevel-sm, 0px) - 0.621px)); }
[data-da="dmpm-hud"] .da-bevel-face.da-bevel::before { clip-path: polygon(1.5px 1.5px, calc(100% - var(--bevel, 0px) - 0.621px) 1.5px, calc(100% - 1.5px) calc(var(--bevel, 0px) + 0.621px), calc(100% - 1.5px) calc(100% - 1.5px), calc(var(--bevel, 0px) + 0.621px) calc(100% - 1.5px), 1.5px calc(100% - var(--bevel, 0px) - 0.621px)); }
[data-da="dmpm-hud"] .da-bevel-face.da-bevel-lg::before { clip-path: polygon(1.5px 1.5px, calc(100% - var(--bevel-lg, 0px) - 0.621px) 1.5px, calc(100% - 1.5px) calc(var(--bevel-lg, 0px) + 0.621px), calc(100% - 1.5px) calc(100% - 1.5px), calc(var(--bevel-lg, 0px) + 0.621px) calc(100% - 1.5px), 1.5px calc(100% - var(--bevel-lg, 0px) - 0.621px)); }
[data-da="dmpm-hud"] .da-bevel-face.da-bevel-xl::before { clip-path: polygon(1.5px 1.5px, calc(100% - var(--bevel-xl, 0px) - 0.621px) 1.5px, calc(100% - 1.5px) calc(var(--bevel-xl, 0px) + 0.621px), calc(100% - 1.5px) calc(100% - 1.5px), calc(var(--bevel-xl, 0px) + 0.621px) calc(100% - 1.5px), 1.5px calc(100% - var(--bevel-xl, 0px) - 0.621px)); }


/* ── COMMANDES DE FORMULAIRE ET BOUTONS RETOMBENT SUR LA DÉCOUPE NUE ──────────
   LA RÈGLE VIT ICI, PAS DANS LA POSE. Corriger la pose ne suffit pas : la chaîne de
   classes d'un champ est souvent écrite dans une CONSTANTE partagée, loin du `<select>`
   qui la porte, et aucune garde statique ne peut y voir la balise. Éprouvé le
   2026-08-26 : après une repose qui visait la balise dans le JSX, il restait des champs
   en `.da-bevel-face` — le motif ne pouvait pas les atteindre. On rend donc la FEUILLE
   sûre, ce qui protège les deux dépôts d'un coup, y compris pour les poses futures.

   Ce que ça évite, mesuré : `.da-bevel-face` retire le fond et neutralise la bordure,
   et confie tout le dessin à un ::before qu'un `<input>` NE GÉNÈRE PAS. Le champ n'a
   alors plus ni fond ni contour — deux des trois champs d'un formulaire réel étaient
   littéralement invisibles — et le filtre du filet, privé de silhouette, trace quatre
   copies de son TEXTE.

   LE `<button>` EST DANS LA MÊME LISTE, pour la raison SYMÉTRIQUE : il génère bien ses
   pseudo-éléments, donc la face lui repeignait son fond en --surface et effaçait
   l'affordance d'un bouton primaire — « Enregistrer » mesuré à 1,28:1 le 2026-08-27,
   contre 7,54:1 pour son témoin non biseauté. Un bouton n'a pas besoin d'une face : il
   A un fond, et il n'a jamais eu de --face-bg déclaré. On lui rend donc sa découpe nue.

   [data-da-own-bg] EST LÀ PARCE QU'UNE LISTE DE BALISES NE DÉCRIT PAS UN RÔLE. Elle
   couvre `<button>` et pas `<a class="btn btn-primary">`, qui est le même bouton pour
   celui qui clique — mesuré le 2026-08-27 sur « Ajouter un Miniserver » : 6,19 → 1,27:1
   en sombre, 4,64 → 1,61:1 en clair, libellé blanc sur blanc ; vingt-cinq `<a>` de bouton
   dans ce seul dépôt. Allonger la liste ne ferme rien : tout `<a>` n'est pas un bouton,
   et un `<label>` stylé en bouton passerait aussi. La qualité qui compte est « cet
   élément peint déjà un fond qui porte une information », et seul le dépôt qui l'emploie
   la connaît. La liste de balises reste donc comme PLANCHER — elle couvre le natif sans
   que personne n'ait à y penser — et [data-da-own-bg] est le marqueur que chaque dépôt
   pose sur ses propres éléments :

       <a class="btn btn-primary" data-da-own-bg>Ajouter un Miniserver</a>

   Attribut et non classe, pour trois raisons : il ne peut pas être balayé par un
   `class="…"` réécrit ailleurs, il se lit dans le gabarit comme une déclaration d'intention
   plutôt que comme un style, et il n'entre dans aucune chaîne de classes partagée où il
   se propagerait à un élément qui ne le voulait pas. Sans valeur : il n'a qu'un état.

   Ici : la règle de base ne les touche plus (elle porte un :not), leurs pseudo-éléments
   ne sont pas générés, et la découpe revient sur l'élément lui-même — il garde son fond
   et sa bordure, qui suivent la coupe. C'est exactement le comportement de la classe
   nue, et c'est celui qu'il fallait. */
[data-da="dmpm-hud"] :is(input, select, textarea, button, [data-da-own-bg]).da-bevel-face::before,
[data-da="dmpm-hud"] :is(input, select, textarea, button, [data-da-own-bg]).da-bevel-face::after { content: none; }
[data-da="dmpm-hud"] :is(input, select, textarea, button, [data-da-own-bg]).da-bevel-face.da-bevel-sm { clip-path: polygon(0 0, calc(100% - var(--bevel-sm, 0px)) 0, 100% var(--bevel-sm, 0px), 100% 100%, var(--bevel-sm, 0px) 100%, 0 calc(100% - var(--bevel-sm, 0px))); }
[data-da="dmpm-hud"] :is(input, select, textarea, button, [data-da-own-bg]).da-bevel-face.da-bevel    { clip-path: polygon(0 0, calc(100% - var(--bevel, 0px)) 0, 100% var(--bevel, 0px), 100% 100%, var(--bevel, 0px) 100%, 0 calc(100% - var(--bevel, 0px))); }
[data-da="dmpm-hud"] :is(input, select, textarea, button, [data-da-own-bg]).da-bevel-face.da-bevel-lg { clip-path: polygon(0 0, calc(100% - var(--bevel-lg, 0px)) 0, 100% var(--bevel-lg, 0px), 100% 100%, var(--bevel-lg, 0px) 100%, 0 calc(100% - var(--bevel-lg, 0px))); }
[data-da="dmpm-hud"] :is(input, select, textarea, button, [data-da-own-bg]).da-bevel-face.da-bevel-xl { clip-path: polygon(0 0, calc(100% - var(--bevel-xl, 0px)) 0, 100% var(--bevel-xl, 0px), 100% 100%, var(--bevel-xl, 0px) 100%, 0 calc(100% - var(--bevel-xl, 0px))); }

/* ── L'anneau de focus ─────────────────────────────────────────────────────────
   Décidé le 2026-08-25, APRÈS mesure. `border-color: transparent` (plus haut) fait
   exactement ce qu'on lui demande, Y COMPRIS AU FOCUS : un champ dont la seule
   affordance était `focus:border-accent` n'en a plus AUCUNE. Mesuré sur le champ de
   Central, qui porte `outline-none` : au focus, bordure `rgba(0,0,0,0)` et
   `outline-style: none`. Rien. `outline` est le mécanisme prévu pour ça, et c'est
   `outline-none` qui a créé le défaut — on le rétablit ici.

   POURQUOI PAS `:not(:focus)` SUR LA BORDURE : ça ne protégerait que les composants
   dont l'affordance de focus EST la bordure — ni ceux qui la marquent autrement, ni
   ceux qui n'ont pas de bordure. Et une bordure rectangulaire sur une face biseautée
   se lit comme un défaut de rendu, là où un contour décalé se lit comme un anneau.

   ⚠ ELLE AJOUTE UN CONTOUR LÀ OÙ IL MANQUE, ELLE N'EN RETIRE AUCUN LÀ OÙ IL EXISTE.
   C'est le rôle de `:where()`, qui ramène la spécificité à ZÉRO : n'importe quelle
   règle d'auteur la bat, y compris `input:focus { outline: … }`. Elle l'emporte tout
   de même sur `outline-none` de Tailwind, parce que celui-ci vit dans
   `@layer utilities` et qu'une règle HORS COUCHE bat n'importe quelle couche, quelle
   que soit sa spécificité. Les deux dépôts n'ont donc pas le même comportement, et
   c'est exactement l'intention : chez loxone-backup, qui n'emploie pas Tailwind, une
   règle d'auteur existante garde la main.

   DEUX CAS, pas un : l'élément biseauté qui REÇOIT le focus, et celui qui CONTIENT un
   descendant focalisé — un groupe segmenté dont l'entrée est masquée, par exemple. Le
   conteneur n'ayant aucun contexte de découpe, le contour s'affiche entier, y compris
   dans l'angle coupé.

   Couleur : --accent-text, jeton existant, prévu pour marquer sur une surface. Aucune
   couleur nouvelle, empreinte du JSON inchangée. */
:where([data-da="dmpm-hud"] .da-bevel-face:focus-visible),
:where([data-da="dmpm-hud"] .da-bevel-face:has(:focus-visible)) {
  outline: 2px solid var(--accent-text);
  outline-offset: 2px;
}

/* ── Ce qui marque la coupe ────────────────────────────────────────────────────
   En mode clair, --surface et --bg ne sont séparés que par 1 % de luminance : sans
   filet, la diagonale est invisible et le biseau ne se lit pas. Le filet est donc
   OBLIGATOIRE en clair, facultatif en sombre (14 % d'écart y suffisent).

   IL N'EST PLUS PRODUIT PAR UN FILTRE — voir le motif « aucun filter sur l'élément qui
   porte du contenu » plus haut. Il est la COUCHE DU DESSOUS (::after), peinte sur le
   polygone extérieur, que la face (::before) recouvre en laissant 1,5 px tout autour.
   Deux fonds découpés, zéro passe de filtre sur le contenu.

   La contrainte que l'ancien motif satisfaisait par construction est conservée, et
   c'est elle qui interdisait un dégradé : le filet doit suivre la diagonale quel que
   soit le rapport de forme. Avec le plafond retiré, la coupe est à 45° partout, donc
   un dégradé serait devenu envisageable — mais il ne saurait toujours pas contourner
   les quatre côtés d'une seule pièce, et le polygone rentré, lui, le fait sans
   dépendre de l'angle.

   Le filet contourne toute la face, pas seulement la diagonale : il REMPLACE donc la
   bordure du composant, qui serait restée rectangulaire et aurait dépassé la coupe.
   Elle est neutralisée par `border-color: transparent` dans la règle .da-bevel-face
   ci-dessus, et NON en retirant `border` des composants — voir le motif du même nom
   plus haut : les composants sont rendus sous trois chartes.

   ⚠ EN CLAIR SEULEMENT — la couche existe dans les deux modes, mais sa COULEUR change.
   En sombre elle prend celle de la face : le trait devient invisible, et la silhouette
   reste là pour porter l'ombre. C'est --bevel-edge qui décide, et il reste surchargeable
   localement (contour d'un état, carte en erreur). Aucune couleur nouvelle des deux
   côtés : --border-strong et --face-bg sont des jetons existants, empreinte du JSON
   inchangée.

   Ce que ça change au COÛT, mesuré : l'ancien filet pesait 84 à 95 % du surcoût de
   .da-bevel-face — quatre passes de filtre permanentes, réinvalidées à chaque repeinte,
   pour un trait de 1,5 px. Il ne reste qu'une passe, sur un pseudo-élément sans texte,
   pour l'ombre portée.

   DEUX SÉLECTEURS, ET C'EST STRUCTUREL. Les deux dépôts ne posent pas data-theme et
   data-da au même endroit : Central met data-theme sur <html> et data-da sur la coque
   du back-office (donc un DESCENDANT), loxone-backup met les deux sur <html> (donc le
   MÊME élément). Un seul des deux sélecteurs matche dans chaque dépôt, et ce fichier
   doit rester identique octet pour octet des deux côtés. */
[data-da="dmpm-hud"] .da-bevel-face { --bevel-edge: var(--face-bg, var(--surface)); }

:root[data-theme="light"] [data-da="dmpm-hud"] .da-bevel-face,
:root[data-theme="light"][data-da="dmpm-hud"] .da-bevel-face {
  --bevel-edge: var(--border-strong);
}

/* Les classes .da-bevel-* NUES découpent l'élément lui-même : il garde son fond et sa
   bordure, qui suivent la coupe, et rien ne le filtre. C'est ce qu'il faut pour une
   COMMANDE DE FORMULAIRE — voir plus haut : un `<input>` ne génère pas de
   pseudo-élément, donc .da-bevel-face l'y priverait de fond ET de contour. C'est aussi
   ce qu'il faut partout où l'élément doit garder son propre fond.
   En revanche elles n'ont pas de filet, et elles créent un contexte de découpe qui
   rogne ombre, infobulle et contour de focus débordants. Pour une carte ou un panneau,
   employer .da-bevel-face. */

/* ══ MOUVEMENTS ════════════════════════════════════════════════════════════════ */
[data-da="dmpm-hud"] .da-scan          { position: relative; overflow: hidden; }
[data-da="dmpm-hud"] .da-scan::after   { content: ""; position: absolute; inset: 0 auto 0 0; width: 9%;
                                         background: linear-gradient(90deg, transparent, var(--accent), transparent);
                                         animation: hud-scan 6s linear infinite; }
[data-da="dmpm-hud"] .da-gauge > span  { transform-origin: left center; animation: hud-fill 1.2s cubic-bezier(.2,.8,.2,1) both; box-shadow: var(--glow, none); }
[data-da="dmpm-hud"] .da-live-dot      { animation: hud-blink 1.4s steps(1, end) infinite; }
[data-da="dmpm-hud"] .da-ping::before  { content: ""; position: absolute; inset: 0; border: 1px solid currentColor; border-radius: 50%; animation: hud-ring 2.6s ease-out infinite; }

/* L'équerre tient sur UN SEUL pseudo-élément : un élément n'en a que deux, et ::before
   est réservé à la face biseautée. Deux dégradés à arête franche dessinent les deux
   branches — un élément peut donc porter l'équerre ET le biseau.

   L'ÉQUERRE EST PROPRIÉTAIRE DE ::after, ET C'EST LE :where() DU FILET QUI LE GARANTIT.
   Quand .da-bracket et .da-bevel-face sont sur le même élément, elles visent le MÊME
   ::after et leurs règles se CASCADENT propriété par propriété. Deux utilitaires ne
   peuvent pas s'arbitrer par une course à la spécificité : le résultat dépendrait du
   nombre de classes empilées en face. Les règles du filet sont donc écrites dans
   :where(), spécificité (0,0,1), et l'équerre l'emporte par CONSTRUCTION.

   Historique de ce même élément (.ms-card da-bracket da-bevel-face da-bevel-lg,
   templates/supervision.html:168), pour qu'aucune de ces étapes ne se refasse :
     — z-index hérité du filet (-2) : l'équerre passait sous la face, 32 % visibles ;
     — z-index: 1 seul : l'équerre au premier plan, mais le clip-path du filet, calculé
       pour la carte à 18 px, restait appliqué à une boîte de 14×14 — polygone qui se
       recoupe, 28 % peints en deux fragments ;
     — + clip-path: none à spécificité (0,2,1) : SANS EFFET, le polygone venait d'une
       règle d'échelle à (0,3,1). Toujours 28 % en deux fragments. Ajouter la classe
       d'échelle à l'équerre n'aurait réparé que da-bevel-lg, et la combinaison suivante
       aurait cassé de nouveau ;
     — état actuel : filet en :where(), l'équerre retrouve sa boîte, 91 % peints.
   Ce qui se perd reste le FILET de cet élément — un trait de 1,5 px, et non la face,
   c'est-à-dire le fond entier. Face, ombre et fond sont intacts. */
[data-da="dmpm-hud"] .da-bracket        { position: relative; }
[data-da="dmpm-hud"] .da-bracket::after { content: ""; position: absolute; left: 0; top: 0; width: 14px; height: 14px;
                                          z-index: 1; clip-path: none; filter: none;
                                          pointer-events: none;
                                          background-image:
                                            linear-gradient(currentColor 0 0),
                                            linear-gradient(currentColor 0 0);
                                          background-size: 14px 2px, 2px 14px;
                                          background-position: 0 0, 0 0;
                                          background-repeat: no-repeat;
                                          animation: hud-bracket 3s ease-in-out infinite; }

[data-da="dmpm-hud"] .da-flow          { height: 2px; background-image: linear-gradient(90deg, var(--accent) 0 10px, transparent 10px 24px);
                                         background-size: 24px 2px; animation: hud-flow .9s linear infinite; opacity: .75; }
[data-da="dmpm-hud"] .nav-link.active,
[data-da="dmpm-hud"] [data-nav-active]  { animation: hud-breathe 3.4s ease-in-out infinite; }

/* La CARTE ne bouge pas : décision du 2026-08-21. Une carte qui balaie en continu
   attire l'œil en permanence sur la zone où il ne se passe rien. Les marqueurs
   gardent leur lueur d'état, pas d'anneau, pas de radar. */
[data-da="dmpm-hud"] .leaflet-container .da-ping::before { content: none; }

@media (prefers-reduced-motion: reduce) {
  [data-da="dmpm-hud"] .da-scan::after,
  [data-da="dmpm-hud"] .da-gauge > span,
  [data-da="dmpm-hud"] .da-live-dot,
  [data-da="dmpm-hud"] .da-ping::before,
  [data-da="dmpm-hud"] .da-bracket::after,
  [data-da="dmpm-hud"] .da-flow,
  [data-da="dmpm-hud"] .nav-link.active,
  [data-da="dmpm-hud"] [data-nav-active] { animation: none; }
  [data-da="dmpm-hud"] .da-scan::after { display: none; }
}
