/* ============================================================
   HRmaps Design System — COMPONENT RECIPES
   ------------------------------------------------------------
   Additive. Loaded LAST (after tokens + bridge). Holds the DS
   component library (buttons, fields, badges, chips, cards)
   ported from the reference specimen (audit/ui-kit.html).

   SCOPED under `.ds` on purpose: the app already ships its own
   global .btn (1965×), .badge, .card, .modal, .nav, .table…
   Redefining those globally would restyle the whole app at once
   (the "big bang" CLAUDE.md forbids). Instead, a screen opts in
   by adding class `ds` on its root; inside it, elements migrated
   to DS classes (.btn .btn-primary, .input, .badge.b-success…)
   get the DS recipe. Everything outside stays untouched — the
   bridge keeps re-theming legacy classes globally.

   Dark mode is automatic: every value is a token, so the app's
   [data-theme=dark] flips them with no extra rules here.
   Migration recipe per screen:
     1) add `ds` on the screen/modal root element,
     2) swap legacy classes to DS classes (see MIGRATION.md §E),
     3) verify light + dark, then remove dead CSS (Phase 6).
   ============================================================ */

/* ---- BOUTONS `ds-btn` : LE VOCABULAIRE SANS COLLISION ---------------------
   POURQUOI UN SECOND NOM. La recette `.btn` plus bas reprend les noms de
   Bootstrap. Or `sitecss.css` porte 130 regles sur `.btn` (60 en !important),
   24 sur `.btn-primary`, 12 sur `.btn-secondary` (10 en !important), 10 sur
   `.btn-icon` : chaque bouton « DS » recoit AUSSI tout l ancien style, et la
   recette passe son temps a le combattre — une vingtaine d !important, plus des
   rustines de coque (`.shell .btn-secondary`, `.btn-icon { min-width }`).

   `ds-btn` n est vise par AUCUNE regle legacy ni du pont. La recette ci-dessous
   n a donc rien a combattre : pas un !important, sauf la couleur d un `<a>`
   (voir en fin de bloc). Les ecrans migrent un par un de `.btn` vers `ds-btn` ;
   `.btn` reste intact en attendant. Premier migre : la topbar.

     ds-btn                 la base (obligatoire)
     ds-btn-primary         action principale — une seule par zone
     ds-btn-secondary       contour neutre
     ds-btn-ghost           glyphe ou libelle sans cadre
     ds-btn-icon            carre, glyphe seul
     ds-btn-sm              zone dense
     ds-btn-count           compteur pose sur le coin du bouton
     ds-btn-caret           marqueur « ce bouton ouvre un menu »
   L etat ouvert d un bouton-menu se lit sur `aria-expanded="true"`. */
:is(.ds .ds-btn, .ds.ds-btn) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--gap-inline);
  box-sizing: border-box;
  min-height: var(--control-h);
  margin: 0;
  padding: 9px 15px;
  border: 1px solid transparent;
  border-radius: var(--radius-button);
  background: transparent;
  color: var(--text-secondary);
  font: var(--font-button);
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-standard);
}
:is(.ds .ds-btn, .ds.ds-btn):hover,
:is(.ds .ds-btn, .ds.ds-btn):focus { text-decoration: none; }
/* Le seul bouton qui porte une ombre : celui qu on atteint au clavier. */
:is(.ds .ds-btn, .ds.ds-btn):focus-visible { outline: none; box-shadow: var(--ring); }

/* Une icone ne grandit ni ne pousse : un bouton « texte » et « texte + icone »
   gardent la meme hauteur. `<i>` et `<svg>` : FontAwesome remplace l un par l
   autre. */
:is(.ds .ds-btn, .ds.ds-btn) > i,
:is(.ds .ds-btn, .ds.ds-btn) > svg {
  flex: none;
  width: var(--icon-md, 16px);
  height: var(--icon-md, 16px);
  margin: 0;
}

.ds .ds-btn-primary { background: var(--primary); color: var(--on-primary); }
.ds .ds-btn-primary:hover,
.ds .ds-btn-primary:focus,
.ds .ds-btn-primary[aria-expanded="true"] { background: var(--primary-hover); color: var(--on-primary); }

.ds .ds-btn-secondary { background: var(--surface); border-color: var(--field-border); color: var(--text-secondary); }
.ds .ds-btn-secondary:hover,
.ds .ds-btn-secondary[aria-expanded="true"] { background: var(--surface-hover); color: var(--text-primary); }

.ds .ds-btn-ghost:hover,
.ds .ds-btn-ghost[aria-expanded="true"] { background: var(--surface-hover); color: var(--text-primary); }

/* EXPORT : jamais un appel a l action (regle DS), donc un contour vert sur la
   surface. 60 % de vert dans le filet : a 36 % il disparaissait sur la carte
   sombre. */
.ds .ds-btn-excel {
  background: var(--surface);
  border-color: color-mix(in srgb, var(--excel) 60%, var(--surface));
  color: var(--excel);
}
.ds .ds-btn-excel:hover { background: var(--excel-soft); color: var(--excel); }

/* DESTRUCTIF : plein, sur le jeton de danger. */
.ds .ds-btn-danger { background: var(--danger); color: var(--gray-0); }
.ds .ds-btn-danger:hover { background: var(--danger-fg); color: var(--gray-0); }

/* REFUS : une DECISION, pas une destruction — le contour et le libelle en rouge sur
   la surface, comme l export l est en vert. Plein, il rivaliserait avec l action
   attendue (valider) posee juste a cote. */
.ds .ds-btn-reject {
  background: var(--surface);
  border-color: color-mix(in srgb, var(--danger) 45%, var(--surface));
  color: var(--danger-fg);
}
.ds .ds-btn-reject:hover { background: var(--danger-soft); color: var(--danger-fg); }

/* DISCRET : une action parmi beaucoup (ligne de tableau, pied de tuile, detail).
   Glyphe en `--text-muted`, aucune surface : le survol seul la revele et passe le
   glyphe au primaire — il designe ce qu on vise, pas ce qui existe. Vingt lignes
   portant chacune trois actions colorees n auraient plus d action principale.
   `is-important` : l action qui compte le plus se lit d abord, par le contraste
   de son libelle, sans crier. */
.ds .ds-btn-quiet { background: transparent; color: var(--text-muted); }
.ds .ds-btn-quiet:hover,
.ds .ds-btn-quiet[aria-expanded="true"] { background: var(--surface-hover); color: var(--primary); }
.ds .ds-btn-quiet.is-important { color: var(--text-primary); }

/* TRES COMPACT : 28px, le pas de la reference pour une rangee d icones — une
   boite de 28px autour d un glyphe de 16px laisse 12px d air entre deux
   actions, sans marge. */
.ds .ds-btn-xs { min-height: 28px; padding: var(--space-1) var(--space-2); gap: 6px; font-size: var(--text-sm); }
.ds .ds-btn-xs.ds-btn-icon { width: 28px; height: 28px; padding: 0; }

/* Enveloppe de glyphe : un <i> qui porte une directive (ng-show) doit etre
   enveloppe, FontAwesome le remplacant par un <svg>. L enveloppe prend la place
   et la taille du glyphe. */
.ds .ds-btn-glyph {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--icon-md, 16px);
  height: var(--icon-md, 16px);
}
.ds .ds-btn-glyph > i,
.ds .ds-btn-glyph > svg { width: 100%; height: 100%; }
/* Un libelle long se coupe plutot que d agrandir ce qui le contient. */
.ds .ds-btn-label {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
:is(.ds .ds-btn, .ds.ds-btn) { max-width: 100%; }

/* CHOIX DE FICHIER : le bouton est un <label> qui contient le champ ; le clic
   ouvre le selecteur natif. Le champ reste dans le document (un `display: none`
   le rendrait inactivable dans certains navigateurs), simplement invisible. */
.ds .ds-btn-file { position: relative; }
.ds .ds-btn-file > input[type="file"] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  overflow: hidden;
  pointer-events: none;
}

/* Desactive sans attribut `disabled` (un <label>) : meme apparence, et il ne
   repond plus. */
:is(.ds .ds-btn, .ds.ds-btn).is-disabled {
  background: var(--surface-sunken);
  border-color: var(--border);
  color: var(--text-disabled);
  cursor: not-allowed;
  pointer-events: none;
}

/* Carre a la hauteur commune : une rangee de boutons icone s aligne sur les
   boutons a libelle poses a cote. */
.ds .ds-btn-icon { width: var(--control-h); height: var(--control-h); padding: 0; }
.ds .ds-btn-sm { min-height: var(--control-h-sm); padding: 5px 11px; gap: 6px; font-size: var(--text-sm); }
.ds .ds-btn-sm.ds-btn-icon { width: var(--control-h-sm); height: var(--control-h-sm); padding: 0; }

/* Desactive : couleurs explicites, pas un voile d opacite (reference §01). */
:is(.ds .ds-btn, .ds.ds-btn)[disabled],
:is(.ds .ds-btn, .ds.ds-btn)[disabled]:hover {
  background: var(--surface-sunken);
  border-color: var(--border);
  color: var(--text-disabled);
  cursor: not-allowed;
}

/* COMPTEUR : le nombre d elements qui attendent derriere le bouton (« Mes
   actions »). Pastille pleine sur le coin haut-droit ; le filet a la couleur de
   la surface la detache du bord du bouton. */
:is(.ds .ds-btn, .ds.ds-btn):has(> .ds-btn-count) { position: relative; }
.ds .ds-btn-count {
  position: absolute;
  top: -6px;
  right: -6px;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border: 2px solid var(--surface);
  border-radius: var(--radius-pill);
  background: var(--primary);
  color: var(--on-primary);
  font: var(--weight-bold) 10px/1 var(--font-sans);
}

/* CARET : dit que le bouton deplie une liste et ne lance pas directement une
   saisie. Plein plutot qu un chevron fin : a cette taille, deux traits de moins
   d un pixel tombent entre deux pixels d ecran et bavent. Discret par sa taille
   et son opacite. (0,3,0) devant `.ds-btn > i` (0,2,1). */
:is(.ds .ds-btn, .ds.ds-btn) > .ds-btn-caret {
  width: .75rem;
  height: .75rem;
  font-size: .75rem;
  opacity: .8;
}

/* LE SEUL !important DE LA FAMILLE. `ds-bridge.css` peint tout `a` en
   `--primary` avec !important : un `<a class="ds-btn ds-btn-primary">` sortirait
   en primaire sur primaire, libelle invisible. Seul un !important bat un
   !important ; il ne vise que les liens, un `<button>` n en a pas besoin. */
.ds a.ds-btn { color: var(--text-secondary) !important; }
.ds a.ds-btn-primary,
.ds a.ds-btn-primary:hover,
.ds a.ds-btn-primary:focus { color: var(--on-primary) !important; }
.ds a.ds-btn-secondary:hover,
.ds a.ds-btn-ghost:hover { color: var(--text-primary) !important; }
.ds a.ds-btn-excel,
.ds a.ds-btn-excel:hover { color: var(--excel) !important; }
.ds a.ds-btn-danger,
.ds a.ds-btn-danger:hover { color: var(--gray-0) !important; }
.ds a.ds-btn-reject,
.ds a.ds-btn-reject:hover { color: var(--danger-fg) !important; }
.ds a.ds-btn-quiet { color: var(--text-muted) !important; }
.ds a.ds-btn-quiet.is-important { color: var(--text-primary) !important; }
.ds a.ds-btn-quiet:hover { color: var(--primary) !important; }
/* Un lien n a pas d etat desactive : sans `pointer-events`, le clic d un `<a ds-btn disabled>` passe quand meme. */
.ds a.ds-btn[disabled] { color: var(--text-disabled) !important; pointer-events: none; }

/* ---- BUTTONS ------------------------------------------------------------- */
.ds .btn {
  font: var(--font-button);
  border-radius: var(--radius-button);
  /* `min-height` et non `height` : le bouton doit pouvoir grandir si son
     libelle passe a la ligne. `height: auto` neutralise le `height: 40px`
     qu une liste de selecteurs legacy de sitecss.css impose a certaines
     variantes et pas a d autres — c est ce qui donnait quatre hauteurs de
     bouton differentes dans un meme pied de modale.
     Le padding laisse le contenu SOUS le token (14px d interligne + 18 de
     padding + 2 de bordures = 34 < 40), donc min-height gouverne. */
  min-height: var(--control-h);
  height: auto;
  line-height: 1;
  padding: 9px 15px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--gap-inline);
  border: 1px solid transparent;
  transition: background var(--duration-fast) var(--ease-standard);
  white-space: nowrap;
  text-decoration: none;
}
/* Place un element EN TETE a l ecran, quelle que soit sa position dans le DOM.
   Sert partout ou un <i> FontAwesome doit preceder un libelle porte par une
   expression Angular : le binding s ecrit d abord, l icone ensuite, et `order`
   retablit l ordre visuel. Sans cela le remplacement <i> -> <svg> de
   FontAwesome decale les noeuds et tue le binding qui suit. Contrepartie
   de `.trail`, qui pose un element en fin de creneau.

   PIEGE DE NOM evite de justesse : ce selecteur s appelait `.lead`, qui est
   une classe BOOTSTRAP (paragraphe d accroche) portant `margin-bottom: 20px`
   dans sitecss.css. Le bouton passait de 40 a 56px. Meme famille que `.affix`
   et `.switch` : verifier un nom dans sitecss.css AVANT de le choisir. */
.ds .ico-lead { order: -1; }

/* Une icone dans un bouton ne doit ni grandir ni pousser : taille fixe, aucune
   marge. C est ce qui garantit qu un bouton « texte » et un bouton
   « texte + icone » gardent la MEME hauteur dans une meme fenetre. */
.ds .btn > i,
.ds .btn > svg {
  flex: none;
  width: var(--icon-md, 16px);
  height: var(--icon-md, 16px);
  margin: 0;
}

/* Le `color` porte un `!important` que `background` n a pas besoin de porter.
   `ds-bridge.css` pose `a { color: var(--primary) !important }` (ligne 39)
   pour recolorer les liens du portail. A (0,0,1) avec `!important`, cette
   regle battait `.ds .btn-primary` (0,2,0) SANS `!important` : un
   `<a class="btn btn-primary">` sortait donc en primaire SUR primaire —
   mesure : rgb(63,81,181) sur rgb(63,81,181), soit un libelle strictement
   invisible. Douze boutons « Valider » dans onze gabarits deja en portee
   `.ds` etaient concernes (delegationModal, createAppraisalModal,
   createCampaignModal, createTaskModal, taskModal, createOrUpdateTaskModal,
   interviewDateModal, createPlanActionModal, et les variantes de campagne).
   Le meme bouton ecrit en `<button>` etait correct : la regle `a` ne le
   visait pas. `btn-success` y echappait aussi, mais seulement parce que
   `sitecss.css` lui donne ses propres `!important`.
   Le `background` reste sans `!important` : la regle `a` ne touche que
   la couleur du texte, et le fond etait deja correct. */
.ds .btn-primary            { background: var(--primary); color: var(--on-primary) !important; border-color: transparent; }
.ds .btn-primary:hover      { background: var(--primary-hover); color: var(--on-primary); }
/* LE SURVOL QUI ECLAIRCISSAIT AU LIEU D ASSOMBRIR. `ds-bridge.css` porte
   `.btn-primary:hover { background: var(--primary-400) !important }` — une
   TEINTE PLUS CLAIRE. Mesure : #ED4337 au repos, rgb(243,127,119) au survol.
   Le bouton d action principale palissait donc quand on le visait, ce qui le
   fait lire comme desactive. La reference assombrit (`--primary-hover`, soit
   88 % de la couleur client melangee a du noir). (0,3,0) pour repasser devant
   le !important du pont. */
.ds .btn.btn-primary:hover  { background: var(--primary-hover) !important; color: var(--on-primary) !important; }
/* … ET LE FOCUS, QUI A LE MEME DEFAUT. Le survol ci-dessus a ete corrige,
   le focus non : `ds-bridge.css` porte aussi
   `.btn-primary:focus { background-color: var(--primary-400) !important }`.
   Or dans le pont, `--primary-400` sert INDIFFEREMMENT au survol, au focus et
   au desactive — trois etats, une seule couleur. Sous `.ds`, le survol et le
   desactive sont deja repris ailleurs ; le focus restait donc seul a garder la
   teinte claire.
   Ce n est pas un etat rare : un bouton GARDE le focus apres un clic. Le
   bouton d action principale palit donc et reste pali tant qu on ne clique pas
   ailleurs, exactement ce qui le fait lire comme desactive.
   Mesure du texte blanc sur le fond du bouton :
     repos          rgb(63, 81, 181)     6.87:1
     survol         rgb(55, 71, 159)     8.15:1
     actif          rgb(49, 63, 141)     9.41:1
     focus AVANT    rgb(124, 137, 205)   3.33:1   sous le seuil AA de 4.5
   Le focus prend donc la meme valeur que le survol. Le signal de focus n est
   pas perdu : `.ds .btn:focus-visible` pose deja le halo `var(--ring)`, qui est
   le traitement prevu par la reference — et qui, lui, ne se declenche pas au
   clic souris. (0,3,0) pour repasser devant le !important du pont. */
.ds .btn.btn-primary:focus  { background: var(--primary-hover) !important; color: var(--on-primary) !important; }
/* `!important` obligatoire : `sitecss.css` porte
   `.v2 .btn-secondary { border: 1px solid var(--primary-color) !important;
   color: var(--primary-color) !important }`. Notre recette, a specificite egale
   et sans !important, ne gagnait JAMAIS — le bouton sortait bleu sur bleu au
   lieu du contour neutre decrit ici. Meme famille de defaut que l asterisque
   d obligation : une regle jamais atteinte ne leve aucune erreur. */
.ds .btn-secondary          { background: var(--surface) !important; color: var(--text-secondary) !important; border-color: var(--field-border) !important; }
/* Au survol, la reference (§01) ne change QUE le fond et le texte :
   `.btn-secondary:hover { background: var(--surface-hover); color: var(--text-primary) }`.
   La bordure reste a `--field-border`. Elle etait passee a
   `--field-border-hover` ici, par analogie avec le champ de saisie — mais un
   champ signale ainsi qu il est saisissable, alors qu un bouton l a deja dit
   par son fond. Deux traits qui bougent au lieu d un, le survol devenait
   lourd. On la REPOSE explicitement plutot que de retirer la declaration :
   `sitecss.css` cible aussi `.v2 .btn-secondary:hover`, et sans valeur de
   notre cote le contour repasserait a la couleur du client. */
.ds .btn-secondary:hover    { background: var(--surface-hover) !important; color: var(--text-primary) !important; border-color: var(--field-border) !important; }
/* Le fond du survol repete aussi la classe, comme le halo et le fantome :
   `sitecss.css` pose `.btn-secondary { background-color: #fff }` sur TOUS les
   etats, sans distinguer le survol. Sans !important a (0,3,0), il suffit
   qu une feuille client s intercale pour que le gris du survol saute et que le
   bouton reste blanc — le defaut se voit alors seulement au passage de la
   souris, donc jamais dans une capture. */
.ds .btn.btn-secondary:hover { background: var(--surface-hover) !important; }
/* LE HALO ROUGE DU BOUTON DE CONTOUR. `sitecss.css` porte
   `.v2 .btn-secondary, :active, :focus, :hover { box-shadow: 0 0 2px 0 var(--primary-color) !important }`
   — soit une aureole a la couleur du client autour de CHAQUE bouton neutre :
   Annuler, Sauvegarder, le plus d une ligne repetable. La reference
   (`audit/formulaire-reference.html` §01) dessine un contour PLAT : la seule
   ombre prevue sur un bouton est l anneau du focus clavier.
   Poids : la regle legacy vaut (0,2,0) avec !important. `.ds .btn.btn-secondary`
   monte a (0,3,0) — la repetition de classe evite de dependre de l ordre de
   chargement des feuilles pour trancher une egalite. */
.ds .btn.btn-secondary,
.ds .btn.btn-secondary:hover,
.ds .btn.btn-secondary:active,
.ds .btn.btn-secondary:focus { box-shadow: none !important; }
/* Le seul bouton qui porte une ombre : celui qu on atteint au clavier.
   `sitecss.css` pose `.btn { box-shadow: none !important }`, d ou le !important.
   La variante secondaire est repetee pour repasser devant le neutralisant
   ci-dessus, qui l atteint aussi par `:focus`. */
.ds .btn.btn-secondary:focus-visible,
.ds .btn:focus-visible      { outline: none; box-shadow: var(--ring) !important; }
/* DOUX : un ton de marque en fond plein leger. Le specimen le classe avec le
   secondaire et le fantome parmi les actions NON principales — la regle etant
   « un seul bouton plein primaire par zone ». */
.ds .btn-soft               { background: var(--primary-soft); color: var(--primary-700) !important; border-color: transparent; }
.ds .btn-soft:hover         { background: var(--primary-soft-hover); }

/* DESTRUCTIF, plein et doux. `!important` sur le plein : `.btn-danger` compte
   75 occurrences dans sitecss.css, la recette ne gagnerait pas sans. La
   variante douce, elle, est un nom libre — aucun !important necessaire. */
.ds .btn-danger             { background: var(--danger) !important; color: var(--gray-0) !important; border-color: transparent !important; }
.ds .btn-danger:hover       { background: var(--danger-fg) !important; }
.ds .btn-danger-soft        { background: var(--danger-soft); color: var(--danger-fg) !important; border-color: transparent; }
.ds .btn-danger-soft:hover  { background: color-mix(in srgb, var(--danger) 18%, #fff); }

.ds .btn-ghost              { background: transparent; color: var(--text-secondary); }
.ds .btn-ghost:hover        { background: var(--surface-hover); color: var(--text-primary); }
/* LE FANTOME QUI N EN ETAIT PAS UN. `ds-bridge.css` porte
   `.btn-icon { background: var(--surface) !important; color: var(--text-primary) !important }`
   — a (0,1,0) mais avec !important, donc il battait `.ds .btn-ghost` (0,2,0)
   des que les deux classes se croisaient. Un bouton-icone fantome sortait
   alors en CARRE BLANC a glyphe presque noir : bien visible sur le fond en
   creux d une ligne de fichier, la ou la reference (§06) ne montre qu un
   glyphe discret sans cadre. `.ds .btn.btn-ghost` monte a (0,3,0) et repasse
   devant, !important contre !important. */
.ds .btn.btn-ghost          { background: transparent !important; color: var(--text-secondary) !important; }
.ds .btn.btn-ghost:hover    { background: var(--surface-hover) !important; color: var(--text-primary) !important; }
/* Excel/export — never a primary CTA (DS rule): outlined green */
.ds .btn-excel              { background: var(--surface); color: var(--excel); border-color: color-mix(in srgb, var(--excel) 36%, #fff); }
.ds .btn-excel:hover        { background: var(--excel-soft); }

/* ---- Excel et filtre : la recette REELLEMENT appliquee -------------------- */
/* Les deux lignes ci-dessus sont le calque de la reference (`composants.html`),
   qui montre un bouton Excel A LIBELLE. Dans l'application, Excel et filtre
   n'existent que dans l'entete des listes — hmGrid, hmCard, hmTreeList,
   hmCalendar, hmTimeline — et n'y portent QUE leur glyphe : ce sont les regles
   ci-dessous qui les dessinent.
   Hors `.ds` a dessein : aucun de ces gabarits ne porte cette classe.
   QUATRE classes repetees = (0,4,0), un cran au-dessus de
   `.v3-list .fixed-style .btn` (0,3,0, dans l ancien `ds-list-v3.css`, supprime) qui posait
   `border: 1px solid transparent !important`. A poids egal l'ordre des feuilles
   trancherait — et celle-ci est chargee AVANT —, donc le filet disparaitrait.
   Meme parade qu'au `.ds .btn.btn-secondary` plus haut : ne jamais dependre de
   l'ordre de chargement.
   Dessin seul : ni marge ni flottement, les deux boutons restent ou ils sont.
   40px et non `--control-h` (38px) : c'est la hauteur de la rangee d'entete,
   posee par le `.btn-filtre` legacy. `padding: 0` les rend carres, sinon le
   `.btn` bootstrap les etire a 47px. */

/* EXCEL — carre blanc, filet vert, glyphe vert. Le vert est reserve a l'export
   dans le DS et n'est jamais une action principale : le bouton reste en
   contour, a cote du bouton plein d'ajout. */
.btn.btn-excel.btn-excel.btn-excel {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  min-width: 40px !important;
  height: 40px !important;
  padding: 0 !important;
  line-height: 1;
  border-radius: var(--radius-button);
  background: var(--surface) !important;
  background-image: none !important;
  color: var(--excel) !important;
  border: 1px solid color-mix(in srgb, var(--excel) 36%, var(--surface)) !important;
  box-shadow: none !important;
  transition: background var(--duration-fast) var(--ease-standard);
}

.btn.btn-excel.btn-excel.btn-excel:hover,
.btn.btn-excel.btn-excel.btn-excel:focus {
  background: var(--excel-soft) !important;
  color: var(--excel) !important;
}

/* Seule ombre admise : l'anneau de focus. `!important` contre
   `.btn { box-shadow: none !important }` de sitecss.css. */
.btn.btn-excel.btn-excel.btn-excel:focus-visible {
  outline: none;
  box-shadow: var(--ring) !important;
}

/* Le glyphe etait blanc (classe `whiteColor`, heritee de l'ancien aplat vert),
   donc invisible sur fond blanc. Repose ici pour couvrir aussi les boutons
   construits en JS, ou `whiteColor` traine encore. */
.btn.btn-excel.btn-excel.btn-excel i,
.btn.btn-excel.btn-excel.btn-excel svg {
  margin: 0 !important;
  color: var(--excel) !important;
  font-size: 17px !important;
}

/* FontAwesome svg-js remplace le `<i>` par un `<svg>` : le `font-size`
   ci-dessus ne le dimensionne pas. */
.btn.btn-excel.btn-excel.btn-excel .svg-inline--fa {
  width: 17px !important;
  height: 17px !important;
}

/* Export en cours (`ng-disabled`) : glyphe grise lui aussi, sinon il restait
   vert vif dans un cadre gris. */
.btn.btn-excel.btn-excel.btn-excel[disabled],
.btn.btn-excel.btn-excel.btn-excel.disabled {
  background: var(--surface) !important;
  color: var(--text-disabled) !important;
  border-color: var(--border) !important;
  cursor: not-allowed;
}

.btn.btn-excel.btn-excel.btn-excel[disabled] i,
.btn.btn-excel.btn-excel.btn-excel[disabled] svg,
.btn.btn-excel.btn-excel.btn-excel.disabled i,
.btn.btn-excel.btn-excel.btn-excel.disabled svg {
  color: var(--text-disabled) !important;
}

/* 36 % de vert disparaissait sur le fond sombre ; 60 % s'y lit. */
[data-theme="dark"] .btn.btn-excel.btn-excel.btn-excel {
  border-color: color-mix(in srgb, var(--excel) 60%, var(--surface)) !important;
}

/* FILTRE — le pendant neutre pose juste a cote : meme carre, meme rayon, filet
   `--border-strong` (#CDD2DC) et glyphe `--text-secondary` (#5A6273), les deux
   valeurs relevees au pixel sur la maquette. */
.btn.btn-filtre.btn-filtre.btn-filtre {
  /* ancre `.filters-count`, le compteur pose en absolu dans le coin */
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  min-width: 40px !important;
  height: 40px !important;
  padding: 0 !important;
  line-height: 1;
  border-radius: var(--radius-button);
  background: var(--surface) !important;
  background-image: none !important;
  color: var(--text-secondary) !important;
  border: 1px solid var(--border-strong) !important;
  box-shadow: none !important;
  transition: background var(--duration-fast) var(--ease-standard);
}

.btn.btn-filtre.btn-filtre.btn-filtre:hover,
.btn.btn-filtre.btn-filtre.btn-filtre:focus {
  background: var(--surface-hover) !important;
}

.btn.btn-filtre.btn-filtre.btn-filtre:focus-visible {
  outline: none;
  box-shadow: var(--ring) !important;
}

/* Le gabarit peint le glyphe en `greyColor` (#97b2b8, hors palette) tant qu'aucun
   filtre n'est pose. `orangeColor`, l'etat « filtres actifs », n'est pas touche :
   c'est lui qui signale le filtre en cours, avec le compteur. */
.btn.btn-filtre.btn-filtre.btn-filtre .greyColor {
  color: var(--text-secondary) !important;
}

/* Seul dans son carre, le glyphe porte tout le bouton : les 17px de l'Excel
   voisin. `!important` contre `.font-16` du gabarit et contre
   `.v3-list .fixed-style .btn i`. */
.btn.btn-filtre.btn-filtre.btn-filtre i,
.btn.btn-filtre.btn-filtre.btn-filtre svg {
  margin: 0 !important;
  font-size: 17px !important;
}

.btn.btn-filtre.btn-filtre.btn-filtre .svg-inline--fa {
  width: 17px !important;
  height: 17px !important;
}

/* En clair le carre blanc se lit par son filet ; sur le fond sombre la carte est
   deja `--surface`, alors le bouton descend d'un cran pour garder un bord. */
[data-theme="dark"] .btn.btn-filtre.btn-filtre.btn-filtre {
  background: var(--surface-sunken) !important;
}

.ds .btn-sm                 { min-height: var(--control-h-sm); padding: 5px 11px; font-size: var(--text-sm); gap: 6px; }
.ds .btn-lg                 { min-height: var(--control-h-lg); padding: 12px 20px; font-size: var(--text-md); }
/* Carre a la hauteur commune : une rangee de boutons icone s aligne sur les
   boutons a libelle poses a cote. */
.ds .btn-icon               { width: var(--control-h); min-width: var(--control-h); height: var(--control-h); padding: 0; }
.ds .btn-sm.btn-icon        { width: var(--control-h-sm); min-width: var(--control-h-sm); height: var(--control-h-sm); }

/* sitecss.css porte `.btn-primary { padding: 9px !important }`. Seul un
   !important reprend la main sur un !important ; la specificite n y suffit
   pas. Sans cette ligne, le bouton d action principale est le seul du pied a
   avoir un padding vertical sans padding horizontal, donc une largeur et une
   hauteur qui ne suivent aucune regle. */
.ds .btn-primary            { padding: 9px 15px !important; }
.ds .btn-primary.btn-sm     { padding: 5px 11px !important; }
.ds .btn-primary.btn-icon   { padding: 0 !important; }

/* DESACTIVE : couleurs EXPLICITES, pas un voile d opacite. La reference
   developpeur (§01) pose
   `background: --surface-sunken; color: --text-disabled; border-color: --border`.
   Le fond etait ecrit `--surface` ici : valeur reprise de l autre specimen
   (`audit/formulaires.html`), le piege du §46. Sur un pied en `--surface`, un
   bouton desactive blanc ne se distinguait plus que par sa bordure.
   L opacite delavait la teinte de chaque variante — un primaire desactive
   devenait bleu pale, un destructif rouge pale, chacun different. Ici tous
   convergent vers le meme gris neutre : un bouton inerte se lit pareil quelle
   que soit sa variante. Les !important battent les variantes ci-dessus, qui en
   portent pour les raisons legacy expliquees plus haut. */
.ds .btn[disabled],
.ds .btn.disabled,
.ds .btn[disabled]:hover,
.ds .btn.disabled:hover {
  background: var(--surface-sunken) !important;
  color: var(--text-disabled) !important;
  border-color: var(--border) !important;
  cursor: not-allowed;
}

/* Normalize icon size inside buttons — overrides stray fa-2x/fa-3x so button
   icons never render oversized (FA renders as <i> or inline <svg>). */
.ds .btn i,
.ds .btn .fa,
.ds .btn .fal, .ds .btn .fas, .ds .btn .far, .ds .btn .fab {
  font-size: 16px !important;
  line-height: 1;
  width: auto;
  height: auto;
}
.ds .btn-icon i,
.ds .btn-icon .fal, .ds .btn-icon .fas, .ds .btn-icon .far { font-size: 17px !important; margin: 0 !important; }
.ds .btn .svg-inline--fa    { width: 16px !important; height: 16px !important; }
.ds .btn-icon .svg-inline--fa { width: 17px !important; height: 17px !important; }

/* ---- FIELDS ------------------------------------------------------------- */
.ds .field                  { display: flex; flex-direction: column; gap: var(--space-2); }
/* Le libelle est un conteneur flex : c est ce qui aligne verticalement son
   asterisque d obligation et son eventuelle coche de validite avec le texte. */
/* Le libelle garde LA MEME couleur quel que soit l etat du champ.

   `ds-bridge.css` porte `.has-success label { color: var(--text-primary)
   !important }`. Un champ rempli voyait donc son libelle passer de
   `--text-secondary` (#5A6273) a `--text-primary` (#171A21) — assez fonce pour
   se lire comme un changement de GRAISSE, alors que le poids ne bougeait pas
   (500 dans les deux cas, mesure faite). Le specimen ne fait pas varier le
   libelle selon l etat : seule la bordure du champ parle. */
.ds .field > label,
.ds .field-label,
.ds .has-success .field > label,
.ds .field.has-success > label {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  /* Le portail rend une `<div>` ; ces trois lignes gardent la recette juste si
     `.tabs` se pose un jour sur un `<ul>` (uib-tabset en rend un). */
  margin: 0;
  padding: 0;
  list-style: none;
  font: var(--font-label);
  color: var(--text-secondary) !important;
  /* `sitecss.css` pose `label { margin-bottom: 5px }` — l heritage Bootstrap.
     Le design system, lui, separe le libelle de son controle par le `gap` du
     `.field`, soit 8px. Les deux s ADDITIONNAIENT : mesure 13px entre le bas du
     libelle et le haut du controle, la ou la recette en prevoit 8. Cinq pixels
     de trop par champ, sur tous les ecrans migres. */
  margin-bottom: 0;
}

/* LA VALEUR EN LECTURE SEULE, pendant du libelle.

   Le systeme n avait que la moitie de la paire : `.field-label` pour le
   libelle, et rien pour ce qui se lit A COTE quand il n y a pas de controle a
   remplir. Chaque ecran s en fabriquait donc une — `black-description` dans les
   tuiles et la fiche collaborateur (`color: black` + 500 + 13,5px),
   `style-info-table` ailleurs — avec une couleur CUITE que le theme sombre ne
   pouvait pas suivre, et que `ds-bridge.css` rattrapait apres coup.

   La valeur est plus GRANDE et plus FONCEE que son libelle : 14px/400/primaire
   contre 13px/500/secondaire. C est la hierarchie d une fiche — ce qu on vient
   lire est la donnee, le libelle ne fait que la nommer. L ancien couple disait
   l inverse : libelle en 300, valeur en 500, a taille quasi egale.

   Conteneur flex : une valeur n est presque jamais un seul mot. Elle porte une
   pastille de statut, une pendule d echeance, une puce — autant d elements que
   les gabarits espacaient jusqu ici avec des `m-r-5` poses a la main. Le `gap`
   le fait une fois, et `align-items: center` pose la pastille SUR la ligne de
   texte au lieu de la laisser flotter au-dessus, ce que rattrapaient les
   `m-t-5`.

   `min-width: 0` : sans lui, un enfant en `cut-name` refuse de retrecir sous sa
   largeur intrinseque — la valeur deborde de sa tuile au lieu de s abreger. */
.ds .field-value {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  font: var(--font-body);
  color: var(--text-primary);
}

/* Le symbole d obligation ne doit pas dicter la hauteur de la ligne.

   Rendu en police FontAwesome, il sortait a 18,9px de haut pour une ligne de
   libelle de 17,55px. Il gonflait donc le libelle des champs OBLIGATOIRES
   d environ 1,3px, ce qui poussait leur controle vers le bas : le select
   finissait 2px plus bas que le champ de date pose a cote — le decalage visible
   entre les deux colonnes. En alignant sa ligne sur celle du libelle, les deux
   colonnes repartent du meme point. */
.ds .field > label .symbol,
.ds .field > label .req {
  font-size: 1em;
  line-height: inherit;
}

/* One field recipe for ALL field types so every input/select/date-picker
   shares the SAME height + radius. Includes legacy `.form-control` (kept by
   the hm-date/time/month picker directive templates) and the cs-select
   plugin trigger, so migrated screens stay visually consistent even where
   the class can't be swapped. --field-height keeps single-line fields equal. */
.ds {
  /* Alias historique. La hauteur de reference est --control-h (tokens/spacing.css),
     commune aux champs ET aux boutons : c est ce qui les fait s aligner quand ils
     sont poses cote a cote. --field-height ne couvrait que les champs. */
  --field-height: var(--control-h);
}
.ds .input,
.ds .select,
.ds .textarea,
.ds .form-control,
.ds .cs-select {
  font: var(--font-body);
  /* !important + color-scheme: the app's sitecss forces select/input bg to
     #fff; needed so native fields repaint in dark. Scoped to .ds only. */
  color: var(--text-primary) !important;
  background-color: var(--field-bg) !important;
  border: 1px solid var(--field-border) !important;
  border-radius: var(--radius-input) !important;
  /* forced height so every single-line field is identical, whatever the
     app's own input rules try to impose */
  padding: 0 12px !important;
  min-height: var(--field-height) !important;
  height: var(--field-height) !important;
  line-height: calc(var(--field-height) - 2px);
  box-sizing: border-box !important;
  width: 100%;
  appearance: none;
  -webkit-appearance: none;
  /* PAS de `color-scheme` ici. `tokens/colors.css` le pilote deja par le THEME
     DE L APPLICATION : `light` sur :root, `dark` dans le bloc sombre. Le poser
     a `light dark` sur chaque champ rendait la decision au SYSTEME : avec un
     Windows en clair, un <select> natif se dessinait blanc en mode sombre, quel
     que soit son background-color — constate a l ecran. En heritant de :root,
     c est le theme choisi dans l application qui gouverne. */
}
/* SURVOL. Le specimen `audit/formulaires.html` le definit
   (`.input:hover { border-color: var(--field-border-hover) }`) et le jeton
   existe en clair comme en sombre — il n etait simplement jamais consomme.
   Il indique qu un champ est actionnable avant le clic.

   Place AVANT focus, erreur, succes et desactive : a specificite egale, la
   regle la plus basse dans le fichier gagne, donc chacun de ces etats
   l emporte sur le survol. Un champ en erreur survole reste rouge.

   Les deux variables sont posees, cf. le piege du <select> plus bas. */
.ds .input:hover,
.ds .select:hover,
.ds .textarea:hover,
.ds .form-control:hover,
.ds .cs-select:hover {
  --field-border: var(--field-border-hover);
  --input-border: var(--field-border-hover);
  border-color: var(--field-border-hover) !important;
}
/* FOCUS. La bordure passe par la VARIABLE et non par `border-color` : le
   selecteur a six `:not()` de ds-bridge.overrides.css peint la bordure a
   (0,12,1), qu un `.ds .input:focus` (0,3,0) ne peut pas battre. En posant
   `--field-border` sur l element, c est lui qui fournit la valeur au selecteur
   gagnant. Deux classes au lieu de treize repetitions.

   Deuxieme piege, propre au SELECT : son selecteur gagnant n est pas le meme
   que celui de l input, et il ne lit pas la meme variable. C est
   `sitecss.css` qui gagne, avec `select:not(.form-control):not(.cs-select)
   :not(.hourselect)...` — six `:not()`, soit (0,12,1) — et il peint
   `border: 1px solid var(--input-border)`. Poser `--field-border` ne
   l atteint pas : `--input-border` a deja ete resolu au niveau de `:root`
   par le remappage de ds-bridge.overrides.css, et une variable ne se
   recalcule pas en amont parce qu on redefinit sa source en aval. Il faut
   donc poser ici la variable que lit REELLEMENT le selecteur gagnant.
   Symptome sans cette ligne : le textarea passait bien en primary au focus,
   le select gardait sa bordure grise avec seulement l anneau. */
.ds .input:focus,
.ds .select:focus,
.ds .textarea:focus,
.ds .form-control:focus,
.ds .cs-select:focus {
  --field-border: var(--primary);
  --input-border: var(--primary);
  outline: none;
  border-color: var(--primary) !important;
  box-shadow: var(--ring);
}
.ds .input::placeholder,
.ds .form-control::placeholder,
.ds textarea.input::placeholder { color: var(--field-placeholder); opacity: 1; }
/* DESACTIVE : le specimen le dessine EN CREUX et grise. Un champ non
   modifiable doit se lire comme tel — « Date demande » est pose par le serveur
   (`ng-disabled="true"`) et rendait pourtant comme un champ actif.

   La technique : on ne se bat PAS contre le selecteur a six `:not()` qui peint
   les champs a (0,12,1). On redefinit les VARIABLES qu il lit, sur l element
   desactive lui-meme. Une custom property posee sur l element l emporte
   toujours sur celle heritee de :root, quelle que soit la specificite du
   selecteur qui la consomme. Deux classes suffisent la ou il aurait fallu en
   repeter treize.

   Redefinir --text-primary sur un champ est sans effet de bord : un input n a
   pas d enfants qui pourraient en heriter. */
/* Cible les CHAMPS uniquement. `.ds [disabled]` aurait aussi attrape les
   `<button disabled>` et surtout les `<fieldset disabled>`, dont tous les
   enfants auraient herite du texte grise. */
.ds .input[disabled],
.ds .select[disabled],
.ds .textarea[disabled],
.ds .form-control[disabled],
.ds .cs-select[disabled] {
  /* fige la bordure : sans cela le survol la rehaussait sur un champ inerte */
  --field-border: var(--border);
  --input-border: var(--border);
  --field-bg: var(--surface-sunken);
  --text-primary: var(--text-disabled);
  --input-bg: var(--surface-sunken);
  --input-color: var(--text-disabled);
  cursor: not-allowed;
}

/* LECTURE SEULE : ce n est PAS un desactive. Le champ garde son fond de champ
   et un texte lisible — la valeur y est deposee par un autre controle (le
   bouton d envoi de fichier, un selecteur de date). Le specimen ne traite pas
   ce cas ; la distinction vient de l usage reel du portail. */
.ds .input[readonly],
.ds .form-control[readonly] { color: var(--text-secondary) !important; }
/* `readonly` above means "inert", but some fields are readonly only because you
   don't type into them: the grid's column filters (hmTextFilter.html) and the
   date/picker triggers are readonly inputs that open a popover on click. They
   are interactive, so `not-allowed` misreads the affordance — it says broken
   where the control works. Keyed on `hm-popover`, which is exactly the marker
   for "clicking this opens something". */
.ds .input[readonly][hm-popover],
.ds .form-control[readonly][hm-popover] { cursor: pointer; }
/* textarea is multi-line: auto height, not the fixed field height */
.ds .textarea,
.ds textarea.input,
.ds textarea.form-control   { min-height: var(--space-20) !important; height: auto !important; line-height: var(--leading-normal); padding: 7px 12px !important; resize: vertical; }
/* cs-select plugin: align its inner trigger to the shared field height */
.ds .cs-select > span       { height: 100%; line-height: calc(var(--field-height) - 2px); padding: 0; display: block; }

/* Chevron du select natif, en token, sans plugin.
   Les trois `!important` sont necessaires : `sitecss.css` pose
   `select { background-image: initial !important }`, et le `padding: 0 12px
   !important` de la recette de champ ci-dessus ecraserait le padding droit qui
   reserve la place du chevron.

   Les trois selecteurs `[disabled]` ne sont pas une redite : `sitecss.css`
   pose `.v2 select[disabled], .v2 select[disabled]:focus,
   .v2 select[disabled]:hover { background: var(--input-disabled-bg)
   !important }`. C est un RACCOURCI `background` — exactement ce que la
   regle n.1 de la reference interdit — et il remet donc `background-image`
   a `none` et `background-position` a `0% 0%`. A (0,2,1) il battait le
   `.ds .select` (0,2,0) ci-dessous, `!important` contre `!important`.
   Mesure sur un select desactive : `background-image: none`,
   `background-position: 0% 0%`, mais `padding-right: 34px` INTACT (le
   raccourci ne touche pas le padding) — soit 34px de vide reserve a un
   chevron qui n etait plus dessine, sur TOUS les selects desactives du
   portail, pas seulement ceux des modales migrees. `.ds .select[disabled]`
   vaut (0,3,0) et reprend la main. La couleur de fond, elle, est laissee
   au legacy : `--input-disabled-bg` rend rgb(244,245,248), soit la valeur
   de `--surface-sunken` que la recette de champ desactive vise deja. */
.ds .select,
.ds select.form-control,
.ds .select[disabled],
.ds .select[disabled]:focus,
.ds .select[disabled]:hover {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' stroke='%237A8294' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4 6 4 4 4-4'/%3E%3C/svg%3E") !important;
  background-repeat: no-repeat !important;
  background-position: right 11px center !important;
  padding-right: 34px !important;
}
/* Le declencheur du plugin `cs-select` est un <div>, pas un <select> : la regle
   ci-dessus ne l atteignait pas, et le champ sortait donc SANS chevron — une
   liste de selection qui ne se signale plus comme telle. La reference (§03) en
   met un sur tout select. Meme image, meme position, meme reserve de padding.
   Poids : `ds-bridge.css` pose `div.cs-select { background: var(--surface)
   !important }` — un RACCOURCI, donc il remet `background-image` a none, a
   (0,1,1). `.ds div.cs-select.cs-select` monte a (0,3,1). */
/* LA COULEUR REVIENT AU PRIMAIRE, ET SORT DE L IMAGE.

   Deux corrections en une :

   1. La fleche du select etait en `--primary` AVANT la migration : le plugin
      dessinait un glyphe FontAwesome, que `ds-bridge.css` repeint en couleur
      client. Le chevron du design system l avait remplacee par un gris. On
      revient au primaire, comme avant.
   2. La couleur etait ECRITE EN DUR dans le SVG (stroke %237A8294), ce que la
      regle 8 de la reference interdit. Un `data:` URI ne peut pas lire une
      variable CSS : tant que l icone est un `background-image`, sa couleur est
      figee — elle ne suivait ni le theme, ni l etat du champ, ni la couleur du
      client.

   L icone passe donc en `mask` sur un pseudo-element : le SVG ne sert plus que
   de POCHOIR — son trait noir ne fait qu opacifier la forme — et la couleur
   vient de `background-color`, donc d un jeton. Themable, surchargeable par
   client, plus aucun hex en dur.

   Possible ici parce que le declencheur du plugin est un `<div>`. Les champs de
   date, eux, sont des `<input>` nus : un input ne porte pas de pseudo-element,
   leur icone est traitee separement.

   Le `!important` sur `background-image` reste necessaire : `ds-bridge.css`
   pose `div.cs-select { background: var(--surface) !important }`, un raccourci
   qui remettrait une image. */
.ds div.cs-select.cs-select {
  background-image: none !important;
  padding-right: 34px !important;
  position: relative;
}
.ds div.cs-select.cs-select::after {
  content: "";
  position: absolute;
  right: 11px;
  top: 50%;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  background-color: var(--text-muted);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4 6 4 4 4-4'/%3E%3C/svg%3E") no-repeat center / 16px 16px;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4 6 4 4 4-4'/%3E%3C/svg%3E") no-repeat center / 16px 16px;
  pointer-events: none;
}
/* Sur un select DESACTIVE, le chevron s eclaircit comme son texte. Il restait
   sinon plus sombre que le libelle qu il accompagne — c est ce contraste
   inverse qui faisait lire l icone comme noire. Possible seulement depuis que
   la couleur vient d un jeton et non d un hex ecrit dans l image. */
.ds div.cs-select.cs-select[disabled]::after {
  background-color: var(--text-disabled);
}
/* Le plugin dessine DEJA une fleche : un glyphe FontAwesome en
   `.cs-placeholder::after`, peint en `--primary` et cale a `right: 14px`.
   Elle etait donc rouge, d une autre forme que le chevron du design system, et
   a une autre distance du bord que tous les autres selects. On la retire au
   profit du chevron ci-dessus : une seule fleche, la meme partout. */
.ds .cs-select .cs-placeholder::after { content: none !important; }

/* ---- champ a liste : desactive a la saisie, mais PAS un champ inerte ----
   Motif partage par 46 gabarits, 83 champs : un `<input disabled>` encadre
   par deux boutons — l un ouvre la liste, l autre vide le champ — et double
   d un `<input type="hidden">` qui porte la vraie valeur et le `ng-required`.
   Le `disabled` empeche de TAPER, il n empeche pas de renseigner le champ.

   Il sortait pourtant avec l habillage du desactive : fond
   `--surface-sunken`, texte `--text-disabled`. Un champ qu on va remplir se
   presentait donc comme un champ mort, et la valeur choisie s affichait en
   gris pale.

   Il reprend ici l apparence NORMALE d un champ. Le `disabled` reste dans le
   gabarit : le retirer laisserait saisir un libelle libre sans identifiant
   correspondant, et le passer en `readonly` changerait sa participation a la
   validation dans 46 gabarits. Un changement d apparence suffit, et il tient
   en une regle au lieu de 83 retouches.

   La portee est etroite a dessein : `:has(.repeat-actions)` exige les boutons.
   Les champs VRAIMENT non modifiables — Salarie, Solde, compteurs, entite du
   contrat — ne sont pas dans un `.affix` a boutons et gardent leur gris.

   COMMENT la regle gagne, car ce n est pas evident. Le champ desactive ne
   pose pas son fond directement : il redefinit les VARIABLES
   (`--field-bg: var(--surface-sunken)`, `--text-primary: var(--text-disabled)`),
   et c est un selecteur de `ds-bridge.overrides.css` a (0,6,1) —
   `input:not(...)` repete six fois — qui les lit et peint. Un
   `background-color: ... !important` a (0,5,0) ne pouvait donc rien : il
   perdait contre ce (0,6,1). Premiere tentative faite, mesuree, sans effet.

   On rend donc leur valeur normale AUX VARIABLES, sur un selecteur qui bat
   celui du champ desactive (0,3,0). Aucun `!important` n est necessaire :
   on ne se bat plus contre la regle qui peint, on lui donne ses valeurs.

   `--text-primary` ne peut pas se redonner a lui-meme : la substitution se
   resout la ou la variable est UTILISEE, donc `var(--text-primary)` y
   renverrait la valeur masquee. D ou `--text-strong`, ajoute dans
   `tokens/colors.css` pour les deux themes, et jamais redefini ailleurs. */
.ds .affix:has(.repeat-actions) .input:disabled,
.ds .affix:has(.repeat-actions) .input[disabled] {
  --field-bg: var(--field-bg-strong);
  --input-bg: var(--field-bg-strong);
  --text-primary: var(--text-strong);
  --input-color: var(--text-strong);
}

/* Les DEUX boutons d un champ a liste se ressemblent.

   La liste et la corbeille sont deux actions du meme champ, posees cote a
   cote. Elles sortaient pourtant differemment : la premiere en
   `.btn-secondary`, encadree, la seconde en `.btn-ghost`, sans cadre et sans
   fond. Le couple se lisait de travers, et le rayon differait en plus — 4px
   contre 8px, le legacy `.v2 .btn-secondary { border-radius: 4px }` gagnant
   sur l un des deux.

   La corbeille prend donc la meme recette que sa voisine, et les deux prennent
   le rayon du jeton. Portee limitee a `.repeat-actions` : le `.btn-ghost` d une
   ligne de fichier ou d une barre d actions garde son absence de cadre, qui y
   est voulue.

   Les `!important` suivent ceux de la recette `.btn-secondary` copiee ici, et
   `.btn.btn-ghost` monte a (0,4,0) pour passer devant le `.v2 .btn-secondary`
   legacy sur le rayon. */
.ds .repeat-actions .btn.btn-ghost {
  background: var(--surface) !important;
  color: var(--text-secondary) !important;
  border-color: var(--field-border) !important;
}
.ds .repeat-actions .btn.btn-ghost:hover {
  background: var(--surface-hover) !important;
  color: var(--text-primary) !important;
}
.ds .repeat-actions .btn.btn-secondary,
.ds .repeat-actions .btn.btn-ghost {
  border-radius: var(--radius-button) !important;
}

/* ---- onglets DANS une fenetre ------------------------------------------
   `sitecss.css` pose `.tab-content { padding: 0 15px }`. Dans une page c est
   ce qui donne sa respiration au contenu ; dans une fenetre, le corps donne
   deja 20px et les deux s ajoutent.

   Mesure sur « Demandes libre-service — voir details », panneau de 665px :
     .modal-title    a 20px
     les onglets     a 20px
     le bouton Fermer a 20px du bord droit
     le CONTENU de l onglet  a 35px          <- seul decale

   Le retrait vertical est garde : il separe le contenu de la barre d onglets,
   ce que le corps de fenetre ne fait pas. Seul l horizontal saute.

   Quatre gabarits migres portent des onglets : detailsCandidatureModal,
   editRequestDetailsModal, CompetencyhistoryModal et editRequestDetailsV2Modal. */
.ds .modal-body .tab-content {
  padding-left: 0;
  padding-right: 0;
}



/* ---- une grille DANS une fenetre defile dans son propre conteneur ------
   `hm-grid` calcule la largeur de sa table a partir de ses colonnes, sans
   savoir qu elle est posee dans un panneau lateral. Quand la somme des
   colonnes depasse la largeur du panneau, c est le CORPS DE FENETRE qui
   deborde : la table sort du cadre et se fait rogner par le bord droit.

   Mesure, `Candidat/addCandidateToJobApplicationModal` en `lg` (890px) :
     avant  corps 943 / 890, conteneur de table 923px, aucun defilement
     apres  corps 890 / 890, conteneur 850px, la table defile DANS son cadre

   Verifie sur deux modales a grille qui n avaient PAS de debordement
   (`Candidat/detailsCandidatureModal` en 1250px,
   `TrainingSession/listMembersTrainingSessionModal` en 890px) : aucune
   difference, la regle ne se declenche que lorsque le contenu est trop large.

   Portee limitee a `.modal-body` : une grille en PLEINE PAGE garde le
   comportement qu elle a toujours eu, ce chantier ne touche pas aux listes.
   60 des 185 modales migrees embarquent une `hm-grid`. */
.ds .modal-body .hm-grid-table-holder {
  max-width: 100%;
  overflow-x: auto;
}

/* ---- BADGES (status = soft fill + label, one convention; no dot) --------------------- */
.ds .badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font: var(--weight-medium) var(--text-xs)/1 var(--font-sans);
  padding: 5px 10px;
  /* Filet TRANSPARENT sur tous les badges : `b-neutral` a besoin d un filet visible (son gris
     pale disparaitrait sur fond blanc) ; sans ce filet commun, il faisait 2px de plus que les
     autres et un « Non » gris paraissait plus gros qu un « Oui » vert. */
  border: 1px solid transparent;
  border-radius: var(--radius-badge);
  white-space: nowrap;
  /* reset the app's global .badge (used elsewhere as a 6px dot/counter) */
  width: auto;
  min-width: 0;
  height: auto;
  text-align: left;
  vertical-align: middle;
}
.ds .b-success              { background: var(--success-soft); color: var(--success-fg); }
.ds .b-warning              { background: var(--warning-soft); color: var(--warning-fg); }
.ds .b-danger               { background: var(--danger-soft);  color: var(--danger-fg);  }
.ds .b-info                 { background: var(--info-soft);    color: var(--info-fg);    }
.ds .b-violet               { background: var(--accent-violet-soft); color: var(--accent-violet-fg); }
.ds .b-neutral              { background: var(--surface-sunken); color: var(--text-secondary); border: 1px solid var(--border); }
/* En sombre, `b-neutral` etait la seule pastille a S ENFONCER dans sa surface.
   Les cinq autres variantes melangent leur teinte DANS `#1C1F26` (voir
   `--success-soft` et consorts dans tokens/colors.css) : elles ressortent donc
   toujours au-dessus du panneau. `--surface-sunken` vaut #15171D quand le
   panneau de modale vaut #1C1F26 — mesure : la pastille sortait PLUS SOMBRE que
   son fond, et se lisait comme un trou noir, alors qu en clair elle est plus
   claire que le fond (#F4F5F8 sur #FFFFFF). La recette est symetrique en clair,
   inversee en sombre.
   On la remonte donc d un cran, sur `--surface-hover` (#262A33), et la bordure
   passe a `--border-strong` pour rester lisible sur ce fond releve. */
[data-theme="dark"] .ds .b-neutral,
.ds [data-theme="dark"] .b-neutral { background: var(--surface-hover); border-color: var(--border-strong); }
/* Badge qui ouvre un detail (`<button class="badge … is-button">`, souhaits en cours d une carte du
   catalogue) : meme dessin, un filet de sa couleur au survol et quand son detail est ouvert. */
.ds button.badge.is-button {
  margin: 0;
  cursor: pointer;
}
.ds button.badge.is-button:hover,
.ds button.badge.is-button[aria-expanded="true"] { border-color: currentColor; }
.ds button.badge.is-button:focus-visible { outline: none; box-shadow: var(--ring); }

/* SUIVI DES SIGNATURES d une carte document (EmployeeAdministration/documentCard.html) : le compte
   « signes / signataires » dans le badge du statut, puis un segment par signataire, de la teinte du
   statut une fois signe. Le segment est plus haut que son trait pour que le survol (son nom) se trouve. */
.ds .badge > .sign-count {
  padding: 1px 6px;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, currentColor 16%, transparent);
  font-weight: var(--weight-bold);
  font-variant-numeric: tabular-nums;
}
.ds .sign-track {
  display: flex;
  gap: 4px;
  width: 90px;
}
.ds .sign-track > span {
  flex: 1;
  padding: 4px 0;
}
.ds .sign-track > span::before {
  content: "";
  display: block;
  height: 4px;
  border-radius: var(--radius-pill);
  background: var(--border-strong);
}
.ds .sign-track.is-progress > .is-signed::before { background: var(--warning); }
.ds .sign-track.is-done > .is-signed::before { background: var(--success); }

/* STATUT DE PROCESS : une variante par categorie (tokens/colors.css,
   `--status-*`), et non par couleur. Un ecran ne choisit pas une teinte, il dit
   ou en est le process ; le filtre `statusBadge` fait la traduction depuis le
   SystemName. Les variantes semantiques ci-dessus (`b-success`…) restent pour
   ce qui n est pas un statut (oui / non, signe, eligible…). */
.ds .b-draft     { background: var(--status-draft-soft);     color: var(--status-draft-fg);     }
.ds .b-planned-light { background: var(--status-planned-light-soft); color: var(--status-planned-light-fg); }
.ds .b-planned   { background: var(--status-planned-soft);   color: var(--status-planned-fg);   }
.ds .b-planned-dark  { background: var(--status-planned-dark-soft);  color: var(--status-planned-dark-fg);  }
.ds .b-progress  { background: var(--status-progress-soft);  color: var(--status-progress-fg);  }
.ds .b-blocked   { background: var(--status-blocked-soft);   color: var(--status-blocked-fg);   }
.ds .b-done      { background: var(--status-done-soft);      color: var(--status-done-fg);      }
.ds .b-refused   { background: var(--status-refused-soft);   color: var(--status-refused-fg);   }
.ds .b-cancelled { background: var(--status-cancelled-soft); color: var(--status-cancelled-fg); }
.ds .b-archived  { background: var(--status-archived-soft);  color: var(--status-archived-fg);  }

/* La TEINTE d une categorie, pour les marques SANS texte : le point d une pastille
   de filtre (`chip-dot`), celui d une cellule (`cell-dot`), la barre d un
   evenement de calendrier. C est la teinte du point `.d` des badges ci-dessus :
   une marque de 8px en fond doux ne se verrait pas. Un texte de statut, lui, se
   pose en badge (fond doux + texte fonce). */
.ds .st-draft     { --st: var(--status-draft); }
.ds .st-planned-light  { --st: var(--status-planned-light); }
.ds .st-planned   { --st: var(--status-planned); }
.ds .st-planned-dark   { --st: var(--status-planned-dark); }
.ds .st-progress  { --st: var(--status-progress); }
.ds .st-blocked   { --st: var(--status-blocked); }
.ds .st-done      { --st: var(--status-done); }
.ds .st-refused   { --st: var(--status-refused); }
.ds .st-cancelled { --st: var(--status-cancelled); }
.ds .st-archived  { --st: var(--status-archived); }
/* Posee par l ecran comme couleur d une option de legende (`legendConfig.options[].class`,
   `<hm-calendar-legend-field-option class>`) : elle arrive sur le point de la pastille, sur
   l icone du point de cellule, et sur l evenement du calendrier dont la barre la peint. */
:is(.ds .chip-dot, .ds .cell-dot > *):is(.st-draft, .st-planned-light, .st-planned, .st-planned-dark, .st-progress, .st-blocked, .st-done, .st-refused, .st-cancelled, .st-archived) { color: var(--st); }
.ds .ds-calendar-event:is(.st-draft, .st-planned-light, .st-planned, .st-planned-dark, .st-progress, .st-blocked, .st-done, .st-refused, .st-cancelled, .st-archived) .ds-calendar-event-bar { background: var(--st); }

/* ---- CHIP (filter pill)------------------------------------------------ */
.ds .chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font: var(--weight-medium) var(--text-sm)/1 var(--font-sans);
  color: var(--text-secondary);
  background: var(--surface);
  border: 1px solid var(--field-border);
  border-radius: var(--radius-pill);
  padding: 7px 13px;
  cursor: pointer;
  white-space: nowrap;
}
.ds .chip:hover             { background: var(--surface-hover); }
.ds .chip.on               { border-color: var(--primary); background: var(--primary-soft); color: var(--primary-700); }
.ds .chip:focus-visible    { outline: none; box-shadow: var(--ring); }
/* Une rangee de pastilles (filtres d une colonne de liste, raccourcis). */
:is(.ds .chip-group, .ds.chip-group) {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
/* Le point de couleur d une valeur : 8px, le meme que la pastille de statut d une
   cellule, pour que la cellule et la pastille qui la filtre se lisent comme la
   meme marque. En tete a l ecran (`order: -1`), en fin dans le DOM (FontAwesome). */
.ds .chip-dot {
  order: -1;
  flex: none;
  width: 8px;
  height: 8px;
  font-size: 8px;
}
.ds .chip.skeleton { min-width: 6.5rem; min-height: 30px; border-color: transparent; }
/* Rangee d ONGLETS en pastilles : le second niveau d onglets (`hm-tabset headers-type="chips"`),
   sous une barre d onglets soulignes — comme les modules de l inbox sous Manager / Valideur.
   Une ligne, jamais a la ligne ni en defilement : le surplus va dans « ⋯ » (hmTabSet.js), et
   pendant la mesure la rangee rogne ce qui depasse (`is-measuring`, comme `.tabs`).
   La rangee est une CARTE : c est la tete de la page de rubrique. Quand le volet ouvert commence
   par la barre d une fiche (`self-service`, plus bas), la carte se PROLONGE sur cette barre, sous
   un filet : les onglets, la recherche, les filtres et les actions forment un seul bloc, qui
   commande les cartes de dessous. */
:is(.ds .chip-group, .ds.chip-group).is-tabs {
  flex-wrap: nowrap;
  padding: var(--space-3) 18px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--card-shadow);
}
:is(.ds .chip-group, .ds.chip-group).is-tabs.is-measuring { overflow-x: clip; }
:is(.ds .chip-group, .ds.chip-group).is-tabs > .chip { flex: none; }
:is(.ds .chip-group, .ds.chip-group).is-tabs > .chip[hidden] { display: none; }
:is(.ds .chip-group, .ds.chip-group).is-tabs > .ac { flex: none; }
/* Rangee COMPACTE : la legende d une liste (hmLegend.html). Elle vit sur le fond de la page,
   au-dessus des donnees : a la taille d un badge (12px, ~24px de haut), elle reste plus discrete
   que ce qu elle filtre, et une pastille activee devient le badge de son statut SANS changer de
   taille. 24px reste la cible minimale recommandee pour un clic. Les autres pastilles (filtres
   de date, couloirs de l inbox) gardent leur taille. */
:is(.ds .chip-group, .ds.chip-group).is-compact .chip {
  gap: 6px;
  font: var(--weight-medium) var(--text-xs)/1 var(--font-sans);
  padding: 5px 11px;
}
:is(.ds .chip-group, .ds.chip-group).is-compact .chip.skeleton { min-height: 24px; }
/* Le NOMBRE de lignes d une valeur, apres son libelle (legende d une liste). Meme couleur que
   le libelle — dans une pastille active il suit donc la couleur du badge — mais en graisse
   normale, le libelle (demi-gras) restant la chose a lire. Pas d opacite : le nombre doit rester
   lisible. Chiffres a chasse fixe : les pastilles ne bougent pas quand les nombres changent. */
.ds .chip-count {
  font-weight: var(--weight-regular);
  font-variant-numeric: tabular-nums;
}
/* Une pastille qui SIGNALE un probleme (les erreurs d une fiche, quand il y en a) : elle prend les
   teintes du badge `b-danger` des le repos, pour se remarquer sans etre cliquee. Active, son filet
   passe a la teinte de texte : on garde le rouge, pas le primaire de `.chip.on`. */
.ds .chip.tone-danger          { background: var(--danger-soft); border-color: var(--danger-border); color: var(--danger-fg); }
.ds .chip.tone-danger:hover    { background: color-mix(in srgb, var(--danger-soft) 70%, var(--danger-border)); }
.ds .chip.tone-danger.on       { border-color: var(--danger-fg); }
/* UN STATUT FILTRE, LES AUTRES RECULENT. Seulement quand une pastille de STATUT est active (pas
   « Tous » : au repos la barre reste nette, rien a mettre en avant). Ni opacite sur toute la
   pastille, ni texte grise : elles auraient l air DESACTIVEES alors qu elles restent cliquables.
   Seul le POINT recule (65 %) ; le libelle garde sa couleur. Au survol, le point reprend sa teinte. */
:is(.ds .chip-group, .ds.chip-group).is-compact:has(> .chip.on > [class*="st-"]) > .chip:not(.on):not(:hover) > .chip-dot {
  opacity: .65;
}
/* PASTILLE DE STATUT ACTIVE = LE BADGE DE SON STATUT. Au repos, la pastille reste un bouton
   neutre (fond blanc, filet, point de la teinte) : la barre de filtres est calme, et on ne la
   confond pas avec les badges — des valeurs — du tableau. Une fois active, elle prend le fond
   doux et le texte du badge de sa categorie (`b-*`, plus haut), filet de la teinte en plus pour
   dire « applique » : on retrouve, sur chaque ligne restante, exactement la marque qu on a
   cliquee. « Tous » et les legendes qui ne sont pas des statuts gardent l actif du primaire.
   Le point (`chip-dot`) porte la categorie ; FontAwesome le remplace par un <svg> qui garde
   ses classes, d ou `:has(> .st-*)`. */
/* OPTION D URGENCE (`b-danger` : tache en retard ; INVENTAIRE-STATUTS.md §4, hors palette de statut).
   Dans la legende, la classe est posee sur le POINT de la pastille : il prend la teinte d erreur,
   sans le fond doux du badge ; active, la pastille prend le badge `b-danger`, comme un statut
   prend le sien. */
.ds :is(.chip-dot, .cell-dot > *).b-danger { background: none; color: var(--danger); }
.ds .chip.on:has(> .b-danger) { background: var(--danger-soft); border-color: var(--danger-border); color: var(--danger-fg); }
.ds .chip.on:has(> .st-draft)         { background: var(--status-draft-soft);         border-color: var(--status-draft);         color: var(--status-draft-fg); }
.ds .chip.on:has(> .st-planned-light) { background: var(--status-planned-light-soft); border-color: var(--status-planned-light); color: var(--status-planned-light-fg); }
.ds .chip.on:has(> .st-planned)       { background: var(--status-planned-soft);       border-color: var(--status-planned);       color: var(--status-planned-fg); }
.ds .chip.on:has(> .st-planned-dark)  { background: var(--status-planned-dark-soft);  border-color: var(--status-planned-dark);  color: var(--status-planned-dark-fg); }
.ds .chip.on:has(> .st-progress)      { background: var(--status-progress-soft);      border-color: var(--status-progress);      color: var(--status-progress-fg); }
.ds .chip.on:has(> .st-blocked)       { background: var(--status-blocked-soft);       border-color: var(--status-blocked);       color: var(--status-blocked-fg); }
.ds .chip.on:has(> .st-done)          { background: var(--status-done-soft);          border-color: var(--status-done);          color: var(--status-done-fg); }
.ds .chip.on:has(> .st-refused)       { background: var(--status-refused-soft);       border-color: var(--status-refused);       color: var(--status-refused-fg); }
.ds .chip.on:has(> .st-cancelled)     { background: var(--status-cancelled-soft);     border-color: var(--status-cancelled);     color: var(--status-cancelled-fg); }
.ds .chip.on:has(> .st-archived)      { background: var(--status-archived-soft);      border-color: var(--status-archived);      color: var(--status-archived-fg); }

/* ---- ONGLETS ------------------------------------------------------------
   Recette du design system (`audit/composants.html` §05) : barre a filet,
   onglet actif en `--primary`, souligne par un `::after` de 2px.

   Le portail rend exactement le balisage du specimen : `<button class="tab">`
   enfant direct de `.tabs`, l etat actif en `.on`. Aucune classe Bootstrap,
   aucun `<li>`, aucun `.active` — d ou une recette sans un seul `!important`
   et sans selecteur de compatibilite. C est le bouton qui est l element flex :
   il est etire sur toute la hauteur de la barre, donc son bas coincide avec le
   filet et le soulignement se cale dessus par construction.

   `hmTabSet.js` lit ce vocabulaire : `_layoutTabs` mesure les `.tab` de
   `.tabs`. Renommer l une de ces deux classes demande de mettre la directive
   a jour.

   LA BARRE NE DEFILE PAS. Ce qui ne tient pas sur la ligne va dans un menu
   « ⋯ » (`.ac` en dernier enfant de `.tabs`, recette `.ac-menu` plus bas),
   l onglet actif restant toujours visible — meme regle que les pastilles de
   legende (hmLegend.js). Un onglet masque porte l attribut `hidden`.

   Ne pas remettre `nav nav-tabs` sur le conteneur : `sitecss.css` y redessine
   l onglet en boite, a une specificite plus haute et en `!important`
   (`.nav-tabs > li.active > a { border-color: #cfdde2 !important }`, (0,3,1)),
   et `ds-bridge.css` y force la couleur du libelle. Toute la guerre de
   specificite repart de la.

   Remplace `<span class="bordertab">`, le soulignement porte par un noeud vide
   et peint dans les feuilles PAR CLIENT (`scss/css-client.css`,
   `scss/Orange/`, `scss/materia/`). Ces regles sont inertes cote `hm-tabset` :
   ne pas les reintroduire.

   PORTEE : `.ds` se pose sur la barre ELLE-MEME, pas sur un ancetre. Le
   conteneur de `hm-tabset` enveloppe aussi `.tab-content`, donc le CONTENU des
   onglets : y declarer `.ds` ferait mordre toutes les recettes du design
   system (`.form-control`, `.symbol.required`, `.cs-select`, `.date-picker`…)
   sur le contenu non migre de 107 ecrans. D ou les deux formes de selecteur —
   `.ds.tabs` quand les deux classes sont sur le meme noeud, `.ds .tabs` quand
   un ancetre porte deja la portee. */
.ds .tabs,
.ds.tabs {
  display: flex;
  gap: var(--space-1);
  /* Le portail rend une `<div>` ; ces trois lignes gardent la recette juste si
     `.tabs` se pose un jour sur un `<ul>` (uib-tabset en rend un). */
  margin: 0;
  padding: 0;
  list-style: none;
  /* Le filet gris est une ombre interne, pas un `border-bottom` : pendant la
     mesure la barre rogne (`is-measuring`), et `overflow` rogne au PADDING box.
     Une bordure tomberait hors du cadrage et le soulignement pose dessus serait
     coupe. */
  box-shadow: inset 0 -1px 0 var(--border);
}
/* LE TEMPS DE LA MESURE, tous les onglets sont affiches (hmTabSet.js) : la barre
   rogne ce qui depasse a droite au lieu d elargir la page. `clip` et non
   `hidden` : il ne cree pas de zone de defilement, et l autre axe reste
   `visible`. Hors mesure, rien n est rogne : le menu « ⋯ » s ouvre sous la
   barre et deborde a gauche du bouton. */
:is(.ds .tabs, .ds.tabs).is-measuring { overflow-x: clip; }
:is(.ds .tabs, .ds.tabs) > .tab[hidden] { display: none; }
/* Le menu des onglets qui ne tiennent pas : il suit le dernier onglet visible et
   garde sa largeur. */
:is(.ds .tabs, .ds.tabs) > .ac { flex: none; }
/* Le glyphe d un onglet (« ⋯ ») : enveloppe, parce que FontAwesome remplace le
   `<i>` par un `<svg>`. */
.ds .tab-glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--icon-md, 16px);
  height: var(--icon-md, 16px);
}
.ds .tab-glyph > :is(i, svg) { width: 100%; height: 100%; }

/* La ligne de tete de `hm-tabset` (hmTabset.html) : les onglets, la meme sur telephone
   (ce qui ne tient pas va dans « Plus » ; plus de fleches mobiles depuis le 08/10/2026).
     .tabs-bar           la ligne
     .tabs-panels        les volets ; `has-bar` quand une barre est rendue au-dessus
       .tabs-panel         un volet (l inactif porte l attribut `hidden` : PAS de `ng-show`, qui passe par ngAnimate et perdait le retrait de `ng-hide` au rechargement)
   Remplace `tab-content row` / `tab-pane` (Bootstrap) : le `row` annulait en marge
   negative le retrait lateral de `.tab-content`, les deux ensemble ne faisaient
   rien — sauf en vertical, ou le legacy posait `15px 0 70px !important` a CHAQUE
   niveau d onglets imbriques. Un seul ecart ici, et seulement sous une barre. */
.ds .tabs-panels { position: relative; }   /* comme `.tab-content` : repere des contenus poses en absolu */
/* LE FIL D ARIANE EST AU-DESSUS DES ONGLETS, UNE FOIS. Chaque page a onglets le pose en tete, avant
   sa barre : le general (ou je suis dans le portail) avant le particulier (quelle vue de la page).
   Les vues d onglet portaient souvent leur propre fil, qui s affichait donc SOUS les onglets, et
   apparaissait ou disparaissait d un onglet a l autre. Un fil pose dans un volet est masque ; la
   meme vue, affichee seule (hors onglet), garde le sien. Le composant porte `.ds.crumbs-host`
   sur son propre element (hmBreadcrumb.js).
   Meme regle pour la page d une rubrique du profil (`profile-view`, new-profil.html) : les
   rubriques sont des onglets de navigation, sans volet, et le fil est pose en tete du profil. */
:is(.tabs-panel, .profile-view) .ds.crumbs-host { display: none; }
.ds .tabs-panels.has-bar { padding-top: var(--space-4); }
.ds .tabs .tab,
.ds.tabs .tab {
  font: var(--font-label);
  /* `--text-secondary` et non `--text-muted` : un onglet inactif est une COMMANDE a lire, pas une
     note. Mesure : --text-muted fait 3,85:1 sur blanc, sous le minimum de 4,5:1 d un texte ;
     --text-secondary fait 6,12:1 (8,73:1 en sombre). Les onglets paraissaient eteints (28/09/2026). */
  color: var(--text-secondary);
  background: none;
  border: none;
  /* Coins du haut arrondis : le fond doux de l onglet actif (plus bas) se lit comme un onglet. */
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  margin: 0;
  /* Zone cliquable genereuse (~46px de haut) : un onglet est une cible, pas une etiquette. */
  padding: 14px 16px;
  /* En ligne flex : un onglet peut porter une icone (profil), remise devant son libelle par
     `ico-lead` (order: -1), a la couleur de l onglet. */
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  position: relative;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  /* `flex: 0 0 auto` : un onglet garde la largeur de son libelle. Par defaut
     un element flex se comprime jusqu a sa largeur mini, d ou les libelles
     longs (« Compteurs des absences avec acquisition ») casses sur deux
     lignes. C est l onglet qui passe dans le menu « ⋯ », pas le libelle qui
     retrecit. */
  flex: 0 0 auto;
}
.ds .tabs .tab:hover,
.ds.tabs .tab:hover { color: var(--text-primary); background: var(--surface-hover); }
/* Le `<button>` est focusable au clavier, ce que l ancien `<a no-href>` n etait
   pas : il faut un anneau. Le design system pose `box-shadow: var(--ring)`,
   soit un halo de 3px A L EXTERIEUR — la barre defile et rogne au padding box,
   il serait coupe en haut et en bas. Meme epaisseur et meme couleur, mais
   dessines vers l interieur. */
.ds .tabs .tab:focus-visible,
.ds.tabs .tab:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 3px var(--focus-ring);
}
/* L ONGLET ACTIF : fond doux ET soulignement. Le soulignement seul, fin et en bas, se perdait
   dans une barre de vingt onglets (profil) ; le fond designe l onglet ouvert d un coup d oeil. */
.ds .tabs .tab.on,
.ds.tabs .tab.on { color: var(--primary-700); background: var(--primary-soft); }
/* `--primary-700` : le texte primaire SUR FOND DOUX, comme la pastille active (`.chip.on`). En clair
   un cran plus fonce que le primaire, en sombre ECLAIRCI (tokens/colors.css) : --primary brut se
   lisait mal sur le fond doux sombre. Le soulignement, lui, reste en --primary. */
.ds .tabs .tab.on::after,
.ds.tabs .tab.on::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: var(--primary);
}

/* ---- CARD -------------------------------------------------------------- */
/* Meme vocabulaire que la fenetre plus bas (`modal-head` / `-body` / `-foot`) :
   une classe par role, la meme sur tous les ecrans. `.card` seule reste la
   surface elevee d avant, pour ne rien changer aux ecrans qui l utilisent
   deja ; les parties et le variant `flat` sont additifs. */
.ds .card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--card-shadow);
}

/* Carte a plat : filet discret, aucune elevation. C est la carte de contenu par
   defaut d un tableau de bord — l elevation y designe le petit nombre de blocs
   qui appellent une action, pas chaque bloc d information. */
.ds .card.flat {
  border-color: var(--border-subtle);
  box-shadow: none;
}

/* La carte ne pade pas : chaque partie se pade elle-meme, sinon un corps qui
   defile verrait son ascenseur rentrer de la valeur du padding au lieu de
   longer l arete. */
.ds .card {
  padding: 0;
  display: flex;
  flex-direction: column;
}

.ds .card-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  /* 18px : gouttiere de carte du design system, sous `--pad-card` (20px) parce
     qu une carte de tableau de bord est plus dense qu une carte de contenu. */
  padding: var(--space-3) 18px;
  /* Hauteur d en-tete constante d une carte a l autre, y compris quand l en-tete
     ne porte qu un titre : c est l alignement des en-tetes entre deux cartes
     cote a cote qui se joue la. */
  min-height: 61px;
  box-sizing: border-box;
  border-bottom: 1px solid var(--border-subtle);
}

.ds .card-head > h2,
.ds .card-head > h3,
.ds .card-head > h4,
.ds .card-head > h5,
.ds .card-head > .card-title {
  flex: 1;
  min-width: 0;
  margin: 0;
  font: var(--font-h3);
  color: var(--text-primary);
}

/* Pastille de glyphe de l en-tete. Neutre : `--primary` est reserve a l
   actionnable, une carte d information ne crie pas. */
.ds .card-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  flex: none;
  border-radius: var(--radius-md);
  background: var(--surface-sunken);
  color: var(--text-muted);
}

/* TEINTES DE LA PASTILLE : quand le glyphe porte une FAMILLE (le sujet d une
   statistique, un type d objet), la pastille prend la teinte douce de cette famille
   semantique. `tone-neutral` est la pastille par defaut, ecrite pour etre demandee
   explicitement. */
.ds .card-icon.tone-primary { background: var(--primary-soft);       color: var(--primary-700); }
.ds .card-icon.tone-info    { background: var(--info-soft);          color: var(--info-fg); }
.ds .card-icon.tone-success { background: var(--success-soft);       color: var(--success-fg); }
.ds .card-icon.tone-warning { background: var(--warning-soft);       color: var(--warning-fg); }
.ds .card-icon.tone-danger  { background: var(--danger-soft);        color: var(--danger-fg); }
.ds .card-icon.tone-violet  { background: var(--accent-violet-soft); color: var(--accent-violet-fg); }
.ds .card-icon.tone-neutral { background: var(--surface-sunken);     color: var(--text-secondary); }
/* Un KPI qui COMPTE UN STATUT prend la couleur de ce statut (jetons `--status-*`, comme les badges
   `b-*`) : on retrouve sur la tuile la couleur des pastilles qu elle filtre au clic. Les autres KPI
   gardent les teintes semantiques ci-dessus (un nombre se lit, il ne signale pas). */
.ds .card-icon.tone-draft     { background: var(--status-draft-soft);     color: var(--status-draft-fg); }
.ds .card-icon.tone-planned   { background: var(--status-planned-soft);   color: var(--status-planned-fg); }
.ds .card-icon.tone-progress  { background: var(--status-progress-soft);  color: var(--status-progress-fg); }
.ds .card-icon.tone-blocked   { background: var(--status-blocked-soft);   color: var(--status-blocked-fg); }
.ds .card-icon.tone-done      { background: var(--status-done-soft);      color: var(--status-done-fg); }
.ds .card-icon.tone-refused   { background: var(--status-refused-soft);   color: var(--status-refused-fg); }
.ds .card-icon.tone-cancelled { background: var(--status-cancelled-soft); color: var(--status-cancelled-fg); }
.ds .card-icon.tone-archived  { background: var(--status-archived-soft);  color: var(--status-archived-fg); }

/* LE GLYPHE D UNE TUILE — a ne pas confondre avec la pastille ci-dessus.

   `.card-icon` est une PASTILLE : une boite de 30px, un fond en creux, un glyphe
   discret dedans. Elle accompagne un titre dans un en-tete, elle ne le porte pas.

   Un glyphe de tuile, lui, EST le sujet : le dossier d une carte de dossier, le
   type d un fichier. Il se lit a distance, comme une vignette. Mis dans une
   pastille il perd les deux tiers de sa surface et se fond dans son fond — c est
   ce qui est arrive aux cartes de dossiers, ou le dossier ambre est devenu un
   detail gris.

   30px, c est la taille que ces ecrans lui donnaient deja (`font-30`) ; elle
   entre ici pour que les prochaines tuiles la reprennent sans la redecider. */
.ds .card-glyph {
  font-size: 30px;
  line-height: 1;
}

.ds .card-body {
  flex: 1;
  min-height: 0;
  padding: var(--space-4) 18px;
}

.ds .card-foot {
  padding: 10px 18px var(--space-3);
  border-top: 1px solid var(--border-subtle);
}

/* SECTION D UN FORMULAIRE (`form-section`) ET GRILLE DE QUESTIONS (`form-questions`) : formulaire
   d entretien, d enquete, de quiz. Une QUESTION est une carte ; la section n en est pas une (des
   clients mettent toutes leurs questions dans une seule section : une carte de section ne ferait
   qu une boite autour de toute la page). Elle est un titre souligne d un filet, au-dessus de ses
   questions.
     .form-section          la section : sa tete, puis son contenu
       .form-section-head     titre (et description) + bouton replier, filet de la couleur du client
         .form-section-title    le titre, en couleur du client ; la description dessous (`ds-hint`)
     .form-questions        les questions (et sous-sections), une par ligne, pleine largeur
   Les enfants de la grille sont des elements de directive (`survey-question`, `survey-section`) :
   ils passent en colonne flex, leur carte prend toute la largeur. */
.ds .form-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.ds .form-section-head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding-bottom: var(--space-2);
  /* La couleur du client repere la section parmi des cartes neutres (choix du 13/10/2026). */
  border-bottom: 2px solid var(--primary);
}
.ds .form-section-title {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  font: var(--font-h3);
  color: var(--primary-700);
}
.ds .form-questions {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-4);
}
.ds .form-questions > * {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.ds .form-questions > * > .card { flex: 1; }
/* Le titre d une question est du texte riche (`<p>`) : sans marge, et le dernier paragraphe en
   ligne pour que l asterisque d obligation le suive au lieu de passer dessous. */
.ds .form-questions .card-title p { margin: 0; }
.ds .form-questions .card-title p:last-child { display: inline; }
/* Les actions d un en-tete de carte, poussees au bord de fin : l ordre du DOM est
   l ordre lu, `margin-left: auto` porte le groupe a droite du titre ou de la
   bascule d affichage. */
.ds .card-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-left: auto;
}

/* CARTE SANS CADRE : les memes parties, sans surface, filet, rayon ni ombre, et
   sans retrait lateral. Pour une carte posee DANS une surface qui en porte deja
   une — une liste dans une fenetre de saisie, dans un widget — ou une seconde
   boite ferait un cadre dans le cadre. */
.ds .card.bare {
  background: none;
  border: none;
  border-radius: 0;
  box-shadow: none;
}
.ds .card.bare > .card-head,
.ds .card.bare > .card-body,
.ds .card.bare > .card-foot {
  padding-left: 0;
  padding-right: 0;
}

/* ---- COQUE (`shell`) ------------------------------------------------------
   Meme principe que la carte : un composant, une classe par role.

     .shell                 la coque, 100vh ; porte aussi `ds` (d ou `.ds.shell`)
     .shell-nav             le menu : rail + panneau
       .is-large / .is-compact   mode epingle (appShellCtrl.navModeClass)
       .is-revealed              panneau replie ouvert au survol / au focus
     .shell-rail            colonne d icones
       .shell-rail-roles      les espaces (defilement propre)
       .shell-rail-btn        un bouton du rail (`.active`, `.utility`)
         .shell-rail-tile       sa tuile de glyphe
         .shell-rail-label      son libelle
         .shell-rail-dot        pastille « nouveau » sur la tuile
       .shell-rail-slot       emplacement de squelette, a la geometrie d un bouton
       .shell-rail-divider / -utils / -avatar / -version
     .shell-panel           le panneau du menu
       .shell-panel-head      son en-tete : marque, espace, perimetre
       .shell-brand (-logo, ou -empty « Pas de logo » s il manque, puis -word : le nom de la societe ;
                     -tile n est plus que le squelette de chargement)
       .shell-space (-icon, -label)      l espace actif
       .shell-scope-row, .shell-scope (-icon, -text, -name, -value, -action)
       .shell-menu            la liste des menus (defilement propre)
         .shell-menu-node, .shell-menu-item (-icon, -label), .shell-menu-chevron
         (.is-closed), .shell-menu-children, .shell-menu-child-row (.is-favorite),
         .shell-menu-child, .shell-menu-fav (etoile de favori), .shell-menu-slot,
         .shell-menu-empty (-icon, -text)
     .shell-body            topbar + zone de contenu
     .shell-content         la zone de contenu, qui DEFILE (pas la fenetre)

   Geometrie reprise a l identique de l ancienne coque (`.home-v3-shell`,
   styleScss.scss). REGLE : cette section ne style que la coque. Ce dont les
   ANCIENNES pages ont besoin pour y vivre (largeur de leurs `.container`,
   boutons `.btn-*` legacy) releve de
   classes legacy : c est styleScss.scss qui le porte. Une classe n est stylee
   qu a un seul endroit. */
.ds.shell {
  /* Source unique des deux largeurs fixes : le nav anime sa largeur (rail +
     panneau), le panneau replie est decale de la largeur du rail, et les enfants
     du panneau tiennent sa largeur pendant qu il est rogne — tous en derivent. */
  --shell-rail-width: 76px;
  --shell-panel-width: 248px;
  display: flex;
  width: 100%;
  height: 100vh;
  overflow: hidden;
  background: var(--surface-sunken);
}
/* `--ring` est compose sur :root avec le `--focus-ring` du CLAIR : un var()
   se resout la ou la propriete est DECLAREE. Le sombre est pose sur la coque et
   non sur <html> : on redeclare donc l anneau ici pour qu il reprenne le
   `--focus-ring` sombre (55 %). */
.ds.shell[data-theme="dark"] { --ring: 0 0 0 3px var(--focus-ring); }

/* Le contenu legacy vit dans le meme contexte d empilement que la coque et use
   de z-index eleves (onglets actifs 99, listes deroulantes 1000). Sous 1100px le
   panneau passe en surimpression : 1035 le garde au-dessus du contenu et sous
   le fond de modale Bootstrap (1040) et uib-modal (1050). */
.ds .shell-nav {
  position: relative;
  z-index: 1035;
  flex: none;
  display: flex;
  height: 100%;
  /* C est le NAV qui porte la largeur, pas seulement le panneau : en replie le
     panneau sort du flux, et un nav dimensionne par son contenu tomberait a la
     largeur du rail en une image pendant que le panneau anime encore la sienne. */
  width: calc(var(--shell-rail-width) + var(--shell-panel-width));
  transition: width var(--duration-base) var(--ease-standard);
}
.ds .shell-nav.is-compact { width: var(--shell-rail-width); }

/* ---- rail ---- */
.ds .shell-rail {
  position: relative;
  z-index: 2;
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: var(--shell-rail-width);
  /* 6px en haut : ajoutes aux 6px du bouton, la premiere tuile tombe a y=12,
     au niveau de la tuile « HR » centree dans l en-tete de 64px du panneau. */
  padding: 6px 8px 12px;
  background: var(--surface);
  border-right: 1px solid var(--border);
}
.ds .shell-rail-roles {
  flex: 1;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 2px;
  overflow-x: hidden;
  overflow-y: auto;
  scrollbar-width: none;
}
.ds .shell-rail-roles::-webkit-scrollbar { display: none; }
.ds .shell-rail-skeleton {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.ds .shell-rail-divider {
  width: 100%;
  margin: 6px 0 8px;
  border-top: 1px solid var(--border-subtle);
}
.ds .shell-rail-utils {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}
.ds .shell-rail-avatar { margin-top: 12px; }
/* Metadonnee, pas action : legende discrete. */
.ds .shell-rail-version {
  margin-top: 10px;
  font-size: 10px;
  line-height: 1;
  letter-spacing: .02em;
  white-space: nowrap;
  color: var(--text-muted);
}

.ds .shell-rail-btn,
.ds .shell-rail-slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  width: 100%;
  padding: 6px 2px;
}
.ds .shell-rail-btn {
  border: none;
  border-radius: var(--radius-md);
  background: transparent;
  text-decoration: none;
  cursor: pointer;
}
.ds .shell-rail-btn:hover { background: var(--surface-hover); }
.ds .shell-rail-btn:hover,
.ds .shell-rail-btn:focus { text-decoration: none; }
/* L anneau est sur la TUILE : `.shell-rail-roles` est en `overflow-x: hidden`,
   un anneau trace aux bords du bouton y serait rogne. */
.ds .shell-rail-btn:focus-visible { outline: none; }
.ds .shell-rail-btn:focus-visible .shell-rail-tile { box-shadow: var(--ring); }

.ds .shell-rail-tile {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-md);
  background: var(--surface-sunken);
  color: var(--text-secondary);
  /* La reference donne 19px aux glyphes ; laisses au 14px ambiant, ils sont
     trop petits et trop fins dans une tuile de 40px. */
  font-size: 18px;
}
.ds .shell-rail-tile svg { width: 18px; height: 18px; }
.ds .shell-rail-btn.active .shell-rail-tile {
  background: var(--primary);
  color: var(--on-primary);
}
.ds .shell-rail-btn.utility .shell-rail-tile { background: transparent; color: var(--text-muted); }
.ds .shell-rail-btn.utility:hover .shell-rail-tile { background: var(--surface-hover); }
.ds .shell-rail-label {
  font-size: 11px;
  line-height: 1.1;
  text-align: center;
  white-space: nowrap;
  color: var(--text-muted);
}
.ds .shell-rail-label > .skeleton-line { width: 34px; }
/* Compteur sur une tuile (A traiter, barre du bas) : cale sur le GLYPHE (18px au centre) et non sur
   le coin de la tuile de 40px, ou il flottait loin de l icone et touchait le haut de la barre. Il part
   juste a droite du centre et s etend vers la droite, comme une pastille d application. */
.ds .shell-rail-tile > .ds-btn-count {
  top: 2px;
  right: auto;
  left: calc(50% + 2px);
}
.ds .shell-rail-dot {
  position: absolute;
  top: 8px;
  right: 9px;
  width: 7px;
  height: 7px;
  border: 1.5px solid var(--surface);
  border-radius: 50%;
  background: var(--danger);
}

/* ---- panneau ---- */
.ds .shell-panel {
  display: flex;
  flex-direction: column;
  width: var(--shell-panel-width);
  /* Tenu a 0 dans TOUS les etats : le `min-width: auto` d un element flex
     opposerait sa largeur de contenu au `width: 0` du replie, et le restaurer a
     l ouverture figerait la largeur animee des la premiere image. Ce sont les
     enfants qui portent la largeur minimale : le texte garde sa mise en page
     pendant que le panneau le rogne. */
  min-width: 0;
  height: 100%;
  overflow: hidden;
  background: var(--surface);
  border-right: 1px solid var(--border);
  transition: width var(--duration-base) var(--ease-standard);
}
.ds .shell-nav.is-large .shell-panel { position: relative; }
.ds .shell-nav.is-compact .shell-panel {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--shell-rail-width);
  width: 0;
  border-right: none;
}
.ds .shell-nav.is-compact.is-revealed .shell-panel {
  z-index: 3;
  width: var(--shell-panel-width);
  box-shadow: var(--shadow-lg);
}

.ds .shell-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: var(--shell-panel-width);
  height: var(--space-16);
  padding: 6px 14px;
  border-bottom: 1px solid var(--border-subtle);
}
.ds .shell-brand-logo {
  flex: none;
  display: block;
  box-sizing: border-box;
  width: auto;
  /* 60 % de la rangee au plus : le nom de la societe garde sa place. */
  max-width: 60%;
  height: auto;
  max-height: 52px;
  border-radius: var(--radius-md);
  object-fit: contain;
  object-position: left center;
}
/* La nuit, une pastille grise garde lisible un logo detoure a encre sombre. */
.ds.shell[data-theme="dark"] .shell-brand-logo {
  padding: 4px;
  background: var(--gray-300);
}
.ds .shell-brand-tile {
  flex: none;
  width: 52px;
  height: 52px;
  border-radius: var(--radius-md);
}
/* Pas de logo client : une mention « Pas de logo », dans le style « Pas d image » des tuiles
   du catalogue (.card-media-empty). */
.ds .shell-brand-empty {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  border-radius: var(--radius-md);
  background: var(--surface-sunken);
  font-size: 12px;
  line-height: 1.15;
  text-align: center;
  color: var(--text-disabled);
}
.ds .shell-brand-word {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  font: var(--weight-bold) var(--text-base) var(--font-sans);
  color: var(--text-primary);
  white-space: nowrap;
  text-overflow: ellipsis;
}

.ds .shell-space {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: var(--shell-panel-width);
  padding: 12px 14px;
}
.ds .shell-space-label {
  flex: 1;
  overflow: hidden;
  font: var(--font-h3);
  color: var(--text-primary);
  white-space: nowrap;
  text-overflow: ellipsis;
}
.ds .shell-space-label > .skeleton-line { width: 6rem; }

/* « Perimetre : N-1 », espace Manager. La rangee porte la gouttiere et, comme
   tout enfant du panneau, sa largeur en MINIMUM ; la pilule fait 100 % de la
   rangee. Sur un seul element, min-width l emportait sur max-width et la pilule
   debordait du panneau. */
.ds .shell-scope-row {
  min-width: var(--shell-panel-width);
  padding: 0 14px 10px;
}
/* Une pilule a libelle, pas un bouton-icone : la valeur courante EST
   l information, et c est elle qui rend le reglage decouvrable. */
.ds .shell-scope {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 7px 12px;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  color: var(--text-secondary);
  font: var(--font-body-sm);
  text-align: left;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-standard),
              border-color var(--duration-fast) var(--ease-standard),
              color var(--duration-fast) var(--ease-standard);
}
.ds .shell-scope:hover,
.ds .shell-scope:focus {
  background: var(--surface-hover);
  border-color: var(--primary-border);
  color: var(--primary-700);
}
.ds .shell-scope-icon,
.ds .shell-scope-action {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
}
.ds .shell-scope-icon svg,
.ds .shell-scope-action svg { width: 14px; height: 14px; }
.ds .shell-scope-action { color: var(--text-muted); }
/* Une ligne, coupee par une ellipse : une pilule qui passe sur deux lignes
   desequilibre l en-tete du menu. La valeur complete reste dans le `title`. */
.ds .shell-scope-text {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
/* La valeur porte le poids, le prefixe reste discret : l oeil tombe sur « N-1 ». */
.ds .shell-scope-value {
  font-weight: var(--weight-bold);
  color: var(--text-primary);
}
.ds .shell-scope:hover .shell-scope-value,
.ds .shell-scope:focus .shell-scope-value,
.ds .shell-scope:hover .shell-scope-action,
.ds .shell-scope:focus .shell-scope-action { color: inherit; }

/* ---- menu ---- */
.ds .shell-menu {
  flex: 1;
  min-width: var(--shell-panel-width);
  padding: 0 10px 14px;
  overflow-x: hidden;
  overflow-y: auto;
}
.ds .shell-menu-node { margin-bottom: 2px; }

/* L emplacement de squelette a la geometrie EXACTE d une entree : meme retrait,
   meme ecart, meme police (donc meme hauteur de ligne).
   Entree principale renforcee (23/10/2026, demande) : 14px et couleur du texte principal, au-dessus
   des sous-menus en 13px gris, qui prenaient le dessus. */
.ds .shell-menu-item,
.ds .shell-menu-slot {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px 10px;
  font: var(--weight-medium) var(--text-base)/var(--leading-snug) var(--font-sans);
}
.ds .shell-menu-skeleton { padding: 6px 0; }
.ds .shell-menu-item {
  width: 100%;
  border: none;
  border-radius: var(--radius-md);
  background: transparent;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  /* !important : `ds-bridge.css` peint tout `a` en `--primary` avec !important
     (et ses :hover / :focus / :active). Les entrees feuilles SONT des <a>. */
  color: var(--text-primary) !important;
}
.ds .shell-menu-item:hover,
.ds .shell-menu-item:focus,
.ds .shell-menu-item:active { background: var(--surface-hover); color: var(--text-primary) !important; }
.ds .shell-menu-item.active,
.ds .shell-menu-item.active:hover,
.ds .shell-menu-item.active:focus,
.ds .shell-menu-item.active:active { background: var(--primary-soft); color: var(--primary-700) !important; }
.ds .shell-menu-item-icon {
  flex: none;
  font-size: 18px;
}
.ds .shell-menu-item-icon svg { width: 18px; height: 18px; }
.ds .shell-menu-item-icon.skeleton {
  display: block;
  width: 18px;
  height: 18px;
  border-radius: var(--radius-xs);
}
.ds .shell-menu-item-label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-align: left;
  white-space: nowrap;
  text-overflow: ellipsis;
}
/* Longueurs des lignes de squelette : par position, pour un menu d aspect
   naturel, et dans la feuille plutot qu en style en ligne. */
.ds .shell-menu-slot:nth-child(7n+1) .skeleton-line { width: 62%; }
.ds .shell-menu-slot:nth-child(7n+2) .skeleton-line { width: 78%; }
.ds .shell-menu-slot:nth-child(7n+3) .skeleton-line { width: 45%; }
.ds .shell-menu-slot:nth-child(7n+4) .skeleton-line { width: 70%; }
.ds .shell-menu-slot:nth-child(7n+5) .skeleton-line { width: 54%; }
.ds .shell-menu-slot:nth-child(7n+6) .skeleton-line { width: 82%; }
.ds .shell-menu-slot:nth-child(7n+7) .skeleton-line { width: 48%; }

/* Le `ng-class` est sur ce <span> stable et non sur le <i> : FontAwesome
   remplace le <i> par un <svg> apres rendu, et ngAnimate restait bloque sur la
   classe transitoire « -add » d un element que FA echangeait. */
.ds .shell-menu-chevron {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 15px;
  height: 15px;
  margin-left: auto;
  font-size: 15px;
  color: var(--text-muted);
  transition: transform var(--duration-fast) var(--ease-standard);
}
.ds .shell-menu-chevron svg { width: 15px; height: 15px; }
.ds .shell-menu-chevron.is-closed { transform: rotate(-90deg); }

.ds .shell-menu-children {
  display: flex;
  flex-direction: column;
  gap: 1px;
  margin: 2px 0 6px;
}
.ds .shell-menu-child {
  display: block;
  width: 100%;
  /* Retrait de 22px (26/10/2026) : juste a droite du libelle du groupe. Les 44px d avant visaient un
     libelle precede d une icone, que les groupes n ont plus (seul l accueil en garde une). */
  padding: 5px 10px 5px 22px;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  font: var(--font-body-sm);
  /* Interligne resserre : avec celui de `--font-body-sm` (1.5) un enfant etait
     presque aussi haut que son parent, et six entrees ouvertes faisaient deja
     defiler le panneau. ~27,5px de ligne, au-dessus des 24px de cible WCAG 2.5.8. */
  line-height: var(--leading-snug);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  color: var(--text-secondary) !important; /* meme regle `a` du pont, voir plus haut */
}
.ds .shell-menu-child:hover,
.ds .shell-menu-child:focus,
.ds .shell-menu-child:active { background: var(--surface-hover); color: var(--text-secondary) !important; }
.ds .shell-menu-child.active,
.ds .shell-menu-child.active:hover,
.ds .shell-menu-child.active:focus,
.ds .shell-menu-child.active:active {
  background: var(--primary-soft);
  color: var(--primary-700) !important;
  font-weight: var(--weight-medium);
}
/* Anneau clavier : en replie, tabuler dans le menu est ce qui l ouvre — une
   entree focalisee identique aux autres laisserait l utilisateur perdu. */
.ds .shell-menu-item:focus-visible,
.ds .shell-menu-child:focus-visible { outline: none; box-shadow: var(--ring); }

/* Etoile de favori, cachee hors survol sauf sur une favorite. Place reservee sur une
   favorite seulement : ailleurs, la reserver ferait passer les libelles longs sur deux lignes. */
.ds .shell-menu-child-row { position: relative; }
.ds .shell-menu-child-row.is-favorite > .shell-menu-child { padding-right: 34px; }
.ds .shell-menu-fav {
  position: absolute;
  top: 50%;
  right: 6px;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  opacity: 0;
}
.ds .shell-menu-fav svg { width: 13px; height: 13px; }
/* Apparition au survol : appareils A SOURIS seulement (`hover: hover`). Sur iPhone, Safari applique
   `:hover` au premier toucher ; si ce survol fait APPARAITRE quelque chose (l etoile), il s arrete la
   et n envoie pas le clic : il fallait toucher deux fois chaque entree du menu. Au doigt, l etoile
   n apparait donc pas (ajouter un favori se fait sur ordinateur) ; celle d une favorite reste visible. */
@media (hover: hover) {
  .ds .shell-menu-child-row:hover > .shell-menu-fav,
  .ds .shell-menu-child-row:focus-within > .shell-menu-fav { opacity: 1; }
  /* Fond de l etoile = fond de la ligne : masque la fin d un libelle long sans cadre.
     Survol porte par la ligne, pour tenir quand le curseur passe sur l etoile. */
  .ds .shell-menu-child-row:hover > .shell-menu-child:not(.active),
  .ds .shell-menu-child-row:hover > .shell-menu-fav { background: var(--surface-hover); }
  .ds .shell-menu-child-row:hover > .shell-menu-child.active + .shell-menu-fav { background: var(--primary-soft); }
}
.ds .shell-menu-child-row.is-favorite > .shell-menu-fav,
.ds .topbar-favs-row > .shell-menu-fav { opacity: 1; color: var(--favorite); }
.ds .shell-menu-fav:focus-visible { outline: none; box-shadow: var(--ring); }
/* Sans survol (tactile), l etoile cachee ne doit pas intercepter un appui sur la fin du libelle. */
@media (hover: none) {
  .ds .shell-menu-child-row:not(.is-favorite) > .shell-menu-fav { pointer-events: none; }
}

/* Espace sans aucun menu accessible : icone + message, jamais une colonne
   blanche. La hauteur minimale garde le panneau a une taille comparable. */
.ds .shell-menu-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 180px;
  padding: 24px 18px;
  text-align: center;
}
.ds .shell-menu-empty-icon { font-size: 26px; color: var(--text-muted); }
.ds .shell-menu-empty-text {
  font-size: 12px;
  line-height: 1.4;
  color: var(--text-secondary);
}

/* ---- tiroir (smartphone, < 600px) ----
   Le rail et le panneau sortent du flux et glissent depuis la gauche ; le nav est
   toujours en `is-large` (pas de mode replie), `is-open` l ouvre. Ferme, il est
   `visibility: hidden` APRES le glissement : ses entrees sortent du clavier.
     .shell-backdrop    le fond assombri ; le toucher referme le tiroir */
.ds .shell-backdrop { display: none; }
@media only screen and (max-width: 599px) {
  /* Au-dessus des barres du haut (1034) et du bas (1033), sous les modales (1040). */
  .ds .shell-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1035;
    display: block;
    background: var(--scrim);
  }
  .ds .shell-nav {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 1036;
    max-width: 88vw;
    transform: translateX(-100%);
    visibility: hidden;
    transition: transform var(--duration-base) var(--ease-standard), visibility 0s linear var(--duration-base);
  }
  .ds .shell-nav.is-open {
    transform: none;
    visibility: visible;
    box-shadow: var(--shadow-lg);
    transition: transform var(--duration-base) var(--ease-standard);
  }
}

@media (prefers-reduced-motion: reduce) {
  .ds .shell-nav,
  .ds .shell-panel,
  .ds .shell-scope,
  .ds .shell-menu-chevron { transition: none; }
}

/* ---- corps et zone de contenu ---- */
.ds .shell-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.ds .shell-content {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  background: var(--surface-sunken);
}
/* Les deux hotes de vue sous la zone de contenu (le `ng-include` de nav-v3.html,
   puis le `ui-view` de main-content.html) : pleine hauteur, pour qu une `page`
   remplisse la zone et defile en elle-meme. Un ecran qui n a pas de `page`
   (l accueil) deborde simplement et c est `.shell-content` qui defile. */
.ds .shell-view { height: 100%; }

/* ---- PAGE -----------------------------------------------------------------
   Le cadre d un ecran. Rendu par la directive `hm-wrapper` (hmWrapper.js) : les
   ~104 gabarits qui ecrivent `<div hm-wrapper>` le recoivent sans etre touches.

     .page          le cadre : pleine hauteur, colonne, et c est LUI qui DEFILE
                    (barres collantes des ecrans, suivi de section de
                    `hm-sections` sur son `scroll`)
     .page-body     le contenu de l ecran (transclu)

   Retraits : la gouttiere de 20px a gauche, a droite et en bas ; 8px seulement en
   haut, ou la topbar laisse deja de l air — 20px y poussaient tout l ecran vers
   le bas avant le premier mot (fil d Ariane compris).

   Remplace `.hm-wrapper` en coque desktop. Deux choses n en sont PAS reprises :
     - son `border-radius: 25px` : sur un fond transparent il ne se voyait pas,
       il ne faisait que rogner les coins du contenu qui defile ;
     - son `background: none !important` : la page n a pas de fond, la zone de
       contenu porte deja le sien. */
.ds .page {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: var(--space-2) var(--space-5) var(--space-5);
  overflow-y: auto;
  /* La place de la barre de defilement est RESERVEE : sans elle, la barre qui apparait
     retire ~17px a la page, une grille proche d un seuil de colonnes (gridLayout.service)
     change de colonnes, donc de hauteur, la barre disparait… et l ecran tremble sans fin.
     Meme correctif que la fenetre laterale (styleScss.scss, `.ng-aside`). */
  scrollbar-gutter: stable;
}

/* ---- LISTE (`list`) --------------------------------------------------------
   Une liste de donnees (hmGrid.html, hmCard.html, hmTreeList.html) : la bande de
   KPI, la barre, puis UNE carte.
     .list            le composant ; `is-widget` dans un widget de l accueil
     > hm-summary     la bande de KPI (sous-composant)
     > .ds-toolbar    bascule + actions, HORS de la carte, sur le fond de la page :
                      la meme barre au meme endroit dans les trois vues, et des
                      tuiles qui ne sont pas enfermees dans une carte de cartes
     > .card          la carte de la liste : `card-body` (selection + tableau),
                      `card-foot` (pastilles de filtre + pagination). Le
                      calendrier (hmCalendar.html) suit la meme anatomie */
:is(.ds .list, .ds.list) > hm-summary { display: block; }
/* Dans un widget, le cadre du widget porte le titre et les actions sont celles de
   l ecran complet : ni en-tete, ni ligne de filtres. */
:is(.ds .list, .ds.list).is-widget > .ds-toolbar,
:is(.ds .list, .ds.list).is-widget .ds-table-filters { display: none; }
/* Sous-composant a element personnalise (inline par defaut) pose dans le pied. */
:is(.ds .list, .ds.list) .card-foot > hm-pagination { display: block; }
/* Les pastilles de filtre (la legende), HORS de la carte, entre la barre et la carte : ce sont
   des commandes, comme la barre ; la carte ne porte que les donnees. Meme ecart sous elles que
   sous la barre. Grille (hmGrid.html) et calendrier (hmCalendar.html). */
:is(.ds .list, .ds.list) > hm-legend { display: block; }
:is(.ds .list, .ds.list) > hm-legend > .chip-group { margin-bottom: var(--space-4); }
/* Une liste vide le dit, au centre, discretement. */
.ds .list-empty {
  padding: var(--space-8) 0;
  text-align: center;
  font: var(--font-body);
  color: var(--text-muted);
}

/* ---- BARRE D OUTILS (`ds-toolbar`) -----------------------------------------
   La ligne de tete d un contenu, posee HORS de sa carte : les listes (hmGrid,
   hmCard, hmTreeList, hmCalendar), hmTimeline, la bascule d un `hm-tabset` en mode `seg-bar`,
   les filtres d un ecran, et les ecrans qui ne posent que la bascule d affichage.
     .ds-toolbar           la barre : bascule d affichage, puis le reste
       .ds-toolbar-title     le titre de la liste (hmGrid, hmTreeList, hmCard) : MASQUE partout — le fil
                             d Ariane nomme deja la page — sauf dans une partie de carte de rubrique
                             du profil (`rubric-part`), ou il dit de quoi parle chaque liste empilee
       .ds-toolbar-actions   les actions, poussees au bord de fin
   La marge basse est portee par les ENFANTS et non par la barre : une barre dont
   rien ne se rend (bascule absente hors d un onglet segmente, aucune action) ne
   laisse ainsi aucune trace. La bascule est ecartee quand elle ne rend pas sa `.seg`
   (`:empty` ne suffit pas : son gabarit laisse un noeud texte blanc). */
.ds .ds-toolbar,
.ds.ds-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}
.ds .ds-toolbar > *,
.ds.ds-toolbar > * { margin: 0 0 var(--space-4); }
.ds .ds-toolbar > hm-view-switch,
.ds.ds-toolbar > hm-view-switch { display: inline-flex; align-items: center; }
.ds .ds-toolbar > hm-view-switch:not(:has(> .seg)),
.ds.ds-toolbar > hm-view-switch:not(:has(> .seg)) { display: none; }
/* Un filtre pose dans la barre (periode d une liste). Item flex, le champ se
   reduirait a la largeur intrinseque de son `<input>` et tronquerait une periode
   « jj/mm/aaaa - jj/mm/aaaa » : on lui donne une largeur de base, qu il cede sur
   un ecran etroit. */
.ds .ds-toolbar > .field,
.ds.ds-toolbar > .field { flex: 0 1 20rem; min-width: 0; }
/* La legende d une liste DANS la barre (hmGrid.html, hmCalendar.html) : elle prend l ESPACE LIBRE
   entre la bascule et les actions (base 0 : sa largeur ne depend pas de son contenu, ce qui evite
   toute boucle de mesure), et ses pastilles ne passent JAMAIS a la ligne — celles qui ne tiennent
   pas vont dans le menu « +N » (hmLegend.js). */
:is(.ds .ds-toolbar, .ds.ds-toolbar) > hm-legend { display: block; flex: 1 1 0; min-width: 0; }
:is(.ds .ds-toolbar, .ds.ds-toolbar) > hm-legend > .chip-group { flex-wrap: nowrap; }
/* UN CHIFFRE MIS EN AVANT DANS LA BARRE d une liste : la moyenne d un plan de formation, des
   competences, des missions (bouton global `customTemplate` de hm-grid). Remplace
   `grid-global-label blueColor font-22`. En couleur de TEXTE, pas en `--primary` : le primaire est
   reserve a ce qui se clique, un chiffre se lit. */
.ds .ds-toolbar-figure {
  font: var(--weight-bold) var(--text-xl)/1 var(--font-sans);
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}
.ds .ds-toolbar-title {
  min-width: 0;
  font: var(--font-h2);
  color: var(--text-primary);
}
/* Le titre d une liste n est rendu visible que la ou il departage : plusieurs listes dans une meme
   carte de rubrique (CV : experiences et diplomes ; formateurs et coachs ; un plan d action par
   partie). Ailleurs, le fil d Ariane et l onglet nomment deja la liste. */
.ds .ds-toolbar > .ds-toolbar-title { display: none; }
.ds .profile-view .rubric-part .ds-toolbar > .ds-toolbar-title {
  display: block;
  font: var(--font-h3);
}
.ds .ds-toolbar-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-left: auto;
}
/* Ecran etroit : les actions (Ajouter, filtres, export) restent AU BORD DE FIN, comme sur
   ordinateur, qu il y ait ou non une bascule d affichage devant elles (decision du 08/10/2026). */
@media only screen and (max-width: 767px) {
  /* La legende (filtres de statut) sur SA ligne, pleine largeur, apres la bascule et les actions :
     au milieu d une barre passee a la ligne, elle se mesurait sur une place qui n etait pas la sienne
     et ses pastilles debordaient sur les boutons. Pleine largeur, le « +N » reprend ce qui ne tient
     pas (pas d `overflow: hidden` : il couperait le menu du « +N », ouvert dans la legende). */
  :is(.ds .ds-toolbar, .ds.ds-toolbar) > hm-legend {
    order: 1;
    flex: 1 1 100%;
  }
}

/* ---- LIGNES (`ds-rows`) ----------------------------------------------------
   Une courte liste d elements DANS une carte (les taches d un plan d action…),
   quand un tableau serait trop lourd : chaque ligne a un statut, deux ou trois
   lignes de texte, et ses actions au bord de fin.
     .ds-row             une ligne ; filet entre deux lignes
       .ds-row-status      le point de statut (`is-done` : fait) ; le texte
                           equivalent est porte par `aria-label`
       .ds-row-text        .ds-row-title (le principal), .ds-row-meta (discret),
                           .ds-row-sub (le complement)
       .ds-actions         les actions de la ligne */
.ds .ds-rows {
  display: flex;
  flex-direction: column;
}
.ds .ds-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) 0;
}
.ds .ds-row + .ds-row { border-top: 1px solid var(--border-subtle); }
.ds .ds-row:first-child { padding-top: 0; }
.ds .ds-row:last-child { padding-bottom: 0; }
.ds .ds-row-status {
  flex: none;
  display: inline-flex;
  padding-top: 5px;
  color: var(--danger);
}
.ds .ds-row-status.is-done { color: var(--success); }
.ds .ds-row-status > i,
.ds .ds-row-status > svg { width: 8px; height: 8px; font-size: 8px; }
.ds .ds-row-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.ds .ds-row-title,
.ds .ds-row-sub {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.ds .ds-row-title { font: var(--font-label); color: var(--text-primary); }
.ds .ds-row-meta  { font: var(--font-meta); color: var(--text-muted); }
.ds .ds-row-sub   { font: var(--font-body-sm); color: var(--text-secondary); }
.ds .ds-row > .ds-actions { flex: none; }
/* `is-lg` : le titre d une ligne qui ouvre un bloc consequent (une competence et ses niveaux, dans une
   question d entretien) : un cran au-dessus, et il prend la place libre de sa ligne (un nom long passe a
   la ligne a cote de sa pastille au lieu de tomber dessous). */
.ds .ds-row-title.is-lg {
  flex: 1 1 0;
  min-width: 0;
  font: var(--weight-medium) var(--text-md)/var(--leading-snug) var(--font-sans);
}
/* `is-wrap` : les textes passent a la ligne au lieu d etre tronques (liste complete
   d une fenetre flottante, ou rien ne doit etre cache). */
.ds .ds-rows.is-wrap :is(.ds-row-title, .ds-row-sub) { white-space: normal; overflow-wrap: anywhere; }
/* `is-loose` : des lignes qui portent chacune un bloc consequent (une competence : niveaux, commentaire,
   actions) : plus d air de part et d autre du filet, pour voir ou chaque bloc commence. */
.ds .ds-rows.is-loose > .ds-row { padding-block: var(--space-4); }
.ds .ds-rows.is-loose > .ds-row:first-child { padding-top: var(--space-2); }
.ds .ds-rows.is-loose > .ds-row:last-child { padding-bottom: 0; }

/* L attente d un GRAPHIQUE (frises d3…) : une boite de squelette a la hauteur
   d un graphique, dans le corps de sa carte. */
.ds .chart-skeleton {
  display: block;
  height: 240px;
  border-radius: var(--radius-md);
}

/* ---- CALENDRIER (`ds-calendar`) --------------------------------------------
   La grille d evenements de hmCalendar.html (conges, planning), posee dans le
   corps de la carte d une `list`.
     .ds-calendar                 `is-grouped` : une colonne de salaries ;
                                  `is-simplified` : widget (carte sans cadre, sans en-tete)
       .ds-calendar-head            la periode et ses creneaux, alignes sur la grille
         .ds-calendar-nav             titre de la periode + precedent / suivant
         .ds-calendar-slots           les creneaux (`.ds-calendar-slot`, `is-active`)
       .ds-calendar-row             une ligne, filet dessous
         .ds-calendar-who             le salarie
         .ds-calendar-events          ses evenements : `.ds-calendar-cell` (fond d un
                                      creneau : `is-day-off`, `is-holiday`, `is-morning`,
                                      `is-afternoon`) puis `.ds-calendar-event`
   Les colonnes, rangees et positions sont calculees par hmCalendar.js (`ng-style`) ;
   la COULEUR d un evenement vient de la legende de l ecran. */
:is(.ds .ds-calendar, .ds.ds-calendar) { --calendar-who: 280px; }
.ds .ds-calendar-grid { min-width: 0; }
.ds .ds-calendar-head {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--border);
}
:is(.ds .ds-calendar, .ds.ds-calendar).is-grouped .ds-calendar-head { padding-left: var(--calendar-who); }
.ds .ds-calendar-nav {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}
.ds .ds-calendar-period {
  margin-right: var(--space-2);
  font: var(--font-label);
  color: var(--text-primary);
}
.ds .ds-calendar-slots {
  display: grid;
  gap: 3px;
}
.ds .ds-calendar-slot {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.ds .ds-calendar-slot-title {
  overflow: hidden;
  padding: 2px var(--space-1);
  border-radius: var(--radius-sm);
  font: var(--font-meta);
  color: var(--text-muted);
  text-align: center;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.ds .ds-calendar-slot-title.is-active { background: var(--primary); color: var(--on-primary); }
.ds .ds-calendar-subslots { display: flex; }
.ds .ds-calendar-subslots > span {
  flex: 1;
  min-width: 0;
  font-size: 10px;
  color: var(--text-muted);
  text-align: center;
}
.ds .ds-calendar-subslots > span.is-active { color: var(--primary); font-weight: var(--weight-bold); }

.ds .ds-calendar-row {
  display: flex;
  border-bottom: 1px solid var(--border-subtle);
}
.ds .ds-calendar-who {
  flex: 0 0 var(--calendar-who);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-1);
  min-width: 0;
  padding: var(--space-3) var(--space-4) var(--space-3) 0;
  border-right: 1px solid var(--border);
}
.ds .ds-calendar-events {
  flex: 1;
  display: grid;
  align-items: center;
  gap: 3px;
  min-width: 0;
  min-height: 56px;
  padding: var(--space-2) 0;
}
/* Sans colonne de salaries, la grille est un seul bloc : elle garde une hauteur
   de lecture meme avec peu d evenements. */
:is(.ds .ds-calendar, .ds.ds-calendar):not(.is-grouped) .ds-calendar-events { min-height: 200px; }

.ds .ds-calendar-cell {
  display: flex;
  height: 100%;
  background: var(--surface-sunken);
}
.ds .ds-calendar-subcell { flex: 1; border-right: 1px solid var(--surface); }
.ds .ds-calendar-subcell:last-child { border-right: none; }
.ds .ds-calendar-subcell.is-day-off,
.ds .ds-calendar-subcell.is-holiday { background: var(--border); }
.ds .ds-calendar-subcell.is-morning   { background: linear-gradient(90deg, var(--surface-sunken) 50%, var(--border) 50%); }
.ds .ds-calendar-subcell.is-afternoon { background: linear-gradient(90deg, var(--border) 50%, var(--surface-sunken) 50%); }

.ds .ds-calendar-slot-event {
  height: 34px;
  padding: 5px;
  min-width: 0;
}
:is(.ds .ds-calendar, .ds.ds-calendar).is-grouped .ds-calendar-slot-event { padding: 5px 0; }
.ds .ds-calendar-event {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-width: 0;
  cursor: pointer;
}
.ds .ds-calendar-event.skeleton { border-radius: var(--radius-sm); }
.ds .ds-calendar-event-label {
  flex: 1;
  overflow: hidden;
  padding: 1px;
  font-size: 11px;
  color: var(--text-secondary);
  white-space: nowrap;
  text-overflow: ellipsis;
}
.ds .ds-calendar-event-bar {
  height: 8px;
  border-radius: 5px;
}
.ds .ds-calendar-event-bar.is-start-cut { border-top-left-radius: 0; border-bottom-left-radius: 0; }
.ds .ds-calendar-event-bar.is-end-cut   { border-top-right-radius: 0; border-bottom-right-radius: 0; }
/* Evenement annule : libelle barre, barre hachuree (visible quelle que soit sa
   couleur). */
.ds .ds-calendar-event.is-cancelled .ds-calendar-event-label { text-decoration: line-through; }
.ds .ds-calendar-event.is-cancelled .ds-calendar-event-bar {
  position: relative;
  overflow: hidden;
}
.ds .ds-calendar-event.is-cancelled .ds-calendar-event-bar::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background-image: repeating-linear-gradient(45deg, transparent 0 3px, rgba(0, 0, 0, .4) 3px 6px);
  pointer-events: none;
}
@media only screen and (max-width: 767px) {
  :is(.ds .ds-calendar, .ds.ds-calendar) { --calendar-who: 120px; }
  /* La periode et ses fleches prennent toute la largeur, sur leur ligne : dans la seule colonne
     des creneaux, le titre se cassait mot par mot. Seuls les creneaux restent alignes sur la grille. */
  :is(.ds .ds-calendar, .ds.ds-calendar).is-grouped .ds-calendar-head { padding-left: 0; }
  :is(.ds .ds-calendar, .ds.ds-calendar).is-grouped .ds-calendar-slots { margin-left: var(--calendar-who); }
}

/* ---- KPI (`kpis`) ----------------------------------------------------------
   La bande de statistiques d une liste (hmSummary.html). Ce n est pas une carte :
   c est une rangee de cartes (`card flat kpi`) posees a plat, qui se partagent la
   largeur et passent a la ligne.
     .kpis-row          la rangee (la vraie et son squelette)
     .kpi               une statistique ; `is-link` si elle mene quelque part
       .card-icon         le glyphe, plus grand qu en en-tete : il porte le SUJET,
                          au niveau du nombre ; teinte par `tone-*`
       .kpi-text          .kpi-value (+ .kpi-unit) et .kpi-label
   La marge basse est sur la BANDE et non sur `<hm-summary>` : l element existe
   toujours, et une liste sans KPI — la plupart — garderait 20px de vide. */
:is(.ds .kpis, .ds.kpis) { margin-bottom: var(--space-5); }
.ds .kpis-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
}
.ds .kpi {
  flex: 1 1 220px;
  min-width: 0;
  color: inherit;
  text-decoration: none;
}
/* Seule une statistique qui mene quelque part reagit au survol : relever les
   autres promettrait une cible morte. La bordure fonce, la carte ne se souleve
   pas — la bande et la liste sont sur le meme plan. */
.ds .kpi.is-link { cursor: pointer; }
.ds .kpi.is-link:hover { border-color: var(--border-strong); text-decoration: none; }
/* Une tuile de LECTURE est plus dense qu une carte de contenu : un glyphe, un
   nombre et trois mots. */
.ds .kpi > .card-body { padding: var(--space-3) var(--space-4); }
.ds .kpi .card-icon {
  width: 40px;
  height: 40px;
  font-size: var(--text-lg);
}
.ds .kpi .card-icon:not([class*="tone-"]):not(.skeleton) { background: var(--primary-soft); color: var(--primary-700); }
.ds .kpi-text { min-width: 0; }
.ds .kpi-value {
  font: var(--weight-bold) var(--text-3xl)/1.1 var(--font-sans);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--tracking-tight);
  color: var(--text-primary);
}
/* L unite (« ans », « % ») tient sur la ligne de base du nombre, petite et estompee :
   l oeil lit le nombre d abord. */
.ds .kpi-unit {
  margin-left: 5px;
  font: var(--weight-medium) var(--text-sm)/1 var(--font-sans);
  letter-spacing: 0;
  color: var(--text-muted);
}
.ds .kpi-label {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font: var(--font-body-sm);
  line-height: var(--leading-snug);
  color: var(--text-muted);
}
.ds .kpi-value > .skeleton-line { width: 44px; }
.ds .kpi-label > .skeleton-line { width: 96px; }

/* ---- SOLDES (`balances`) -------------------------------------------------
   Ce qui reste a un salarie sur un compteur (conges, recuperation…), une tuile par
   periode, cote a cote : le NOMBRE est le message, la periode sa legende. Ce ne sont
   pas des champs — on les lit d un coup d oeil avant de decider.
     .balances          la rangee ; les tuiles se partagent la largeur, passent a la ligne
       .balance           une periode ; `is-current` : celle sur laquelle la demande est
                          prise, lue en premier (surface pleine, chiffre en vert) ; les
                          autres restent en retrait
         .balance-value     le solde, et son unite (.balance-unit)
         .balance-label     la periode */
.ds .balances {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}
.ds .balance {
  flex: 1 1 0;
  min-width: 120px;
  padding: var(--space-4);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--surface-sunken);
  text-align: center;
}
.ds .balance.is-current {
  border-color: var(--border);
  background: var(--surface);
}
.ds .balance-value {
  font: var(--weight-bold) var(--text-xl)/var(--leading-tight) var(--font-sans);
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
}
.ds .balance.is-current .balance-value { color: var(--success); }
.ds .balance-unit {
  margin-left: 4px;
  font: var(--weight-medium) var(--text-sm)/1 var(--font-sans);
}
.ds .balance-label {
  margin-top: var(--space-1);
  font: var(--font-meta);
  color: var(--text-muted);
}

/* ---- TUILES (`tiles`) ------------------------------------------------------
   L autre vue d une liste (hmCard.html, et deux ecrans qui posent leurs tuiles a
   la main) : une grille de cartes.
     .list.is-tiles      la liste en tuiles : sa carte est `bare` (les tuiles sont
                         les cartes) et son pied ne trace pas de filet — sous des
                         surfaces separees, il se lirait comme un trait pose SUR la
                         derniere rangee
     .tiles              la grille ; `is-wide` pour un contenu plus dense
       > .card           une tuile (`card flat`) ; `is-selected` une fois cochee
         .card-check     la case de selection, posee dans le coin haut-gauche

   AUCUNE dimension de tuile : `auto-fill` met autant de colonnes que la place le
   permet, `1fr` les etire. Le plancher 22rem n est pas une largeur de carte, c est
   la largeur SOUS laquelle une tuile cesse d etre lisible ; 30rem pour `is-wide`
   (une demande de recrutement porte six champs). `min(100%, …)` le desarme sur
   telephone. Les tuiles d une rangee font la hauteur de la plus haute : c est le
   comportement par defaut d un element de grille. */
hm-card { display: block; }
:is(.ds .list, .ds.list).is-tiles > .card > .card-foot { border-top: none; }
/* Sous la barre, une carte SANS CADRE ne retire pas son corps : la marge basse de la
   barre fait deja l ecart. Cadree (tableau), ce retrait est a l interieur du blanc et
   la carte commence juste sous la barre ; sans cadre, il s ajoutait a la marge et les
   tuiles tombaient deux fois plus loin de la barre que le cadre du tableau. */
:is(.ds .list, .ds.list) > .ds-toolbar + .card.bare > .card-body { padding-top: 0; }
.ds .tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 22rem), 1fr));
  gap: var(--space-5);
}
.ds .tiles.is-wide { grid-template-columns: repeat(auto-fill, minmax(min(100%, 30rem), 1fr)); }
.ds .tiles > .card {
  position: relative;
  min-width: 0;
}
/* Cochee : la bordure passe au primaire et se double vers l interieur, la boite
   ne bouge pas d un pixel. */
.ds .tiles > .card.is-selected {
  border-color: var(--primary);
  box-shadow: 0 0 0 1px var(--primary) inset;
}
/* Dans une liste, les rangees alignent deja les tuiles : les 61px d en-tete qui
   alignent deux cartes cote a cote sur un tableau de bord n alignent rien ici. */
.ds .tiles > .card > .card-head { min-height: 0; }
.ds .tiles > .card > .card-head > .card-title {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.ds .tiles > .card > .card-head > .card-title > .skeleton-line { width: 60%; }
/* Dans une tuile, la pastille de l en-tete porte le SUJET (type d un fichier,
   couleur de categorie) : son glyphe remplit la boite. */
.ds .tiles > .card > .card-head > .card-icon > i,
.ds .tiles > .card > .card-head > .card-icon > svg { font-size: 20px; }
.ds .tiles .card-body { position: relative; }

.ds .card-check {
  position: absolute;
  top: var(--space-3);
  left: var(--space-3);
  z-index: 3;
}
.ds .card-check > .skeleton {
  display: block;
  width: 15px;
  height: 15px;
  border-radius: var(--radius-xs);
}
/* Une tuile cochable a en-tete lui fait de la place : cocher ne doit pas revenir a
   cliquer sur son avatar. 44px = 12 de retrait + ~20 de cible + 12 de gouttiere. */
.ds .tiles > .card:has(> .card-check) > .card-head { padding-left: 44px; }

/* LE CONTENU DE L ECRAN (`hm-custom-card-template`). Par defaut il EST le corps :
   il en recoit le retrait. S il declare ses propres parties (`card-body`…), son
   enveloppe et celle de son `ng-include` s effacent (`display: contents`), pour que
   l en-tete et le corps soient enfants de la carte et que sa colonne flex les voie. */
.ds .tiles hm-custom-card-template {
  display: block;
  flex: 1;
  min-height: 0;
  padding: var(--space-4) 18px;
}
.ds .tiles hm-custom-card-template:has(.card-body),
.ds .tiles hm-custom-card-template:has(.card-body) > ng-include { display: contents; }

/* Pied a actions : la rangee `ds-actions is-card` porte deja son retrait, le pied
   ne fait que le filet — et n en trace aucun s il n a aucune action visible. Un
   pied de CONTENU (date, pastilles d un document) garde le retrait du DS. */
.ds .tiles .card-foot:has(.ds-actions.is-card) { padding: 0; }
.ds .tiles .card-foot:has(.ds-actions.is-card.is-empty) { border-top: none; }
/* Actions en surimpression (ecrans documents, `config.cardAbsoluteButtonsPosition`) :
   le pied quitte le flux et se pose dans le coin de la tuile. */
.ds .tiles .card-foot.is-floating {
  position: absolute;
  top: 0;
  right: 0;
  border-top: none;
}

/* ---- CONTENU DE TUILE A LIGNES (`card-media`, `card-row`) ---------------------
   Des parties que le GABARIT d une tuile pose lui-meme (catalogue de formation, sessions
   de la fiche module) : la tuile, son en-tete (`cardHead`) et son pied restent ceux de
   hmCard, comme pour toutes les listes en tuiles.
     .card-media         l image fournie par le client, au format impose 350 x 150
       .card-media-empty   « Pas d image » : la bande garde sa place
       .card-media-edit    ajouter / modifier l image, en surimpression
     .card-row           une ligne du corps : 26px, jamais a la ligne
       .card-row-short     element court (glyphe + texte), entier (45% au plus)
       .card-row-long      element long, seul a ceder de la place
         .card-row-text      son texte, qui se tronque ; l element porte l info-bulle
                             `data-tip` (« Theme : … ») et ne rogne rien
       .card-row-end       valeur rangee au bord de fin
       .card-row-sep       le point entre deux elements
       .card-row-none      valeur absente (« – », « Aucune competence »)
     .card-state         le bloc d etat en creux (statut / disponibilite)
     (les `data-tip` de ces parties : voir INFO-BULLE) */
/* Titre-lien vers le detail (`cardHead.uiSref`) : il garde la couleur du titre et se
   souligne au survol. !important : `ds-bridge.css` force `a { color: var(--primary) }`. */
.ds .tiles > .card > .card-head > .card-title > a {
  color: var(--text-primary) !important;
  text-decoration: none;
}
.ds .tiles > .card > .card-head > .card-title > a:hover {
  color: var(--primary-700) !important;
  text-decoration: underline;
}
.ds .tiles > .card > .card-head > .card-title > a:focus-visible {
  outline: none;
  border-radius: var(--radius-xs);
  box-shadow: var(--ring);
}

.ds .card-media {
  position: relative;
  aspect-ratio: 350 / 150;
  overflow: hidden;
  background: var(--surface-sunken);
  border-bottom: 1px solid var(--border-subtle);
}
.ds .card-media > a { display: block; height: 100%; }
.ds .card-media img { display: block; width: 100%; height: 100%; object-fit: cover; }
.ds .card-media-empty {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  font: var(--font-body-sm);
  color: var(--text-disabled);
}
/* `.card-media >` : le bouton porte une info-bulle, et `.ds [data-tip] { position: relative }`
   (meme poids, ecrite plus bas) le remettait dans le flux, SOUS l image, rogne par
   `overflow: hidden` : le bouton « Modifier l image » etait invisible. */
.ds .card-media > .card-media-edit {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  z-index: 1;
}

.ds .card-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  height: 26px;
  min-width: 0;
  font: var(--font-body-sm);
  color: var(--text-secondary);
  white-space: nowrap;
}
.ds .card-row :is(i, svg) { flex: none; font-size: 14px; }
/* Un element de ligne = glyphe + texte, et porte l info-bulle (`data-tip`) qui dit de
   quoi il s agit. Il ne rogne RIEN (la bulle deborde en dessous) : seul son texte
   (`card-row-text`) se tronque. */
.ds :is(.card-row-short, .card-row-long) {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.ds .card-row-short { flex: none; max-width: 45%; }
.ds .card-row-long { flex: 1 1 auto; }
.ds .card-row-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ds .card-row-end {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  margin-left: auto;
}
/* Squelette : des lignes de longueurs differentes, comme les vraies. */
.ds .card-body > .card-row:nth-child(1) > .skeleton-line { width: 85%; }
.ds .card-body > .card-row:nth-child(2) > .skeleton-line { width: 65%; }
.ds .card-body > .card-row:nth-child(4) > .skeleton-line { width: 40%; }
.ds .card-row-sep { flex: none; color: var(--text-disabled); }
.ds .card-row-none { color: var(--text-disabled); }
.ds .card-state {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 44px;
  min-width: 0;
  padding: var(--space-2) var(--space-3);
  background: var(--surface-sunken);
  border-radius: var(--radius-md);
  font: var(--font-body-sm);
  color: var(--text-secondary);
  white-space: nowrap;
}


/* ---- FICHE (`page-split`, `card-split`) --------------------------------------
   La page de detail d un element (`DETAIL-MODULE.md`, design-reference) : une colonne
   de contenu fluide et une colonne d informations de 340px, qui passe DESSOUS sous
   1080px. Les cartes y reprennent l anatomie commune (`card-head` / `-body` / `-foot`).
     .page-split           la grille ; `.page-split-main`, `.page-split-side` ses colonnes
     .card-split           image a gauche, texte a droite, alignes en haut ET en bas ;
                           le texte passe sous l image quand la place manque
       .card-media              l image, a la hauteur du texte (recadree, 180px au moins)
       .card-split-body         titre, ligne type · niveau · duree, theme, informations
       .card-split-title
       .card-split-info         les informations (`details-list`), sous un filet
       .is-compact              en bandeau au-dessus d une liste (poste actuel epingle du
                                repertoire des fiches de poste) : image etroite, titre
                                de tuile, lien vers la fiche
     .collapsible          un texte long replie a ~220px, fondu vers la surface ;
                           `is-open` le deplie
     .prose                un paragraphe de lecture (78 caracteres au plus)
     .num-list             liste numerotee a pastilles rondes (objectifs)
     .figures              des chiffres cote a cote (effectifs) : `.figure-value` +
                           `.figure-label`
     .figure-hero          un montant mis en avant (cout estime par participant)
     .sum-group            des montants groupes : `.sum-row.is-total` (sous-total) puis
                           ses `.sum-row` (les lignes qui le composent)
     .date-tile            le pave date d une session : mois + jour
     .card-dated           corps d une carte de session : `.date-tile` + `.card-dated-body` */
.ds .page-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  gap: var(--space-5);
  align-items: start;
}
@media (max-width: 1080px) {
  .ds .page-split { grid-template-columns: minmax(0, 1fr); }
}
/* `is-wide-side` : une colonne d informations plus large (420px), quand son contenu compte autant
   que la colonne principale (fiche de poste : qualifications, prochains postes). Elle passe dessous
   un peu plus tot, pour que la colonne principale garde de la place. */
.ds .page-split.is-wide-side { grid-template-columns: minmax(0, 1fr) 420px; }
@media (max-width: 1180px) {
  .ds .page-split.is-wide-side { grid-template-columns: minmax(0, 1fr); }
}
.ds :is(.page-split-main, .page-split-side) {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  min-width: 0;
}
/* Une liste en tuiles DANS la colonne (prochaines sessions) : pas de marge sous les tuiles, et
   son pied de pagination est retire quand il ne porte pas de pagination. Sinon ces deux blancs
   (16 + 22px) s ajoutaient a l ecart de la colonne et eloignaient le bloc suivant. */
.ds .page-split-side .list.is-tiles > .card > .card-body { padding-bottom: 0; }
.ds .page-split-side .list.is-tiles > .card > .card-foot:not(:has(.ds-pager)) { display: none; }

.ds .card-split {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: var(--space-5);
  padding: var(--space-5);
}
/* L image suit la HAUTEUR DU TEXTE (`stretch`), et non le format 350 x 150 des cartes du
   catalogue : le texte d une fiche depasse souvent 180px, et une image a son format laissait
   un grand blanc sous elle. Elle remplit donc sa colonne (`cover`, recadree au besoin),
   180px au moins. */
.ds .card-split > .card-media {
  flex: 0 1 420px;
  max-width: 100%;
  align-self: stretch;
  aspect-ratio: auto;
  min-height: 180px;
  border: none;
  border-radius: var(--radius-md);
}
/* Le texte s empile DEPUIS LE HAUT, a pas serre. Il n est pas etire sur la hauteur de
   l image (`space-between`) : trois lignes reparties sur 180px laissaient deux grands
   blancs entre le titre, la ligne d identification et le theme. */
.ds .card-split-body {
  flex: 1 1 280px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
/* Les informations de l element, sous son theme, dans la colonne de texte : elles
   occupent la place a cote de l image au lieu d une bande a part dessous. */
.ds .details-list.card-split-info {
  gap: var(--space-3) var(--space-5);
  margin-top: var(--space-2);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-subtle);
}
/* La description sous l identite d une fiche (fiche de poste) : un corps de carte sous un filet. */
.ds .card-split + .card-body { border-top: 1px solid var(--border-subtle); }
.ds .card-split-title {
  margin: 0;
  font: var(--weight-bold) var(--text-2xl)/1.25 var(--font-sans);
  color: var(--text-primary);
  overflow-wrap: anywhere;
}
/* Bandeau au-dessus d une liste : l image garde le format des tuiles (350 x 150) sur une
   colonne etroite, et le titre n est pas celui d une fiche (2xl) — le bandeau precede la
   liste, il ne doit pas se lire comme la page d un element. Le titre est le lien vers la
   fiche, dessine comme le titre-lien d une tuile. */
.ds .card-split.is-compact {
  gap: var(--space-4);
  padding: var(--space-4);
}
.ds .card-split.is-compact > .card-media {
  flex: 0 1 240px;
  min-height: 0;
  aspect-ratio: 350 / 150;
  align-self: center;
}
.ds .card-split.is-compact .card-split-body { gap: var(--space-1); justify-content: center; }
.ds .card-split.is-compact .card-split-title { font-size: var(--text-lg); }
/* !important : `ds-bridge.css` force `a { color: var(--primary) }`. */
.ds .card-split-title > a {
  color: var(--text-primary) !important;
  text-decoration: none;
}
.ds .card-split-title > a:hover {
  color: var(--primary-700) !important;
  text-decoration: underline;
}
.ds .card-split-title > a:focus-visible {
  outline: none;
  border-radius: var(--radius-xs);
  box-shadow: var(--ring);
}
@media only screen and (max-width: 767px) {
  .ds .details-list.card-split-info.cols-2 { grid-template-columns: minmax(0, 1fr); }
}

/* ---- MON ADEQUATION AU POSTE (`fit-chart`) ------------------------------------
   Deux graphiques d une fiche de poste (skill dataviz : forme, marques, palette validee
   par validate_palette.js).
     La SYNTHESE, carte de la colonne d informations :
     .fit-eyebrow       son titre, en petites capitales
     .fit-percent       la part au niveau, en grand
     .fit-bar           part du tout : un segment par etat, largeurs en `flex` (chaine,
                        cf. piege jQuery), 2px de surface entre les segments, 4px aux bouts
     .fit-legend        la legende chiffree (pastille + libelle + nombre) ; `.is-list` en
                        liste, le nombre range au bord de fin
     .fit-sentence      l ecart dit en clair (« Il vous manque un niveau sur… »)
     LE DETAIL, onglet « Mes ecarts » : par famille (`.fit-family`, en-tete `.fit-family-head`),
     une ligne par competence (`.fit-row`), lue de gauche a droite :
       .fit-name          le nom
       .fit-track         la piste graduee : `.fit-tick` un niveau de l echelle, `.fit-fill` MA
                          barre (longueur calculee), `.fit-target` le trait du niveau requis
       .fit-levels        « Application → Maitrise » en clair (la couleur n est jamais seule)
       .fit-delta         l ecart en badge ; `.fit-action-cell` > le bouton « Formations › »
     Mesure face a une cible : une barre et un repere (le miroir essaye avant comparait deux
     longueurs de sens opposes, peu lisible).
   Etats de ligne : `.is-atlevel` (ma barre --chart-good), `.is-towork` (--chart-warn),
   `.is-notevaluated` (pas de barre). `.is-drawn` sur `.fit-chart` : ma barre s allonge. */
.ds .fit-eyebrow {
  font: var(--weight-medium) var(--text-xs)/1.2 var(--font-sans);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
}
/* Grand chiffre : chiffres proportionnels (pas `tabular-nums`, cf. skill dataviz). */
.ds .fit-percent {
  font: var(--weight-bold) var(--text-4xl)/1 var(--font-sans);
  color: var(--text-primary);
}
.ds .fit-sentence {
  margin: 0;
  font: var(--font-body-sm);
  color: var(--text-secondary);
}
.ds .fit-sentence b { font-weight: var(--weight-bold); color: var(--text-primary); }
.ds .fit-bar {
  display: flex;
  gap: 2px;
  height: 12px;
}
.ds .fit-bar > span { min-width: 4px; }
.ds .fit-bar > span:first-child { border-radius: var(--radius-xs) 0 0 var(--radius-xs); }
.ds .fit-bar > span:last-child { border-radius: 0 var(--radius-xs) var(--radius-xs) 0; }
.ds .fit-bar > span:only-child { border-radius: var(--radius-xs); }
.ds :is(.fit-bar, .fit-legend) > .is-atlevel { --fit-color: var(--chart-good); }
.ds :is(.fit-bar, .fit-legend) > .is-towork { --fit-color: var(--chart-warn); }
.ds :is(.fit-bar, .fit-legend) > .is-notevaluated { --fit-color: var(--border-strong); }
.ds .fit-bar > span { background: var(--fit-color); }
.ds .fit-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-4);
  font: var(--font-meta);
  color: var(--text-secondary);
}
.ds .fit-legend > span {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
.ds .fit-legend > span > b { font-weight: var(--weight-bold); color: var(--text-primary); }
.ds .fit-legend > span::before {
  content: "";
  width: 10px;
  height: 10px;
  border-radius: var(--radius-pill);
  background: var(--fit-color);
}
/* En liste : une ligne par etat, le nombre au bord de fin (synthese de la colonne). */
.ds .fit-legend.is-list {
  flex-direction: column;
  flex-wrap: nowrap;
  gap: var(--space-2);
  font: var(--font-body-sm);
}
.ds .fit-legend.is-list > span { display: flex; }
.ds .fit-legend.is-list > span > b { margin-left: auto; font-variant-numeric: tabular-nums; }

/* Le miroir : trois colonnes — mon niveau | nom + barres | niveau requis. */
/* LE DETAIL : une ligne par competence, lue de gauche a droite — nom | piste | niveaux | ecart |
   action. Toutes les lignes ont la meme grille, et la meme hauteur. */
.ds .fit-row {
  display: grid;
  grid-template-columns: minmax(0, 220px) minmax(140px, 1fr) minmax(0, 220px) 44px 112px;
  gap: var(--space-4);
  align-items: center;
  min-height: 44px;
  padding: var(--space-2) 0;
}
@media only screen and (max-width: 767px) {
  .ds .fit-row { grid-template-columns: minmax(0, 1fr) auto; row-gap: var(--space-2); }
  .ds .fit-row > .fit-track { grid-column: 1 / -1; }
}
.ds .fit-family-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  padding: var(--space-4) 0 var(--space-2);
  border-bottom: 1px solid var(--border-subtle);
}
.ds .fit-family-name { font: var(--font-label); color: var(--text-primary); }
.ds .fit-family-count { font: var(--font-meta); color: var(--text-muted); }
.ds .fit-name {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font: var(--font-label);
  color: var(--text-primary);
}
/* La piste : une ligne fine, un point par niveau de l echelle (le niveau k a k/n), MA barre depuis le
   debut (aucun niveau) jusqu a mon niveau, et le TRAIT vertical du niveau requis. L ecart se lit
   comme le vide entre le bout de la barre et le trait. */
.ds .fit-track {
  position: relative;
  height: 20px;
}
.ds .fit-track::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 4px;
  transform: translateY(-50%);
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
}
.ds .fit-tick {
  position: absolute;
  top: 50%;
  width: 4px;
  height: 4px;
  transform: translate(-50%, -50%);
  border-radius: var(--radius-pill);
  background: var(--border-strong);
}
.ds .fit-fill {
  position: absolute;
  left: 0;
  top: 50%;
  height: 6px;
  transform: translateY(-50%);
  border-radius: var(--radius-pill);
  background: var(--chart-good);
}
.ds .fit-row.is-towork .fit-fill { background: var(--chart-warn); }
/* Le repere du niveau requis : un trait vertical, encre principale, cercle de surface autour pour
   rester lisible par-dessus la barre. */
.ds .fit-target {
  position: absolute;
  top: 50%;
  width: 3px;
  height: 18px;
  transform: translate(-50%, -50%);
  border-radius: var(--radius-pill);
  background: var(--text-primary);
  box-shadow: 0 0 0 2px var(--surface);
}
.ds .fit-levels {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-1);
  min-width: 0;
  overflow-wrap: anywhere;
  font: var(--font-body-sm);
  color: var(--text-secondary);
}
.ds .fit-levels > :first-child { font-weight: var(--weight-bold); color: var(--text-primary); }
.ds .fit-row.is-notevaluated .fit-levels > :first-child { font-weight: var(--weight-regular); color: var(--text-muted); }
.ds .fit-delta { display: flex; justify-content: center; }
/* L action (« Formations › ») est un bouton du DS (`ds-btn-secondary ds-btn-xs`) pose sur le lien ;
   sa cellule le range au bord de fin. */
.ds .fit-action-cell { display: flex; justify-content: flex-end; }
/* Legende des marques : les memes dessins que sur la piste (barre, trait). */
.ds .fit-legend.is-marks { color: var(--text-muted); }
.ds .fit-legend.is-marks > .is-mine::before { width: 16px; height: 6px; background: var(--chart-warn); }
.ds .fit-legend.is-marks > .is-required::before { width: 3px; height: 14px; background: var(--text-primary); }
/* Le dessin : avant `is-drawn`, ma barre est nulle et s allonge jusqu a mon niveau.
   !important : la longueur vient d un `ng-style` (style en ligne, donnee calculee). */
.ds .fit-chart .fit-fill { transition: width 0.6s ease-out; }
.ds .fit-chart:not(.is-drawn) .fit-fill { width: 0 !important; }
@media (prefers-reduced-motion: reduce) {
  .ds .fit-chart .fit-fill { transition: none; }
}
/* « Voir mes écarts » : la carte du detail s allume un instant (anneau de focus qui s eteint), pour
   que le clic ait un effet visible meme quand la carte est deja a l ecran. Sans animation, l anneau
   reste le temps du signal. */
.ds .card.is-pulsed { animation: fit-pulse 1.6s ease-out; }
@keyframes fit-pulse {
  0%, 30% { box-shadow: var(--ring); }
  100% { box-shadow: 0 0 0 3px transparent; }
}
@media (prefers-reduced-motion: reduce) {
  .ds .card.is-pulsed { animation: none; box-shadow: var(--ring); }
}

.ds .card-foot.is-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-5);
  font: var(--font-body-sm);
  color: var(--text-secondary);
}
.ds .card-foot.is-meta > * {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.ds .collapsible {
  position: relative;
  max-height: 220px;
  overflow: hidden;
}
.ds .collapsible::after {
  content: "";
  position: absolute;
  inset: auto 0 0;
  height: 64px;
  background: linear-gradient(to bottom, transparent, var(--surface));
  pointer-events: none;
}
.ds .collapsible.is-open { max-height: none; }
.ds .collapsible.is-open::after { display: none; }
/* `is-tall` : replie plus tard (400px), pour un texte dont le debut ne suffit pas (description d un
   poste : la finalite d abord, les activites principales ensuite — ce qu on vient lire). */
.ds .collapsible.is-tall:not(.is-open) { max-height: 400px; }
/* TEXTE RICHE : un texte saisi a l editeur du back-office (titres, paragraphes, listes), affiche tel
   quel (`ng-bind-html`). Remplace `custom-ckeditor` + `prose` : `prose` est un paragraphe simple
   (`white-space: pre-line` y doublait les sauts de ligne de l HTML, d ou les grands blancs), et les
   titres heritaient d un style d etiquette (petit, gris). 78 caracteres par ligne au plus. */
.ds .rich-text {
  max-width: 78ch;
  font: var(--font-body);
  line-height: var(--leading-relaxed);
  color: var(--text-primary);
}
.ds .rich-text > :first-child { margin-top: 0; }
.ds .rich-text > :last-child { margin-bottom: 0; }
.ds .rich-text :is(h1, h2, h3, h4) {
  margin: var(--space-4) 0 var(--space-1);
  font: var(--weight-bold) var(--text-lg)/1.3 var(--font-sans);
  color: var(--text-primary);
}
/* `color: inherit` : `sitecss.css` pose `body p { color: #000 }`, qui l emporte sur la couleur HERITEE du bloc :
   en theme sombre, le texte d une notification sortait noir sur fond sombre. */
.ds .rich-text :is(p, ul, ol) { margin: 0 0 var(--space-2); color: inherit; }
/* Les marqueurs de liste : une remise a zero heritee les retirait (« activites principales » sans
   puces). */
.ds .rich-text :is(ul, ol) { padding-left: var(--space-5); }
.ds .rich-text ul { list-style: disc; }
.ds .rich-text ol { list-style: decimal; }
.ds .rich-text li { display: list-item; }
.ds .rich-text li + li { margin-top: var(--space-1); }
.ds .rich-text :is(span, a, b, strong, em, li) { font-size: inherit; }
.ds .prose {
  max-width: 78ch;
  margin: 0 0 var(--space-3);
  font: var(--font-body);
  line-height: var(--leading-relaxed);
  color: var(--text-primary);
  white-space: pre-line;
}
.ds .prose-title {
  margin: var(--space-4) 0 var(--space-2);
  font: var(--font-label);
  color: var(--text-primary);
}
.ds .num-list {
  max-width: 78ch;
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: num;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.ds .num-list > li {
  counter-increment: num;
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  font: var(--font-body);
  line-height: var(--leading-relaxed);
  color: var(--text-primary);
}
.ds .num-list > li::before {
  content: counter(num);
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--surface-sunken);
  font: var(--weight-medium) var(--text-xs)/1 var(--font-sans);
  color: var(--text-secondary);
}
/* Un element avec un intitule et sa description dessous (missions d une fiche de poste) :
   `.num-list-text` > `.num-list-title` + `.num-list-desc`. Le texte passe a la ligne. */
.ds .num-list-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.ds .num-list-title { font: var(--font-label); color: var(--text-primary); overflow-wrap: anywhere; }
.ds .num-list-desc { font: var(--font-body-sm); color: var(--text-secondary); overflow-wrap: anywhere; }

.ds .figures {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-3);
}
.ds .figure-value {
  display: block;
  font: var(--weight-bold) var(--text-2xl)/1.2 var(--font-sans);
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}
.ds .figure-label {
  display: block;
  font: var(--font-body-sm);
  color: var(--text-secondary);
}
/* Des repères DANS une carte a image (fiche de poste : competences, missions, indicateurs,
   qualifications) : sous un filet, pousses en bas du texte pour s aligner sur le bas de l image,
   autant de colonnes que de chiffres (de 1 a 4). Le second libelle (« dont 3 cles ») est discret. */
.ds .card-split-body > .figures {
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  margin-top: auto;
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-subtle);
}
/* Le second libelle (« dont 3 cles ») suit le premier SUR LA MEME LIGNE, apres un point ; il ne passe
   dessous que si la place manque. */
.ds .card-split-body > .figures .figure-label:has(+ .figure-label) { display: inline; }
.ds .card-split-body > .figures .figure-label + .figure-label { display: inline; font: var(--font-meta); color: var(--text-muted); }
.ds .card-split-body > .figures .figure-label + .figure-label::before { content: " · "; }
/* Chaque repere porte la pastille d icone de son sujet, comme une statistique du resume des listes
   (`kpi` : `card-icon` 40px, teinte `tone-*`) ; la pastille ne quitte jamais son chiffre. */
.ds .card-split-body > .figures > .hstack { flex-wrap: nowrap; gap: var(--space-3); }
.ds .card-split-body > .figures .card-icon { width: 40px; height: 40px; font-size: var(--text-lg); }
.ds .figure-hero {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}
.ds .figure-hero > .figure-value { font-size: var(--text-3xl); }
/* Montants groupes : un sous-total (`is-total`), puis les lignes qui le composent, en
   retrait. On lit ainsi que 1 400 = 1 000 + 400. Montants alignes a droite, chiffres
   tabulaires. */
.ds .sum-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.ds .sum-group + .sum-group {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border-subtle);
}
.ds .sum-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  padding-left: var(--space-3);
  font: var(--font-body-sm);
  color: var(--text-secondary);
}
.ds .sum-row > :first-child { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.ds .sum-row > :last-child { flex: none; font-variant-numeric: tabular-nums; }
.ds .sum-row.is-total {
  padding-left: 0;
  margin-bottom: var(--space-1);
  font: var(--font-label);
  color: var(--text-primary);
}

.ds .date-tile {
  flex: none;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 52px;
  border-radius: var(--radius-md);
  background: var(--surface-sunken);
  color: var(--text-primary);
}
.ds .date-tile-month {
  font: var(--weight-medium) var(--text-xs)/1 var(--font-sans);
  color: var(--text-secondary);
  text-transform: uppercase;
}
.ds .date-tile-day {
  font: var(--weight-bold) var(--text-2xl)/1.1 var(--font-sans);
  font-variant-numeric: tabular-nums;
}
.ds .card-row-danger { color: var(--danger-fg); }
/* Une information discrete au bord de fin d un en-tete de carte (date de mise a jour). */
.ds .card-head-meta {
  flex: none;
  margin-left: auto;
  font: var(--font-meta);
  color: var(--text-muted);
}
/* Corps d une carte datee (session) : le pave date, puis ses lignes. */
.ds .card-dated {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  min-width: 0;
}
.ds .card-dated-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

/* ---- PROFIL (`profile-head`) ------------------------------------------------
   La carte en tete du profil d un salarie (new-profil.html). Toujours affichee : mon profil
   comme celui d un autre. L identite en haut, les onglets des rubriques en bas, DANS la carte.
     .profile-head          la carte (`card`)
       .profile-head-band     le bandeau du haut, decor seul ; la photo chevauche son bord
         .profile-head-shape    ses trois formes (cercles translucides, a droite)
       .profile-head-main     avatar, identite, et au bout de la ligne les coordonnees ; elles
                              passent dessous quand la place manque
         .profile-head-id       nom, poste, note (`cell-rating`)
           .profile-head-name     le nom
           .profile-head-job      le poste
         .profile-head-contacts telephone et email, en `card-row` (glyphe + texte tronque)
         .profile-head-actions  le menu « ⋮ » des actions sur la fiche (`ac` + `ac-menu`)
       hm-tabset              les rubriques (onglets de navigation, sans volet)
   Remplace `panel-user`, `display-user`, `div-icon`, `black-description`, `title-profile`. */
.ds .profile-head-main {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4) var(--space-5);
  padding: var(--space-4) 18px;
}
.ds .profile-head-main > hm-avatar { flex: none; }
/* La photo chevauche le bandeau de la carte : un anneau de la couleur de la carte la detache du
   bandeau, en clair comme en sombre. */
.ds .profile-head-main > hm-avatar .av { box-shadow: 0 0 0 4px var(--surface); }
/* LE BANDEAU (29/09/2026, demande, piste « D — Formes ») : un aplat doux et des cercles
   translucides, tous tires de `--primary` — il suit la couleur principale du client, et
   `--primary-soft` se redefinit en sombre. Les formes sont a droite, loin de la photo. La photo
   remonte a cheval sur son bord (75px, taille `md`) ; nom, poste et coordonnees s alignent sur son
   pied, sous le bandeau. (Essaye puis ecarte le 29/09/2026 : le bandeau sur toute la hauteur de
   l identite ; la photo a cheval se lit mieux.) */
.ds .profile-head-band {
  position: relative;
  /* 60px et non les 76px de la maquette (29/09/2026) : la carte ne doit pas repousser le contenu
     de la rubrique ; l effet reste. */
  height: 60px;
  overflow: hidden;
  border-radius: var(--radius-card) var(--radius-card) 0 0;
  /* Un leger degrade, plus soutenu a gauche derriere la photo, qui s eclaircit vers les formes : il
     lie la photo au decor, la bande ne se lit plus comme un aplat vide sur une carte large. Tire de
     `--primary` sur `--primary-soft` : suit la couleur du client, clair comme sombre. */
  background-color: var(--primary-soft);
  background-image: linear-gradient(90deg,
    color-mix(in srgb, var(--primary) 14%, var(--primary-soft)) 0%,
    var(--primary-soft) 65%);
}
.ds .profile-head-shape {
  position: absolute;
  border-radius: 50%;
  background: color-mix(in srgb, var(--primary) 22%, transparent);
}
/* Places en PIXELS depuis la droite, pas en % : la carte fait jusqu a ~1500px, en pourcentage les
   formes s eparpillaient sur toute la bande ; groupees a droite, elles gardent le dessin de la
   maquette (~660px de large) quelle que soit la largeur. */
.ds .profile-head-shape:nth-child(1) { width: 180px; height: 180px; right: 40px;  top: -110px; }
.ds .profile-head-shape:nth-child(2) { width: 110px; height: 110px; right: 145px; top: 20px; background: color-mix(in srgb, var(--primary) 14%, transparent); }
.ds .profile-head-shape:nth-child(3) { width: 70px;  height: 70px;  right: 340px; top: -34px; background: color-mix(in srgb, var(--primary) 10%, transparent); }
/* Mesures de la maquette D (Content/design-system/maquettes/bandeau-moderne.html), bandeau ramene a 60px :
   la photo remonte de la moitie de sa hauteur, 16px entre la photo et le nom. */
.ds .profile-head-band + .profile-head-main {
  align-items: flex-end;
  column-gap: var(--space-4);
  padding-top: 0;
  padding-bottom: 14px;
}
.ds .profile-head-band + .profile-head-main > hm-avatar {
  position: relative;
  margin-top: -38px;
}
.ds .profile-head-band + .profile-head-main > .profile-head-id { padding-bottom: 2px; }
.ds .profile-head-band + .profile-head-main > .profile-head-contacts { padding-bottom: 6px; }
.ds .profile-head-id {
  flex: 1 1 240px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-1);
}
.ds .profile-head-name {
  margin: 0;
  font: var(--font-h2);
  color: var(--text-primary);
  overflow-wrap: anywhere;
}
.ds .profile-head-job {
  font: var(--font-body-sm);
  color: var(--text-secondary);
}
/* Telephone puis email sur UNE ligne, au bout de la ligne d identite. */
.ds .profile-head-contacts {
  flex: 0 1 auto;
  min-width: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-5);
  margin-left: auto;
}
/* Le menu « ⋮ » des actions sur la fiche, au bout de la ligne : apres les coordonnees (qui portent
   deja le `margin-left: auto`), ou seul au bout quand il n y en a pas. */
.ds .profile-head-actions { flex: none; margin-left: auto; }
.ds .profile-head-contacts ~ .profile-head-actions { margin-left: 0; }
/* LES ONGLETS AU PIED DE LA CARTE. Un filet les separe de l identite ; celui de la barre
   (`.tabs`, ombre interne en bas) doublerait le bord de la carte : il saute, et le
   soulignement de l onglet actif se pose sur ce bord. Retrait lateral = l ecart ENTRE deux onglets
   (`--space-1`, 4px) : le premier onglet est a la meme distance du bord que de son voisin, et le fond
   de l onglet actif garde ce rythme (28/09/2026, demande). */
.ds .profile-head .tabs-bar {
  padding: 0 var(--space-1);
  border-top: 1px solid var(--border-subtle);
}
.ds .profile-head .tabs { box-shadow: none; }

/* ---- RUBRIQUE DU PROFIL (`.rubric`) ------------------------------------------
   Une rubrique du profil = UNE carte, quelle que soit sa forme, pour que toutes commencent et se
   lisent de la meme facon. Les listes y sont des PARTIES de la carte, sans cadre propre (la recette
   de `.card.bare`) : pas de carte dans une carte.
     hm-tabset.rubric        rubrique a onglets : la page pose la classe sur son premier `hm-tabset`
                             - en pastilles (`chips`) : la carte des pastilles se prolonge, sous un
                               filet, sur le contenu de l onglet ouvert (Carriere, Documents…) ;
                             - en bascule (`seg`, liste / graphique) : le contenu est la carte, la
                               bascule dans la barre de la liste (Objectifs, Competences…).
                             Seuls ses enfants directs sont vises : un tabset imbrique n est pas touche.
     div.rubric              rubrique d une seule page (Remuneration, Delegation…) : la carte, qui
                             commence par la barre de la liste
       .rubric-part            une partie, quand la carte en porte plusieurs (deux listes, deux
                               frises…) : un filet la separe de la precedente
         .rubric-part-head       sa ligne de tete : titre, commandes, actions au bout
           .rubric-part-title      son titre
         .rubric-part-map        une carte geographique, a hauteur fixe
   EN TUILES (`list is-tiles`), la carte s arrete a la barre de la liste : les tuiles sont des
   cartes, elles restent sur le fond de la page.
   PORTEE : sous `.profile-view` seulement (la zone de rubrique, new-profil.html). Un gabarit de
   rubrique qui sert aussi ailleurs (competency.html, page Competences) garde la classe sans effet
   hors du profil ; coque mobile (sans `.ds`) : rien ne change. */

/* Le cadre. */
.ds .profile-view hm-tabset.rubric > div > .tabs-panels,
.ds .profile-view div.rubric {
  padding: var(--space-4) 18px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
}
/* Sous des pastilles : le cadre les continue, sous un filet. */
.ds .profile-view hm-tabset.rubric > div > .tabs-bar > .chip-group.is-tabs {
  border-bottom: none;
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
  box-shadow: none;
}
.ds .profile-view hm-tabset.rubric > div > .tabs-panels.has-bar {
  border-top-color: var(--border-subtle);
  border-radius: 0 0 var(--radius-card) var(--radius-card);
}
/* Les listes : des parties de la carte. */
.ds .profile-view .rubric .list > .card {
  background: none;
  border: none;
  border-radius: 0;
  box-shadow: none;
}
.ds .profile-view .rubric .list > .card > :is(.card-head, .card-body, .card-foot) {
  padding-left: 0;
  padding-right: 0;
}
/* La barre de la liste porte deja l ecart au-dessus du tableau (marge basse de ses enfants) : le
   retrait haut du corps s y ajoutait, un blanc de ~50px entre la bascule et l en-tete du tableau. */
.ds .profile-view .rubric .list > .card > .card-body { padding-top: 0; }
.ds .profile-view .rubric .rubric-part + .rubric-part {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border-subtle);
}
.ds .rubric-part-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}
.ds .rubric-part-title {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  font: var(--font-h3);
  color: var(--text-primary);
}
.ds .rubric-part-map { height: 450px; }

/* ---- FICHE DU LIBRE-SERVICE V1 (`self-service-v1`, selfServiceGroup.html / selfServiceBloc.html) --
   Profil V1, fenetre du contrat V1, creation de salarie V1. Dans le profil, chaque bloc est une
   partie de la carte de la rubrique (`rubric-part`) :
     .rubric-part-subtitle  titre d un sous-bloc (Adresse…)
     .rubric-part-fields    les champs du bloc
     .rubric-part-foot      Annuler / Valider pendant la modification
   Chaque bloc vit dans son propre `<self-service-bloc>` : les parties ne sont pas soeurs, le filet
   de `.rubric-part + .rubric-part` ne les atteint pas. Il se pose donc entre deux blocs AFFICHES
   (ni masques par `bloc.isVisible` — `ng-hide` —, ni vides — pas de `rubric-part`). */
.ds .self-service-v1 { display: flow-root; }
.ds .self-service-v1 > self-service-bloc:has(.rubric-part):not(:has(.ng-hide .rubric-part))
  ~ self-service-bloc:has(.rubric-part):not(:has(.ng-hide .rubric-part)) .rubric-part {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border-subtle);
}
.ds .rubric-part-subtitle {
  margin: var(--space-4) 0 var(--space-3);
  font: var(--font-label);
  color: var(--text-secondary);
}
/* Les champs en grille (`fields-grid`, la recette V2 plus bas) : l element `<self-service-field>` est
   la case. Un champ masque (`ng-show`, droits ou type) laisse l element vide : il ne doit pas occuper
   de case, sinon la grille se troue. */
.ds .fields-grid > self-service-field { display: block; min-width: 0; }
.ds :is(.fields-grid, .self-service-v1-bloc .stack) > self-service-field:not(:has(> div:not(.ng-hide))) { display: none; }
/* EN CREATION (fenetre du contrat V1, creation de salarie V1) : les blocs gardent la disposition
   d avant — un tiers de largeur, TASSES dans l ordre (un bloc court laisse le suivant remonter sous
   lui, au lieu d attendre la fin du plus long de sa rangee : decision du 30/09/2026, option A). Seul
   le flottement le permet sans changer l ordre ; une grille alignerait les rangees. `is-full` : toute
   la largeur. L ecart entre deux colonnes est porte par le bloc (`padding-inline-end`). */
.ds .self-service-v1-bloc {
  float: left;
  box-sizing: border-box;
  width: calc(100% / 3);
  padding: 0 var(--space-6) var(--space-6) 0;
}
.ds .self-service-v1-bloc.is-full { width: 100%; padding-right: 0; }

/* ---- PANNEAU DE RESULTAT (`outcome-panel`) -------------------------------------------------
   Le resultat d une recherche ou d un controle, a cote d un formulaire (creation de salarie V1,
   etape 4 : contexte trouve ou non). Centre dans les deux sens sur toute la hauteur de sa colonne,
   sur le fond en creux, filet pointille : une zone d information, pas une carte de plus.
     .card-icon.tone-*   la pastille d icone, agrandie ici, a la teinte du resultat
     .outcome-title      le resultat, en titre
     .hint / .badge      l aide, ou la valeur trouvee
   `is-success` : le filet et le fond prennent la teinte de reussite. */
.ds .outcome-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  box-sizing: border-box;
  height: 100%;
  min-height: 220px;
  padding: var(--space-6);
  text-align: center;
  background: var(--surface-sunken);
  border: 1px dashed var(--border);
  border-radius: var(--radius-card);
}
.ds .outcome-panel.is-success {
  background: color-mix(in srgb, var(--success-soft) 45%, var(--surface));
  border-color: var(--success-border);
  border-style: solid;
}
.ds .outcome-panel > .card-icon {
  width: 48px;
  height: 48px;
  margin-bottom: var(--space-2);
  border-radius: var(--radius-pill);
}
.ds .outcome-panel > .card-icon > :is(i, svg) { width: 22px; height: 22px; font-size: 22px; }
.ds .outcome-title {
  margin: 0;
  font: var(--font-h3);
  color: var(--text-primary);
}
.ds .outcome-panel > .hint { max-width: 36ch; margin: 0; }
@media (max-width: 860px) {
  .ds .self-service-v1-bloc { width: 100%; padding-right: 0; }
}
.ds .rubric-part-foot { margin-top: var(--space-4); }
/* Une FICHE en sections (`self-service`, onglet Infos) a sa propre mise en page : la barre prolonge
   la carte des onglets, les sections suivent, le sommaire est a droite (plus bas, « FICHE EN
   SECTIONS »). Le cadre de la rubrique saute donc pour elle. Une fiche SANS barre (`is-plain`,
   personnes a charge) reste dans le cadre, comme une liste. */
.ds .profile-view hm-tabset.rubric > div > .tabs-panels:has(> .tabs-panel:not([hidden]) .self-service:not(.is-plain)),
/* En tuiles : le cadre saute, la barre de la liste ferme la carte. */
.ds .profile-view hm-tabset.rubric > div > .tabs-panels:has(> .tabs-panel:not([hidden]) .list.is-tiles) {
  padding: 0;
  background: none;
  border: none;
  border-radius: 0;
}
.ds .profile-view hm-tabset.rubric .list.is-tiles > .ds-toolbar {
  margin-bottom: var(--space-4);
  padding: var(--space-3) 18px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
}
.ds .profile-view hm-tabset.rubric > div > .tabs-panels.has-bar .list.is-tiles > .ds-toolbar {
  border-top-color: var(--border-subtle);
  border-radius: 0 0 var(--radius-card) var(--radius-card);
}
.ds .profile-view hm-tabset.rubric .list.is-tiles > .ds-toolbar > * { margin-bottom: 0; }

/* ---- COMPTEUR EN ANNEAU (`quota-chart`) ---------------------------------------
   Le compteur d une absence sur une periode (profil > Absences > compteurs, vacation-hrmaps.html) :
   un anneau pris / solde, le solde au centre, et sa legende a cote. Repris de l ancien donut
   (`donut-chart`, `chart-legend`), que les utilisateurs appreciaient, avec les couleurs du theme.
     .quota-charts          les periodes d un type d absence, cote a cote (passent a la ligne)
       .quota-chart           une periode : l anneau + sa legende
         .quota-ring            l anneau ; la part PRISE est une DONNEE, en `ng-style`
                                (`--quota-taken-pct`, calculee par VacationCompteurs.js) : le reste
                                de l anneau est le solde
           .quota-ring-value      le solde, au centre
         .quota-legend          la legende
           .quota-legend-title    l annee et l echeance
           .quota-legend-row      une ligne : pastille, libelle, valeur (`strong`)
             .quota-dot             la pastille : pris (defaut), `is-remaining`, `is-transferred` */
.ds .quota-charts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4) var(--space-6);
}
.ds .quota-chart {
  --quota-taken: var(--border);
  display: flex;
  align-items: center;
  gap: var(--space-4);
  min-width: 0;
}
/* `display: flex` l emporte sur l attribut `hidden`. */
.ds .quota-chart[hidden] { display: none; }
.ds .quota-ring {
  --quota-taken-pct: 0%;
  position: relative;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100px;
  height: 100px;
  border-radius: 50%;
  background: conic-gradient(var(--quota-taken) 0 var(--quota-taken-pct), var(--success) 0);
}
/* Le trou de l anneau, a la couleur de la carte. */
.ds .quota-ring::before {
  content: "";
  position: absolute;
  inset: 10px;
  border-radius: 50%;
  background: var(--surface);
}
.ds .quota-ring-value {
  position: relative;
  font: var(--weight-bold) var(--text-2xl)/1 var(--font-sans);
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}
.ds .quota-legend {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
  font: var(--font-body-sm);
  color: var(--text-secondary);
}
.ds .quota-legend-title {
  font: var(--font-label);
  color: var(--text-primary);
}
.ds .quota-legend-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.ds .quota-legend-row > strong {
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}
.ds .quota-dot {
  flex: none;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--quota-taken);
}
.ds .quota-dot.is-remaining { background: var(--success); }
.ds .quota-dot.is-transferred { background: var(--info); }

/* ---- FICHE EN SECTIONS (`self-service`, `sections`) --------------------------
   Le formulaire dynamique du libre-service (selfServiceV2Page.html : infos du profil, fenetre
   du contrat, personnes a charge, assistant de creation de collaborateur), et ses sections
   (hmSections.html).
     .self-service          le composant ; `is-plain` sans barre, titres ni sommaire (hide-toolbar)
       > .ds-toolbar          recherche, filtres, actions : HORS des cartes, COLLANTE en haut de ce
                              qui defile (la page, ou le `.modal-content` d une fenetre laterale)
         .self-service-progress « x/y sections completes » et sa jauge, au bout de la barre avec les
                                actions (`.self-service-progress-bar`)
       .self-service-empty    aucun champ ne repond au filtre ou a la recherche
     .sections              les sections et leur sommaire (colonne de 260px ; dessus sous 992px)
       .sections-main         les sections
         .card.sections-card    une section ; `is-active` : celle qu on lit ou qu on remplit
           .sections-count        « x/y champs remplis », au bout de l en-tete
       .sections-aside        la colonne du sommaire
         .card.sections-nav     le sommaire, collant ; `is-collapsed` replie sur mobile
           .sections-nav-toggle   le chevron (mobile seulement)
           .sections-jump         aller en haut / en bas
           .sections-list         les sections ; `is-active` sur l en-cours
             .sections-list-label   son nom, tronque
             .sections-dot          son etat : `is-complete`, `is-required`, `is-partial`
       .sections-glyph        un glyphe du composant (enveloppe : FontAwesome remplace le `<i>`)
   Remplace `ss-topnav*`, `ss-container`, `ss-main`, `ss-section*`, `ss-sidebar`, `ss-nav-*`,
   `ss-empty-state*` (CustomHrMaps.css). */

/* LA BARRE COLLANTE. `.page` defile et pose 8px de retrait en haut : la barre remonte de ces 8px
   et les rend en retrait interieur, pour qu aucun champ ne defile visible au-dessus d elle. Elle
   porte le fond de la zone de contenu, qui passe derriere. */
:is(.ds .self-service, .ds.self-service) > .ds-toolbar {
  position: sticky;
  top: calc(-1 * var(--space-2));
  z-index: 3;
  padding-top: var(--space-2);
  background: var(--surface-sunken);
}
/* Dans une fenetre laterale, c est `.modal-content` qui defile, sans retrait : collee au bord, sur
   le fond de page que prend alors le corps de la fenetre (plus bas, « dans une fenetre »). */
.ds .modal-body .self-service > .ds-toolbar {
  top: 0;
  padding-top: 0;
}
.ds .self-service-progress {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 10rem;
  margin-right: var(--space-2);
  font: var(--font-meta);
  color: var(--text-secondary);
}
.ds .self-service-progress-bar {
  display: block;
  height: 6px;
  overflow: hidden;
  background: var(--surface-sunken);
  border-radius: var(--radius-pill);
}
.ds .self-service-progress-bar > span {
  display: block;
  height: 100%;
  background: var(--success);
  border-radius: inherit;
  transition: width .25s ease;
}

/* UNE FICHE = UNE SEULE CARTE, PARTOUT (29/09/2026, demande : profil, creation de salarie, fenetre
   du contrat, personnes a charge ont le meme affichage) :
     - en tete, sur toute la largeur : la recherche, les filtres, l avancement et les actions (au bout) ;
     - dessous, DANS LA MEME CARTE, la colonne des sections, separees par un filet : ce ne sont plus
       des cartes posees les unes sous les autres, mais les parties d une seule (pas de carte dans
       une carte) ;
     - le sommaire pend a droite sous la tete, attache a elle.
   La carte est a plat (sans ombre) : ses morceaux ne se superposent pas. La barre reste collante :
   collee en haut, elle garde son filet. Une fiche sans barre (`is-plain`) n est pas concernee. */
:is(.ds .self-service, .ds.self-service):not(.is-plain) > .ds-toolbar {
  margin-bottom: 0;
  padding: var(--space-3) 18px;
  background: var(--surface);
  border: 1px solid var(--border);
  /* Ses deux coins du bas continuent dans les colonnes qui pendent dessous : sections a gauche,
     sommaire a droite. */
  border-radius: var(--radius-card) var(--radius-card) 0 0;
}
:is(.ds .self-service, .ds.self-service):not(.is-plain) > .ds-toolbar > * {
  /* Le bas seulement : `margin-left: auto` de `.ds-toolbar-actions` pousse l avancement et les
     actions au bout de la barre. */
  margin-bottom: 0;
}
/* La colonne des sections porte le cadre ; les sections y sont des parties sans cadre. */
:is(.ds .self-service, .ds.self-service):not(.is-plain) .sections-main {
  gap: 0;
  /* PAS d `overflow` : il rognerait les listes et calendriers des champs. */
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: none;
  border-radius: 0 0 var(--radius-card) var(--radius-card);
}
:is(.ds .self-service, .ds.self-service):not(.is-plain) .sections-card {
  background: none;
  border: none;
  border-radius: 0;
  box-shadow: none;
}
:is(.ds .self-service, .ds.self-service):not(.is-plain) .sections-card:not([hidden]) ~ .sections-card:not([hidden]) { border-top: 1px solid var(--border); }
/* LE SOMMAIRE PEND SOUS LA TETE (28/09/2026, demande) : colle a la barre, sans ecart, sans bord ni
   arrondi en haut — les deux colonnes, sections et sommaire, se lisent comme un seul bloc avec la
   tete. A plat, comme le reste du bloc. Colle en haut au defilement, il reprend sa place de carte. */
:is(.ds .self-service, .ds.self-service):not(.is-plain) .sections-aside { padding-top: 0; }
:is(.ds .self-service, .ds.self-service):not(.is-plain) .sections-nav {
  border-top: none;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
  box-shadow: none;
}
/* Aucun champ ne repond : le message ferme la carte a la place des sections. */
:is(.ds .self-service, .ds.self-service):not(.is-plain) .self-service-empty {
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: none;
  border-radius: 0 0 var(--radius-card) var(--radius-card);
}
/* Ecran etroit : le sommaire passe entre la tete et les sections, toujours colle a la tete ; la colonne
   des sections, en dessous, se referme en haut. */
@media (max-width: 991px) {
  :is(.ds .self-service, .ds.self-service):not(.is-plain) .sections-main {
    border-top: 1px solid var(--border);
    border-radius: var(--radius-card);
  }
}
/* SOUS DES ONGLETS EN PASTILLES (Infos du profil) : la tete de la carte, ce sont les pastilles ; la
   barre de la fiche s y accroche dessous, sous un filet discret, sans arrondi. La tete garde toute la
   largeur : c est la barre d onglets de la page, elle ne doit pas changer de largeur d un onglet a
   l autre. */
:is(.ds .tabs-bar, .ds.tabs-bar):has(> .chip-group.is-tabs):has(+ .tabs-panels > .tabs-panel:not([hidden]) .self-service:not(.is-plain) > .ds-toolbar) > .chip-group.is-tabs {
  border-bottom: none;
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
  box-shadow: none;
}
:is(.ds .tabs-bar, .ds.tabs-bar):has(> .chip-group.is-tabs):has(+ .tabs-panels > .tabs-panel:not([hidden]) .self-service:not(.is-plain) > .ds-toolbar) + .tabs-panels.has-bar {
  padding-top: 0;
}
:is(.ds .tabs-bar, .ds.tabs-bar):has(> .chip-group.is-tabs) + .tabs-panels .self-service:not(.is-plain) > .ds-toolbar {
  border-top-color: var(--border-subtle);
  border-radius: 0;
}

.ds .self-service-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-8) var(--space-5);
  text-align: center;
  font: var(--font-body);
  color: var(--text-muted);
}
.ds .self-service-empty > p { margin: 0; }
.ds .self-service-empty-icon { font-size: 40px; line-height: 1; }

/* LES CHAMPS DE LA FICHE (selfServiceV2Field.html), dans la recette de champ du design system.
     .fields-grid        la grille : 3 colonnes, 1 sous 860px (repli de la reference) ; `is-full` sur un champ pleine largeur (type grille)
     .field-line         le controle (`field-line-main`) et, a cote, le justificatif (`field-doc`)
     .field-info         l icone d information du libelle, visible au survol du champ
     .field-request      le badge « modification en cours », cliquable (details de la demande)
     .field-doc          le justificatif : bouton, badge du type attendu dans le libelle (`field-doc-badge`), pastille « joint »
                         (`field-doc-check`, `is-stale` quand il faut le remplacer) ; etats
                         `is-required` (a joindre), `is-attached` (joint), `is-error` (a refournir) */
.ds .fields-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-5) var(--space-6);
  align-items: start;
}
/* L element de la directive est en ligne par defaut, et un element de grille refuse de retrecir sous
   sa largeur de contenu : sans `min-width: 0`, un champ a liste (champ + bouton) debordait de sa
   colonne. */
.ds .fields-grid > self-service-v2-field { display: block; min-width: 0; }
.ds .fields-grid > .is-full { grid-column: 1 / -1; }
/* Une seule colonne sous 860px : le repli de la reference (`.grid2` / `.grid3`). */
@media (max-width: 860px) {
  .ds .fields-grid { grid-template-columns: minmax(0, 1fr); }
}
.ds .field-line {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
}
.ds .field-line-main { flex: 1 1 auto; min-width: 0; }
.ds .field-line-main > :is(hm-date-picker, hm-auto-complete, hm-auto-complete-pick-list, hm-auto-complete-multiple) {
  display: block;
}
.ds .field-info {
  display: inline-flex;
  align-items: center;
  margin-left: auto;
  color: var(--text-muted);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-standard);
}
.ds .field-info > :is(i, svg) { width: 14px; height: 14px; }
.ds .field:hover .field-info,
.ds .field-info:focus-within { opacity: 1; }
.ds .field-request { cursor: pointer; }
/* Un bouton-icone DANS le libelle (telecharger le justificatif joint, libre-service V1) : 24px et une
   marge negative, pour ne pas grandir la ligne du libelle — sinon le champ descendrait de ~15px par
   rapport a ses voisins de rangee. */
.ds .field > label > .ds-btn-icon.ds-btn-sm {
  width: 24px;
  height: 24px;
  min-height: 0;
  margin-block: -4px;
}
.ds .field-doc {
  position: relative;
  flex: none;
}
/* Le badge du type attendu, DANS le libelle, pousse a droite (au-dessus du bouton). Il etait pose en
   absolu au-dessus du bouton et recouvrait la fin d un libelle long ; en flux, le libelle se replie a
   cote. Marge negative : 24px de badge dans une ligne de ~17,5px ne doivent pas grandir le libelle,
   sinon le champ descendrait par rapport a ses voisins de rangee. Le nom complet du type est en
   info-bulle. */
.ds .field > label > .field-doc-badge {
  flex: none;
  max-width: 170px;
  margin-block: -4px;
  margin-left: auto;
}
/* Deux `margin-left: auto` se partageraient la place libre : l icone d information suit le badge. */
.ds .field > label > .field-doc-badge ~ .field-info { margin-left: 0; }
.ds .field-doc-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Les etats du bouton, en tons doux du theme. Pas sur un bouton desactive : il reste gris. */
.ds .field-doc.is-required > .ds-btn:not([disabled]) {
  background: var(--warning-soft);
  border-color: color-mix(in srgb, var(--warning) 45%, transparent);
  color: var(--warning-fg);
}
.ds .field-doc.is-attached > .ds-btn:not([disabled]) {
  background: var(--success-soft);
  border-color: var(--success-border);
  color: var(--success-fg);
}
.ds .field-doc.is-error > .ds-btn:not([disabled]) {
  background: var(--danger-soft);
  border-color: var(--danger-border);
  color: var(--danger-fg);
}
/* La pastille « joint », sur le coin du bouton : elle ne capte pas le clic. */
.ds .field-doc-check {
  position: absolute;
  top: -6px;
  right: -6px;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border: 2px solid var(--surface);
  border-radius: 50%;
  background: var(--success);
  color: var(--on-primary);
  pointer-events: none;
}
.ds .field-doc-check.is-stale { background: var(--danger); }
.ds .field-doc-check :is(i, svg) { width: 8px; height: 8px; }

/* UN CHAMP VERROUILLE SE LIT : etat « Lecture seule » de la reference (formulaire-reference.html,
   « Valeur heritee du contrat ») — fond en creux, texte `--text-secondary` — et non « Desactive »
   (`--text-disabled`), fait pour un champ vide et inerte. Vaut en CONSULTATION (tous les champs sont
   desactives) et EN SAISIE pour un champ verrouille par le serveur : sa valeur reste a lire.
   Mesure : --text-disabled faisait 2,0:1 (clair) et 2,9:1 (sombre) sur le creux ; --text-secondary
   fait 5,6:1 et 9,5:1 (28/09/2026). En saisie, le verrouille se distingue d un champ modifiable par son
   texte secondaire, son fond en creux et le curseur « interdit ». */
/* La V1 (`self-service-v1`) lit ses valeurs de la meme facon (30/09/2026). */
:is(.ds .self-service, .ds.self-service, .ds .self-service-v1) .fields-grid :is(.input, .select, .textarea, .form-control, .cs-select)[disabled] {
  --text-primary: var(--text-secondary);
  --input-color: var(--text-secondary);
}
/* En consultation, rien n est « interdit » : on lit. */
:is(.ds .self-service, .ds.self-service):not(.is-editing) .fields-grid :is(.input, .select, .textarea, .form-control, .cs-select)[disabled] {
  cursor: default;
}
/* La coche « rempli » d un champ obligatoire ne s affiche qu en saisie ; en consultation, seul
   l asterisque d un obligatoire VIDE reste, pour voir ce qui manque. */
:is(.ds .self-service, .ds.self-service):not(.is-editing) .field.has-success .symbol.required { display: none; }

:is(.ds .sections, .ds.sections) {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 260px;
  gap: var(--space-5);
  align-items: start;
}
/* La colonne du sommaire s etire sur toute la hauteur des sections (la grille aligne en haut) : un
   element collant ne bouge que DANS son parent, a la hauteur du sommaire il ne suivrait pas le
   defilement. */
.ds .sections-aside { align-self: stretch; }
.ds .sections-main {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  min-width: 0;
}
/* `scroll-margin-top` : une section atteinte depuis le sommaire s arrete SOUS la barre collante.
   hmSections.js lit cette valeur pour savoir quelle section est en cours. */
.ds .sections-card {
  scroll-margin-top: 72px;
}
/* `.card` pose `display: flex`, qui l emporte sur l attribut `hidden`. */
.ds .sections-card[hidden] { display: none; }
/* LA SECTION EN COURS : un fond primaire tres doux sur son en-tete, rien de plus. La barre de 3px
   a gauche qu elle portait doublait le bord de la carte et se lisait comme une selection ou une
   erreur ; le sommaire designe deja la section en cours (28/09/2026). */
.ds .sections-card > .card-head {
  /* 48px et non les 61px d une carte de tableau de bord : dans une fiche, les sections
     s enchainent, et chaque en-tete gagne 13px de champs visibles. */
  min-height: 48px;
  transition: background-color .4s ease;
}
.ds .sections-card.is-active > .card-head {
  background: color-mix(in srgb, var(--primary-soft) 55%, transparent);
}
.ds .sections-count {
  flex: none;
  font: var(--font-meta);
  color: var(--text-muted);
}

.ds .sections-nav {
  position: sticky;
  top: 72px;
}
.ds .sections-nav > .card-head { min-height: 0; }
.ds .sections-nav-toggle {
  display: none;
  color: var(--text-muted);
}
.ds .sections-glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--icon-md, 16px);
  height: var(--icon-md, 16px);
}
.ds .sections-glyph > :is(i, svg) { width: 100%; height: 100%; }
/* Liens : `ds-bridge.css` force `a { color: var(--primary) !important }` ; les couleurs du
   sommaire se reposent au meme poids. */
.ds a.sections-jump {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 6px 8px;
  border-radius: var(--radius-sm);
  font: var(--font-body-sm);
  color: var(--text-link) !important;
  text-decoration: none;
}
.ds a.sections-jump:is(:hover, :focus) { background: var(--surface-hover); text-decoration: none; }
.ds .sections-list {
  list-style: none;
  margin: var(--space-2) 0;
  padding: 0;
  max-height: 50vh;
  overflow-y: auto;
  border-top: 1px solid var(--border-subtle);
  border-bottom: 1px solid var(--border-subtle);
}
.ds .sections-list > li { border-left: 2px solid transparent; }
.ds .sections-list > li[hidden] { display: none; }
.ds .sections-list > li.is-active { border-left-color: var(--primary); }
.ds .sections-list a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 9px 12px;
  font: var(--font-body-sm);
  color: var(--text-secondary) !important;
  text-decoration: none;
}
.ds .sections-list a:is(:hover, :focus),
.ds .sections-list > li.is-active a { color: var(--text-link) !important; text-decoration: none; }
.ds .sections-list > li.is-active a { font-weight: var(--weight-medium); }
.ds .sections-list-label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ds .sections-dot {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  cursor: pointer;
}
.ds .sections-dot .sections-glyph { width: 9px; height: 9px; }
.ds .sections-dot:hover { box-shadow: 0 0 0 3px var(--surface-hover); }
/* COMME UN BADGE (28/09/2026, demande) : fond doux du statut, glyphe dans sa teinte de texte — pas de
   pastille pleine et vive, a la dose des badges et des statuts du portail. */
.ds .sections-dot.is-complete { background: var(--success-soft); color: var(--success-fg); }
.ds .sections-dot.is-required { background: var(--danger-soft); color: var(--danger-fg); }
.ds .sections-dot.is-partial  { border: 1.5px solid var(--border-strong); }

/* ECRAN ETROIT : le sommaire passe AU-DESSUS des sections, replie ; son en-tete le deplie. */
@media (max-width: 991px) {
  :is(.ds .sections, .ds.sections) { grid-template-columns: minmax(0, 1fr); }
  .ds .sections-aside { order: -1; }
  .ds .sections-nav { position: static; }
  .ds .sections-nav > .card-head { cursor: pointer; }
  .ds .sections-nav-toggle { display: inline-flex; }
  .ds .sections-nav.is-collapsed > .card-body { display: none; }
  .ds .sections-list { max-height: 40vh; }
}

/* DANS UNE FENETRE, LA FICHE GARDE SA FORME DE PAGE (29/09/2026, demande) : la meme carte unique
   (barre, sections, sommaire) que dans le profil et la creation de salarie. Pour ne pas la poser
   sur la surface blanche de la fenetre (carte dans la carte), le corps de la fenetre prend le fond
   de page. */
.ds .modal-body:has(.self-service:not(.is-plain)) { background: var(--surface-sunken); }

/* SANS BARRE (`is-plain`, hide-toolbar) : les champs seuls, sans titre de section ni sommaire. */
:is(.ds .self-service, .ds.self-service).is-plain .sections { grid-template-columns: minmax(0, 1fr); }
:is(.ds .self-service, .ds.self-service).is-plain :is(.sections-aside, .sections-card > .card-head) { display: none; }
:is(.ds .self-service, .ds.self-service).is-plain .sections-card {
  background: none;
  border: none;
  box-shadow: none;
}
:is(.ds .self-service, .ds.self-service).is-plain .sections-card > .card-body { padding: 0; }

/* ---- FILTRES EPINGLES (`pinned-filters`) -----------------------------------
   La bande des filtres epingles au-dessus d une liste (hmPinnedFilters.html) :
   une `card flat`, dont les filtres (`field`) sont ranges par une grille — quatre
   par rangee sur une page courante, deux sur ecran etroit, sans point de rupture.
   AUCUN flottant : la bande precede directement la grille des tuiles, et une
   grille se retrecit a cote d un flottant au lieu de passer dessous. */
hm-pinned-filters { display: block; }
.ds .pinned-filters {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
  padding: var(--space-4);
}

/* ---- BARRE DE SELECTION ----------------------------------------------------
   Entre l en-tete et la liste tant que des lignes sont cochees (hmGrid.html,
   hmCard.html) : surface teintee du primaire, filet primaire, le compte en
   primaire, puis les actions de lot.
     .selection-bar     la barre ; .selection-count le compte ; .selection-clear
                        « annuler la selection »
     .selection-hint    meme emplacement, rien de coche : une bande discrete qui
                        dit ou sont les actions. MEME BOITE que la barre (hauteur,
                        marge) : cocher une ligne echange le contenu sans pousser
                        le tableau. */
.ds .selection-bar,
.ds .selection-hint {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 50px;
  margin-bottom: var(--space-4);
  padding: 8px 12px 8px 14px;
  border-radius: var(--radius-md);
}
.ds .selection-bar {
  flex-wrap: wrap;
  background: var(--primary-soft);
  border: 1px solid var(--primary-border);
}
.ds .selection-count {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font: var(--font-label);
  color: var(--primary-700);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.ds .selection-clear {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  margin-left: auto;
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--primary-700);
}
.ds .selection-clear:hover { background: var(--primary-soft-hover); }
.ds .selection-clear:focus-visible { outline: none; box-shadow: var(--ring); }
.ds .selection-hint {
  background: var(--surface-sunken);
  border: 1px solid var(--border-subtle);
  font: var(--font-body-sm);
  color: var(--text-muted);
}
.ds .selection-hint > i,
.ds .selection-hint > svg { flex: none; font-size: var(--text-base); }
/* Sur la surface teintee, les actions de lot sont toutes des controles NEUTRES,
   compacts, dans l ordre ou l ecran les declare — quelle que soit la variante
   de leur configuration, sauf le destructif, qui garde sa couleur. */
.ds .selection-bar .ds-actions.is-global { flex-direction: row; flex-wrap: wrap; }
.ds .selection-bar .ds-btn {
  min-height: var(--control-h-sm);
  padding: 5px 11px;
  gap: 6px;
  font-size: var(--text-sm);
  background: var(--surface);
  border-color: var(--field-border);
  color: var(--text-secondary);
}
.ds .selection-bar .ds-btn:hover { background: var(--surface-hover); color: var(--text-primary); }
.ds .selection-bar .ds-btn-danger { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 30%, var(--surface)); }
.ds .selection-bar .ds-btn-danger:hover { background: var(--danger-soft); color: var(--danger-fg); }
.ds .selection-bar a.ds-btn { color: var(--text-secondary) !important; }
.ds .selection-bar a.ds-btn:hover { color: var(--text-primary) !important; }
.ds .selection-bar a.ds-btn-danger { color: var(--danger) !important; }
.ds .selection-bar a.ds-btn-danger:hover { color: var(--danger-fg) !important; }

/* ---- ACTIONS (`ds-actions`) ------------------------------------------------
   Un groupe d actions (hmButtons.html) : des `ds-btn`, et au besoin un menu
   « … » (`.ac` + `ac-menu`) pour celles qui ne tiennent pas.
     .ds-actions.is-global   en-tete de liste, barre de selection : boutons a libelle
     .ds-actions.is-row      ligne de tableau, detail, ligne de filtres : icones
                             discretes de 28px, sans ecart (le pas est dans la boite)
     .ds-actions.is-card     pied de tuile : actions a libelle discretes, qui
                             passent a la ligne plutot que de sortir de la tuile ;
                             `is-empty` quand aucune action n est visible
       .ds-actions-more        le « … » d une tuile, range au bout
     .ds-action              un bouton du groupe (l element de `hm-button`) */
:is(.ds .ds-actions, .ds.ds-actions) {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
/* L ordre de lecture que les ecrans ont toujours eu : l action principale au
   bord de fin (elles etaient posees en `pull-right`, donc a rebours du DOM). */
:is(.ds .ds-actions, .ds.ds-actions).is-global {
  flex-direction: row-reverse;
  flex-wrap: wrap-reverse;
}
:is(.ds .ds-actions, .ds.ds-actions).is-row {
  justify-content: flex-end;
  gap: 0;
}
:is(.ds .ds-actions, .ds.ds-actions).is-card {
  flex-wrap: wrap;
  justify-content: space-around;
  gap: var(--space-2);
  min-height: 44px;
  padding: var(--space-2) var(--space-3);
}
/* Chaque action se dimensionne sur son libelle (`flex-basis: auto`) et peut
   descendre sous sa largeur de contenu (`min-width: 0`) : c est ce qui la fait
   passer a la ligne au lieu de deborder. */
:is(.ds .ds-actions, .ds.ds-actions).is-card > hm-button { flex: 0 1 auto; min-width: 0; }
.ds .ds-actions-more { flex: none; margin-left: auto; }
.ds .ds-action { min-width: 0; }
/* Emplacement de squelette d une action de tuile. */
.ds .ds-actions-sk {
  display: inline-block;
  width: 20px;
  height: 20px;
  border-radius: var(--radius-xs);
}

/* ---- TABLEAU (`ds-table`) --------------------------------------------------
   `ds-table` et non `.table` : Bootstrap porte `.table` et le restyle (marges,
   filets doubles, cellules encadrees). Aucune regle legacy ne vise `ds-table`,
   la recette n a donc rien a combattre.

   La reference REGLE les lignes et n encadre rien : la carte ferme deja le bloc.
     .ds-table-filters     1re ligne du corps : les champs de filtre par colonne
     tr.is-selected        ligne dont le detail est ouvert
     tr.is-selectable      ligne cliquable (liste de choix)
     td.is-detail-trigger  cellule qui ouvre le detail (indice « Details › » au survol)
       .ds-table-cell        son contenu ; .ds-table-cell-value la valeur ;
       .ds-table-value       valeur + pastille/avatar alignes ;
       .ds-table-detail-hint l indice
     .ds-table-actions     colonne des actions de ligne
     .ds-table-empty       ligne « aucune donnee »
     .ds-table-col-check   colonne de la case a cocher
     .ds-table-sk-*        emplacements de squelette (case, avatar, action) */
.ds .ds-table {
  width: 100%;
  margin: 0;
  border-collapse: collapse;
  background: transparent;
}
.ds .ds-table-col-check { width: 30px; }
.ds .ds-table thead > tr { border-bottom: 1px solid var(--border); }
/* Retrait symetrique et `middle` : la hauteur d un `th` est celle de la plus
   haute cellule de la rangee, un libelle aligne en bas y sortait decentre. */
.ds .ds-table th {
  padding: var(--space-3);
  vertical-align: middle;
  background: transparent;
}
.ds .ds-table td {
  padding: var(--space-2);
  vertical-align: middle;
  background: transparent;
  font: var(--font-body-sm);
  color: var(--text-secondary);
}
/* Les FILETS sont portes par le haut de chaque ligne, sauf la premiere : ainsi
   la derniere ligne ne trace rien sous elle, et le pied de la carte (qui porte
   son propre filet) ne double pas la regle. La ligne de filtres ferme l en-tete
   par le bas. `--border` et non `--border-subtle` : les lignes portent TOUTE la
   structure ici, un filet a 3 % du blanc n existe que dans le modele de boite. */
.ds .ds-table-filters > td {
  padding: var(--space-3) var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border);
}
.ds .ds-table tbody > tr:not(.ds-table-filters) + tr > td { border-top: 1px solid var(--border); }
.ds .ds-table tbody > tr:not(.ds-table-filters):not(.ds-table-empty):hover > td { background: var(--surface-hover); }
.ds .ds-table tbody > tr.is-selected > td { background: var(--primary-soft); }
.ds .ds-table tbody > tr.is-selectable > td { cursor: pointer; }

.ds .ds-table td.is-detail-trigger { cursor: pointer; }
.ds .ds-table td.is-detail-trigger > .ds-table-cell {
  display: flex;
  align-items: center;
}
.ds .ds-table td.is-detail-trigger .ds-table-cell-value { flex: 1; min-width: 0; }
.ds .ds-table-value {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}
.ds .ds-table-detail-hint {
  display: flex;
  align-items: center;
  gap: 3px;
  margin-left: auto;
  font: var(--font-meta);
  color: var(--text-muted);
  white-space: nowrap;
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-standard);
}
.ds .ds-table td.is-detail-trigger:hover .ds-table-detail-hint { opacity: 1; }

.ds .ds-table-actions {
  text-align: right;
  white-space: nowrap;
}
.ds .ds-table-empty > td {
  padding: var(--space-8) 0;
  text-align: center;
  font: var(--font-body);
  color: var(--text-muted);
}

/* Squelette : des boites `skeleton` aux dimensions des elements qu elles
   annoncent ; la ligne de texte est une `skeleton-line`. */
.ds .ds-table-sk-check {
  display: block;
  width: 15px;
  height: 15px;
  border-radius: var(--radius-xs);
}
.ds .ds-table-sk-avatar {
  flex: none;
  width: 35px;
  height: 35px;
  border-radius: 50%;
}
.ds .ds-table-sk-action {
  display: inline-block;
  width: 20px;
  height: 10px;
  margin: 0 5px;
  border-radius: var(--radius-xs);
}
@media (prefers-reduced-motion: reduce) {
  .ds .ds-table-detail-hint { transition: none; }
}

/* ARBRE (hmTreeList.html) : le meme tableau, dont la PREMIERE cellule porte le
   niveau de la ligne.
     .ds-tree-cell      la cellule : l ouvreur, puis la valeur
     .ds-tree-toggle    ouvre / ferme la branche ; `is-leaf` : une feuille — la
                        place reste prise, pour que les valeurs d un meme niveau
                        s alignent
   Le RETRAIT (niveau x pas, hmTreeList.js) est une DONNEE calculee : il arrive en
   `ng-style`. */
.ds .ds-tree-cell {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  min-width: 0;
}
.ds .ds-tree-cell > .ds-table-cell-value { flex: 1; min-width: 0; }
.ds .ds-tree-toggle {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-xs);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}
.ds .ds-tree-toggle:hover { background: var(--surface-hover); color: var(--primary); }
.ds .ds-tree-toggle:focus-visible { outline: none; box-shadow: var(--ring); }
.ds .ds-tree-toggle.is-leaf { visibility: hidden; }
.ds .ds-tree-toggle > i,
.ds .ds-tree-toggle > svg { width: 12px; height: 12px; }

/* ---- CELLULE (`cell-value`) ------------------------------------------------
   La valeur d une colonne (hmCell.html), dans un tableau, un detail, un arbre ou
   la frise. Les couleurs d une valeur (statut, booleen, icone d information) sont
   des DONNEES de l ecran et arrivent en classe ou en `ng-style` : cette recette
   n en porte aucune.
     .cell-avatar      avatar(s) et nom
     .cell-link        valeur qui mene quelque part (lien, ou action de la colonne)
     .cell-dot         point de statut : 8px, comme la pastille qui le filtre
     .cell-rating      etoiles d une note ; `is-on` sur celles qui sont atteintes
     .cell-icons       icones d information de la colonne
     .is-breakable     coupe les mots longs (reglage de la societe) */
:is(.ds .cell-value, .ds.cell-value),
.ds .cell-avatar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}
:is(.ds .cell-value, .ds.cell-value) .is-breakable { word-break: break-all; }
.ds .cell-link {
  font-weight: var(--weight-bold);
  color: var(--text-link);
  text-decoration: none;
  cursor: pointer;
}
.ds .cell-link:hover { text-decoration: underline; }
.ds .cell-dot {
  display: inline-flex;
  align-items: center;
}
.ds .cell-dot > i,
.ds .cell-dot > svg { width: 8px; height: 8px; font-size: 8px; }
.ds .cell-rating {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}
.ds .cell-star { display: inline-flex; color: var(--border-strong); }
.ds .cell-star.is-on { color: var(--warning); }
.ds .cell-star > i,
.ds .cell-star > svg { width: 14px; height: 14px; }
.ds .cell-icons {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}
.ds .cell-icon { display: inline-flex; }

/* TABLEAU ET DETAIL COTE A COTE (hmGrid.html). La repartition est une donnee de
   l ecran (`breakPoint.detailsWidthPercentage`), posee en `flex` calcule sur les
   deux volets.
     .list-split                  la rangee
       .list-table                le volet du tableau ; defile horizontalement sur
                                  ecran etroit plutot que de pousser la page
       .list-details              le volet du detail (plus bas)
     .list-split.is-details-full  le detail occupe toute la largeur : le volet du
                                  tableau est replie, en largeur ET en hauteur — sinon
                                  la rangee garde la hauteur d un arbre ouvert sous le
                                  detail (il reste dans le DOM, son etat de defilement
                                  et de filtres est conserve) */
.ds .list-split {
  display: flex;
  width: 100%;
}
.ds .list-table { min-width: 0; }
.ds .list-split.is-details-full > .list-table {
  width: 0;
  height: 0;
  overflow: hidden;
  visibility: hidden;
}
@media only screen and (max-width: 767px) {
  .ds .list-table { overflow-x: auto; }
}

/* Le panneau de detail A COTE du tableau (hmGrid.html) : un filet vertical et
   une gouttiere le separent des lignes. Quand il occupe toute la largeur
   (`is-full`, le tableau est masque), il n a plus rien a separer. */
.ds .list-details {
  min-width: 0;
  margin-left: var(--space-4);
  padding-left: var(--space-5);
  border-left: 1px solid var(--border);
}
.ds .list-details.is-full {
  margin-left: 0;
  padding-left: 0;
  border-left: none;
}

/* ---- DETAIL (`details`) ----------------------------------------------------
   Le detail d un element (hmDetails.html, pour hm-grid et hm-tree-list).
     .details            le panneau : colonne, pleine hauteur
     .details-head       masquer + titre + actions, sur une ligne, filet dessous
       .details-title      le nom de l element
       .details-actions    ses actions, poussees a droite
     .details-body       le contenu ; `is-scroll` : il defile sur place au lieu
                         d agrandir la ligne (config.details.withScroll)
     .details-list       les champs, en grille : 1 colonne, ou 2 (`cols-2`)
       .details-item       un champ : .details-label (discret) + .details-value
     .details-foot       les actions sur l element (valider, refuser, « … »), au bord
                         de fin, filet dessus */
:is(.ds .details, .ds.details) {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-width: 0;
}
.ds .details-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--border-subtle);
}
.ds .details-title {
  flex: 1;
  min-width: 0;
  margin: 0;
  overflow: hidden;
  font: var(--font-h3);
  color: var(--text-primary);
  white-space: nowrap;
  text-overflow: ellipsis;
}
.ds .details-title > .skeleton-line { width: 10rem; }
.ds .details-actions {
  display: flex;
  align-items: center;
  margin-left: auto;
}
.ds .details-body {
  position: relative;
  flex: 1;
  min-height: 0;
  padding-top: var(--space-4);
}
/* Le contenu sort du flux pour que la hauteur du panneau reste celle du
   tableau voisin : sans cela, un long detail agrandirait toute la rangee. */
.ds .details-body.is-scroll > .details-list {
  position: absolute;
  inset: var(--space-4) 0 0;
  overflow-y: auto;
}
.ds .details-list {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-4) var(--space-5);
}
.ds .details-list.cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
/* Trois colonnes : le resume d un objet en tete de page (la demande au-dessus de
   ses candidatures). Une colonne sur telephone, comme `.grid3`. */
.ds .details-list.cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media only screen and (max-width: 767px) {
  .ds .details-list.cols-3 { grid-template-columns: minmax(0, 1fr); }
}
.ds .details-item {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}
.ds .details-label {
  font: var(--font-meta);
  color: var(--text-muted);
}
.ds .details-label > .skeleton-line { width: 6rem; }
.ds .details-value {
  min-width: 0;
  font: var(--font-body-sm);
  color: var(--text-primary);
}
.ds .details-value > .skeleton-line { width: 9rem; }
.ds .details-foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-subtle);
}

/* ---- TRI (`ds-sort`) -------------------------------------------------------
   L en-tete d une colonne triable (hmSort.html) : un <button>, pour que le tri se
   prenne au clavier. Le libelle est discret (petit, gris), coupe par une ellipse :
   l en-tete ne doit jamais elargir sa colonne ni deborder sur la voisine. En casse
   NORMALE, tel qu il est traduit (28/09/2026) : les capitales espacees prenaient 15 a
   25 % de largeur en plus et se lisaient moins bien sur un libelle long. Colonne non
   triable : bouton desactive, sans fleches.
     .ds-sort-label    le nom de la colonne
     .ds-sort-arrows   les deux fleches, empilees ; `is-active` sur le sens courant */
:is(.ds .ds-sort, .ds.ds-sort) {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  max-width: 100%;
  margin: 0;
  padding: 0;
  border: none;
  background: none;
  text-align: left;
  cursor: pointer;
}
/* L ELLIPSE NE SUFFIT PAS SEULE. En mise en page automatique, un tableau ne retrecit jamais une
   colonne sous la largeur de son contenu : un libelle `nowrap` impose donc sa largeur a la colonne,
   l ellipse ne se declenche pas, et un tableau aux libelles longs pousse la page (constate sur la
   liste des questionnaires du profil, 7 colonnes en capitales). `max-width: 0` sur la cellule d en-tete
   retire le libelle du calcul : la colonne prend la largeur de ses VALEURS, le libelle se coupe
   (entier en `title`). Seuls les en-tetes a libelle (`ds-sort`) : la case a cocher et les actions
   gardent leur largeur. */
.ds .ds-table th:has(.ds-sort) { max-width: 0; }
:is(.ds .ds-sort, .ds.ds-sort)[disabled] { cursor: default; }
:is(.ds .ds-sort, .ds.ds-sort):focus-visible { outline: none; box-shadow: var(--ring); border-radius: var(--radius-xs); }
.ds .ds-sort-label {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font: var(--weight-medium) var(--text-xs)/1.4 var(--font-sans);
  color: var(--text-muted);
}
/* `fa-sort-up` et `fa-sort-down` sont les deux MOITIES d une meme boite : posees
   dans la meme cellule de grille, elles forment l icone de tri entiere, et chacune
   garde sa couleur. */
.ds .ds-sort-arrows {
  flex: none;
  display: inline-grid;
  color: var(--text-disabled);
}
.ds .ds-sort-arrow { grid-area: 1 / 1; display: inline-flex; }
.ds .ds-sort-arrow > i,
.ds .ds-sort-arrow > svg { width: 12px; height: 12px; }
.ds .ds-sort-arrow.is-active { color: var(--primary); }
:is(.ds .ds-sort, .ds.ds-sort):hover:not([disabled]) .ds-sort-label { color: var(--text-secondary); }

/* ---- FILTRES ---------------------------------------------------------------
   Le filtre d une colonne (hmFilter.html) et ses fenetres (HmFilters/*.html).
     .ds-filter              le filtre ; `is-filtered` quand une valeur est appliquee
                             Son champ declencheur est un `.input-icon.trail-pad` :
                             le champ (en lecture seule, il ouvre la fenetre) et
                             `.trail`, le bouton « effacer » en fin de champ.
     .filter-pop-body        le contenu d une fenetre de filtre : une colonne
       .filter-pop-title       son intitule
       .filter-pop-shortcuts   les raccourcis de date (des `chip`)
     .filter-pop-foot        « Annuler » (fantome) + « Valider » (primaire) — un seul
                             bouton plein : deux contours cote a cote se liraient comme
                             deux choix egaux, quand un seul applique le filtre
     .filter-list            la fenetre des colonnes masquees (hmHiddenFilters.html)
       .filter-item            un filtre : son en-tete, puis le filtre
         .filter-item-head       le tri (`ds-sort`) + l epingle (`filter-pin`)
       .filter-list-foot       « reinitialiser »

   FILTRE APPLIQUE : la bordure passe a `--warning` — la meme teinte que le
   compteur de filtres de l en-tete, l un dit combien, l autre ou. Pas `--primary` :
   c est la couleur du focus, un champ filtre et un champ actif seraient
   indiscernables. La couleur passe par la VARIABLE `--field-border` : c est elle
   que lisent la recette `.input` ET la regle de `ds-bridge.overrides.css` qui
   peint la bordure de tout champ en !important — elle peint donc l orange
   elle-meme, sans surenchere. Le focus garde l orange : il est porte par l anneau. */
:is(.ds .ds-filter, .ds.ds-filter) { min-width: 0; }
:is(.ds .ds-filter, .ds.ds-filter).is-filtered .input,
:is(.ds .ds-filter, .ds.ds-filter).is-filtered .select {
  --field-border: var(--warning);
  --input-border: var(--warning);
}
:is(.ds .ds-filter, .ds.ds-filter).is-filtered .input:focus,
:is(.ds .ds-filter, .ds.ds-filter).is-filtered .select:focus { border-color: var(--warning) !important; }
/* La valeur s abrege AVANT la croix au lieu de passer dessous. */
:is(.ds .ds-filter, .ds.ds-filter) .input { text-overflow: ellipsis; }
:is(.ds .ds-filter, .ds.ds-filter) .input-icon > .trail {
  width: 24px;
  height: 24px;
  right: 6px;
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  background: none;
}
:is(.ds .ds-filter, .ds.ds-filter) .input-icon > .trail:hover { background: var(--surface-hover); }

.ds .filter-pop-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.ds .filter-pop-title { font: var(--font-label); color: var(--text-secondary); }
.ds .filter-pop-shortcuts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
.ds .filter-pop-foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-top: var(--space-4);
}

/* Onze filtres empiles avec le meme ecart partout : l oeil ne savait plus quel
   libelle allait avec quel champ. La proximite suffit — l ecart DANS un filtre
   (en-tete → champ) est la moitie de l ecart ENTRE deux filtres. */
.ds .filter-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}
.ds .filter-item {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}
.ds .filter-item-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}
.ds .filter-pin > .ds-btn-glyph { transform: rotate(45deg); }
.ds .filter-pin[aria-pressed="true"] { color: var(--primary); }
.ds .filter-list-foot > .ds-btn { width: 100%; }

/* Bouton d en-tete des filtres, quand des filtres sont appliques : le glyphe prend
   la teinte des filtres, et leur nombre s affiche dans un compteur de cette teinte. */
:is(.ds .ds-btn, .ds.ds-btn).is-filtered { color: var(--warning); }
.ds .ds-btn-count.is-warning { background: var(--warning); color: var(--gray-0); }

/* ---- PAGINATION (`ds-pager`) — `.pager` est une classe Bootstrap --------------------------------------------------
   hmPagination.html, pour les listes, le calendrier et la frise.
     .ds-pager-count   le nombre d elements, en retrait : un compte, pas un controle ; d un tenant
     .ds-pager-pages   premiere / precedente / numeros / suivante / derniere, dans un `seg` au meme
                    habillage que le nombre par page (choix du 23/10/2026) : la page courante en
                    `on`, une fleche sans page ou aller grisee. Les frises de Mon equipe y posent
                    encore deux `ds-btn` fantomes.
     .ds-pager-size    le nombre de lignes par page (`seg`, ou `select`), pousse au
                    bord de fin
   Les boutons des deux `seg` sont des carres du pas de 32px.
   Pas de filet : dans une liste, c est le pied de la carte qui ferme les lignes. */
:is(.ds .ds-pager, .ds.ds-pager) {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  font: var(--font-body-sm);
}
.ds .ds-pager-count { color: var(--text-muted); white-space: nowrap; }
.ds .ds-pager-pages {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px;
}
/* Une fleche sans page ou aller : grisee, sans la boite du bouton desactive (frises de Mon equipe). */
:is(.ds .ds-pager, .ds.ds-pager) .ds-btn-ghost[disabled] { background: none; border-color: transparent; }
.ds .ds-pager-size { margin-left: auto; }
:is(.ds .ds-pager, .ds.ds-pager) .seg button { min-width: var(--control-h-sm); padding: 0 var(--space-2); }
/* La page courante garde la couleur principale, pleine : c est le repere de la liste. */
:is(.ds .ds-pager, .ds.ds-pager) .ds-pager-pages .seg button.on { background: var(--primary); color: var(--on-primary); }
.ds .ds-pager-size .select.select {
  min-height: var(--control-h-sm) !important;
  height: var(--control-h-sm) !important;
}

/* Le pied d une liste empile ses deux sous-composants (pastilles de filtre, puis
   pagination) avec un ecart ; en widget d accueil, il se resserre. */
:is(.ds .list, .ds.list) > .card > .card-foot {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
:is(.ds .list, .ds.list).is-widget .ds-pager,
:is(.ds .list, .ds.list).is-widget > hm-legend > .chip-group { padding: var(--space-1) var(--space-2); }

/* ---- ECRANS ETROITS (listes) ----------------------------------------------
   La barre passe a la ligne d elle-meme (`ds-toolbar`, plus haut). Les tuiles passent a UNE
   colonne : le plancher de 22rem en laisserait deux de 308px sur un telephone, ce
   ne serait plus une liste de cartes mais un tableau. */
@media only screen and (max-width: 767px) {
  .ds .tiles { grid-template-columns: 1fr; }
  .ds .ds-pager-size { margin-left: 0; }
}
/* Smartphone : le compte, puis le rail des pages, aux extremes de la ligne ; le nombre de lignes par
   page est masque. paginationLayout.service reduit le rail selon la largeur de la pagination : deux
   numeros sous 480px, plus de premiere / derniere page sous 340px, pour tout garder sur UNE ligne
   (choix du 23/10/2026). Le passage a la ligne ne reste qu un filet contre le debordement. */
@media only screen and (max-width: 599px) {
  .ds .ds-pager-size { display: none; }
  :is(.ds .ds-pager, .ds.ds-pager) { justify-content: space-between; }
}

/* ---- TOPBAR (barre du haut de la coque) ----------------------------------
   Meme principe que la carte : UN composant, UNE classe par role, et rien
   d autre dans le gabarit que ces roles et les recettes du design system.

     .topbar              la barre
     .topbar-user         lien vers le profil : avatar + salutation
       .topbar-user-text    bloc de texte (masque en etroit)
       .topbar-user-name    la salutation
       .topbar-user-meta    la date
     .topbar-search       creneau de la recherche (recette `.ac`)
     .topbar-actions      rangee de boutons de fin
     .topbar-label        libelle de bouton qui s efface en etroit
     .topbar-desk         bouton absent sur smartphone (< 600px) : il passe dans la barre du bas
     .topbar-favs         « Mes favoris » : bouton etoile + liste
       (-menu, -head, -title, -list, -row, -text, -empty, -foot)
     .topbar-skeleton-btn (.is-wide), .topbar-skeleton-search   squelettes avant topbarReady

   Les boutons (`.ds-btn`), le champ (`.input-icon`), le menu (`.ac-menu`), le
   compteur (`.ds-btn-count`) sont ceux du design system : la barre les PLACE, elle
   ne les redessine pas. Source de la geometrie : maquette « Accueil HRmaps ». */
/* Au-dessus du contenu (barres collantes, menus a 99/1000) et sous les modales (1040/1050),
   mais sous le nav (1035) : le panneau replie qui s ouvre par-dessus doit couvrir la barre. */
.ds .topbar {
  position: relative;
  z-index: 1034;
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--space-4);
  height: var(--space-16);
  padding: 0 22px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

.ds .topbar-user {
  flex: none;
  display: flex;
  align-items: center;
  gap: 11px;
  padding: var(--space-1) 10px var(--space-1) var(--space-1);
  border-radius: var(--radius-pill);
  text-decoration: none;
}
.ds .topbar-user:hover { background: var(--surface-hover); text-decoration: none; }
.ds .topbar-user:focus-visible { outline: none; box-shadow: var(--ring); }

/* CE BLOC EST UN LIEN, ET RIEN NE LE DISAIT : au repos, un avatar et deux lignes
   de texte se lisent comme une information, pas comme une action — seul le fond
   du survol trahissait le lien, donc seulement a qui passait deja la souris
   dessus. Trois signaux, du plus permanent au plus ponctuel :
     1. au repos, un chevron discret en fin de bloc — la convention « mene
        ailleurs » ;
     2. au survol et au focus, la salutation et le chevron passent au primaire,
        le chevron avance d un cran ;
     3. l info-bulle « Mon profil » (`data-tip`), qui nomme la destination. */
.ds .topbar-user-go {
  flex: none;
  width: .75rem;
  height: .75rem;
  font-size: .75rem;
  color: var(--text-muted);
  transition: transform var(--duration-fast) var(--ease-standard);
}
.ds .topbar-user:hover .topbar-user-name,
.ds .topbar-user:focus-visible .topbar-user-name,
.ds .topbar-user:hover .topbar-user-go,
.ds .topbar-user:focus-visible .topbar-user-go { color: var(--primary); }
.ds .topbar-user:hover .topbar-user-go,
.ds .topbar-user:focus-visible .topbar-user-go { transform: translateX(2px); }
@media (prefers-reduced-motion: reduce) {
  .ds .topbar-user-go { transition: none; }
}

.ds .topbar-user-text {
  display: flex;
  flex-direction: column;
  line-height: 1.2;
  text-align: left;
}
/* La couleur est posee sur le texte et non sur le lien : `ds-bridge.css` peint
   tout `a` en `--primary` avec !important. Le texte la reprend donc sur son
   propre element, plutot que de combattre ce !important. */
.ds .topbar-user-name { font: var(--weight-bold) var(--text-base) var(--font-sans); color: var(--text-primary); }
.ds .topbar-user-meta { font: var(--font-meta); color: var(--text-muted); }
/* Squelette de la salutation (elle attend /api/translations) : une
   `skeleton-line` DANS chaque ligne de texte, qui garde donc sa hauteur reelle —
   rien ne bouge quand le texte arrive. Seule la longueur est propre a la barre. */
.ds .topbar-user-name > .skeleton-line { width: 8rem; }
.ds .topbar-user-meta > .skeleton-line { width: 5.75rem; }

.ds .topbar-search {
  flex: 1;
  min-width: 120px;
  max-width: 620px;
  margin: 0 auto;
}
/* Element personnalise = inline par defaut : sans cela, le `.ac` (bloc) qu il
   contient ne prend pas la largeur du creneau. */
.ds .topbar-search hm-global-search { display: block; }
/* Le champ en pilule de la maquette. `!important` : `.ds .input` pose
   `border-radius: var(--radius-input) !important` ; (0,3,0) passe devant (0,2,0). */
.ds .topbar-search .input { border-radius: var(--radius-pill) !important; }
/* Le panneau defile sur place : le clavier (fleches) le fait defiler jusqu a la
   ligne active (hmGlobalSearch.js), ce qui suppose un plafond et un `overflow`. */
.ds .topbar-search .ac-menu {
  max-height: 360px;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.ds .topbar-search .ac-menu > .ds-btn { width: 100%; }

.ds .topbar-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.ds .topbar-skeleton-btn {
  width: var(--control-h);
  height: var(--control-h);
  border-radius: var(--radius-button);
}
/* « Créer » : libelle + icone + caret, mesure a 108px. */
.ds .topbar-skeleton-btn.is-wide { width: 108px; }
/* Squelette de l avatar de la coque, aux tailles de hm-avatar (avatarService.getSizePx) : sm dans la barre, xs dans le rail. */
.ds .shell-avatar-skeleton { display: block; flex: none; width: 55px; height: 55px; border-radius: 50%; }
.ds .shell-avatar-skeleton.is-xs { width: 35px; height: 35px; }
.ds .topbar-skeleton-search {
  height: var(--control-h);
  border-radius: var(--radius-pill);
}

.ds .topbar-favs > .ds-btn[aria-expanded="true"] {
  background: var(--primary-soft);
  border-color: var(--primary);
  color: var(--primary-700);
}
/* `.ac-menu` en plus : `.ds .ac-menu` est plus bas dans la feuille et reposerait son padding. */
.ds .ac-menu.topbar-favs-menu { width: 472px; max-width: calc(100vw - 32px); padding: 0; overflow: hidden; }
.ds .topbar-favs-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 8px 10px 4px 16px;
  font: var(--font-meta);
  font-weight: var(--weight-bold);
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.ds .topbar-favs-head svg { width: 12px; height: 12px; }
.ds .topbar-favs-title { flex: 1; }
.ds .topbar-favs-head .x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--text-muted);
  cursor: pointer;
}
.ds .topbar-favs-head .x svg { width: 14px; height: 14px; }
.ds .topbar-favs-head .x:hover { background: var(--surface-hover); color: var(--text-primary); }
.ds .topbar-favs-head .x:focus-visible { outline: none; box-shadow: var(--ring); }
.ds .topbar-favs-list {
  max-height: 360px;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0 8px 8px;
}
.ds .topbar-favs-row { position: relative; }
.ds .topbar-favs-row > .ac-item { padding: 10px 40px 10px 12px; gap: 14px; }
.ds .topbar-favs-row > .shell-menu-fav { right: 10px; }
.ds .topbar-favs-text { display: flex; flex-direction: column; min-width: 0; }
.ds .topbar-favs-text > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ds .topbar-favs-empty { padding: 8px 16px 16px; font: var(--font-body-sm); color: var(--text-muted); }
.ds .topbar-favs-foot {
  display: flex;
  justify-content: flex-end;
  padding: 10px 12px;
  border-top: 1px solid var(--border);
}
/* Reordering by drag (hm-sortable): the page icon turns into the widgets' move arrows on hover, the
   moved row fades, and a line shows where it will land. */
.ds .topbar-favs-grip { display: none; cursor: all-scroll; }
.ds .topbar-favs-row:hover .topbar-favs-grip { display: inline-block; }
.ds .topbar-favs-row:hover .topbar-favs-file { display: none; }
.ds .topbar-favs-row.is-dragging { opacity: .4; }
.ds .topbar-favs-row.is-drop-before::before,
.ds .topbar-favs-row.is-drop-after::after {
  content: "";
  position: absolute;
  left: 8px;
  right: 8px;
  height: 2px;
  border-radius: 1px;
  background: var(--primary);
  z-index: 1;
}
.ds .topbar-favs-row.is-drop-before::before { top: -1px; }
.ds .topbar-favs-row.is-drop-after::after { bottom: -1px; }

@media only screen and (max-width: 900px) {
  .ds .topbar-user-text { display: none; }
}
/* Le bouton garde son « + » et son caret : c est justement reduit a son glyphe
   qu il aurait le plus l air d une action directe. */
@media only screen and (max-width: 720px) {
  .ds .topbar-label { display: none; }
}
/* Smartphone (< 600px, le seuil de `app.isMobile`) : photo, recherche, theme.
   Favoris, Mes actions et Creer passent dans la barre du bas ; la photo n est
   plus qu une pastille (avatar de 40px choisi dans le gabarit), sans chevron. */
@media only screen and (max-width: 599px) {
  .ds .topbar { gap: var(--space-2); padding: 0 var(--space-3); }
  .ds .topbar-user { padding: 0; }
  .ds .topbar-user-go,
  .ds .topbar-desk { display: none; }
  .ds .topbar-search { min-width: 0; max-width: none; margin: 0; }
  .ds .topbar .shell-avatar-skeleton { width: 40px; height: 40px; }
}

/* ---- BARRE DU BAS (`bottombar`, smartphone seulement) ----------------------
   Sous 600px, ce que la barre du haut a laisse : Accueil, A traiter, Creer,
   Favoris, Menu. AUCUN style propre : les entrees sont les boutons du rail
   (`shell-rail-btn`, `shell-rail-tile`, `shell-rail-label`), Creer le bouton
   `ds-btn-primary` du web, le compteur `ds-btn-count`. La barre ne fait que les
   ranger en bas ; le smartphone garde le graphisme du web. Les menus Creer et
   Favoris sont ceux de la barre du haut (memes gabarits, shell-add-actions.html /
   shell-favorites.html), ouverts en feuille au-dessus de la barre.

     .bottombar           la barre, fixe en bas (masquee au-dessus de 600px)
     .bottombar-sheet     un `.ac-menu` ouvert en feuille au-dessus de la barre

   Hauteur : celle d un bouton du rail (6 + tuile 40 + 4 + libelle ~12 + 6). */
.ds .bottombar { display: none; }
@media only screen and (max-width: 599px) {
  .ds.shell { --bottombar-height: 68px; }
  /* Le contenu s arrete au-dessus de la barre : rien n est cache dessous. */
  .ds .shell-body { padding-bottom: calc(var(--bottombar-height) + env(safe-area-inset-bottom)); }
  /* La page ne glisse JAMAIS de cote sur un telephone. `overflow-y: auto` seul passe aussi
     `overflow-x` en `auto` : un element plus large que l ecran (la rangee de pastilles d une
     legende, avant sa mise en page) faisait defiler toute la page a gauche et a droite, jusqu a
     sortir la liste de l ecran (iPhone surtout). Les deux sont deja des zones de defilement :
     `hidden` n y change rien d autre (les elements colles restent colles). */
  .ds .shell-content,
  .ds .page { overflow-x: hidden; }
  /* Sous les menus de la coque (topbar 1034, nav 1035) et les modales (1040). */
  .ds .bottombar {
    position: fixed;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 1033;
    display: flex;
    align-items: center;
    height: calc(var(--bottombar-height) + env(safe-area-inset-bottom));
    padding: 0 var(--space-1) env(safe-area-inset-bottom);
    background: var(--surface);
    border-top: 1px solid var(--border);
  }
}
/* Cinq cases egales ; toute la case est tactile (le bouton du rail prend sa largeur). */
.ds .bottombar > * { flex: 1 1 0; min-width: 0; }
.ds .bottombar > .ac { display: flex; justify-content: center; }
/* (0,3,0) : passe devant `.ds .ac-menu` et, place apres elle, devant
   `.ds .ac-menu.topbar-favs-menu` (largeur du menu de la barre du haut). */
.ds .ac-menu.bottombar-sheet {
  position: fixed;
  top: auto;
  right: var(--space-3);
  bottom: calc(var(--bottombar-height) + var(--space-3) + env(safe-area-inset-bottom));
  left: var(--space-3);
  width: auto;
  max-width: none;
  max-height: calc(100vh - var(--bottombar-height) - var(--space-16) - var(--space-6));
  overflow-y: auto;
  box-shadow: var(--shadow-lg);
}

/* ---- FENETRE FLOTTANTE (`ds-popover`) --------------------------------------
   La fenetre de `hm-popover` (hmPopoverPopup.html) — fiche d un salarie au survol
   d un avatar, fenetres de filtre, details… ~1 000 usages, une seule coque.
     data-side           le cote du declencheur ou elle s ouvre : elle s en ecarte
                         de 10px, la place de la fleche
     .no-arrow           sans fleche : collee a son declencheur
     .size-xs … lg       la largeur demandee par l ecran ; sans taille, 276px au plus
     .is-capped          le contenu defile au-dela de 300px
     .ds-popover-arrow   la fleche : un carre tourne, dont deux cotes portent le
                         filet ; place par la feuille selon `data-side`, aligne au
                         depart / a la fin par popupPosition.service.js
     .ds-popover-inner   la surface
       .ds-popover-head  le titre
       .ds-popover-body  le contenu
   La POSITION (top / left) est calculee par le service et arrive en ligne. Jamais
   plus large que l ecran : `min(…, 100vw - 32px)`. */
.ds .ds-popover,
.ds.ds-popover {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 10004;
  display: block;
  max-width: min(276px, calc(100vw - 32px));
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--popover-shadow);
  font: var(--font-body);
  color: var(--text-primary);
  text-align: left;
  white-space: normal;
}
.ds-popover.size-xs { width: min(320px, calc(100vw - 32px)); max-width: min(320px, calc(100vw - 32px)); }
.ds-popover.size-sm { width: min(380px, calc(100vw - 32px)); max-width: min(380px, calc(100vw - 32px)); }
.ds-popover.size-md { width: min(480px, calc(100vw - 32px)); max-width: min(480px, calc(100vw - 32px)); }
.ds-popover.size-lg { width: min(780px, calc(100vw - 32px)); max-width: min(780px, calc(100vw - 32px)); }
.ds-popover[data-side="top"]    { margin-top: -10px; }
.ds-popover[data-side="bottom"] { margin-top: 10px; }
.ds-popover[data-side="left"]   { margin-left: -10px; }
.ds-popover[data-side="right"]  { margin-left: 10px; }
.ds-popover.no-arrow { margin: 0; }
.ds-popover-inner { border-radius: inherit; }
.ds-popover-head {
  display: flex;
  align-items: center;
  min-height: 48px;
  padding: 0 var(--space-4);
  border-bottom: 1px solid var(--border);
  font: var(--font-h3);
  color: var(--text-primary);
}
.ds-popover-body { padding: var(--space-3) var(--space-4); }
.ds-popover.is-capped .ds-popover-body {
  max-height: 300px;
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* La fleche passe PAR-DESSUS le filet de la surface (z-index) : son fond efface
   le trait a sa base, et ses deux cotes bordes prolongent le contour. */
.ds-popover-arrow {
  position: absolute;
  z-index: 1;
  width: 12px;
  height: 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  transform: rotate(45deg);
}
.ds-popover[data-side="top"] > .ds-popover-arrow    { bottom: -7px; left: calc(50% - 6px); border-top: none; border-left: none; }
.ds-popover[data-side="bottom"] > .ds-popover-arrow { top: -7px;    left: calc(50% - 6px); border-bottom: none; border-right: none; }
.ds-popover[data-side="left"] > .ds-popover-arrow   { right: -7px;  top: calc(50% - 6px);  border-bottom: none; border-left: none; }
.ds-popover[data-side="right"] > .ds-popover-arrow  { left: -7px;   top: calc(50% - 6px);  border-top: none; border-right: none; }

/* ---- INFO-BULLE (`data-tip`) ----------------------------------------------
   Le libelle d un bouton a glyphe seul. `uib-tooltip` ajoutait en fin de page
   un `<div class="tooltip">` aux classes Bootstrap ; ici, rien n est cree : la
   bulle est le `::after` du bouton lui-meme.

   UNE SEULE SOURCE POUR LE TEXTE : la bulle affiche `aria-label`, que le bouton
   porte de toute facon pour les lecteurs d ecran. Le libelle lu et le libelle vu
   ne peuvent donc pas diverger.

     <button class="ds-btn ds-btn-secondary ds-btn-icon"
             aria-label="{{'menu.collapseexpand' | translate}}" data-tip="start">

   `data-tip` porte l alignement de la bulle sous le bouton : vide = centree,
   `start` = calee sur le bord gauche (bouton en debut de barre, la bulle
   centree deborderait), `end` = calee sur le bord droit. Elle apparait au survol
   et au focus clavier, apres un court delai pour ne pas clignoter au passage de
   la souris. */
.ds [data-tip] { position: relative; }
.ds [data-tip]::after {
  content: attr(aria-label);
  position: absolute;
  top: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 30;
  padding: 5px 8px;
  border-radius: var(--radius-sm);
  background: var(--text-primary);
  color: var(--surface);
  box-shadow: var(--popover-shadow);
  font: var(--weight-medium) var(--text-xs)/1.2 var(--font-sans);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--duration-fast) var(--ease-standard) 0s, visibility 0s linear var(--duration-fast);
}
.ds [data-tip="start"]::after { left: 0; transform: none; }
.ds [data-tip="end"]::after { left: auto; right: 0; transform: none; }
/* A droite du bouton, centree en hauteur : les boutons du rail de la coque. */
.ds [data-tip="right"]::after { top: 50%; left: calc(100% + 6px); transform: translateY(-50%); }
/* Menu ouvert : la bulle de son declencheur se tairait par-dessus le menu. */
.ds [data-tip][aria-expanded="true"]::after { display: none; }
.ds [data-tip]:hover::after,
.ds [data-tip]:focus-visible::after {
  opacity: 1;
  visibility: visible;
  transition-delay: 300ms, 300ms;
}

/* ---- TABLEAU DE BORD (`board`) ---------------------------------------------
   Une page de cartes : une colonne principale et une colonne laterale, qui se
   replient en une seule colonne quand la zone de contenu se retrecit.
     .board               la grille ; elle fait de `.shell-content` un conteneur
       .board-main          la colonne principale (une carte d appel, puis les groupes)
         .board-group         un groupe de cartes ; `is-dense` resserre ses ecarts
       .board-side          la colonne laterale
     .board-stack         la pile de cartes d un groupe (gabarit de hm-widgets)
       .board-cell          l emplacement d une carte ; sa hauteur vient des donnees
     .board-end           pose dans le contenu d une carte : sur une colonne, la
                          carte passe en fin de page
   Placee AVANT les squelettes : `skeleton`, `is-skeleton` l emportent a poids egal. */
.ds .shell-content:has(.board) {
  container-type: inline-size;
}
.ds .board {
  --board-side-width: clamp(320px, 26%, 440px);
  --board-gap: 24px;
  --board-main-gap: 32px;
  --board-card-gap: 20px;
  --board-side-gap: 16px;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--board-gap);
  padding: var(--board-gap) var(--board-gap) 0;
  align-items: start;
  width: 100%;
  height: 100%;
  margin: 0;
}
/* Reserve de fin de page en vraie boite : un padding-bottom tomberait dans le
   debordement de la vue et n arriverait jamais a l ecran. */
.ds .board::after {
  content: "";
  grid-column: 1 / -1;
  height: 16px;
}
.ds .board:has(> .board-side) {
  grid-template-columns: minmax(0, 1fr) var(--board-side-width);
}
.ds .board-main {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--board-main-gap);
  min-width: 0;
}
.ds .board-side {
  display: flex;
  flex-direction: column;
  gap: var(--board-side-gap);
  min-width: 0;
  padding: 0;
}
.ds .board-group.is-dense {
  --board-card-gap: 14px;
}
.ds .board-stack {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--board-card-gap);
}
.ds .board-side .board-stack {
  gap: var(--board-side-gap);
}
.ds .board-cell {
  position: relative;
}
.ds .board-cell > .card {
  height: 100%;
}
/* Glisser-deposer (hmWidgets.js) : la carte deplacee suit le pointeur, sa position et sa taille
   viennent du JS ; l emplacement qu elle libere garde sa taille. */
.ds .board-cell > .card.is-moving {
  position: fixed;
  opacity: .8;
}
.ds .board-placeholder {
  background: color-mix(in srgb, var(--surface) 50%, transparent);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card);
  box-shadow: none;
}

/* Une colonne : la colonne laterale se deplie (`display: contents`), ses cartes
   rejoignent la colonne principale, et celle qui porte `board-end` passe en
   dernier. */
@container (max-width: 1040px) {
  .ds .board:has(> .board-side) { grid-template-columns: minmax(0, 1fr); }
  .ds .board .board-side,
  .ds .board .board-side > div,
  .ds .board .board-side hm-widgets,
  .ds .board .board-side .board-stack { display: contents; }
  .ds .board .board-side .board-cell { min-width: 0; }
  .ds .board .board-side .board-cell:has(.board-end) { order: 1; }
  .ds .board::after { order: 2; }
}
/* Filet pour un navigateur sans container queries. */
@media only screen and (max-width: 991px) {
  .ds .board { overflow-y: auto; overflow-x: hidden; }
  .ds .board:has(> .board-side) { grid-template-columns: minmax(0, 1fr); }
  .ds .board .board-side,
  .ds .board .board-side > div,
  .ds .board .board-side hm-widgets,
  .ds .board .board-side .board-stack { display: contents; }
  .ds .board .board-side .board-cell { min-width: 0; }
  .ds .board .board-side .board-cell:has(.board-end) { order: 1; }
  .ds .board::after { order: 2; }
}
/* Smartphone : les cartes de la colonne laterale passent en tete (sauf `board-end`). */
@media only screen and (max-width: 599px) {
  .ds .board {
    --board-gap: 14px;
    padding: 14px 12px 0;
  }
  .ds .board .board-side .board-cell { order: -1; }
}

/* ---- CARTE DE TABLEAU DE BORD ----------------------------------------------
   La carte (`card flat`) et ses parties, telles que hm-widgets les pose.
     .card-heading        glyphe + titre ; prend la place libre de l en-tete
       .card-title          le titre ; il passe a la ligne
     .card-drag           la poignee de deplacement
     .card-body.is-scroll le corps d une carte de hauteur fixe, qui defile
     .board-foot          le pied : points du carrousel au centre, lien a droite,
                          bouton « voir tout » sur toute la largeur
     .card-more           le bouton « voir tout », en pointilles
     .card-empty          l etat vide d une carte ; `is-compact`, `is-inline`
   Une carte qui porte une mise en avant (`spotlight`) est elevee. */
.ds .card-heading {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  flex: 1;
}
/* `white-space`, `overflow`, `padding-right` repondent a la regle globale
   `h5 { white-space: nowrap; overflow: hidden; padding-right: 1% }` de l ancien
   CSS : sans eux le titre restait coupe sur une ligne. */
.ds .card-heading > .card-title {
  margin: 0;
  font: var(--font-h3);
  white-space: normal;
  overflow: visible;
  padding-right: 0;
  overflow-wrap: break-word;
}
.ds .card-drag {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  flex: none;
  margin-left: calc(var(--space-1) * -1);
  border-radius: var(--radius-sm);
  color: var(--text-disabled);
  cursor: all-scroll;
  transition: color var(--duration-fast) var(--ease-standard),
              background var(--duration-fast) var(--ease-standard);
}
.ds .card:hover .card-drag { color: var(--text-muted); }
.ds .card-drag:hover {
  background: var(--surface-hover);
  color: var(--text-secondary);
}
.ds .card-drag > svg {
  color: var(--text-muted);
  font-size: 14px;
}
/* Le corps est l element de transclusion du cadre, inline par defaut. `is-scroll` : une carte
   de hauteur fixe dont le contenu peut depasser defile dans son corps, verticalement seulement. */
.ds hm-widget-transclude.card-body {
  display: flex;
  flex-direction: column;
}
.ds hm-widget-transclude.card-body.is-scroll {
  position: relative;
  overflow-x: hidden;
  overflow-y: auto;
}
.ds .board-cell .card-body:has(> .link-list, > .link-list-empty) {
  padding: var(--space-2);
}
.ds .board-cell > .card.flat:has(.spotlight, .spotlight-empty) {
  border-color: var(--border);
  box-shadow: var(--card-shadow);
}
/* En-tete de la colonne laterale : avec un controle (onglets), l en-tete passe
   sur deux lignes plutot que d ecraser le titre. */
.ds .board-side .card-head:has(> hm-widget-transclude > *) {
  flex-wrap: wrap;
  row-gap: 10px;
}
.ds .board-side .card-head:has(> hm-widget-transclude > *) > .card-heading {
  flex: 1 1 auto;
}
/* Smartphone : le controle d en-tete passe sous le titre, s il y en a un. */
@media only screen and (max-width: 599px) {
  .ds .board .card-head {
    flex-wrap: wrap;
    row-gap: var(--space-2);
  }
  .ds .board .card-head > hm-widget-transclude:has(> *) {
    order: 1;
    flex-basis: 100%;
  }
}
.ds .board-foot {
  display: grid;
  grid-template: "a b c" auto / 1fr 1fr 1fr;
}
.ds .board-foot > .card-more { grid-column: 1 / -1; }
.ds .board-foot > hm-slider-nav {
  grid-area: b;
  display: flex;
  justify-content: center;
}
.ds .board-foot > .board-foot-link {
  grid-area: c;
  display: flex;
  justify-content: end;
}
/* Pied sans filet (carrousel + « voir tout »). */
.ds hm-widget-footer.is-flush > .board-foot {
  row-gap: 10px;
  border-top: 0;
}
/* Pied sur une ligne : les points au debut, « voir tout » en lien au bord de fin. */
.ds hm-widget-footer.is-inline > .board-foot {
  display: flex;
  align-items: center;
  gap: 12px;
}
.ds hm-widget-footer.is-inline > .board-foot > .card-more {
  width: auto;
  margin-left: auto;
  padding: 6px 10px;
  border: 0;
}
.ds .card-more {
  width: 100%;
  padding: 9px;
  font: var(--weight-medium) var(--text-sm) var(--font-sans);
  background: transparent;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-md);
  color: var(--primary-700);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
}
.ds .card-more:hover { background: var(--surface-hover); }
.ds .card-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 8px;
  padding: 28px 16px;
  color: var(--text-muted);
}
.ds .card-empty.is-compact { padding: 24px 16px; }
/* Sur une ligne : pour une carte d une seule ligne de haut. */
.ds .card-empty.is-inline {
  flex-direction: row;
  text-align: inherit;
  gap: 10px;
  padding: 0;
}
.ds .card-empty span { font: var(--font-body-sm); }

/* Contenu d en-tete de carte : l emplacement que pose hmWidgetHeader, un pourcentage, un bouton
   discret, une periode. */
.ds .card-head-slot {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.ds .card-head-percent {
  font-weight: var(--weight-bold);
  color: var(--primary);
}
.ds .card-head-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 11px;
  cursor: pointer;
  white-space: nowrap;
  font: var(--weight-medium) var(--text-xs) var(--font-sans);
  background: var(--surface);
  color: var(--text-secondary);
  border: 1px solid var(--field-border);
  border-radius: var(--radius-button);
}
.ds .card-head-btn:hover { background: var(--surface-hover); }
.ds .card-head-period {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  color: var(--text-secondary);
  font: var(--weight-medium) var(--text-sm)/1 var(--font-sans);
  white-space: nowrap;
}
/* Controle segmente compact, pour un en-tete de carte. */
.ds .seg.is-sm {
  flex-wrap: wrap;
  align-self: auto;
  padding: 2px;
  border-color: var(--field-border);
}
.ds .seg.is-sm button {
  min-height: auto;
  padding: 5px 13px;
  font: var(--weight-medium) var(--text-xs) var(--font-sans);
}
.ds .seg.is-sm button:hover { color: var(--text-primary); }
.ds .seg.is-sm button.on {
  background: var(--surface);
  color: var(--primary-700);
  box-shadow: var(--shadow-xs);
}

/* ---- CARTE D APPEL (`action-card`) ------------------------------------------
   Le bloc de tete d un tableau de bord : un glyphe neutre, ce qui attend (le
   nombre en tete) et une action. `is-link`, sur un <button> : toute la carte
   ouvre, elle se souleve au survol, et l action pulse en continu (deux halos
   en `box-shadow`, sans `scale` : meme epaisseur quelle que soit la largeur).
   18px, 46px, 2px, 4px et 14px sont hors echelle, voulus.
     .action-card-icon · .action-card-body (.action-card-title, .action-card-total,
     .action-card-subtitle, .action-card-part) · .action-card-cta
     .action-card-skeleton   son attente, a la meme boite */
.ds .action-card {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  padding: 18px var(--pad-card);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--card-shadow);
  isolation: isolate;
}
.ds button.action-card {
  width: 100%;
  text-align: left;
  font: inherit;
  color: inherit;
}
.ds .action-card.is-link {
  cursor: pointer;
  transition: transform var(--duration-fast) var(--ease-standard),
              box-shadow var(--duration-fast) var(--ease-standard),
              border-color var(--duration-fast) var(--ease-standard);
}
.ds .action-card.is-link:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
  border-color: var(--primary-border);
}
.ds .action-card.is-link:focus-visible { box-shadow: var(--ring), var(--card-shadow); }
.ds .action-card-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 46px;
  height: 46px;
  border-radius: var(--radius-md);
  background: var(--surface-sunken);
  color: var(--text-secondary);
  font-size: 23px;
}
.ds .action-card-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  flex: 1 1 auto;
  min-width: 0;
}
.ds .action-card-title {
  font-size: var(--text-xl);
  font-weight: var(--weight-bold);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
  color: var(--text-primary);
  white-space: nowrap;
}
.ds .action-card-total {
  font-size: var(--text-3xl);
  font-weight: var(--weight-bold);
  font-variant-numeric: tabular-nums;
  color: var(--primary-700);
}
.ds .action-card-subtitle {
  font: var(--font-body-sm);
  color: var(--text-secondary);
}
/* Le separateur est porte par la partie : une partie absente emporte le sien. */
.ds .action-card-part + .action-card-part::before { content: " · "; }
.ds .action-card-cta {
  position: relative;
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  font: var(--font-button);
  color: var(--on-primary);
  background: var(--primary);
  border-radius: var(--radius-button);
  padding: 10px 18px;
  white-space: nowrap;
  transition: background-color var(--duration-fast) var(--ease-standard);
}
.ds .action-card.is-link:hover .action-card-cta { background: var(--primary-hover); }
.ds .action-card-cta::before,
.ds .action-card-cta::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  opacity: 0;
  pointer-events: none;
  animation: action-card-pulse 3s ease-out infinite;
}
.ds .action-card-cta::after { animation-delay: .5s; }
@keyframes action-card-pulse {
  0% {
    opacity: .7;
    box-shadow: 0 0 0 0 var(--primary);
  }
  83.333%,
  100% {
    opacity: 0;
    box-shadow: 0 0 0 14px var(--primary);
  }
}
.ds .action-card-cta > i,
.ds .action-card-cta > svg {
  margin-left: var(--space-1);
  font-size: var(--text-xs);
  transition: transform var(--duration-fast) var(--ease-standard);
}
.ds .action-card.is-link:hover .action-card-cta > i,
.ds .action-card.is-link:hover .action-card-cta > svg { transform: translateX(4px); }
@media (prefers-reduced-motion: reduce) {
  .ds .action-card.is-link,
  .ds .action-card-cta,
  .ds .action-card-cta > i,
  .ds .action-card-cta > svg { transition: none; }
  .ds .action-card-cta::before,
  .ds .action-card-cta::after { animation: none; }
}
.ds .action-card-skeleton {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: 18px var(--pad-card);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
}
.ds .action-card-sk-icon {
  display: block;
  width: 46px;
  height: 46px;
  border-radius: var(--radius-md);
}
.ds .action-card-sk-body { flex: 1; }
.ds .action-card-sk-line {
  display: block;
  height: 20px;
  border-radius: 4px;
}
.ds .action-card-sk-line:first-child {
  width: 40%;
  margin-bottom: 8px;
}
.ds .action-card-sk-line:last-child { width: 25%; }

/* ---- TUILES DE STATISTIQUE (`stat-tiles`) -----------------------------------
   Un compteur par tuile : pastille de glyphe, nombre (et sa precision), libelle.
   La gravite tient dans la pastille et le nombre (`is-danger`, `is-warning`,
   `is-info`, `is-success`) ; la tuile reste neutre. `is-link` : elle mene
   quelque part. */
.ds hm-widget-alert-tiles { display: block; }
.ds hm-widget-alert-tiles button {
  font-family: var(--font-sans);
  color: inherit;
}
.ds .stat-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(216px, 1fr));
  gap: 10px;
}
.ds .stat-tile {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  min-width: 0;
  cursor: default;
}
.ds .stat-tile.is-link { cursor: pointer; }
.ds .stat-tile.is-link:hover {
  background: var(--surface-hover);
  border-color: var(--primary-border);
}
.ds .stat-tile-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  flex: none;
  border-radius: var(--radius-md);
  background: var(--surface-sunken);
  color: var(--text-muted);
}
.ds .stat-tile-icon.is-danger  { background: var(--danger-soft);  color: var(--danger-fg); }
.ds .stat-tile-icon.is-warning { background: var(--warning-soft); color: var(--warning-fg); }
.ds .stat-tile-icon.is-info    { background: var(--info-soft);    color: var(--info-fg); }
.ds .stat-tile-icon.is-success { background: var(--success-soft); color: var(--success-fg); }
.ds .stat-tile-main {
  flex: 1;
  min-width: 0;
}
.ds .stat-tile-head {
  display: flex;
  align-items: baseline;
  gap: 7px;
}
.ds .stat-tile-count {
  font: var(--weight-bold) var(--text-xl) var(--font-sans);
  line-height: 1;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}
.ds .stat-tile-count.is-danger  { color: var(--danger-fg); }
.ds .stat-tile-count.is-warning { color: var(--warning-fg); }
.ds .stat-tile-count.is-info    { color: var(--info-fg); }
.ds .stat-tile-count.is-success { color: var(--success-fg); }
.ds .stat-tile-when {
  font: var(--font-meta);
  color: var(--text-muted);
  white-space: nowrap;
}
.ds .stat-tile-when::before {
  content: '\00B7';
  margin-right: 5px;
}
.ds .stat-tile-label {
  display: block;
  margin-top: 2px;
  font: var(--font-body-sm);
  line-height: 1.25;
  color: var(--text-secondary);
}
.ds .stat-tile-go {
  flex: none;
  font-size: 15px;
  color: var(--text-muted);
}
.ds .stat-tile-head > .skeleton-line { width: 32px; }
.ds .stat-tile-label > .skeleton-line { width: 74%; }

/* ---- LIGNES D ELEMENTS (`entry-list`) ---------------------------------------
   Une ligne par personne ou par objet : avatar (ou pastille de glyphe), titre et
   precision, un emplacement a droite. `is-link` : la ligne ouvre l element.
   `pill` et `entry-count` sont les contenus habituels de l emplacement. */
.ds hm-widget-list {
  display: flex;
  flex-direction: column;
  min-height: 0;
  flex: 1;
}
.ds hm-widget-list button {
  font-family: var(--font-sans);
  color: inherit;
}
.ds .entry-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.ds .entry {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 9px 10px;
  border: none;
  background: transparent;
  border-radius: var(--radius-md);
  text-align: left;
  width: 100%;
}
.ds .entry.is-link { cursor: pointer; }
.ds .entry.is-link:hover { background: var(--surface-hover); }
.ds .entry > hm-avatar {
  display: block;
  flex: none;
}
.ds .entry-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex: none;
  border-radius: var(--radius-md);
  background: var(--surface-sunken);
  color: var(--text-secondary);
}
.ds .entry-main {
  flex: 1;
  min-width: 0;
}
.ds .entry-title {
  font: var(--weight-medium) var(--text-sm) var(--font-sans);
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ds .entry-meta {
  font: var(--font-meta);
  color: var(--text-muted);
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ds .entry-action {
  flex: none;
  display: flex;
  align-items: center;
}
.ds .entry-chev {
  flex: none;
  color: var(--text-muted);
  text-align: center;
  font-size: 15px;
}
.ds .entry-title > .skeleton-line { width: 54%; }
.ds .entry-meta > .skeleton-line { width: 32%; }
.ds .entry-action > .skeleton-line { width: 26px; }
.ds .pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font: var(--weight-medium) 11px var(--font-sans);
  padding: 3px 9px;
  border-radius: var(--radius-pill);
  white-space: nowrap;
  background: var(--surface-sunken);
  color: var(--text-muted);
}
.ds .pill.is-success { background: var(--success-soft); color: var(--success-fg); }
.ds .pill.is-danger  { background: var(--danger-soft);  color: var(--danger-fg); }
.ds .pill.is-warning { background: var(--warning-soft); color: var(--warning-fg); }
.ds .pill.is-info    { background: var(--info-soft);    color: var(--info-fg); }
/* Le seul aplat primaire : il signale du NOUVEAU a traiter. */
.ds .pill.is-primary { background: var(--primary); color: var(--on-primary); }
.ds .entry-count {
  font: var(--weight-bold) var(--text-base) var(--font-sans);
  color: var(--primary);
  font-variant-numeric: tabular-nums;
  min-width: 24px;
  text-align: right;
}

/* ---- JAUGES (`meter-list`) --------------------------------------------------
   Une ligne par mesure : libelle, precision et valeur sur une ligne de base,
   la jauge dessous, une ligne optionnelle sous la jauge. `is-link` : chaque
   ligne s ouvre ; le libelle y devient le sujet de la ligne. */
.ds hm-widget-chart-bar {
  display: flex;
  flex-direction: column;
  min-height: 0;
  flex: 1;
}
.ds hm-widget-chart-bar button {
  font-family: var(--font-sans);
  color: inherit;
}
.ds .meter-list {
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.ds .meter-list.is-link { gap: 14px; }
.ds .meter-list.is-link > .meter {
  padding: 6px;
  border-radius: var(--radius-md);
  cursor: pointer;
}
.ds .meter-list.is-link > .meter:hover { background: var(--surface-hover); }
.ds .meter-list.is-link .meter-label {
  font: var(--weight-medium) var(--text-sm) var(--font-sans);
  color: var(--text-primary);
}
.ds .meter {
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.ds .meter-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.ds .meter-label {
  flex: 1;
  min-width: 0;
  font: var(--font-body-sm);
  color: var(--text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ds .meter-meta {
  flex: none;
  font: var(--font-meta);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.ds .meter-value {
  flex: none;
  font: var(--weight-medium) var(--text-sm) var(--font-sans);
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.ds .meter-chev {
  flex: none;
  color: var(--text-muted);
  text-align: center;
  font-size: 15px;
}
.ds .meter-track {
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  overflow: hidden;
}
.ds .meter-fill {
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--primary);
}
.ds .meter-fill.is-info    { background: var(--info); }
.ds .meter-fill.is-success { background: var(--success); }
.ds .meter-fill.is-warning { background: var(--warning); }
.ds .meter-fill.is-danger  { background: var(--danger); }
/* Le pied porte lui-meme ses deux colonnes : le squelette y pose ses barres
   sans passer par l element de transclusion. */
.ds .meter-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font: var(--font-meta);
  color: var(--text-muted);
}
.ds .meter-foot > hm-widget-item-transclude {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.ds .meter-label > .skeleton-line { width: 58%; }
.ds .meter-value > .skeleton-line { width: 32px; }
.ds .meter-foot > .skeleton-line:first-child { width: 104px; }
.ds .meter-foot > .skeleton-line:last-child { width: 68px; }

/* ---- NAVIGATION DE PERIODE (`period-nav`) -----------------------------------
   Precedent · periode · suivant, dans un en-tete de carte. */
.ds .period-nav {
  display: flex;
  align-items: center;
  gap: 8px;
}
.ds .period-nav-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  flex: none;
  background: var(--surface);
  color: var(--text-muted);
  border: 1px solid var(--field-border);
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.ds .period-nav-btn:hover { background: var(--surface-hover); }
.ds .period-nav-label {
  font: var(--font-label);
  color: var(--text-primary);
  min-width: 130px;
  text-align: center;
  white-space: nowrap;
}

/* ---- AGENDA (`agenda`) ------------------------------------------------------
   La legende des categories, qui filtre, puis une ligne par evenement : barre de
   la couleur de sa categorie, titre, periode, etiquette. Les pastilles ne
   prennent la couleur qu une fois actives (`on`). La liste defile au-dela de
   ~5 lignes : la carte ne s allonge pas. La ligne n ouvre rien : ni curseur ni
   survol. */
.ds .agenda button {
  font-family: var(--font-sans);
  color: inherit;
}
.ds .agenda-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 14px;
}
.ds .agenda-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 9px;
  font: var(--weight-medium) 11px var(--font-sans);
  background: var(--surface);
  color: var(--text-secondary);
  border: 1px solid var(--field-border);
  border-radius: var(--radius-pill);
  cursor: pointer;
}
.ds .agenda-chip:hover { background: var(--surface-hover); }
.ds .agenda-chip.on {
  background: var(--primary-soft);
  color: var(--primary-700);
  border-color: var(--primary-border);
}
.ds .agenda-chip-dot {
  width: 8px;
  height: 8px;
  flex: none;
  border-radius: 50%;
  background: var(--border-strong);
}
.ds .agenda-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: 320px;
  overflow-y: auto;
}
.ds .agenda-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 11px 12px;
  width: 100%;
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
}
.ds .agenda-item-bar {
  width: 3px;
  flex: none;
  align-self: stretch;
  border-radius: var(--radius-pill);
}
/* Grise tant qu aucune categorie n est choisie ; la couleur de la categorie vient
   des donnees. La barre du squelette reste vide. */
.ds .agenda > .agenda-list > .agenda-item > .agenda-item-bar { background: var(--border-strong); }
.ds .agenda-item-main {
  flex: 1;
  min-width: 0;
}
.ds .agenda-item-title {
  font: var(--weight-medium) var(--text-sm) var(--font-sans);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ds .agenda-item-range {
  margin-top: 2px;
  font: var(--font-meta);
  color: var(--text-muted);
}
.ds .agenda-item-tag {
  padding: 3px 9px;
  font: var(--weight-medium) 11px var(--font-sans);
  background: var(--surface-sunken);
  color: var(--text-muted);
  border-radius: var(--radius-pill);
  white-space: nowrap;
}
.ds .agenda-chip > .skeleton-line { width: 54px; }
.ds .agenda-item-title > .skeleton-line { width: 58%; }
.ds .agenda-item-range > .skeleton-line { width: 34%; }

/* ---- COMPTEURS (`counters`) -------------------------------------------------
   Trois compteurs par page de carrousel, en colonnes fixes : une derniere page
   plus courte ne les etire pas. Neutres ; `is-available` (vert) et
   `is-expiring` (orange) quand le compteur porte un sens. */
.ds .counters button { font-family: var(--font-sans); }
.ds .counters-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
}
.ds .counter {
  text-align: center;
  padding: 12px 8px;
  background: var(--surface-sunken);
  border-radius: var(--radius-md);
  min-width: 0;
}
.ds .counter-value {
  font: var(--weight-bold) var(--text-2xl) var(--font-sans);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: var(--text-muted);
}
.ds .counter-value.is-available { color: var(--success-fg); }
.ds .counter-value.is-expiring  { color: var(--warning-fg); }
.ds .counter-label {
  font: var(--font-meta);
  color: var(--text-muted);
  margin-top: 4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ds .counter-value > .skeleton-line { width: 28px; }
.ds .counter-label > .skeleton-line { width: 70%; }

/* ---- CARROUSEL (`ds-carousel`) ----------------------------------------------
   Les pages cote a cote sur un rail ; la fenetre n en montre qu une et le rail
   glisse (sa position vient des donnees, en `ng-style`). Le rail s etire : la
   carte prend la hauteur de la plus haute page. */
.ds .ds-carousel { overflow: hidden; }
.ds .ds-carousel-track {
  display: flex;
  align-items: stretch;
  transition: transform .5s ease-in-out;
}
.ds .ds-carousel-slide {
  flex: 0 0 100%;
  min-width: 0;
}
@media (prefers-reduced-motion: reduce) {
  .ds .ds-carousel-track { transition: none; }
}

/* ---- ELEMENT MIS EN VITRINE (`showcase`) ------------------------------------
   Un element seul (une session, une offre), sur une ligne : un pave a gauche (la date d une
   session, `date-tile` ; le glyphe d une offre, `showcase-glyph`), au centre le titre tronque (nom
   complet en infobulle), un sous-titre et une ligne de champs, l action au bord de fin ; sur une
   carte etroite, l action passe a la ligne.
     .showcase-row          la ligne
       .showcase-glyph        le pave du sujet, quand il n y a pas de date a montrer
       .showcase-main         .showcase-title (`is-strong` : l identite de l element, plus lourde),
                              .showcase-sub (le sous-titre), .showcase-meta : l heure
                              (.showcase-time), puis chaque champ en .showcase-meta-item
                              (.showcase-meta-label + valeur ; `is-muted` : une information
                              secondaire) ; .showcase-seats, `is-full` : plus de place
       .showcase-btn          l action, en primaire doux ; `is-plain` : le libelle prend la couleur
                              du texte */
.ds .showcase button { font-family: var(--font-sans); }
.ds .showcase-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
  min-width: 0;
}
.ds .showcase-glyph {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 52px;
  border-radius: var(--radius-md);
  background: var(--surface-sunken);
  color: var(--text-secondary);
  font-size: 20px;
}
.ds .showcase-main {
  flex: 1 1 220px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.ds .showcase-title {
  font: var(--weight-medium) var(--text-base) var(--font-sans);
  line-height: var(--leading-normal);
}
.ds .showcase-title.is-strong { font: var(--weight-bold) var(--text-lg) var(--font-sans); }
.ds .showcase-main > .showcase-title,
.ds .showcase-sub {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ds .showcase-sub {
  font: var(--font-body-sm);
  color: var(--text-secondary);
}
.ds .showcase-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 20px;
  min-width: 0;
  font: var(--font-body-sm);
}
/* Une valeur longue (nom de salle) passe a la ligne, coupee au besoin : elle ne sort jamais de sa
   colonne ni ne passe sous l action. Le libelle reste d un tenant. */
.ds .showcase-meta-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  max-width: 100%;
}
.ds .showcase-meta-item > :last-child {
  min-width: 0;
  overflow-wrap: anywhere;
}
.ds .showcase-meta-item.is-muted { color: var(--text-muted); }
.ds .showcase-meta-label {
  color: var(--text-muted);
  white-space: nowrap;
}
.ds .showcase-time {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--warning-fg);
  font-weight: var(--weight-medium);
}
.ds .showcase-seats {
  font: var(--weight-medium) var(--text-base) var(--font-sans);
  font-variant-numeric: tabular-nums;
}
.ds .showcase-seats.is-full { color: var(--danger-fg); }
.ds .showcase-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 11px;
  background: var(--primary-soft);
  color: var(--primary-700);
  border: 1px solid transparent;
  border-radius: var(--radius-button);
  font: var(--font-button);
  font-size: var(--text-sm);
  cursor: pointer;
}
.ds .showcase-btn:hover { background: var(--primary-soft-hover); }
.ds .showcase-btn.is-plain:not(.is-skeleton) { color: inherit; }
.ds .showcase-btn.is-skeleton { pointer-events: none; }
.ds .showcase-row > .showcase-btn {
  flex: none;
  margin-left: auto;
}
.ds .showcase-title > .skeleton-line { width: 68%; }
.ds .showcase-title.is-strong > .skeleton-line { width: 210px; }
.ds .showcase-sub > .skeleton-line { width: 30%; }
.ds .showcase-meta > .skeleton-line { width: 54%; }
.ds .showcase-btn > .skeleton-line { width: 70px; }
.ds .showcase-btn.is-plain > .skeleton-line { width: 110px; }

/* ---- CARTE DE SESSION (`session-card`) --------------------------------------
   Une session de formation ou d accompagnement : date et titre, champs sur deux
   colonnes, le role (formateur, coach), les participants, un lien vers la liste. */
.ds .session-card { padding: 0 10px; }
.ds .session-card-head {
  display: flex;
  gap: 5px;
  align-items: center;
  padding-bottom: 5px;
}
.ds .session-card-date {
  display: inline-block;
  align-self: flex-start;
  padding: 5px;
  font-size: 14px;
  font-weight: 700;
  line-height: 1;
  text-align: center;
  white-space: nowrap;
  vertical-align: baseline;
  border-radius: .375rem;
  background: var(--primary);
  color: var(--on-primary);
}
.ds .session-card-title {
  flex: 1;
  font-size: 14px;
  font-weight: bold;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.ds .session-card-fields {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 5px;
  padding-top: 20px;
}
.ds .session-card-field > * { font-size: 14px; }
.ds .session-card-label {
  color: var(--text-muted);
  margin-bottom: 5px;
}
.ds .session-card-dot.is-trainer { color: var(--warning); }
.ds .session-card-dot.is-coach { color: var(--success); }
.ds .session-card-role {
  line-height: 6px;
  padding-left: 9px;
  color: var(--text-primary);
  font-size: 13px;
}
.ds .session-card-people {
  margin-right: -15px;
  margin-left: -15px;
}
.ds .session-card-people::before,
.ds .session-card-people::after {
  content: " ";
  display: table;
}
.ds .session-card-people::after { clear: both; }
.ds .session-card-more {
  float: right;
  min-width: 84px;
  margin: 15px 0;
  padding: 6px;
  border: 1px solid;
  border-radius: 2px;
  font-size: 13px;
  text-align: center;
}
.ds .session-card-more.is-coach { margin: 15px 5px; }
.ds .session-card-more:hover { background: var(--primary); }
.ds .session-card-head > .skeleton {
  display: block;
  width: 250px;
  height: 25px;
  margin-bottom: 5px;
  border-radius: 4px;
}
.ds .session-card-field > .skeleton {
  display: block;
  height: 20px;
  border-radius: 4px;
}
.ds .session-card-field > .skeleton:first-child {
  width: 50px;
  margin-bottom: 5px;
}
.ds .session-card-field > .skeleton:last-child {
  width: 150px;
  margin-bottom: 10px;
}

/* ---- LIGNE DE SYNTHESE (`tally`) --------------------------------------------
   Un total et sa variation a gauche, une pastille par categorie poussee a
   droite ; les pastilles passent a la ligne quand la carte est etroite. La
   variation est verte, rouge si elle baisse (`is-negative`). */
.ds .tally {
  display: flex;
  align-items: center;
  gap: 20px;
  flex-wrap: wrap;
}
.ds .tally-total {
  display: flex;
  align-items: baseline;
  gap: 9px;
  flex-wrap: wrap;
}
.ds .tally-count {
  font: var(--weight-bold) var(--text-3xl) var(--font-sans);
  line-height: 1;
  color: var(--primary);
  font-variant-numeric: tabular-nums;
}
.ds .tally-unit {
  font: var(--font-body-sm);
  color: var(--text-secondary);
}
.ds .tally-delta {
  font: var(--font-meta);
  font-weight: var(--weight-medium);
  color: var(--success-fg);
}
.ds .tally-delta.is-negative { color: var(--danger-fg); }
.ds .tally-types {
  display: flex;
  gap: 8px;
  margin-left: auto;
  flex-wrap: wrap;
}
.ds .tally-type {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  font: var(--font-body-sm);
  color: var(--text-secondary);
  background: var(--surface-sunken);
  border-radius: var(--radius-pill);
  white-space: nowrap;
}
.ds .tally-type b {
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}
.ds .tally-count > .skeleton-line { width: 46px; }
.ds .tally-unit > .skeleton-line { width: 56px; }
.ds .tally-type > .skeleton-line { width: 52px; }

/* ---- ENTONNOIR (`funnel-list`) ----------------------------------------------
   Un element par ligne (un poste), puis ses etapes en trois cases dessous. La
   ligne n est qu un cadre : la tete et chaque case sont des boutons voisins.
   Une case `is-alert` signale ce qui est en cours ; seules les cases `is-link`
   menent quelque part. */
.ds .funnel-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.ds .funnel {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 100%;
  padding: 12px 14px;
  color: var(--text-primary);
  background: var(--surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
}
.ds .funnel-head {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  width: 100%;
  padding: 0;
  text-align: left;
  cursor: pointer;
  font-family: var(--font-sans);
  color: var(--text-primary);
  background: none;
  border: 0;
}
.ds .funnel-head:hover .funnel-title { text-decoration: underline; }
.ds .funnel-head:hover .funnel-chev { color: var(--primary); }
.ds .funnel-title {
  min-width: 0;
  font: var(--weight-medium) var(--text-base) var(--font-sans);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ds .funnel-meta {
  flex: 0 1 auto;
  min-width: 0;
  font: var(--font-meta);
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ds .funnel-new {
  flex: none;
  padding: 2px 8px;
  font: var(--weight-medium) var(--text-xs) var(--font-sans);
  background: var(--primary);
  color: var(--on-primary);
  border-radius: var(--radius-pill);
}
.ds .funnel-chev {
  flex: none;
  margin-left: auto;
  color: var(--text-muted);
}
.ds .funnel-cells {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}
.ds .funnel-cell {
  width: 100%;
  padding: 8px 12px;
  text-align: center;
  font-family: var(--font-sans);
  color: var(--text-primary);
  background: var(--surface-sunken);
  border: 0;
  border-radius: var(--radius-sm);
}
.ds .funnel-cell.is-alert { background: var(--primary-soft); }
.ds .funnel-cell.is-alert .funnel-cell-value { color: var(--primary-700); }
.ds .funnel-cell.is-link { cursor: pointer; }
.ds .funnel-cell.is-link:hover { background: var(--surface-hover); }
.ds .funnel-cell.is-link:hover .funnel-cell-label { text-decoration: underline; }
.ds .funnel-cell.is-link.is-alert:hover { background: var(--primary-soft-hover); }
.ds .funnel-cell-value {
  font: var(--weight-bold) var(--text-lg) var(--font-sans);
  font-variant-numeric: tabular-nums;
  line-height: var(--leading-tight);
}
.ds .funnel-cell-label {
  margin-top: 4px;
  font: var(--font-meta);
  color: var(--text-muted);
}
.ds .funnel-title > .skeleton-line { width: 172px; }
.ds .funnel-meta > .skeleton-line { width: 92px; }
.ds .funnel-cell-value > .skeleton-line { width: 22px; }
.ds .funnel-cell-label > .skeleton-line { width: 70%; }

/* ---- BANDEAU D APPEL ET MINI-TUILES (`cta-group`) ---------------------------
   Ce qui attend d etre examine, en bandeau primaire doux (seule surface teintee
   de la carte), au-dessus d une rangee de lectures neutres.
     .cta-banner (.cta-banner-icon, .cta-banner-main, .cta-banner-title,
     .cta-banner-sub, .cta-banner-arrow) · .cta-banner-none : rien a examiner
     .mini-tiles (.mini-tile, .mini-tile-icon, .mini-tile-main, .mini-tile-label,
     .mini-tile-count) */
.ds .cta-group {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.ds .cta-banner {
  display: flex;
  align-items: center;
  gap: 13px;
  width: 100%;
  padding: 12px 14px;
  cursor: pointer;
  text-align: left;
  font-family: var(--font-sans);
  color: var(--primary-700);
  background: var(--primary-soft);
  border: 1px solid var(--primary-border);
  border-radius: var(--radius-md);
}
.ds .cta-banner:hover { background: var(--primary-soft-hover); }
.ds .cta-banner-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex: none;
  border-radius: var(--radius-md);
  background: var(--primary);
  color: var(--on-primary);
}
.ds .cta-banner-main {
  flex: 1;
  min-width: 0;
}
.ds .cta-banner-title { font: var(--weight-medium) var(--text-sm) var(--font-sans); }
.ds .cta-banner-title b { font-weight: var(--weight-bold); }
.ds .cta-banner-sub {
  font: var(--font-meta);
  opacity: .8;
}
.ds .cta-banner-arrow { flex: none; }
.ds .cta-banner-none {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  font: var(--font-body-sm);
  color: var(--text-muted);
}
.ds .mini-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 8px;
}
.ds .mini-tile {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  padding: 12px;
  background: var(--surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
}
.ds .mini-tile-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex: none;
  border-radius: var(--radius-md);
  background: var(--surface-sunken);
  color: var(--text-secondary);
}
.ds .mini-tile-main {
  flex: 1;
  min-width: 0;
}
.ds .mini-tile-label {
  font: var(--weight-medium) var(--text-sm) var(--font-sans);
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ds .mini-tile-count {
  font: var(--font-meta);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.ds .cta-banner-title > .skeleton-line { width: 62%; }
.ds .cta-banner-sub > .skeleton-line { width: 44%; }
.ds .mini-tile-label > .skeleton-line { width: 78%; }
.ds .mini-tile-count > .skeleton-line { width: 52%; }

/* ---- PERSONNES (`people-grid`) ----------------------------------------------
   Une rangee qui passe a la ligne de vignettes de largeur fixe : avatar, prenom,
   date. Sans droit d ouvrir la fiche, la vignette reste lisible mais ne promet
   plus de clic (`:disabled`). */
.ds .people button {
  font-family: var(--font-sans);
  color: inherit;
}
.ds .people-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
}
.ds .people-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  width: 56px;
  padding: 4px;
  text-align: center;
  background: transparent;
  border: none;
  border-radius: var(--radius-md);
  cursor: pointer;
}
.ds .people-cell:hover { background: var(--surface-hover); }
.ds .people-cell:disabled { cursor: default; }
.ds .people-cell:disabled:hover { background: transparent; }
.ds .people-cell-name {
  max-width: 100%;
  font: var(--weight-medium) var(--text-xs)/1.3 var(--font-sans);
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ds .people-cell-date {
  font: var(--font-meta);
  color: var(--text-muted);
  white-space: nowrap;
}
.ds .people-cell-avatar {
  display: block;
  width: 45px;
  height: 45px;
  border-radius: 50%;
}
.ds .people-cell-name > .skeleton-line { width: 90%; }
.ds .people-cell-date > .skeleton-line { width: 72%; }

/* ---- LIENS (`link-list`) ----------------------------------------------------
   Une ligne-lien par raccourci : logo (ou initiale) dans une boite neutre,
   libelle, fleche de sortie. Au-dela de ~9 lignes la liste defile dans la carte. */
.ds .link-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 420px;
  overflow-y: auto;
}
.ds .link-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px 10px;
  border-radius: var(--radius-md);
  color: var(--text-primary);
  text-decoration: none;
}
.ds .link-row:hover,
.ds .link-row:focus {
  background: var(--surface-hover);
  color: var(--text-primary);
  text-decoration: none;
}
.ds .link-row-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex: none;
  overflow: hidden;
  border-radius: var(--radius-md);
  background: var(--surface-sunken);
  color: var(--text-secondary);
}
/* `contain` : un logo est une marque, le rogner le trahit. */
.ds .link-row-icon img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.ds .link-row-initial {
  font: var(--weight-bold) var(--text-sm) var(--font-sans);
  line-height: 1;
  color: var(--text-secondary);
}
.ds .link-row-label {
  flex: 1;
  min-width: 0;
  font: var(--font-body-sm);
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ds .link-row-go {
  flex: none;
  font-size: 14px;
  color: var(--text-muted);
}
.ds .link-row-label > .skeleton-line { width: 62%; }

/* ---- MISE EN AVANT (`spotlight`) --------------------------------------------
   Une accroche par page de carrousel : visuel (ou reserve degradee), sur-titre
   sur 1 ligne, titre sur 2, chapo sur 3 ; puis l action et les points. Un
   contenu long ne rallonge jamais la carte.
     .spotlight-media (.spotlight-placeholder) · .spotlight-overline ·
     .spotlight-title · .spotlight-excerpt (`is-loading` : son squelette) ·
     .spotlight-actions (.spotlight-cta) */
.ds .spotlight {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: 0;
}
.ds .spotlight .ds-carousel-track { align-items: normal; }
.ds .spotlight .ds-carousel-slide {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.ds .spotlight-media {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-card);
  overflow: hidden;
  background: var(--surface-sunken);
}
.ds .spotlight-media img,
.ds .spotlight-media iframe {
  width: 100%;
  height: 100%;
  border: 0;
  object-fit: cover;
  display: block;
}
/* La taille et la couleur du glyphe sont posees sur le CONTENEUR : FontAwesome
   remplace le <i> par un <svg> qui fait 1em et prend `currentColor`. */
.ds .spotlight-placeholder {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: linear-gradient(135deg, var(--primary) 0%, var(--primary-700) 100%);
  font-size: 64px;
  color: var(--on-primary);
}
.ds .spotlight-placeholder::before,
.ds .spotlight-placeholder::after {
  content: '';
  position: absolute;
  border-radius: 50%;
  background: color-mix(in srgb, var(--gray-0) 12%, transparent);
}
.ds .spotlight-placeholder::before { width: 46%; padding-bottom: 46%; top: -12%; right: -8%; }
.ds .spotlight-placeholder::after { width: 34%; padding-bottom: 34%; bottom: -14%; left: -6%; }
.ds .spotlight-placeholder > i,
.ds .spotlight-placeholder > svg {
  position: relative;
  line-height: 1;
}
.ds .spotlight-overline {
  margin: 0;
  font: var(--weight-bold) var(--text-xs)/1.2 var(--font-sans);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--primary-700);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ds .spotlight-title {
  margin: 0;
  font: var(--weight-bold) var(--text-xl)/1.3 var(--font-sans);
  color: var(--text-primary);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.ds .spotlight-excerpt {
  margin: 0;
  font: var(--weight-regular) var(--text-base)/1.55 var(--font-sans);
  color: var(--text-secondary);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.ds .spotlight-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-subtle);
}
/* Se dimensionne sur son libelle, meme seul sur sa ligne. */
.ds .spotlight-cta {
  flex: 0 0 auto;
  width: auto;
  display: inline-flex;
  align-items: center;
  gap: var(--gap-inline);
  padding: 9px 16px;
  cursor: pointer;
  white-space: nowrap;
  border: 0;
  border-radius: var(--radius-button);
  font: var(--weight-medium) var(--text-base) var(--font-sans);
  background: var(--primary);
  color: var(--on-primary);
}
.ds .spotlight-cta:hover { background: var(--primary-hover); }
/* Le chapo du squelette repasse en bloc (le `-webkit-box` perd l interligne de
   ses enfants) : trois barres occupent exactement trois lignes. */
.ds .spotlight-excerpt.is-loading { display: block; }
.ds .spotlight-excerpt.is-loading > .skeleton-line {
  display: block;
  margin: calc((1.55em - 0.7em) / 2) 0;
}
.ds .spotlight-overline > .skeleton-line { width: 38%; }
.ds .spotlight-title > .skeleton-line { width: 82%; }
.ds .spotlight-excerpt.is-loading > .skeleton-line:nth-child(2) { width: 94%; }
.ds .spotlight-excerpt.is-loading > .skeleton-line:nth-child(3) { width: 62%; }
.ds .spotlight-cta > .skeleton-line { width: 78px; }

/* ---- AVATAR (`av`) ----------------------------------------------------------
   La photo d une personne, ou ses initiales sur sa couleur d identite : hmAvatar, seul, et
   hmAvatars, en groupe. Chacun pose `ds` sur sa racine (le composant vit aussi dans des pages
   hors du design system). Un disque plat, sans anneau (choix du 23/10/2026).
     .av-box          racine de hmAvatar : l avatar et, si demande (`with-name`), le nom a cote
       .av            le disque : 40px ; taille par le parametre `size` de la directive :
                      `is-xxs` 24, `is-xs` 35, `is-s` 45, `is-sm` 55, `is-md` 75, `is-lg` 100,
                      `is-xlg` 150 (memes valeurs que avatarService.getSizePx)
         .is-tone-1…6   couleur d identite des initiales, tiree des initiales par la directive
         .is-deleted    personne supprimee : photo grisee, initiales sur gris
         .av-edit       la camera qui ouvre la fenetre de la photo, quand on peut la modifier
       .av-name       le nom, sur une ligne
     .av-group        racine de hmAvatars : les avatars se chevauchent ; un `.av` sans couleur
                      d identite y dit « +N » (les personnes non affichees)
   Sans couleur d identite, le disque est neutre et suit le theme. Les initiales grandissent
   avec le disque (10px au moins). */
:is(.ds .av-box, .ds.av-box) {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}
:is(.ds .av-group, .ds.av-group) {
  display: flex;
  align-items: center;
  min-width: 0;
}
.ds .av {
  --av-size: 40px;
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: var(--av-size);
  height: var(--av-size);
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  color: var(--text-secondary);
  font: var(--weight-bold) max(10px, calc(var(--av-size) * 0.36))/1 var(--font-sans);
  white-space: nowrap;
  user-select: none;
}
.ds .av.is-xxs { --av-size: 24px; }
.ds .av.is-xs  { --av-size: 35px; }
.ds .av.is-s   { --av-size: 45px; }
.ds .av.is-sm  { --av-size: 55px; }
.ds .av.is-md  { --av-size: 75px; }
.ds .av.is-lg  { --av-size: 100px; }
.ds .av.is-xlg { --av-size: 150px; }
.ds .av > img {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: inherit;
  object-fit: cover;
}
.ds .av.is-tone-1 { background: var(--avatar-1); color: var(--gray-0); }
.ds .av.is-tone-2 { background: var(--avatar-2); color: var(--gray-0); }
.ds .av.is-tone-3 { background: var(--avatar-3); color: var(--gray-0); }
.ds .av.is-tone-4 { background: var(--avatar-4); color: var(--gray-800); }
.ds .av.is-tone-5 { background: var(--avatar-5); color: var(--gray-0); }
.ds .av.is-tone-6 { background: var(--avatar-6); color: var(--gray-0); }
.ds .av.is-deleted { background: var(--avatar-deleted); color: var(--gray-0); }
.ds .av.is-deleted > img { filter: grayscale(100%); opacity: 0.5; }
/* La camera : une pastille foncee dans le coin bas droit, cerclee de la surface (maquette D du
   profil). Plus specifique que `.ds [data-tip]`, qui la remettrait dans le flux. */
.ds .av > .av-edit {
  position: absolute;
  right: -2px;
  bottom: -2px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 2px solid var(--surface);
  border-radius: var(--radius-pill);
  background: var(--text-primary);
  color: var(--surface);
  cursor: pointer;
}
.ds .av > .av-edit :is(i, svg) { width: 11px; height: 11px; font-size: 11px; }
.ds .av > .av-edit:focus-visible { outline: none; box-shadow: var(--ring); }
/* Le nom passe a la ligne, comme dans l ancien composant : sur une seule ligne, il empechait la
   colonne d un tableau de se resserrer et le tableau defilait sur telephone. */
.ds .av-name {
  min-width: 0;
  font: var(--weight-medium) var(--text-base)/var(--leading-snug) var(--font-sans);
  color: var(--text-primary);
}
.ds .av-box.is-deleted > .av-name { color: var(--text-disabled); }
/* En groupe, les disques se chevauchent de 5px ; un filet de la surface les separe. Une personne
   seule avec son nom peut se comprimer, le nom se tronque. */
.ds .av-group > :not(:first-child) { margin-left: -5px; }
.ds .av-group .av { box-shadow: 0 0 0 2px var(--surface); }
.ds .av-group > :has(.av-name) { flex: 0 1 auto; min-width: 0; }

/* ---- CHARGEMENT (`ds-loader`) -----------------------------------------------
   L attente d une reponse du serveur (choix du 23/10/2026, variante A de maquettes/loader.html) :
   un anneau dont l arc prend la couleur principale, a la place du GIF load-hrmaps.gif.
     .ds-loader          un voile de la surface sur toute la zone qui attend, l anneau au centre :
                         le gabarit de cg-busy (angular-busy met la zone en `position: relative`).
                         Sur une zone tres haute, l anneau se cale a 160px du haut pour rester visible.
       .is-inline          dans le flux, a la place du contenu qui n est pas encore la (ecrans qui
                           affichent leur propre chargement)
     .ds-loader-ring     l anneau, 36px ; `is-lg` 48px pour une page entiere (connexion)
   Chaque racine pose `ds` : le composant vit aussi dans des pages hors du design system. */
:is(.ds .ds-loader, .ds.ds-loader) {
  position: absolute;
  inset: 0;
  z-index: 1002;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--surface) 70%, transparent);
  container: ds-loader / size;
}
@container ds-loader (min-height: 480px) {
  .ds .ds-loader-ring { align-self: flex-start; margin-top: 160px; }
}
:is(.ds .ds-loader, .ds.ds-loader).is-inline {
  position: static;
  inset: auto;
  z-index: auto;
  padding: var(--space-10) 0;
  background: none;
  container-type: normal;
}
.ds .ds-loader-ring {
  display: block;
  flex: none;
  width: 36px;
  height: 36px;
  border: 3px solid var(--border);
  border-top-color: var(--primary);
  border-radius: var(--radius-pill);
  animation: ds-loader-spin 0.8s linear infinite;
}
.ds .ds-loader-ring.is-lg { width: 48px; height: 48px; border-width: 4px; }
@keyframes ds-loader-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .ds .ds-loader-ring { animation-duration: 2.4s; }
}

/* ---- SKELETON LINE ------------------------------------------------------
   Barre de chargement qui remplace UN TEXTE, à poser dans l element de texte
   reel. Elle porte elle-meme son fond et son reflet : `ssc-square` n est plus
   necessaire (voir plus bas).

   `inline-block` et hauteur en `em` : la barre participe a la ligne sans la
   dicter. Le bloc de texte garde donc la hauteur que lui donnent sa police et
   son interligne, exactement comme une fois charge. C est ce qui empeche la
   derive : un squelette qui redeclare une hauteur en pixels duplique la boite
   reelle et se desynchronise au premier changement de padding ou de typo.
   La largeur se pose au cas par cas (style en ligne), c est la seule chose qui
   varie d une ligne a l autre. */
.ds .skeleton-line {
  position: relative;
  overflow: hidden;
  display: inline-block;
  vertical-align: middle;
  width: 100%;
  height: 0.7em;
  border-radius: var(--radius-xs);
  background: var(--border-strong);
}
/* LA BOITE DE SQUELETTE : meme fond, meme reflet que la ligne, mais sans forme
   propre. Elle se pose SUR l element reel qu elle remplace et en prend la
   taille et le rayon par son role (`<span class="shell-rail-tile skeleton">`) :
   rien ne bouge quand le contenu arrive, et aucune dimension n est redeclaree.
   Posee APRES les recettes de composants : a poids egal, son fond passe devant
   celui du role. */
.ds .skeleton {
  position: relative;
  overflow: hidden;
  background: var(--border-strong);
  color: transparent;
}
/* LE SCINTILLEMENT, EN JETONS. Il venait de `ssc-square` (skeleton-screen-css,
   dans `sitecss.css`) : un fond `rgba(0,0,0,.17)` et un reflet blanc code en
   dur, dont la variante sombre attend `.v2.dark-theme` — la coque, elle, passe
   en sombre par `data-theme="dark"` : le reflet restait celui du clair. Ici le
   fond est `--border-strong` (#CDD2DC en clair, au ton de l ancien gris sur
   blanc ; #3C424E en sombre) et le reflet est la surface, qui suit le theme.

   Les gabarits qui ecrivent encore `ssc-square skeleton-line` ne changent pas de
   forme : a (0,2,x), ces regles passent devant celles de `.ssc-square` (0,1,x),
   `::after` compris. `ssc-square` y devient simplement inutile. */
.ds .skeleton-line::after,
.ds .skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--surface) 45%, transparent), transparent);
  animation: ds-shimmer 1.3s infinite;
}
@keyframes ds-shimmer {
  to { transform: translateX(100%); }
}
@media (prefers-reduced-motion: reduce) {
  .ds .skeleton-line::after,
  .ds .skeleton::after { animation: none; }
}

/* Neutralise un composant COLORE reutilise comme boite de squelette (bouton
   primaire, pastille de statut). On garde sa boite — c est elle qui porte la
   hauteur exacte — mais pas sa couleur : rien n est charge, rien ne doit
   crier. Deux classes et chargee apres sitecss.css, donc pas d `!important`. */
.ds .is-skeleton {
  background: var(--surface-sunken);
  color: transparent;
  border-color: transparent;
  box-shadow: none;
  pointer-events: none;
}

/* LA BOITE D ATTENTE D UN COMPOSANT QUI SE DIMENSIONNE SUR SON TEXTE. Une
   pastille vide se reduit a ses retraits : sans largeur, le squelette annonce
   une boite qui n existe pas, et le contenu la fait grandir en arrivant.

   Cette largeur est une DIMENSION : elle appartient a la feuille, pas au
   balisage. Les gabarits de squelette n ecrivent donc plus de `style="width"`
   que sur les `skeleton-line`, ou la longueur de la ligne est la seule chose qui
   varie d une ligne a l autre — c est ce que fait deja le squelette des widgets.

   Valeurs prises sur les contenus reels : une pastille de statut du portail fait
   entre 70 et 130px, une pastille de badge entre 50 et 90. */
/* `height: auto` N EST PAS DECORATIF. La bibliotheque de squelettes du portail
   (skeleton-screen-css, dans le paquet `sitecss.css`) pose TROIS regles sur
   `.ssc-square` :

     .ssc-square { position: relative; overflow: hidden }
     .ssc-square { display: block; background-color: rgba(0,0,0,.17); width: 100% }
     .ssc-square { height: 150px }

   La derniere est sa valeur par defaut de « bloc d image ». Elle s applique donc a
   TOUT element qui emprunte la classe pour son scintillement — une pastille, une
   vignette, un rond. Une pastille de statut de squelette sortait a 150px de haut,
   et la bande d etapes qui la contient a 166px : le pave gris.

   Le reste du portail s en sort en posant une hauteur a cote (`h-10`, `h-45`,
   tous en !important) ou en surclassant sous une coque (`.shell`, voir le
   commentaire de `styleScss.scss`). Ici la boite tire sa hauteur de ce qu elle
   EST — une pastille — donc on rend la main a sa recette. */
.ds .badge.is-skeleton,
.ds .badge.ssc-square {
  min-width: 4rem;
  min-height: 1.6em;
  height: auto;
}

/* ---- STEPPER (fil d etapes) ---------------------------------------------
   Reference : `patterns.html` §06 Navigation. Le portail garde SES noms de
   classes (`hm-wizard-*`) et non ceux du specimen (`.step`, `.dot`, `.lab`,
   `.conn`) : le balisage est produit par une directive, monte par 29
   assistants, et surtout `.dot` est DEJA PRIS dans `sitecss.css`
   (`.dot { height: 30px; width: 30px; color: #03A9F4 }`) — meme piege de nom
   que `.lead`, `.affix`, `.switch` et `.row`.

   RECETTE UNIQUE. `Views/Templates/hmWizard.html` est le seul gabarit de
   stepper du portail, page comme modale : une regle ici vaut pour les 29
   assistants. Pas de variante par ecran (le bloc `.hm-mcc-window` de
   `styleScss.scss` en etait le brouillon, il se replie sur celle-ci).

   L etat « a venir » est l etat DE BASE : le gabarit ne pose aucune classe
   pour lui (seulement `active` et `done`). Il va donc sur la regle nue, et
   les deux etats qualifies passent apres — specificite plus forte, aucune
   egalite a arbitrer par l ordre des regles.

   EN ATTENTE D ARBITRAGE, donc PAS traite ici : le glyphe de l etape franchie
   (le portail remplace le numero par `fa-check` ; le specimen ne montre aucune
   etape franchie, il ne tranche donc pas). */
/* LA CARTE DU SPECIMEN VA SUR LE BANDEAU, PAS SUR LE FIL. Dans `patterns.html`
   les boutons Annuler / Suivant sont DEDANS : un seul bloc au fond sourd porte
   le fil ET les actions. Poser la carte sur le `<ul>` laisserait les boutons
   dehors — c est le premier jet, corrige a l ecran.

   `:not(.hm-wizard-modal)` : en mode modale le bandeau n est plus une carte
   posee dans la page mais l EN-TETE de la fenetre — pleine largeur, a bords
   francs, colle en haut, et dont la hauteur commande les decalages de la
   barre et du sommaire du libre-service (`.self-service`, `CustomHrMaps.css`). On le laisse donc
   entierement a l existant : rien ici ne doit deplacer une fenetre deja
   validee. Les pastilles, libelles et traits ci-dessous, eux, valent dans
   les deux modes. */
.ds:not(.hm-wizard-modal) .hm-wizard-bar {
  gap: 18px;
  padding: 12px 14px;
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}

/* TOUS LES LIBELLES RESTENT VISIBLES, A TOUTE LARGEUR, ET ENTIERS.
   `CustomHrMaps.css` porte un `@media (max-width: 1500px)` qui MASQUE le
   libelle de toute etape non active : sous cette largeur l utilisateur ne
   lisait plus que l etape courante, une suite de pastilles muettes autour.
   Le specimen ne masque rien et ne tronque rien — il fait DEFILER le fil.
   C est la regle retenue.

   Une requete media n ajoute pas de specificite : `.ds .hm-wizard-step
   .hm-wizard-label` pese autant que le selecteur qu il annule, et
   `ds-components.css` etant charge apres `sitecss.css`, il passe devant. Pas
   besoin d `!important`, et surtout pas d une requete media concurrente qui
   aurait a suivre celle d en face a chaque changement de seuil.

   `flex: none` sur l etape et `overflow-x: auto` sur le fil vont ensemble :
   sans le premier les etapes se compriment et le libelle se coupe malgre
   tout ; sans le second le fil pousserait les boutons hors du bandeau. */
.ds .hm-wizard-steps {
  gap: 14px;
  overflow-x: auto;
  /* Barre de defilement masquee, comme la rangee d onglets de `ds-inbox.css` :
     une barre systeme de 15px sous le fil se lit comme un habillage casse, et
     elle grandirait le bandeau — donc, en page, decalerait la barre collante.
     L etape coupee au bord est elle-meme l indice qu il y a une suite. */
  scrollbar-width: none;
}

.ds .hm-wizard-steps::-webkit-scrollbar {
  display: none;
}

.ds .hm-wizard-step {
  gap: 10px;
  flex: none;
  white-space: nowrap;
}

.ds .hm-wizard-step .hm-wizard-label {
  display: inline;
  overflow: visible;
  text-overflow: clip;
}

/* A VENIR : blanche, cerclee, chiffre en texte discret.
   `!important` OBLIGATOIRE et non negociable : `ds-bridge.css` — qui est
   GENERE, donc a ne jamais corriger a la main — porte
   `.hm-wizard-dot { background: var(--wizard-bg, var(--surface)) !important }`.
   Un `!important` de specificite superieure est le seul moyen de le battre ;
   sans lui la pastille se vide et l etape courante perd son fond plein. */
.ds .hm-wizard-dot {
  width: 26px;
  height: 26px;
  background: var(--surface) !important;
  border: 1.5px solid var(--border-strong);
  color: var(--text-muted);
  font: var(--weight-bold) var(--text-sm) var(--font-sans);
}

/* COURANTE et FRANCHIE : pleine, en primaire — donc a la couleur du client.
   Le vert que le portail mettait sur l etape franchie n existe nulle part
   dans le specimen ; il avait deja ete retire une premiere fois, a la main,
   pour la modale Media / Communication. */
.ds .hm-wizard-step.active .hm-wizard-dot,
.ds .hm-wizard-step.done .hm-wizard-dot {
  background: var(--primary) !important;
  border-color: var(--primary);
  color: var(--on-primary);
}

.ds .hm-wizard-label {
  font: var(--font-label);
  color: var(--text-muted);
}

.ds .hm-wizard-step.done .hm-wizard-label {
  color: var(--text-primary);
}

.ds .hm-wizard-step.active .hm-wizard-label {
  color: var(--primary-700);
  font-weight: var(--weight-bold);
}

/* ETAPE EN AVANT QU ON PEUT OUVRIR (`reachable`, ex. entretien en navigation libre) : elle se clique
   comme une etape franchie, mais garde son aspect « a venir » — seul le curseur l annonce. */
.ds .hm-wizard-step.is-reachable {
  cursor: pointer;
}

/* LE TRAIT DE LIAISON : un filet d 1px, franchi ou non. Le specimen n a qu un
   seul trait, sans etat — le vert du portail disparait ici comme il disparait
   de la pastille.
   Le gabarit insere le trait comme un `<li>` du meme conteneur flex que les
   etapes : le `gap` du fil fait deja l ecart, d ou pas de `margin` (le
   specimen, lui, en a besoin car son trait n est pas dans le meme flux).

   LONGUEUR FIXE, PAS UN TRAIT QUI S ETIRE. Un `flex: 1` etale les etapes sur
   toute la largeur du bandeau : sur un ecran large le fil devient une frise
   distendue de pres de 1530px, alors que la maquette le montre COMPACT, la
   place libre se trouvant entre la derniere etape et les boutons. 40px au
   repos (la valeur que le portail utilisait deja), reductible jusqu a 18px
   quand la largeur manque. */
.ds .hm-wizard-sep,
.ds .hm-wizard-sep.done {
  flex: 0 1 40px;
  min-width: 18px;
  height: 1px;
  background: var(--border-strong);
}

/* LE FIL ET SES FLECHES (`hm-wizard-rail`). La barre de defilement du fil est masquee (plus haut) :
   a la souris, rien ne permettait d atteindre les dernieres etapes d un long assistant (l entretien
   en neuf pages, en navigation libre). Deux boutons aux bouts le font defiler ; hmWizard.js ne les
   rend que s il reste des etapes de leur cote. */
.ds .hm-wizard-rail {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  flex: 1 1 auto;
  min-width: 0;
}
/* Les fleches : rondes et a taille fixe (le fil, qui s etire, les ecrasait en hauteur). */
.ds .hm-wizard-rail > .ds-btn {
  flex: none;
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-xs);
}

/* LA BARRE COLLANTE remonte du retrait haut de `.page` (8px) et pas davantage : a `-20px`
   (sitecss.css), son haut passait sous le bord de la zone qui defile. Niveau 3 et non le 999 de
   sitecss.css : au-dessus du contenu qui defile, mais sous les menus (`.ac-menu`, niveau 5) ouverts au-dessus
   d elle, comme « Generer pdf » de l entretien, qui passait dessous. */
.ds:not(.hm-wizard-modal) .hm-wizard-bar { top: calc(-1 * var(--space-2)); }
.hm-wizard.ds:not(.hm-wizard-modal) > .hm-wizard-bar { z-index: 3; }

/* TELEPHONE : l etape en cours a la place du fil (`hm-wizard-compact`). Un appui ouvre la liste de
   toutes les etapes (menu `.ac`), celles qu on ne peut pas ouvrir grisees ; une jauge fine dit
   l avancement. Les boutons passent dessous. La barre reste collante : on change d etape sans
   remonter en haut de la page. */
.ds .hm-wizard-compact { display: none; }
@media (max-width: 599px) {
  .ds .hm-wizard-bar {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-2);
  }
  .ds .hm-wizard-rail { display: none; }
  .ds .hm-wizard-compact { display: grid; gap: var(--space-2); }
  .ds .hm-wizard-compact > .ac > .ac-item {
    width: 100%;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--surface);
  }
  .ds .hm-wizard-compact > .ac > .ac-item > .ds-stack { flex: 1 1 auto; min-width: 0; gap: 0; }
  .ds .hm-wizard-compact > .ac > .ac-item .nm {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    color: var(--primary-700);
    font-weight: var(--weight-bold);
  }
  .ds .hm-wizard-compact > .ac > .ac-menu {
    right: 0;
    left: 0;
    max-height: 60vh;
    overflow-y: auto;
  }
  .ds .hm-wizard-compact .ds-meter { display: block; width: 100%; height: 4px; background: var(--border); }
}

/* ============================================================
   FENETRE DE SAISIE ET FORMULAIRE
   ------------------------------------------------------------
   Recettes reprises de `audit/formulaire-reference.html` du projet de design
   system. Vocabulaire partage : une classe par role, la meme sur tous les
   ecrans, pour qu un changement de style se propage partout.

   ECART ASSUME, DECIDE, A NE PAS « CORRIGER » : la reference impose
   `max-height: 62vh; overflow-y: auto` sur le corps. Le portail garde son
   comportement actuel — la fenetre s etend et on defile pour retrouver
   Annuler / Valider. Donc pas de hauteur fixe ici, et pas de
   `position: sticky` sur le pied pour compenser : ce serait revenir sur la
   decision par un autre chemin.
   ============================================================ */

/* ---- bandeau de titre -------------------------------------------------
   En fond primaire, donc il suit la couleur choisie par le client : --primary
   est injecte au runtime par le reglage « Couleur principale ». Aucun rouge
   en dur, jamais. */
.ds .modal-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  /* !important : le gabarit porte aussi la classe legacy `vertical-modal`, que
     `sitecss.css` force a `padding-left: 15px !important`. L entete demarrait
     donc a 15px quand le corps et le pied demarrent a 20px — mesure :
     pastille a 15, libelles de champ a 20, legende du pied a 20. Un decalage
     de 5px, discret mais visible sur la colonne de gauche. */
  padding: var(--space-4) var(--space-5) !important;
  background: var(--primary);
}
.ds .modal-head h4,
.ds .modal-head h3,
.ds .modal-head .modal-title {
  flex: 1;
  min-width: 0;
  margin: 0;
  font: var(--weight-bold) var(--text-lg) var(--font-sans);
  /* !important : `ds-bridge.css` porte `h3 { color: var(--text-primary)
     !important }` pour les titres de page. Un titre de modale ecrit en `h3`
     (les quatre gabarits `*ProfileModal` de la timeline) sortait donc en
     #171A21 SUR LE BANDEAU PRIMAIRE — mesure : rgb(23, 26, 33) sur
     rgb(63, 81, 181), soit un texte quasi illisible. Les modales deja migrees
     utilisent `h4`, ce qui masquait le defaut. Seul un `!important` bat un
     `!important`. */
  color: var(--on-primary) !important;
}
.ds .modal-head .x {
  flex: none;
  display: inline-flex;
  color: var(--on-primary);
  opacity: .8;
  cursor: pointer;
}
.ds .modal-head .x:hover { opacity: 1; }

/* ---- corps ------------------------------------------------------------ */
.ds .modal-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-5);
}

/* ---- pied ------------------------------------------------------------
   `.sp` pousse les actions a droite ; la legende des champs obligatoires
   reste a gauche. Un seul bouton plein par fenetre.
   PAS de fond en creux : `audit/formulaire-reference.html` §01 ne pose que le
   filet du haut sur `.modal-foot`. Le fond `--surface-sunken` venait de l autre
   specimen (`audit/formulaires.html`), qui decrit une BOITE CENTREE ; il a ete
   retire, la reference developpeur faisant foi. */
.ds .modal-foot {
  display: flex;
  align-items: center;
  /* Le pied PASSE A LA LIGNE quand ses actions ne tiennent pas.
     Sans `wrap`, un flex en `nowrap` comprime ses enfants sous leur largeur
     intrinseque : mesure sur `appraisalProfileModal`, qui porte sept boutons —
     588px de boutons dans 665px de pied, 27px de debordement, et surtout des
     libelles qui se chevauchaient (« Entretien » ecrit par-dessus « Entretien
     avant comite »). Ce n est pas un cas nouveau : l ancien `modal-footer`
     Bootstrap etait un bloc a boutons `inline-block`, donc il passait deja a la
     ligne — `sitecss.css` porte meme `.custom-btn-footer .btn { margin-bottom:
     5px }`, pose pour aerer ces lignes-la. `wrap` restitue ce comportement ; il
     ne change rien aux pieds qui tiennent sur une ligne. Le `gap` vaut pour les
     deux axes, il espace donc aussi les lignes. */
  flex-wrap: wrap;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-top: 1px solid var(--border);
}
.ds .modal-foot .sp { margin-left: auto; }


/* ---- champ : libelle, aide, erreur, obligation ----------------------- */
/* LES COULEURS D ETAT A LA DOSE DES BADGES (28/09/2026) : SEULE l ERREUR colore la bordure, en
   `--danger-border` (la teinte adoucie, 3:1 sur la surface, tokens/colors.css) ; un champ valide garde
   sa bordure neutre, sa coche suffit. Coche et asterisque en `--success-fg` / `--danger-fg` (la teinte
   de texte des badges). Une fiche de vingt champs ne vire plus au vert, ni au rouge vif en erreur. */
/* LE MARQUEUR D OBLIGATION : asterisque rouge, coche verte quand c est rempli.

   Le systeme legacy `.symbol` de sitecss.css se sert de ce marqueur comme
   INDICATEUR D ETAT, en echangeant son glyphe ET sa couleur — mesure :
     aucun etat  -> `*`          #E6674A
     has-success -> coche FA     #1DBBA6
     has-error   -> croix FA     #FF0000
     has-warning -> triangle FA  #EEA236
   Quatre couleurs en dur, et un marqueur qui change de sens selon l etat.

   On en garde DEUX etats et on neutralise les autres :

   - HORS succes, le contenu est FORCE a l asterisque, en `--danger`. Sinon la
     croix ou le triangle du legacy remplacent le marqueur d obligation, qui
     doit rester lisible quel que soit l etat du champ.
   - EN succes, on laisse le legacy poser sa COCHE et on ne reprend que la
     couleur, alignee sur `--success`. Le champ valide se confirme alors deux
     fois : bordure verte et coche.

   `content` et `color` vont ensemble : en ne posant que la couleur, on
   repeignait la coche en rouge ; en ne posant que le contenu, l asterisque
   sortait turquoise en succes. Les deux tentatives ont ete faites.

   `:not(.required)` ne marcherait pas comme selecteur : le gabarit ecrit
   toujours `class="symbol required"`, les deux classes sont donc presentes. */
/* Portee ELARGIE : `.symbol.required` sans exiger un `label` ancetre. Le
   marqueur d obligation ne vit pas seulement dans un libelle — la zone de
   depot de fichiers le porte dans son declencheur (`.drop .btn-file`) quand
   le type d absence exige un justificatif. Scope au `label`, la regle ne
   l atteignait pas et le legacy gardait la main avec son orange en dur. */
.ds .field:not(.has-success) > label .symbol.required::before,
.ds .field:not(.has-success) .symbol.required::before {
  content: "*";
  color: var(--danger-fg) !important;
}
.ds .field.has-success > label .symbol.required::before,
.ds .field.has-success .symbol.required::before {
  color: var(--success-fg) !important;
}
/* UN MARQUEUR DE COLONNE. Dans une ligne repetable (`repeat-grid`), les
   libelles ne sortent que sur la premiere ligne : leur marqueur parle donc
   pour TOUTE la colonne, pas pour la ligne qui le porte. Sans cette regle une
   colonne s annonce complete des que sa PREMIERE ligne est remplie, les autres
   encore vides — vu sur le bloc Documents des Demandes RH.
   `is-incomplete`, pose par le gabarit, ramene le marqueur a l asterisque rouge
   tant qu il reste une ligne a remplir. La bordure, elle, ne bouge pas : elle
   reste l etat de SA ligne, qui passe au vert des qu elle est valide. */
.ds .field.has-success > label .symbol.required.is-incomplete::before,
.ds .field.has-success .symbol.required.is-incomplete::before {
  content: "*";
  color: var(--danger-fg) !important;
}

/* SAUF quand le champ affiche une erreur : le marqueur revient a l asterisque
   rouge. Meme cause que la bordure plus bas — `has-success` suit la validation
   ANGULAR, les erreurs SERVEUR arrivent en `.err-msg` sans passer par elle. La
   bordure seule ne suffisait pas : on lisait un champ borde de rouge, un
   message rouge, et une coche VERTE dans son libelle. */
.ds .field.has-success:has(.err-msg:not(.ng-hide)) > label .symbol.required::before,
.ds .field.has-success:has(.err-msg:not(.ng-hide)) .symbol.required::before {
  content: "*";
  color: var(--danger-fg) !important;
}

/* `.ds .hint` et non `.ds .field .hint` : une aide n est pas toujours dans un
   champ — la legende « * Champ obligatoire » du pied de fenetre en est une, et
   elle sortait donc en #333 herite du reset de sitecss, illisible en sombre.
   Le !important bat le `*, ::after, ::before { color: #000 !important }` de
   sitecss, que la seule specificite ne peut pas battre. */
.ds .hint {
  font: var(--font-meta);
  color: var(--text-muted) !important;
}
/* L asterisque de la legende "* Champ obligatoire". Le specimen ecrit
   `.req { color: var(--danger) }`, mais aucune regle ne visait ce `.req` hors
   des libelles : l asterisque heritait donc la couleur de la legende. Une
   regle posee sur l ENFANT l emporte sur la valeur heritee de `.ds .hint`,
   donc pas de !important necessaire.

   `.ds .hint-warning` a ete retire d ici : apres le retrait de cette classe du
   gabarit de la modale Demandes RH, il ne lui restait AUCUN usage dans le
   depot. Le specimen ne definit pas de variante d aide en ton d attention, et
   la legende des champs obligatoires est une information, pas une alerte. */
.ds .hint .req { color: var(--danger-fg); }
/* Le message d erreur REMPLACE l aide, il ne s ajoute pas — et il dit quoi
   faire, pas seulement ce qui est faux. */
/* `.ds .err-msg` et non `.ds .field .err-msg` — meme lecon que `.hint` juste
   au-dessus : un message d erreur n est pas toujours dans un champ. La modale
   de creation d absence en porte un bloc HORS champ (les elements deja poses
   sur la periode demandee : conges, formation, entretien, date bloquee), qui
   sortait donc en #333 herite du reset de sitecss. */
.ds .err-msg {
  display: flex;
  align-items: center;
  gap: 5px;
  font: var(--font-meta);
  /* `--danger-fg` et non `--danger` : le premier est la TEINTE DE TEXTE, le
     second la teinte de REMPLISSAGE. La difference ne se voit pas tant qu on
     ne mesure pas — le rouge plein tombait a 3,41:1 sur la surface sombre,
     sous le minimum de 4,5:1. Le jeton de texte existe des le depart pour ca,
     et il ameliore AUSSI le mode clair : #B42121 au lieu de #DC2626.
     Mesure : clair 4,83 -> 6,60 ; sombre 3,41 -> 6,80. */
  color: var(--danger-fg, var(--danger));
}
/* Le message se pose TOUJOURS en dernier dans le champ.

   Le plugin `cs-select` deplace le <select> d origine et insere son declencheur
   `<div class="cs-select">` a la FIN du champ — donc apres le message d erreur,
   qui se retrouvait AU-DESSUS du controle. Mesure : ordre DOM
   `label > span.err-msg > div.cs-select`, message a y=413 pour un select a
   y=435. `order` corrige l affichage sans toucher au gabarit ni au plugin.

   Le libelle et le controle gardent `order: 0`, donc leur ordre DOM. */
.ds .field > .err-msg { order: 2; }

/* L etat d erreur se pose sur le CONTROLE dans le design system. Le portail,
   lui, met `has-error` sur l enveloppe via ng-class — on redefinit donc la
   classe telle qu Angular l applique, plutot que de toucher au JS. */
/* Les classes sont REPETEES pour gagner en poids. `ds-bridge.overrides.css`
   pose la bordure des champs avec `input:not([type=checkbox]):not(...)` — six
   clauses `:not()`, chacune comptee comme une classe, soit une specificite de
   (0,6,1). Un simple `.ds .input.err` vaut (0,3,0) et perd, meme en
   `!important`. La repetition est la technique deja employee ailleurs dans ce
   depot (voir la recherche de la topbar dans ds-app-shell.css) : elle ne change
   rien au sens du selecteur, seulement son poids. */
/* ERREUR et SUCCES, par la variable pour la meme raison que le focus.
   `.has-error` est la classe que le portail pose sur l ENVELOPPE via ng-class :
   on la traite telle qu Angular l applique, sans toucher au JS. */
.ds .has-error .input,
.ds .has-error .select,
.ds .has-error .textarea,
.ds .has-error .form-control,
.ds .has-error .cs-select,
.ds .input.err,
.ds .select.err,
.ds .textarea.err {
  --field-border: var(--danger-border);
  --input-border: var(--danger-border);   /* le select lit celle-ci, cf. le focus */
  border-color: var(--danger-border) !important;
}

/* SUCCES : demande explicitement par `.ok` sur le CONTROLE.

   `has-success` n est volontairement PAS mappe. La grille « Champs & etats » du
   specimen distingue deux vignettes : « SAISIE — REMPLI », bordure NORMALE, et
   « SUCCES », bordure verte. Un champ rempli n est donc pas un champ en succes.

   Or le portail pose `has-success` des que le champ est VALIDE, c est-a-dire des
   qu il est rempli. Le mapper verdissait tout le formulaire au fil de la saisie —
   ce que le specimen ne fait pas. Le vert reste disponible, mais il se demande.

   Les deux variables, pour la raison du focus : le select lit `--input-border`.

   DECISION DU CLIENT, prise apres trois allers-retours : un champ VALIDE se
   signale en vert. `has-success` est donc mappe, en plus de `.ok`.

   Ecart assume avec la grille « Champs & etats » du specimen, qui distingue
   « SAISIE — REMPLI » (bordure neutre) de « SUCCES » (bordure verte). Le portail
   pose `has-success` des que le champ est valide : en pratique, un formulaire
   correctement rempli sera donc entierement vert. C est voulu.

   `:not(.has-error)` garde la priorite a l erreur, quelle que soit la position
   des blocs dans le fichier. */
/* … ET SEULEMENT SUR UN CHAMP OBLIGATOIRE.

   Decision du client, affinee : le vert marque un champ REQUIS qui a ete
   rempli. Un champ facultatif rempli garde une bordure neutre.

   Sans cette clause, `has-success` verdissait tout ce qui etait valide, y
   compris ce que rien n obligeait a saisir : sur la modale d absence, choisir
   un type de document — facultatif tant qu aucun fichier n est joint — passait
   le champ au vert alors qu il n avait aucune obligation a satisfaire.

   « Obligatoire » se lit sur DEUX marqueurs, parce que les gabarits ne portent
   pas tous le meme :
     - `[required]` : l attribut, pose en dur ou par `ng-required`, donc suivi
       dynamiquement par Angular. Il est parfois sur le CONTROLE, parfois sur
       l hote de directive (`<hm-date-range-picker required>`) — d ou le
       selecteur d attribut nu, sans nom de balise.
     - `.symbol.required` : l asterisque du libelle, souvent le seul marqueur
       quand l obligation est conditionnelle. Sur le type d un document, il est
       garde par `ng-if="pending.File"` : le champ ne devient obligatoire — et
       donc verdissable — qu une fois le fichier joint.

   `.ok` sur le controle reste inconditionnel : le vert explicitement demande
   ne depend pas de l obligation.

   Specificite (0,5,0), donc toujours au-dessous de la regle « message d erreur
   affiche » qui suit, et toujours au-dessus de rien d autre : `:not(.has-error)`
   garde la priorite a l erreur quelle que soit la position des blocs. */
/* `:not(:disabled)` : un champ DESACTIVE ne porte aucun etat de validite.
   Constate sur la modale « Personnes a charge », dont le formulaire est rendu
   par le composant self-service : un champ de date VIDE et DESACTIVE sortait
   borde de vert, parce que le composant lui pose `has-success`. Un champ qu on
   ne peut pas remplir n a rien a confirmer. Le §07 de la reference montre
   d ailleurs l etat « Desactive » avec une bordure neutre.
   `[disabled]` en plus de `:disabled` pour le declencheur du plugin cs-select,
   qui est un <div> et porte donc l attribut sans etre un controle natif. */
.ds .has-success:not(.has-error):has([required], .symbol.required:not(.ng-hide)) .input:not(:disabled),
.ds .has-success:not(.has-error):has([required], .symbol.required:not(.ng-hide)) .select:not(:disabled),
.ds .has-success:not(.has-error):has([required], .symbol.required:not(.ng-hide)) .textarea:not(:disabled),
.ds .has-success:not(.has-error):has([required], .symbol.required:not(.ng-hide)) .form-control:not(:disabled),
.ds .has-success:not(.has-error):has([required], .symbol.required:not(.ng-hide)) .cs-select:not([disabled]):not(:disabled),
.ds .input.ok,
.ds .select.ok,
.ds .textarea.ok,
.ds .form-control.ok,
.ds .cs-select.ok {
  /* PLUS DE BORDURE VERTE (28/09/2026, decision) : la coche du libelle suffit a dire « rempli » ;
     vingt bordures vertes sur une fiche faisaient trop. La regle reste, mais repose la bordure
     NEUTRE du champ : sans elle, `.has-success .form-control` de Bootstrap remettrait son vert en
     dur. `var(--field-border)` suit le survol et le focus, qui redefinissent la variable sur
     l element. Seule l ERREUR colore encore la bordure. */
  border-color: var(--field-border) !important;
}
/* … SAUF LE CHAMP A LISTE, QUI EST `disabled` SANS ETRE INERTE.

   Le `:not(:disabled)` ci-dessus ecarte les champs desactives de tout etat de
   validite — juste pour un champ qu on ne peut pas remplir. Le champ a liste
   n en est pas un : il se remplit par son bouton. Il restait donc neutre une
   fois choisi, alors que son LIBELLE, lui, affichait deja la coche verte —
   l etat etait a moitie rendu.

   Meme condition d obligation que le bloc ci-dessus, et meme reperage que pour
   l apparence : `:has(.repeat-actions)` exige les boutons. Specificite (0,8,0),
   donc au-dessus de la regle du desactive (0,3,0) qui fige la bordure. */
.ds .has-success:not(.has-error):has([required], .symbol.required:not(.ng-hide)) .affix:has(.repeat-actions) .input[disabled] {
  /* bordure neutre, comme au-dessus : la coche du libelle suffit */
  border-color: var(--field-border) !important;
}

/* MAIS PAS QUAND LE CHAMP AFFICHE UNE ERREUR.

   `has-success` suit `Form.<champ>.$valid`, donc la validation ANGULAR : champ
   rempli, format correct. Une partie des erreurs du portail ne passe pas par
   la : elles viennent du SERVEUR et sont rendues en `.err-msg` sous le champ
   (`ng-repeat="error in model.errors"`). Les deux sont independants, et on
   obtenait un champ BORDE DE VERT avec un message ROUGE dessous.

   Constate a l ecran sur la modale d absence : periode 13/07 - 14/07 saisie,
   donc `Form.period.$valid` vrai, donc bordure rgb(22, 163, 74) — et juste
   dessous « Delai avant debut d absence non respecte ». Le champ se disait bon
   et mauvais en meme temps.

   Ce n est pas la passe de classes qui l a introduit : le `ng-class` est le
   meme avant et apres migration. C est le mappage de `has-success`, decide
   depuis, qui rend la contradiction VISIBLE.

   `:not(.ng-hide)` est necessaire : un `.err-msg` garde par `ng-show` reste
   dans le DOM et ne porte que cette classe quand il est masque. Les `.err-msg`
   gardes par `ng-if`, eux, ne sont pas rendus du tout.

   Specificite (0,5,0), au-dessus du bloc de succes (0,4,0) ; les variables sont
   posees en plus de `border-color` pour la meme raison que le focus. */
.ds .field:has(.err-msg:not(.ng-hide)) .input,
.ds .field:has(.err-msg:not(.ng-hide)) .select,
.ds .field:has(.err-msg:not(.ng-hide)) .textarea,
.ds .field:has(.err-msg:not(.ng-hide)) .form-control,
.ds .field:has(.err-msg:not(.ng-hide)) .cs-select {
  --field-border: var(--danger-border);
  --input-border: var(--danger-border);
  border-color: var(--danger-border) !important;
}

.ds .has-error .input:focus,
.ds .input.err:focus { box-shadow: 0 0 0 3px var(--danger-soft); }

/* LE LIBELLE NE PASSE PAS EN ROUGE — regle RETIREE, verifiee a l ecran.

   Ce bloc posait `.ds .field.has-error > label { color: var(--danger) }` en
   s appuyant sur l autre specimen. Deux raisons de le retirer :

   1. `audit/formulaire-reference.html` §07 « Etats » montre le champ en erreur
      avec son libelle en `--text-secondary`, inchange. Seules la bordure et le
      message parlent. La recette de la reference ne contient aucune regle de
      libelle en erreur.
   2. La regle etait de toute facon MORTE : `.ds .field > label` porte
      `color: var(--text-secondary) !important` (pose pour empecher
      `ds-bridge.css` de foncer le libelle d un champ rempli), et un
      !important bat une regle plus specifique sans !important. Mesure sur la
      modale Demandes RH, champ Type en erreur : libelle a #5A6273, message a
      #DC2626, bordure a #DC2626. Le rouge n arrivait jamais.

   Cas d ecole du §35 : une recette qu on croit active, que le poids annule en
   silence — et qui, verification faite, n avait pas lieu d exister. */
.ds .input.ok { border-color: var(--field-border) !important; }

/* ---- prefixe / suffixe (unite, devise) ------------------------------- */
/* PIEGE DE NOM : `.affix` est aussi une classe Bootstrap — celle du plugin
   Affix, qui pose `position: fixed`. Sans `position: static` ici, le groupe
   sortait du flux et se dessinait par-dessus son propre libelle. C est le cas
   type des noms du design system qui collident avec Bootstrap. */
.ds .affix {
  position: static;
  display: flex;
  align-items: stretch;
  min-width: 0;
}
/* Un champ a icone pose dans un .affix prend la place restante ; sans
   `min-width: 0` il refuserait de retrecir et ferait deborder la rangee. */
.ds .affix .input-icon { flex: 1; min-width: 0; }
/* Un adjoint n est pas toujours un `.tag` : ce peut etre un BOUTON (declencheur
   de fichier, suppression de la piece jointe). Meme lecon que `.repeat-actions`
   plus bas — le champ prend la place restante, le bouton garde sa taille de
   controle. Sans `min-width: 0` le champ refuse de retrecir et fait deborder la
   ligne ; les boutons se groupent dans un `.repeat-actions`, qui porte deja
   `flex: none` — poses nus dans le `.affix` ils s ecrasaient a 16px. */
.ds .affix > .input { flex: 1 1 auto; min-width: 0; }
.ds .affix > .repeat-actions { margin-left: var(--space-2); }
.ds .affix .tag {
  flex: none;
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  padding: 0 11px;
  min-height: var(--control-h);
  font: var(--font-body-sm);
  color: var(--text-muted);
  background: var(--surface-sunken);
  border: 1px solid var(--field-border);
}
.ds .affix .tag.pre  { border-radius: var(--radius-input) 0 0 var(--radius-input); border-right: none; }
.ds .affix .tag.post { border-radius: 0 var(--radius-input) var(--radius-input) 0; border-left: none; }
.ds .affix .input.has-pre  { border-radius: 0 var(--radius-input) var(--radius-input) 0 !important; }
.ds .affix .input.has-post { border-radius: var(--radius-input) 0 0 var(--radius-input) !important; }

/* ---- champ a icone --------------------------------------------------- */
.ds .input-icon { position: relative; display: block; min-width: 0; }
/* Le creneau de tete comme celui de fin acceptent un element cliquable et
   pas seulement une icone : un declencheur de fichier a besoin d un <span>
   pour porter l <input type="file"> masque. */
.ds .input-icon > i,
.ds .input-icon > svg,
.ds .input-icon > .trail {
  position: absolute;
  left: 11px;
  top: 50%;
  transform: translateY(-50%);
  width: var(--icon-md, 16px);
  height: var(--icon-md, 16px);
  /* !important : ds-bridge.css repeint les svg FontAwesome en --primary a
     travers toute l application. Le glyphe d un creneau de champ est un repere,
     pas une action — il doit rester discret. Seul un !important bat un
     !important. */
  color: var(--text-muted) !important;
  pointer-events: none;
}
/* La place de l icone de TETE, seulement quand il y en a une (`<i>` ou le `<svg>` que FontAwesome lui
   substitue, enfant direct). Sans elle, un champ qui n a qu une icone de FIN (`.trail` : la croix des
   filtres de colonne, l oeil du mot de passe) gardait 35px vides a gauche en plus des 35px de
   `trail-pad` : dans une colonne etroite, plus rien du texte ne restait visible (29/09/2026). */
.ds .input-icon:has(> i, > svg) .input { padding-left: 35px !important; }
.ds .input-icon .trail { left: auto; right: 11px; pointer-events: auto; cursor: pointer; }
.ds .input-icon .trail:hover { color: var(--text-primary); }
.ds .input-icon.trail-pad .input { padding-right: 35px !important; }
/* Le creneau de fin CENTRE son contenu, et son glyphe remplit la boite.

   Defaut constate a l ecran : l icone d envoi du champ fichier sortait
   difforme, comme coupee en bas. Cause mesuree — le creneau fait 16x16 (pose
   plus haut), il portait `overflow: hidden` (herite de `.btn-file`, qui en a
   besoin pour masquer le controle natif du navigateur), et le `<svg>` que
   FontAwesome substitue au `<i>` se dimensionne sur la font-size : 14x14, pose
   sur la LIGNE DE BASE du span. Le bas du glyphe tombait donc hors de la boite
   et se faisait rogner.

   Deux corrections, pas une : centrer (le span n etait pas un conteneur flex)
   ET dimensionner le glyphe au jeton, pour qu il occupe exactement la boite.
   Le `!important` sur la couleur est le meme cas que partout ailleurs :
   `ds-bridge.css` repeint tous les svg FontAwesome en `--primary`. */
.ds .input-icon > .trail {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.ds .input-icon > .trail > i,
.ds .input-icon > .trail > svg {
  width: var(--icon-md, 16px);
  height: var(--icon-md, 16px);
  color: var(--text-muted) !important;
}

/* Declencheur de fichier : l <input type="file"> est rendu invisible et
   etale sur tout le declencheur, pour que le clic sur le glyphe ouvre le
   selecteur natif sans qu on voie le controle du navigateur. */
.ds .btn-file { position: relative; overflow: hidden; cursor: pointer; }
.ds .btn-file input[type="file"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}

/* ---- une section a filet, hors grille Bootstrap -----------------------
   `sitecss.css` ecrit `.style-border { margin-left: -15px; width: calc(100% +
   30px); padding: 15px 0 0 15px }` : des marges NEGATIVES, faites pour
   s echapper des gouttieres de la grille Bootstrap et faire courir le filet
   d un bord a l autre. Une fois la grille retiree du gabarit, ces gouttieres
   n existent plus et la section debordait de 15px a gauche — mesure : bloc a
   x5 quand le corps commence a x20, filet desaligne de tout le reste.

   On rend la section a la largeur de son conteneur, et son retrait haut au
   jeton. Scope `.ds` : hors des ecrans migres, la grille est encore la et les
   marges negatives y ont toujours leur role. */
.ds .style-border {
  margin-left: 0;
  width: auto;
  padding: var(--space-4) 0 0;
}

.ds .calendar-popup #cal-week-box {
  left: auto !important;
  right: -600%;
}

.ds .calendar-popup .cal-day-today span[data-cal-date] { color: var(--on-primary) !important; }

.ds .calendar-popup .cal-month-day.cal-day-today:hover { background-color: var(--primary) !important; }

.ds .calendar-popup .cal-month-day span[data-cal-date] { margin-top: 9.5px; }
.ds .calendar-popup .cal-month-day.cal-day-today span[data-cal-date] { margin-top: 7.5px; }

/* ---- la fleche nue d un mini-calendrier -------------------------------
   `sitecss.css` ecrit `.btn-arrow { background: 0 0; border: 0; padding: 0 }` :
   une fleche de navigation de mois n est pas un bouton, elle n a pas de boite.
   Mais elle est posee sur un `<button class="btn btn-arrow">`, et `.ds .btn`
   (0,2,0) bat `.btn-arrow` (0,1,0) : la recette lui rendait donc 9px 15px de
   retrait et 38px de hauteur minimale. Mesure : padding 9px 15px, min-height
   38px, la ou le legacy voulait zero.

   Les gabarits contournaient avec un `style="display:contents"` en dur, qui
   supprime la boite entiere. On retablit plutot l intention du legacy avec le
   poids qu il faut, et l inline devient inutile.

   Portee : neuf gabarits portent `.btn-arrow` — les trois calendriers de
   l accueil, la fiche Details, et cinq modales de coaching et de formation. */
.ds .btn-arrow {
  min-height: 0;
  height: auto;
  padding: 0;
  background: none;
  border: 0;
}

/* ---- cases, radios, options ----------------------------------------- */
/* `display` pose explicitement : `sitecss.css` masque les radios natifs
   (`input[type="radio"] { display: none }`) au profit d un skin maison. Un
   radio du design system doit rester le controle natif — c est lui qui porte
   `accent-color`, donc la couleur du client, et l accessibilite clavier. */
.ds .chk,
.ds .rad {
  display: inline-block;
  width: 17px;
  height: 17px;
  margin: 0;
  flex: none;
  accent-color: var(--primary);
  cursor: pointer;
}
.ds .opt {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--control-h-sm);
  font: var(--font-body);
  color: var(--text-primary);
  cursor: pointer;
  /* MEME CAUSE QUE `.ds .field > label` plus haut : une option EST un `<label>`,
     elle heritait donc du `label { margin-bottom: 5px }` de `sitecss.css`. Cette
     marge s ajoutait au `gap: 2px` de `.optgroup` — mesure : 7px entre deux
     options empilees la ou la recette en prevoit 2. En variante `row` le defaut
     ne se voit pas, l ecart y est horizontal (20px, juste), mais les 5px
     grossissaient quand meme le groupe vers le bas. */
  margin: 0;
}
.ds .opt.disabled { color: var(--text-disabled); cursor: not-allowed; }

/* Une option presentee en CARTE, quand le choix porte plus que son intitule :
   un logo, un nom, une description, et le controle qui l active.

   `.opt` donne deja la ligne de choix et `.card` la surface bordee — mais
   AUCUNE des deux ne pose de retrait interieur, et pour de bonnes raisons : une
   carte n en a pas par defaut parce que son contenu varie, une option nue n en
   veut pas. C est donc la COMBINAISON qui le pose. Recette partagee et non
   propre a un ecran : toute liste d options presentee en cartes la reutilise.

   `display: grid` et non le `inline-flex` de `.opt` : en carte, l option
   occupe sa cellule de grille, et le nom doit se poser AU-DESSUS de sa
   description tout en laissant le controle a droite sur les deux lignes. Trois
   colonnes — le glyphe, le texte, le controle — dont seule celle du milieu
   s etire, en `minmax(0, 1fr)` pour qu un nom long se comprime au lieu de
   faire deborder la carte. */
.ds .opt.card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-rows: auto auto;
  align-items: center;
  gap: 0 var(--space-3);
  padding: var(--space-4);
}
/* Le glyphe qui ouvre la ligne, sur les deux rangees. Ni `.av` ni `.token .av`
   ne conviennent ici : toutes deux sont en `--radius-pill`, et un logo
   rectangulaire mis en rond se retrouve rogne. `object-fit: contain` le laisse
   entier dans sa boite. */
.ds .opt.card > img {
  grid-row: 1 / 3;
  flex: none;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-sm);
  object-fit: contain;
}
.ds .opt.card > .nm { grid-column: 2; grid-row: 1; }
.ds .opt.card > .ps { grid-column: 2; grid-row: 2; }
/* Le controle tient la troisieme colonne sur les deux rangees. `hm-switch` est
   nomme a cote de `.ds-switch` pour que la recette marche avec la directive du
   portail comme avec l interrupteur du design system — meme precedent que
   `.ds .field > hm-time-picker` plus bas. */
.ds .opt.card > hm-switch,
.ds .opt.card > .ds-switch,
.ds .opt.card > .chk,
.ds .opt.card > .rad { grid-column: 3; grid-row: 1 / 3; justify-self: end; }
.ds .optgroup { display: flex; flex-direction: column; gap: 2px; }
/* PIEGE DE NOM, comme `.affix`, `.switch` et `.lead` : `.row` est une classe
   BOOTSTRAP. Elle porte `margin-left: -15px; margin-right: -15px` — le groupe
   d options debordait donc de 15px de chaque cote de son champ — ET deux
   pseudo-elements de clearfix (`.row::before` / `.row::after` en
   `display: table; content: " "`). Dans un conteneur flex, ces deux
   pseudo-elements deviennent des ITEMS FLEX : ils consommaient chacun un
   `gap` de 20px, soit 40px de vide fantome entre les options.
   Specificite (0,3,0) contre (0,1,0) : aucun !important necessaire. */
.ds .optgroup.row { flex-direction: row; flex-wrap: wrap; gap: var(--space-5); margin: 0; }
.ds .optgroup.row::before,
.ds .optgroup.row::after { content: none; }
/* `is-scale` : les niveaux d une echelle (competence, formulaire d entretien), UN PAR LIGNE : le
   bouton, le nom suivi de sa place dans l echelle (`.nm`, precede de la cible du niveau requis,
   `.nm-req`, et termine par `.nm-tail` : la place et la marque du niveau choisi, qui ne se separent
   jamais et passent a la ligne avec le dernier mot du nom), les preparations juste apres, puis la
   definition sur une seule ligne (`.ps`). Le niveau coche est teinte. Une definition trop longue pour
   sa ligne finit en « … » ; l ecran la rend alors cliquable (un `button.ps`) et l affiche en entier
   dans une fenetre (`hm-popover`).
   Les colonnes sont celles du groupe (`subgrid`) : d une ligne a l autre, les reperes et les
   definitions commencent au meme endroit. Le nom prend la largeur du plus long, 260px au plus, plus la
   place de la cible du niveau requis quand elle est posee : avec ou sans cible, le nom garde la meme
   place et ne passe pas a la ligne plus tot. */
.ds .optgroup.is-scale {
  --scale-name-max: 260px;
  display: grid;
  grid-template-columns: auto fit-content(var(--scale-name-max)) auto minmax(0, 1fr);
}
.ds .optgroup.is-scale:has(.nm-req) { --scale-name-max: calc(260px + 16px + var(--space-2)); }
.ds .optgroup.is-scale > .opt {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
  align-items: center;
  gap: var(--space-1) var(--space-3);
  /* Ligne serree : ni la hauteur minimale d une option seule (`.opt`, 32px), ni marge haute et basse
     de plus de 4px : cinq niveaux restent compacts, toute la ligne reste cliquable. */
  min-height: 0;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-sm);
}
/* Survol seulement quand le niveau se choisit : en lecture seule, la ligne ne promet pas un clic. */
.ds .optgroup.is-scale > .opt:not(:has(.rad:disabled)):hover { background: var(--surface-hover); }
.ds .optgroup.is-scale > .opt:has(.rad:disabled),
.ds .optgroup.is-scale > .opt > .rad:disabled { cursor: default; }
/* Le niveau choisi : pas de fond, son nom en gras (le bouton dit deja qu il est coche). */
.ds .optgroup.is-scale > .opt:has(.rad:checked) > .nm { font-weight: var(--weight-bold); }
.ds .optgroup.is-scale > .opt > .nm > .nm-tail { white-space: nowrap; }
.ds .optgroup.is-scale > .opt > .nm > .nm-tail > .ps { margin-left: var(--space-1); }
.ds .optgroup.is-scale > .opt > .ps {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
/* La definition coupee, cliquable : un bouton sans habillage, qui garde le texte de `.ps` ; le
   soulignement pointille dit qu il y a la suite. */
.ds .optgroup.is-scale > .opt > button.ps {
  padding: 0;
  border: 0;
  background: none;
  text-align: left;
  text-decoration: underline dotted;
  text-underline-offset: 3px;
  cursor: pointer;
}
.ds .optgroup.is-scale > .opt > button.ps:focus-visible { outline: none; box-shadow: var(--ring); }
.ds .optgroup.is-scale > .opt > .ds-hstack { gap: var(--space-1); flex-wrap: nowrap; }
.ds .optgroup.is-scale > .opt > .ds-hstack > .badge { padding: 2px 7px; font-size: 11px; }
/* NIVEAU CHOISI COMPARE AU REQUIS (choix du 15/10/2026) : sans fond. Sous le requis, nom en rouge et fleche
   vers le bas ; au requis ou au-dessus, nom en vert et coche (`ds-text-danger` / `ds-text-success` poses par
   le gabarit). Le bouton prend la meme couleur. Le vert passe au ton fonce (--success-fg) : --success seul ne se lit
   pas sur fond blanc, et Chrome assombrit le contour d un bouton a couleur claire. « Non applicable » ou compétence sans requis : couleur de selection habituelle. */
.ds .optgroup.is-scale > .opt > .nm > .nm-tail > span:not(.ps) { margin-left: var(--space-1); font-size: 11px; }
/* NIVEAU REQUIS (choix du 21/10/2026) : une cible verte, jamais une pastille (les pastilles sont les
   preparations). Devant le nom dans l echelle, et dans la legende de la question (`ds-hint`). Dans
   l echelle, la place est gardee sur chaque niveau d une competence qui a un requis : les noms restent alignes. */
.ds .nm-req {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 16px;
  font-size: 15px;
  line-height: 1;
  color: var(--success-fg);
}
.ds .optgroup.is-scale > .opt > .nm > .nm-req {
  margin-right: var(--space-2);
  vertical-align: middle;
}
.ds .optgroup.is-scale > .opt > .nm.ds-text-success { color: var(--success-fg); }
.ds .optgroup.is-scale > .opt:has(> .nm.ds-text-danger) > .rad { accent-color: var(--danger); }
.ds .optgroup.is-scale > .opt:has(> .nm.ds-text-success) > .rad { accent-color: var(--success-fg); }
.ds .optgroup.is-scale > .opt:has(> .nm.ds-text-danger) > .rad:disabled:checked { border-color: var(--danger); }
.ds .optgroup.is-scale > .opt:has(> .nm.ds-text-danger) > .rad:disabled:checked::before { background: var(--danger); }
.ds .optgroup.is-scale > .opt:has(> .nm.ds-text-success) > .rad:disabled:checked { border-color: var(--success-fg); }
.ds .optgroup.is-scale > .opt:has(> .nm.ds-text-success) > .rad:disabled:checked::before { background: var(--success-fg); }
/* Telephone : la definition passe sous le nom, les reperes restent sur la ligne du nom. */
@media (max-width: 599px) {
  .ds .optgroup.is-scale { grid-template-columns: auto minmax(0, 1fr) auto; }
  .ds .optgroup.is-scale > .opt > .ps { grid-column: 2 / -1; grid-row: 2; }
}

/* JAUGE (`ds-meter`) : une valeur sur un maximum, en barre (moyenne d une famille de competences). La
   longueur est posee par l ecran sur l enfant (`width` en %). */
.ds .ds-meter {
  display: inline-block;
  width: 84px;
  height: 6px;
  overflow: hidden;
  vertical-align: middle;
  background: var(--surface-sunken);
  border-radius: var(--radius-pill);
}
.ds .ds-meter > span {
  display: block;
  height: 100%;
  background: var(--primary);
  border-radius: inherit;
}

/* ---- mise en page du formulaire ------------------------------------- */
/* `minmax(0, 1fr)` et non `1fr` : le minimum implicite de `1fr` est la largeur
   INTRINSEQUE du contenu, et un champ a `width: 100%` la fait grandir — les
   colonnes refusent alors de retrecir et la grille deborde de sa carte. */
.ds .grid2 { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--space-4) var(--space-5); }
.ds .grid3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-4) var(--space-5); }
.ds .stack { display: flex; flex-direction: column; gap: var(--space-4); }
/* Empilement CENTRE : l identite d une tuile de personne — avatar, nom, poste.
   Un `text-center` seul ne suffit pas : l avatar est un element flex, le
   centrage du texte ne l atteint pas et il reste colle a gauche. `align-items`
   centre la boite, `text-align` centre les lignes. */
.ds .stack.center { align-items: center; text-align: center; }
/* Centrer, c est aussi laisser chaque enfant prendre la largeur de SON contenu, sans limite : un
   libelle long (nom de document dans une tuile) debordait de la carte au lieu de s abreger. Un
   enfant ne depasse jamais la pile ; son ellipse se declenche au bord (29/09/2026). */
.ds .stack.center > * { max-width: 100%; }
/* Empilement DENSE : une tuile qui empile sept lignes courtes — un titre, une
   note, une duree, une competence, une date — n a pas le meme souffle qu un
   formulaire. L ecart de base (16px) y ajouterait une centaine de pixels de
   hauteur pour rien. C est le MEME objet avec un autre ecart, pas un second.
   Meme convention de suffixe que `.grid2` / `.grid3` : un modificateur pose a
   cote de `.stack`. */
.ds .stack.tight { gap: var(--space-2); }
/* Empilement COUCHE : les memes elements, mais sur une ligne — une rangee de
   pastilles, un glyphe et sa date, un avatar et son nom. C est le pendant
   horizontal de `.stack`, et il porte le meme ecart par jeton.

   `flex-wrap` : une rangee de pastilles dans une tuile de 350px passe a la
   ligne plutot que de deborder. `min-width: 0` pour qu un enfant en `cut-name`
   puisse retrecir au lieu de pousser la ligne.

   Le nom vient de la paire `stack` / `hstack` : `.row` etait exclu, c est une
   classe de la grille Bootstrap, et `.line` existe deja dans `sitecss.css`
   (le greffon multiSelect). Une recette du systeme ne doit jamais tomber sur
   un nom deja pris ailleurs. */
.ds .hstack {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  min-width: 0;
}

/* AU BOUT DE LA LIGNE. Ce que `pull-right` et `float: right` faisaient avant, sans
   sortir l element du flux : dans une ligne flex, la marge automatique mange la
   place restante et pousse ce qui suit contre le bord. */
.ds .push-end {
  margin-left: auto;
}

/* UN GLYPHE NE SE LAISSE NI ECRASER NI COUPER. Dans une rangee, tout le monde
   retrecit a la meme enseigne : une fleche de 16px posee apres un nom long se
   faisait donc rogner, ou coller au texte faute de place. Elle garde sa taille,
   et c est le texte qui s abrege — c est lui qui peut se permettre de ne pas tout
   dire. */
.ds .hstack > i {
  flex: none;
}

/* UNE RANGEE QUI PEUT PORTER UN AVATAR EN GARDE LA HAUTEUR, meme quand elle n en
   montre pas. Sans cela, la meme ligne tombe 17px plus haut sur les tuiles ou il
   n y a personne — un avatar `xs` fait 35px, un tiret 18 — et les tuiles d une
   meme rangee cessent de s aligner entre elles. La grille les etire a la meme
   hauteur totale, mais leurs LIGNES, elles, se decalent.

   35px : la taille `xs` d un avatar, celle que sert `avatarService.getSizePx`. */
.ds .hstack.line-avatar {
  min-height: 35px;
}

/* ---- BANDE D ETAPES -----------------------------------------------------
   Ou en est un dossier qui passe par plusieurs mains : l etape EN COURS, nommee,
   puis celles qui restent, par leur numero, et le compte a droite. C est le motif
   deja en place sur les entretiens — on le pose ici pour qu il serve partout.

   L etape en cours n a pas de recette a elle : c est la pastille de statut de
   l ecran (`badge b-*`), posee en premier dans la bande. Le statut
   EST le nom de l etape, il n y a donc rien a dupliquer.

   Le fond en creux fait le groupe. Sans lui, une barre posee sous d autres lignes
   se lisait comme la jauge de la ligne du dessus — constate sur les demandes de
   recrutement, ou elle semblait mesurer l entite juridique.

   Ce qu elle remplace : un `<progress>` HTML5 dont la valeur valait toujours 100,
   surmonte d un `<div>` a `style="width: {{...}}%"` calcule dans le gabarit, plus
   un pourcentage arrondi a cote. */
.ds .steps {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  background: var(--surface-sunken);
  /* AUCUN `flex` ICI. La bande est une ligne dans une colonne : un `flex: 1`
     — reste de la barre qui partageait une ligne avec le statut — la faisait
     grandir jusqu au bas de la tuile, et le squelette sortait un pave gris de
     160px de haut. Sa hauteur est celle de son contenu, un point c est tout. */
  flex: none;
}

/* Une etape A VENIR : son rang, et rien d autre. Ronde et en creux — elle se
   distingue de la pastille nommee par sa forme autant que par sa couleur. */
.ds .steps .step {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text-muted);
  font: var(--font-meta);
}

/* Le rond D ATTENTE d une etape a la taille de l etape qu il annonce. Le
   squelette n emprunte PAS `.step` : cette pastille-la est blanche a filet, elle
   se lirait comme une etape deja affichee qui attend son numero. */
.ds .steps .ssc-circle {
  width: 22px;
  height: 22px;
}

/* Une etape FAITE : le meme rond, rempli de GRIS neutre. Elle se distingue de
   celles qui restent (blanches a filet) sans coche ni second objet, et le chemin
   parcouru se lit d un coup d oeil, de gauche a droite. Neutre A DESSEIN : la
   seule couleur de la bande est celle du STATUT de l etape en cours (orange en
   validation, bleu en recrutement) ; un rond teinte a cote la concurrencait. */
.ds .steps .step.done {
  background: var(--border);   /* le gris des filets : gray-200 en clair, il suit le theme sombre */
  border-color: transparent;
  color: var(--text-secondary);
}
/* Dans un empilement, l ecart vient du `gap` : les marges d origine d un titre
   s y AJOUTENT et cassent la regularite de la colonne. Un `h5` porte 1,67em de
   marge haute ET basse — sur une tuile, cela faisait un ecart nom/poste de
   30px la ou la recette en prevoit 16. */
.ds .stack > h1, .ds .stack > h2, .ds .stack > h3,
.ds .stack > h4, .ds .stack > h5, .ds .stack > h6 { margin: 0; }
.ds .span2 { grid-column: span 2; }
@media (max-width: 860px) {
  .ds .grid2, .ds .grid3 { grid-template-columns: minmax(0, 1fr); }
  .ds .span2 { grid-column: auto; }
}

/* ---- bloc repetable (une ligne + / -) ------------------------------- */
.ds .repeat {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
/* `align-items: start` et non `end`. La reference aligne sur le bas, ce qui
   marche pour ses colonnes de meme hauteur ; ici la colonne Type porte un
   message d erreur conditionnel, donc elle grandit — et l alignement par le
   bas remontait le libelle de la premiere colonne jusqu a chevaucher la
   legende du bloc. Par le haut, tous les libelles s alignent.
   La colonne d actions se recale sur la ligne de saisie avec margin-top. */
.ds .repeat-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
  gap: var(--space-3);
  align-items: start;
}
/* Pousse les boutons + / - au niveau du champ, sous la ligne des libelles. */
.ds .repeat-grid .repeat-actions { margin-top: auto; }

/* Variantes a TROIS et QUATRE colonnes de contenu. `.repeat-grid` seule en pose
   deux, ce qui couvre la ligne « type + valeur » de la reference. Une ligne de
   financement en demande davantage — budget, montant, montant par membre, reste
   sur l enveloppe — plus sa colonne d actions.
   Le suffixe numerique suit la convention deja en place pour `.grid2` /
   `.grid3` : c est un MODIFICATEUR de la recette existante, a poser A COTE de
   `.repeat-grid`, pas un second objet. Seules les colonnes changent ; l ecart,
   l alignement `start` et le repli en une colonne restent ceux de la recette de
   base — d ou la position de ce bloc AVANT le `@media`, qui les ramene toutes a
   une seule colonne sous 860px. */
.ds .repeat-grid3 { grid-template-columns: repeat(3, minmax(0, 1fr)) auto; }
.ds .repeat-grid4 { grid-template-columns: repeat(4, minmax(0, 1fr)) auto; }
/* DEUX actions sur une ligne, et pas sur toutes les lignes.
   Le `ng-repeat` porte sur `.repeat-grid` : chaque ligne est donc SA PROPRE
   grille, et une colonne `auto` se dimensionne ligne par ligne. Des que la
   derniere ligne porte une action de plus (le `+` en `ng-if="$last"`), sa
   colonne d actions s elargit et decale ses deux colonnes de contenu — mesure
   sur `diplomaCollaboratorModal` : 174,6px de large sur les lignes a un bouton,
   ~44px de moins sur celle a deux. La grille Bootstrap d avant, elle, tenait
   des pourcentages fixes et restait alignee.
   On reserve donc la place des deux boutons sur TOUTES les lignes. La valeur
   sort des jetons — deux controles et leur ecart — et non d un nombre choisi a
   l oeil. */
.ds .repeat-grid.actions-2 { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) calc(var(--control-h) * 2 + var(--space-2)); }
/* Variante a UNE colonne de contenu, meme convention de suffixe. Une ligne de
   piece jointe sans colonne « type » n a qu un champ et ses actions : avec la
   recette de base, l unique enfant de contenu prend la premiere `1fr` et la
   colonne d actions atterrit dans la SECONDE, donc une colonne de boutons large
   d une demi-ligne. Vu sur `Survey/outOfCatalogRequest`. */
.ds .repeat-grid1 { grid-template-columns: minmax(0, 1fr) auto; }
/* Le poids supplementaire (`.repeat-grid` ET `.repeat-grid1`) est VOULU : sans
   lui, cette regle et `.ds .repeat-grid.actions-2` pesent pareil et seul l ordre
   du fichier les departagerait. */
.ds .repeat-grid.repeat-grid1.actions-2 { grid-template-columns: minmax(0, 1fr) calc(var(--control-h) * 2 + var(--space-2)); }
/* MEME COMBINAISON POUR TROIS COLONNES. Le piege est identique et il manquait :
   `.ds .repeat-grid.actions-2` pese (0,3,0), `.ds .repeat-grid3` (0,2,0) — la
   premiere gagnait, et une ligne a TROIS colonnes de contenu en sortait avec
   DEUX. Mesure sur `addTrainingToEnvelopeCompanyEntityModal`, ligne de
   financement portant Organisme, Budget et Reste : 346,5px / 346,5px / 84px
   pour quatre enfants — le troisieme champ passait a la ligne.
   Constate en posant la recette, pas en la relisant. */
.ds .repeat-grid.repeat-grid3.actions-2 { grid-template-columns: repeat(3, minmax(0, 1fr)) calc(var(--control-h) * 2 + var(--space-2)); }
.ds .repeat-grid.repeat-grid4.actions-2 { grid-template-columns: repeat(4, minmax(0, 1fr)) calc(var(--control-h) * 2 + var(--space-2)); }

@media (max-width: 860px) {
  .ds .repeat-grid,
  .ds .repeat-grid1,
  .ds .repeat-grid3,
  .ds .repeat-grid4 { grid-template-columns: 1fr; }
}
/* La colonne d actions d une ligne repetable (+ / -). Ce n est PAS `.affix` :
   `.affix` est fait pour un controle et son adjoint, et ses enfants y sont
   compressibles — deux boutons de 40px s y ecrasaient a 16px et debordaient
   de la carte. Ici les boutons ne se compriment pas. */
.ds .repeat-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: none;
}

.ds .fs-legend {
  font: var(--weight-bold) var(--text-xs)/1 var(--font-sans);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* Une legende de bloc qui porte une ACTION A SA DROITE — un chevron de repli,
   une action de groupe. Le portail posait ca en `float: left` sur le titre et
   `pull-right` sur l action : deux flottants, donc une barre qui ne se ferme
   pas sur son contenu et un titre remonte de 7px par un `margin` negatif en
   `!important`. Une ligne flex fait la meme chose sans hack, et la legende
   prend la place libre pour que l action reste collee a droite.

   `margin: 0` est porte ICI et non sur `.fs-legend` : la legende est parfois
   un `<h5>`, qui traine les marges de titre de Bootstrap (10px haut et bas).
   Les rangees deja en place ailleurs (`<div>`, `<p>`) ne sont pas touchees. */
.ds .fs-legend-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.ds .fs-legend-row > .fs-legend { flex: 1 1 auto; margin: 0; }
/* `is-band` : la legende en bandeau gris sur toute la largeur, quand elle ouvre un GROUPE de blocs
   (les familles d une question de competences) : on voit d un coup d oeil ou chaque groupe commence.
   Elle passe a la ligne sur ecran etroit (le complement sous le titre). */
.ds .fs-legend-row.is-band {
  flex-wrap: wrap;
  padding: var(--space-2) var(--space-3);
  background: var(--surface-sunken);
  border-radius: var(--radius-md);
}
/* Une jauge dans le bandeau : son rail (gris, comme le bandeau) passe au fond de carte pour rester visible. */
.ds .fs-legend-row.is-band .ds-meter { background: var(--surface); }

/* ---- interrupteur -----------------------------------------------------
   Un reglage qui s applique immediatement, la ou une case a cocher attend une
   validation. La pastille est en --gray-0 et non `#fff` : elle doit rester
   claire sur le rail sombre du mode nuit. */
/* `ds-switch` et non `.switch` : `.switch` existe deja dans `sitecss.css` (l ancien
   plugin d interrupteur), qui lui impose un fond gris, une largeur et une hauteur en
   !important. Renommee le 14/10/2026 plutot que combattue : aucun !important ici. */
.ds .ds-switch {
  position: relative;
  display: inline-block;
  width: 38px;
  height: 22px;
  flex: none;
}
.ds .ds-switch input { opacity: 0; width: 0; height: 0; }
.ds .ds-switch .tr {
  position: absolute;
  inset: 0;
  background: var(--gray-300);
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: background var(--duration-base) var(--ease-standard);
}
.ds .ds-switch .tr::before {
  content: "";
  position: absolute;
  width: 16px;
  height: 16px;
  left: 3px;
  top: 3px;
  background: var(--gray-0);
  border-radius: 50%;
  box-shadow: var(--shadow-sm);
  transition: transform var(--duration-base) var(--ease-standard);
}
/* En sombre, `--gray-300` est trop clair pour un rail au repos : il se lit
   comme un interrupteur allume. Le specimen le ramene a --border-strong. */
[data-theme="dark"] .ds .ds-switch .tr,
.ds [data-theme="dark"] .ds-switch .tr { background: var(--border-strong); }
.ds .ds-switch input:checked + .tr { background: var(--primary); }
.ds .ds-switch input:checked + .tr::before { transform: translateX(16px); }
.ds .ds-switch input:focus-visible + .tr { box-shadow: var(--ring); }

/* ---- controle segmente ------------------------------------------------
   Deux ou trois options courtes et exclusives. Rail en creux, option active
   SURELEVEE : le fond remonte a --surface avec une ombre xs.

   PORTEE : comme les onglets, deux formes de selecteur. `.ds.seg` quand les
   deux classes sont sur le meme noeud -- c'est le cas de `hmViewSwitch.html`,
   pose dans l'en-tete d'une grille dont le contenu n'est PAS migre : y
   declarer `.ds` sur un ancetre ferait mordre toutes les recettes du design
   system sur la liste. `.ds .seg` quand un ancetre porte deja la portee. */
.ds .seg,
.ds.seg {
  display: inline-flex;
  align-self: flex-start;
  gap: 2px;
  padding: 3px;
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
.ds .seg button,
.ds.seg button {
  min-height: var(--control-h-sm);
  padding: 8px 14px;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-secondary);
  font: var(--weight-medium) var(--text-sm)/1 var(--font-sans);
  cursor: pointer;
  white-space: nowrap;
}
.ds .seg button.on,
.ds.seg button.on {
  background: var(--surface);
  color: var(--text-link);
  box-shadow: var(--shadow-xs);
}
.ds .seg button:focus-visible,
.ds.seg button:focus-visible { outline: none; box-shadow: var(--ring); }
/* Une option indisponible (la fleche d une pagination sans page ou aller) : grisee, sans curseur de clic. */
.ds .seg button:disabled,
.ds.seg button:disabled { color: var(--text-disabled); cursor: default; }

/* Variante icone : les options ne sont pas des libelles mais des formes du
   meme contenu (cartes, liste, graphique). Le bouton devient carre, l'icone
   porte le sens et le libelle passe en `title` / `aria-label` -- sans quoi le
   controle n'a aucun nom accessible. Gabarit repris de l'etude du design
   system (`audit/before-after-recrutement.html`, `.a-viewsw` : 34 x 30). */
.ds .seg.icons button,
.ds.seg.icons button {
  min-width: 34px;
  min-height: 30px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.ds .seg.icons button i,
.ds.seg.icons button i { font-size: var(--text-md); }

/* ---- curseur ---------------------------------------------------------- */
.ds .range { width: 100%; height: var(--control-h-sm); accent-color: var(--primary); }
.ds .range-row { display: flex; align-items: center; gap: var(--space-3); }
.ds .range-row .val {
  min-width: 44px;
  text-align: right;
  font: var(--weight-medium) var(--text-sm) var(--font-sans);
  font-variant-numeric: tabular-nums;
}

/* ---- jetons (multi-selection) -----------------------------------------
   Le conteneur EST le champ : il en reprend le fond, la bordure et la hauteur
   minimale, puis grandit avec les jetons. */
.ds .tokens {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--control-h);
  padding: 4px 8px;
  background-color: var(--field-bg);
  border: 1px solid var(--field-border);
  border-radius: var(--radius-input);
}
.ds .token {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 6px 5px 10px;
  background: var(--primary-soft);
  color: var(--primary-700);
  border: 1px solid var(--primary-border);
  border-radius: var(--radius-pill);
  font: var(--font-label);
}
.ds .token .x { display: inline-flex; opacity: .7; cursor: pointer; }
.ds .token .x:hover { opacity: 1; }
/* La saisie fantome : elle prend la place restante dans le conteneur, sans
   redessiner un champ dans le champ. */
.ds .tokens .ghost {
  flex: 1;
  min-width: 60px;
  border: none;
  background: none;
  outline: none;
  font: var(--font-body-sm);
  color: var(--text-primary);
}
.ds .tokens .ghost::placeholder { color: var(--field-placeholder); }
/* La saisie fantome n a pas de cadre : c est le conteneur qui prend le focus d un champ. */
.ds .tokens:focus-within {
  border-color: var(--primary);
  box-shadow: var(--ring);
}

/* ---- recherche + autocompletion --------------------------------------- */
.ds .ac { position: relative; }
/* Le « Plus » d une barre d onglets quand rien ne deborde (hm-tabset) : toujours rendu pour que sa
   largeur se mesure, mais invisible, hors du flux et hors du clavier (`visibility: hidden`). Pas de
   `display: none` : la largeur mesuree serait 0. */
.ds .tabs-more.is-idle {
  position: absolute;
  top: 0;
  left: 0;
  visibility: hidden;
  pointer-events: none;
}
/* LE MENU D AUTOCOMPLETION DU PORTAIL SE BRANCHE ICI. La reference decrit ce
   composant en propre (§03, « Recherche + autocompletion ») : c est `.ac` /
   `.ac-menu` / `.ac-item`. Le portail le rend par la directive
   `hm-auto-complete` (`Views/Templates/hmAutoComplete.html`), avec ses propres
   noms de classes et des couleurs EN DUR dans `sitecss.css` — #ececec pour la
   bordure, #FFF pour le fond, #333 pour le texte, #f5f5f5 au survol, #428bca
   sur la ligne retenue, #137aec sur « ajouter » — donc insensibles au theme
   comme a la couleur du client.

   On ne cree pas une seconde recette : on branche ces noms sur celle qui
   existe deja. Un champ de type `PickList` du self-service (« Lien de
   parente ») et un champ `Enum` rendu en liste deroulante se lisent alors
   comme deux membres de la meme famille.

   Poids : `.ds .hm-auto-complete-dropdown` vaut (0,2,0) contre (0,1,0) au
   legacy ; `.ds .hm-auto-complete-dropdown > div` (0,3,0) contre (0,2,0) ; la
   ligne retenue (0,4,0) contre (0,3,0). Aucun !important : le
   `*, :after, :before { color: #000 !important }` de `sitecss.css` est dans sa
   feuille d IMPRESSION, il ne s applique pas a l ecran. */
.ds .ac-menu,
.ds .hm-auto-complete-dropdown {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: 5;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--popover-shadow);
  /* Le legacy cale son panneau par `margin-top: -6px` SANS `top` : combine au
     `top: calc(100% + 6px)` ci-dessus, le panneau revenait coller le champ. */
  margin-top: 0;
}
.ds .ac-item,
.ds .hm-auto-complete-dropdown > div {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  /* Le legacy pose `padding: 5px`, `margin-bottom: 4px` et `color: #333` sur
     chaque ligne : le retrait vient de la recette, l ecart entre lignes du
     `gap` du panneau, et la couleur du jeton de texte. */
  margin-bottom: 0;
  color: var(--text-primary);
}
/* UNE LIGNE DE MENU QUI DECLENCHE UNE ACTION s ecrit en `<button>` : elle se
   prend au clavier, et un lecteur d ecran l annonce comme une action. On rend au
   bouton natif ce que la ligne n a pas — ni cadre, ni fond, ni centrage, ni
   police propre. Pose AVANT le survol : a poids egal, le fond du survol doit
   passer apres. */
.ds button.ac-item {
  width: 100%;
  border: 0;
  background: none;
  text-align: left;
  font: var(--font-body-sm);
}
.ds .ac-item:hover,
.ds .ac-item.active,
.ds .hm-auto-complete-dropdown > div:hover,
.ds .hm-auto-complete-dropdown > div.hm-auto-complete-selected-row {
  /* La reference ecrit `--surface-hover` pour CE panneau — contrairement au
     panneau du `cs-select`, ou la bande pleine est une decision produit prise
     faute de reference (voir plus bas). Ici il y a une reference, elle prime. */
  background: var(--surface-hover);
  color: var(--text-primary);
}
.ds button.ac-item:focus-visible { outline: none; box-shadow: var(--ring); }

/* MENU ACCROCHE A UN BOUTON. Sous un champ, le panneau prend la largeur du
   champ (`left: 0; right: 0`). Sous un bouton, cette largeur est celle du
   bouton — trop etroite pour un libelle. `at-end` aligne le panneau sur le
   bord de FIN du bouton (celui qui est pres du bord de l ecran) et lui donne
   une largeur de lecture. */
.ds .ac-menu.at-end {
  left: auto;
  right: 0;
  min-width: 16.5rem;
  /* jamais plus large que la fenetre ; hmMenu.js le recale ensuite entre ses bords */
  max-width: calc(100vw - 24px);
}
/* S ouvre AU-DESSUS de son declencheur : quand celui-ci est en bas de ce qui le
   contient (pied de tuile). */
.ds .ac-menu.is-up {
  top: auto;
  bottom: calc(100% + 6px);
}
/* Une ligne de menu qui MENE quelque part s ecrit en <a>. `ds-bridge.css` peint
   tout `a` en `--primary` avec !important : la ligne reprend sa couleur au meme
   poids. Desactivee : grisee, et elle ne repond plus. */
.ds a.ac-item,
.ds a.ac-item:hover,
.ds a.ac-item:focus { color: var(--text-primary) !important; text-decoration: none; }
.ds a.ac-item:focus-visible { outline: none; box-shadow: var(--ring); }
.ds .ac-item[disabled] {
  color: var(--text-disabled) !important;
  pointer-events: none;
}
/* Le glyphe d une ligne de menu d actions : discret, a taille fixe, pour que les
   libelles s alignent en colonne. */
.ds .ac-glyph {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--icon-md, 16px);
  height: var(--icon-md, 16px);
  color: var(--text-muted);
}
.ds .ac-glyph > i,
.ds .ac-glyph > svg { width: 100%; height: 100%; }

/* Pastille de glyphe d une ligne de menu : l icone en tete d une action. En ton
   de marque, contrairement a `.card-icon` : ici la ligne EST une action. */
.ds .ac-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 30px;
  height: 30px;
  border-radius: var(--radius-sm);
  background: var(--primary-soft);
  color: var(--primary-700);
  font-size: var(--text-base);
  line-height: 1;
}
/* ---- ui-select : LA MULTI-SELECTION DU PORTAIL SE BRANCHE ICI -----------
   `ui-select` (`Content/bower_components/angular-ui-select`, 0.17.1, toujours
   en `theme="bootstrap"`) rend les selections multiples de 12 gabarits :
   langues, groupes, entites juridiques, metiers, types de document, libelles…
   La reference en decrit DEUX composants, et ce plugin les rend tous les deux
   d un bloc (§03) :

     le declencheur  ->  `.tokens` > `.token` (+ `.x`) + `.ghost`
     le panneau      ->  `.ac-menu` > `.ac-item`

   On ne cree donc pas une troisieme recette : on branche les noms du plugin
   sur celles qui existent, comme pour `hm-auto-complete` juste au-dessus et
   pour `cs-select` plus bas.

   POURQUOI IL N Y AVAIT RIEN A REPRENDRE. `select.css`, la feuille du plugin,
   n est PAS dans `bundleconfig.json` — elle n a donc jamais ete servie. Tout ce
   que le composant affichait venait de Bootstrap et de regles eparses du
   portail, d ou les defauts mesures sur banc :

     conteneur     `height: 38px` fige par `.ds .form-control`, alors que les
                   jetons qu il contient font 48px : ils debordaient
     jetons        rendus en BOUTONS (`btn btn-default btn-xs`) : fond blanc,
                   rayon 8px, bordure #eee, 48px de haut
     croix         masquee par `.swMain .close { display: none }` (styles.css)
                   des que le composant vit dans un assistant `hm-wizard` —
                   donc IMPOSSIBLE de retirer une valeur sur cet ecran
     recherche     une SECONDE boite dans le champ : bordure, rayon 2px, 40px
                   de haut, 190px de large (`max-width` de hrMaps.css)
     panneau       160px de large au lieu de la largeur du champ, ombre `#ccc`
                   en dur, AUCUNE limite de hauteur — 8 langues donnaient deja
                   298px, et ca grandissait sans fin
     options       des `<a>` nus : `display: inline`, aucun retrait, et la
                   couleur de lien du portail
     option active fond `--primary` (ds-bridge) SOUS un texte lui aussi repeint
                   en `--primary` : la ligne survolee etait ILLISIBLE

   Les poids a battre sont tous dans `sitecss.css`, et ils sont hauts parce que
   le portail a bricole ce plugin a cinq endroits (`css-client.scss`,
   `hrMaps.css`, `styles.css`, `styleScss.scss`, `CustomHrMaps.css`) :

     `.v2 .ui-select-multiple.ui-select-bootstrap .ui-select-match-item`    (0,4,0) !
     `.v2 .ui-select-bootstrap .ui-select-choices-row > a:hover`            (0,4,1) !
     `.ds .modal-body a:not(.btn)`  (ds-bridge.overrides)                   (0,4,1) !
     `.ui-select-multiple.ui-select-bootstrap input.ui-select-search`       (0,3,1) !
     `.ui-select-multiple.ui-select-bootstrap .ui-select-match-item .close` (0,4,0) !

   D ou la repetition de classe — deja employee dans `ds-app-shell.css` : elle
   ne change pas le sens du selecteur, seulement son poids. Chaque repetition
   porte en commentaire la regle contre laquelle elle lutte.

   `.ds` en tete : les ecrans non encore migres gardent leur rendu actuel.

   A DEDUPLIQUER PLUS TARD : `styleScss.scss` porte deja cette meme recette,
   scopee `.hm-mcc-window` (fenetre de communication). Ses selecteurs sont plus
   specifiques, elle garde donc la main sur cet ecran-la ; les valeurs sont les
   memes, il n y a pas d ecart visible. Le retrait du bloc legacy est une passe
   a part, sur un ecran a re-verifier. */

/* LE DECLENCHEUR EST LE CHAMP. Il porte deja `form-control`, donc le fond, la
   bordure et le rayon de la recette de champ. On lui retire seulement sa
   hauteur FIXE et son padding, que `.ds .form-control` pose en !important :
   un conteneur de jetons doit grandir avec eux. (0,3,0) contre (0,2,0). */
.ds .ui-select-container.ui-select-multiple {
  display: block;
  height: auto !important;
  min-height: var(--control-h) !important;
  padding: 0 !important;
  line-height: normal;
  box-shadow: none !important;
}
/* LE FOCUS SE PORTE SUR LE CHAMP ENTIER, pas sur la saisie fantome — cette
   derniere n a plus ni bordure ni anneau (voir plus bas). Sans cette regle, le
   champ ne montrait plus AUCUN etat actif : la recette de champ pose son focus
   sur l element focalise, or ici c est un <input> devenu invisible.
   `:focus-within` compte comme une pseudo-classe, donc (0,4,0) contre (0,2,0)
   a `.ds .form-control` — les deux etant en !important. */
.ds .ui-select-container.ui-select-multiple:focus-within {
  border-color: var(--primary) !important;
  box-shadow: var(--ring) !important;
}
/* Le gabarit du plugin emballe les jetons ET la recherche dans un `<div>` SANS
   classe. C est donc lui, et pas le conteneur, qui porte la mise en ligne et la
   gouttiere interieure. Le padding revient ici : le laisser sur le conteneur
   decalerait le panneau flottant, qui se cale sur la boite de PADDING. */
.ds .ui-select-multiple > div:first-child {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  min-height: calc(var(--control-h) - 2px);
  /* 3px et non 4 : le jeton mesure 29px, le champ doit rester a --control-h
     tant que les jetons tiennent sur UNE ligne (29 + 6 = 35 < 36, donc
     min-height gouverne). Avec 4px il montait a 39 et se desalignait du champ
     pose a cote. */
  padding: 3px 11px;
}
/* Trois emballages separent le conteneur de ses jetons : `div.ui-select-match >
   span.ui-select-match > span (ng-repeat) > span.ui-select-match-item`.
   `display: contents` les efface de la mise en page, et les jetons deviennent
   les elements flex du `<div>` ci-dessus — donc ils s enroulent avec la
   recherche dans UN SEUL flux, comme les `.token` de `.tokens`.
   `.ui-select-match > span` attrape les deux niveaux interieurs a la fois ; le
   jeton lui-meme, petit-fils, n est pas touche. */
.ds .ui-select-multiple .ui-select-match,
.ds .ui-select-multiple .ui-select-match > span { display: contents; }

/* LE JETON — les valeurs de `.ds .token`, a l identique.
   Poids : `.v2 .ui-select-multiple.ui-select-bootstrap .ui-select-match-item`
   (css-client.scss) vaut (0,4,0) et pose fond, couleur et bordure en
   !important ; `.ds .btn` (plus haut dans ce fichier) lui donne en plus
   `min-height: var(--control-h)`. La classe repetee monte a (0,5,0). */
.ds .ui-select-multiple.ui-select-bootstrap .ui-select-match-item.ui-select-match-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0 !important;
  padding: 4px 10px !important;
  min-height: 0;
  height: auto;
  background: var(--primary-soft) !important;
  color: var(--primary-700) !important;
  border: 1px solid var(--primary-border) !important;
  border-radius: var(--radius-pill) !important;
  font: var(--font-label);
  white-space: normal;
}
/* LA CROIX. Deux regles legacy a defaire, et la premiere est un DEFAUT DE
   FONCTIONNEMENT, pas de style :

   1. `.swMain .close { display: none }` (styles.css, (0,2,0)) masque toutes les
      croix a l interieur d un assistant — or `Views/Templates/hmWizard.html`
      pose `swMain` sur CHAQUE corps d assistant. Sur la creation d une demande
      de recrutement, une langue choisie ne pouvait donc plus etre retiree.
   2. `.close` de Bootstrap la pose en `float: right` (ignore dans un conteneur
      flex, elle remontait donc a GAUCHE du libelle), a 21px, opacite .2, noir
      en dur, avec une ombre de texte blanche.

   `order: 2` la remet a droite du libelle, ou la reference la dessine — c est
   le seul deplacement visuel de cette passe, et il porte sur un glyphe interne
   au jeton, pas sur un champ. Poids (0,5,0) contre (0,4,0) pour le
   `line-height: 28px !important` de styleScss.scss. */
.ds .ui-select-multiple .ui-select-match-item .ui-select-match-close.ui-select-match-close {
  display: inline-flex;
  order: 2;
  float: none;
  margin: 0;
  opacity: .7;
  color: inherit !important;
  text-shadow: none;
  font: var(--weight-regular) 15px/1 var(--font-sans) !important;
  cursor: pointer;
}
.ds .ui-select-multiple .ui-select-match-item .ui-select-match-close.ui-select-match-close:hover { opacity: 1; }

/* LA RECHERCHE EST UNE SAISIE FANTOME (`.tokens .ghost`) : elle prend la place
   restante dans le champ, sans redessiner un champ dans le champ.

   `--field-bg` et `--field-border` sont REDEFINIS SUR L ELEMENT plutot que
   combattus : `ds-bridge.overrides.css` les lit depuis un selecteur a six
   `:not()`, soit (0,12,1) en !important, qu aucune repetition raisonnable ne
   bat. Une custom property posee sur l element l emporte toujours sur celle
   heritee de `:root`, quelle que soit la specificite du selecteur qui la
   consomme. Le reste (hauteur, padding, largeur, ombre) vient de regles a
   (0,3,1) au plus, que (0,4,1) suffit a battre. */
.ds .ui-select-multiple.ui-select-bootstrap input.ui-select-search {
  --field-bg: transparent;
  --field-border: transparent;
  --ring: none;
  flex: 1 1 90px;
  min-width: 90px;
  width: auto !important;
  max-width: none !important;
  height: auto !important;
  min-height: 0 !important;
  margin: 0 !important;
  padding: 0 !important;
  border: 0 !important;
  outline: 0 !important;
  box-shadow: none !important;
  border-radius: 0 !important;
  font: var(--font-body-sm);
  color: var(--text-primary) !important;
}
.ds .ui-select-multiple.ui-select-bootstrap input.ui-select-search::placeholder {
  color: var(--field-placeholder);
  opacity: 1;
}


.ds .ui-select-bootstrap > .ui-select-match > .btn.ui-select-toggle {
  justify-content: flex-start;
  border-color: var(--field-border) !important;
}
.ds .ui-select-bootstrap > .ui-select-match > .btn.ui-select-toggle > .caret { margin-left: auto; }

.ds .ui-select-bootstrap > .ui-select-match.btn-default-focus {
  outline: none;
  box-shadow: none;
}
.ds .ui-select-bootstrap > .ui-select-match.btn-default-focus > .btn.ui-select-toggle {
  border-color: var(--primary) !important;
  box-shadow: var(--ring) !important;
}

/* LE PANNEAU — les valeurs de `.ac-menu`, a l identique.
   Poids : `.v2 .dropdown-menu` (0,2,0) pose fond et ombre, `.dropdown-menu`
   (0,1,0) pose `border-radius: 4px !important` ; la classe repetee donne
   (0,4,0) et le !important ne sert qu au rayon. */
.ds .ui-select-bootstrap .ui-select-choices.ui-select-choices,
.ds .ui-select-bootstrap .ui-select-no-choice.ui-select-no-choice {
  /* `top` est pose EN LIGNE par le plugin quand il ouvre vers le haut
     (`top: -<hauteur>px`, cf. `setDropdownPosUp` de select.js) : on n y touche
     pas, et on prend l ecart au champ par la marge, dans les deux sens. */
  left: -1px;
  right: -1px;
  width: auto;
  min-width: 0;
  /* 230px : la valeur que le portail donne deja a un panneau flottant de choix
     (`.cs-skin-slide .cs-options`, styles.css). Sans elle le panneau n avait
     AUCUNE borne et couvrait la fenetre des la huitieme option. */
  max-height: 230px;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 6px;
  margin: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md) !important;
  box-shadow: var(--popover-shadow);
}
.ds .ui-select-bootstrap .ui-select-choices.ui-select-choices:not(:has(.ui-select-choices-row)) { display: none !important; }

.ds .ui-select-bootstrap .ui-select-no-choice.ui-select-no-choice:empty { display: none !important; }
.ds .ui-select-container:not(.direction-up) > .ui-select-choices.ui-select-choices,
.ds .ui-select-container:not(.direction-up) > .ui-select-no-choice.ui-select-no-choice { margin-top: 6px; }
.ds .ui-select-container.direction-up > .ui-select-choices.ui-select-choices,
.ds .ui-select-container.direction-up > .ui-select-no-choice.ui-select-no-choice { margin-top: -6px; }
.ds .ui-select-bootstrap .ui-select-choices-group { list-style: none; margin: 0; padding: 0; }

/* L OPTION — les valeurs de `.ac-item`, a l identique. Le gabarit du plugin la
   rend en `<a href="">`, donc elle heritait de la couleur de lien : rouge, ou
   la couleur du client. `display: flex` remplace le `display: inline` d un lien
   nu, qui empechait tout retrait de s appliquer.

   Poids : `.v2 .ui-select-bootstrap .ui-select-choices-row > a` (css-client,
   (0,3,1) !) et surtout `.ds .modal-body a:not(.btn)` (ds-bridge.overrides,
   (0,4,1) !) sur la couleur. La classe repetee donne (0,5,1). */
.ds .ui-select-bootstrap .ui-select-choices-row.ui-select-choices-row > a.ui-select-choices-row-inner {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  margin: 0;
  border-radius: var(--radius-sm);
  font: var(--font-body-sm);
  color: var(--text-primary) !important;
  text-decoration: none;
  white-space: normal;
  cursor: pointer;
}
/* Le curseur — souris ET clavier — prend `--surface-hover`, la valeur que la
   reference ecrit pour CE panneau (`.ac-item:hover`, `.ac-item.active`), a la
   difference du panneau du `cs-select` ou la bande pleine est une decision
   prise faute de reference.

   Ce que ca corrige : `ds-bridge.css` peignait `.ui-select-choices-row.active >
   a` en `background-color: var(--primary) !important` pendant que le texte du
   lien etait lui aussi repeint en `--primary` — la ligne sous le curseur etait
   donc ILLISIBLE, mesure rgb(237,67,55) sur rgb(237,67,55).
   Poids (0,6,1) contre (0,4,1) au plus haut des regles legacy. */
.ds .ui-select-bootstrap .ui-select-choices-row.ui-select-choices-row > a.ui-select-choices-row-inner:hover,
.ds .ui-select-bootstrap .ui-select-choices-row.ui-select-choices-row.active > a.ui-select-choices-row-inner {
  background: var(--surface-hover) !important;
  color: var(--text-primary) !important;
}
.ds .ui-select-bootstrap .ui-select-choices-row.ui-select-choices-row.disabled > a.ui-select-choices-row-inner,
.ds .ui-select-bootstrap .ui-select-choices-row.ui-select-choices-row.disabled.active > a.ui-select-choices-row-inner {
  background: transparent !important;
  color: var(--text-disabled) !important;
  cursor: not-allowed;
}
/* « Aucun resultat » : une ligne du panneau, pas un bloc a part. */
.ds .ui-select-bootstrap .ui-select-no-choice.ui-select-no-choice > li {
  list-style: none;
  padding: 8px 10px;
  font: var(--font-body-sm);
  color: var(--text-muted);
}

/* ---- LE MENU « … » DES ACTIONS : MEME OBJET, MEME RECETTE ---------------
   Un panneau flottant de choix, c est `.ac-menu` / `.ac-item` — deja branche
   juste au-dessus pour l autocompletion, et plus bas pour le panneau du
   cs-select. Le menu des actions masquees d une liste est le MEME objet : le
   « … » d une ligne de tableau, le « ⋮ » d une tuile et le « ⋯ » d une carte
   d accueil sortent tous de `hm-buttons` ou de `hmWidgets`, et ils n ont aucune
   raison de se dessiner autrement que le reste des panneaux du portail.

   CE QU IL PORTAIT A LA PLACE, et qui ne suivait ni le theme ni le client :
     `.visible-menu`       `background: #FFF` et une ombre `1px 1px 5px
                           rgba(0,0,0,.3)`, tous deux en dur
     `.action-pub-rse`     `padding: 2% 0` — un POURCENTAGE sur un panneau
                           flottant, donc un retrait qui change avec la largeur
                           de ce qui l entoure
     `.style-icon-scroll`  `color: #333`, aucun rayon, aucun survol

   POIDS : la regle legacy du panneau est `.dropdown.current-user.open
   .visible-menu`, soit (0,4,0) ; il en faut donc autant ici, et c est l ordre de
   chargement qui departage — cette feuille vient apres `sitecss.css`. Le
   `!important` du retrait, lui, repond a celui de `.dropdownMenuCard`. */
.ds .list-evaluator .dropdown .dropdown-menu,
.ds .list-evaluator .dropdown.open .dropdown-menu {
  padding: 6px !important;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--popover-shadow);
  /* Le defilement du panneau, quand il y a vraiment trop d actions : celui du
     navigateur, pas une barre dessinee. `.visible-menu` plafonne deja la hauteur
     a 200px ; sans `overflow`, ce plafond rognait le menu sans permettre d y
     acceder. `contain` retient la molette dans le panneau au lieu de la laisser
     filer vers la page — c est ce que `wheel-propagation` cherchait a faire. */
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* Chaque action est une ligne de choix : le retrait, le rayon et le survol de
   `.ac-item`. L icone ne porte plus sa colonne de 30px ni son retrait a droite —
   c est le `gap` qui la separe de son libelle, comme dans tous les autres
   panneaux. */
.ds .list-evaluator .dropdown-menu .style-icon-scroll {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  color: var(--text-primary);
}

.ds .list-evaluator .dropdown-menu .style-icon-scroll:hover,
.ds .list-evaluator .dropdown-menu .style-icon-scroll:focus {
  background: var(--surface-hover);
  color: var(--text-primary);
}

.ds .list-evaluator .dropdown-menu .icon-list-display {
  flex: none;
  width: auto;
  padding: 0;
}

/* `.nm` (le nom) et `.ps` (la ligne secondaire) etaient cablees SOUS
   `.ac-item` seulement, donc inutilisables ailleurs. Ce sont deux roles
   generaux — un intitule et sa precision en retrait — et pas une specificite
   du menu d autocompletion : la liste de selecteurs s ouvre a `.opt`, pour que
   toute ligne de choix portant un nom et une description reutilise la MEME
   recette au lieu d en recreer une. */
.ds .hm-auto-complete-title,
.ds .ac-item .nm,
.ds .opt .nm { font: var(--font-body-sm); font-weight: var(--weight-medium); }
.ds .hm-auto-complete-description,
.ds .ac-item .ps,
.ds .opt .ps { font: var(--font-meta); color: var(--text-muted); }

/* L ICONE DE RECHERCHE. La reference met une loupe en tete du champ
   d autocompletion (§03 : `<span class="input-icon"><i search></i>`). Le gabarit
   de la directive rend un `<input>` nu, sans conteneur ni place pour une icone.
   Meme technique que pour le calendrier des champs de date : le glyphe passe en
   `mask` sur un pseudo-element de l HOTE, qui porte deja `position: relative`
   depuis `sitecss.css`. La couleur vient de `background-color`, donc elle suit le
   jeton et le theme — un SVG pose en `background-image` aurait sa couleur figee.

   `> input` et non `input` : un autocompletion en mode JETONS
   (`hm-auto-complete-tokens`) porte d autres champs, qu on ne decale pas.

   Le `padding-left` reserve la place, comme `.input-icon .input` du meme
   fichier : 35px, la valeur de la reference.
   Pas dans la selection MULTIPLE (`> .tokens`, hmAutoCompleteMultiple.html) : elle partage la classe
   de l hote, et la loupe, centree sur la hauteur, tombait sur le premier jeton de la 2e ligne. */
.ds .hm-auto-complete-holder:not(:has(> .tokens, > .hm-auto-complete-tokens))::before {
  content: "";
  position: absolute;
  left: 11px;
  top: 50%;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  background-color: var(--text-muted);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='16' height='16' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.35-4.35'/%3E%3C/svg%3E") no-repeat center / 16px 16px;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='16' height='16' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.35-4.35'/%3E%3C/svg%3E") no-repeat center / 16px 16px;
  pointer-events: none;
  z-index: 1;
}
.ds .hm-auto-complete-holder > input.form-control {
  /* !important pour la MEME raison que `.ds .date-range.date-range` : le champ est
     peint par le selecteur a six `:not()` de `ds-bridge.overrides.css`, qui pese
     (0,12,1). Aucune repetition de classe raisonnable ne le bat. */
  padding-left: 35px !important;
}
.ds .ac-empty {
  padding: 14px;
  text-align: center;
  font: var(--font-body-sm);
  color: var(--text-muted);
}

/* ---- fichiers ---------------------------------------------------------
   Zone de depot quand le fichier est le SUJET du formulaire ; champ a icone
   quand ce n est qu un attribut parmi d autres. Les fichiers deja joints se
   listent sous le champ. */
.ds .drop {
  /* Bloc de reference du declencheur : c est ce qui lui permet de couvrir la
     zone ENTIERE, cf. `.ds .drop .btn-file` plus bas. */
  position: relative;
  padding: 18px;
  text-align: center;
  background: var(--surface-sunken);
  border: 1.5px dashed var(--border-strong);
  border-radius: var(--radius-md);
  color: var(--text-muted);
  font: var(--font-body-sm);
  cursor: pointer;
  transition: border-color var(--duration-fast), background var(--duration-fast);
}
.ds .drop:hover {
  border-color: var(--primary);
  background: var(--primary-soft);
  color: var(--primary-700);
}
.ds .drop i,
.ds .drop svg { display: block; width: 20px; height: 20px; margin: 0 auto 7px; }
/* LE CLIC N A PAS A VISER LE MOT. `.ds .btn-file` pose `position: relative`, donc
   son `<input type="file">` — etale en `inset: 0` — ne couvrait que le libelle :
   le padding de 18px de la zone et le glyphe au-dessus restaient inertes, et il
   fallait viser le texte pour ouvrir le selecteur. En repassant le declencheur en
   `position: static`, le bloc de reference de l input devient `.ds .drop` : il
   couvre alors la zone entiere, glyphe et marges comprises.

   L `overflow: hidden` de `.btn-file` ne le rogne pas : un absolu n est rognee que
   par les ancetres de son bloc de reference, et celui-ci est desormais `.drop`.

   Le glisser-deposer, lui, est porte par `nv-file-drop` sur le conteneur. */
.ds .drop .btn-file {
  display: block;
  position: static;
  font-weight: var(--weight-medium);
  /* Meme lecon que `.err-msg` juste au-dessus : `--primary` est la teinte de
     REMPLISSAGE de la marque, pas une teinte de texte. Posee sur le fond en
     creux de la zone de depot elle tombait a 2,61:1 en sombre. `--primary-700`
     est la declinaison de texte — assombrie en clair, eclaircie en sombre —
     et elle suit toujours la couleur choisie par le client.
     Mesure : clair 6,30 -> 9,40 ; sombre 2,61 -> 7,46. */
  color: var(--primary-700);
}
/* Zone de recadrage d une image (Croppie), sous la zone de depot : centree, sur le fond
   en creux. Le cadre et la poignee de zoom sont ceux de Croppie (croppie.css). */
.ds .crop-stage {
  display: flex;
  justify-content: center;
  padding: var(--space-3);
  background: var(--surface-sunken);
  border-radius: var(--radius-md);
}


.ds .filerow {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 7px 10px;
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
/* Le glyphe de tete d une ligne de fichier. La reference ecrit
   `<i data-lucide="file-text" class="ic-sm" style="color:var(--text-muted)">` :
   un repere, pas une action. Le `!important` est le meme cas que `.input-icon`
   et `.modal-head .ico` — `ds-bridge.css` repeint tous les svg FontAwesome en
   `--primary` a travers l application, et seul un !important bat un
   !important. `flex: none` pour qu il ne se comprime pas quand le nom du
   fichier est long. */
.ds .filerow > i,
.ds .filerow > svg {
  flex: none;
  width: var(--icon-md, 16px);
  height: var(--icon-md, 16px);
  color: var(--text-muted) !important;
}
.ds .filerow .nm {
  flex: 1;
  min-width: 0;
  font: var(--font-body-sm);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ds .filerow .sz {
  font: var(--font-meta);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
/* Justificatif obligatoire pas encore fourni (libre-service V1, selfServiceField.html) : le filet de
   la ligne passe a la teinte d erreur, comme la bordure d un champ en erreur. */
.ds .filerow.has-error { border-color: var(--danger-border); }

/* ---- champs de date -----------------------------------------------------
   Les trois directives de saisie de date (`hm-date-picker`,
   `hm-date-range-picker`, `hm-time-picker`) rendent toutes le meme balisage :
   un `<input class="form-control date-range">` suivi d une croix
   d effacement. Le champ lui-meme est deja couvert par la recette de champ
   ci-dessus, via `.form-control` — il fait donc la meme hauteur, la meme
   bordure et le meme rayon que le select pose a cote. Verifie : 40px, rayon 8.

   La croix, elle, portait `blackColor` — un noir EN DUR, identique dans les
   deux themes, donc invisible sur fond sombre. C est un repere discret, pas
   une action principale : --text-muted au repos, --text-primary au survol.
   Le !important bat la classe utilitaire legacy, qui en porte un. */
/* La croix a besoin d un ANCETRE POSITIONNE, sinon elle sort du champ.

   `sitecss.css` la pose en `position: absolute; right: 15px; margin-top: -30px`.
   Le `right` se mesure donc sur le premier ancetre positionne — et les trois
   directives n en posent aucun : leur hote est un element inline non
   positionne, et l `<input>` est le FRERE de la croix, pas son parent. Le
   calage remontait jusqu au `.modal-body`. Mesure faite dans la modale
   d absence : croix a 1505,8px alors que le champ finit a 1500,8 — 5px dehors,
   rognee par le bord.

   On fait de l hote de la directive le repere. Aucune touche au gabarit des
   trois directives, qui sert 134 vues. Portee volontairement limitee a un
   enfant DIRECT de `.field` : seuls les ecrans deja passes au design system
   sont concernes, les gabarits legacy gardent leur comportement actuel.
   Le cas `.input-icon > hm-date-picker` est deja couvert : `.input-icon`
   porte `position: relative`. */
.ds .field > hm-date-picker,
.ds .field > hm-date-range-picker,
.ds .field > hm-time-picker {
  position: relative;
  display: block;
}
/* Le calage vertical devient DETERMINISTE. Le legacy centrait par un
   `margin-top: -30px` applique a la position statique de la croix — un calcul
   qui ne tombe juste que pour une hauteur de champ donnee : mesure a 3px trop
   haut sur nos 38px. On centre comme `.input-icon .trail`, la croix suit donc
   `--control-h` quelle que soit sa valeur.

   PORTEE ELARGIE — le `.field >` exige a tort une filiation. La regle demandait
   que la directive soit ENFANT DIRECT de `.field`. Des que le champ est
   enveloppe, elle ne matchait plus et le legacy reprenait la main : c est le cas
   du motif `<span class="input-icon"><hm-date-picker> + glyphe legacy` (§41),
   porte par les modales de diplomes et de qualifications. La croix y retombait
   sur `margin-top: -30px`, visiblement decentree. Meme lecon que les trois
   portees du §40 : une croix d effacement ne vit pas forcement sous un `.field`.

   `hm-month-picker` AJOUTE : son gabarit rend le meme balisage que les trois
   autres. Il avait deja ete oublie une fois dans la regle d icone, meme cause.

   Poids : `.ds hm-date-picker > .delete-date` vaut (0,2,1), il bat les deux
   regles legacy de `sitecss.css` — `.delete-date { margin-top: -30px }` et
   `.delete-date { top: 50%; margin-top: -10px }` — qui pesent (0,1,0). Le
   `right` n est pas touche : il est correct, et le §21 dit de ne corriger que ce
   qui est casse. */
.ds hm-date-picker > .delete-date,
.ds hm-date-range-picker > .delete-date,
.ds hm-month-picker > .delete-date,
.ds hm-time-picker > .delete-date {
  top: 50%;
  margin-top: 0;
  transform: translateY(-50%);
}

.ds .delete-date {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.ds .delete-date i,
.ds .delete-date svg {
  color: var(--text-muted) !important;
  font-size: var(--icon-md, 16px) !important;
}

.ds .delete-date:hover i,
.ds .delete-date:hover svg {
  color: var(--text-primary) !important;
}


/* ---- glyphe de calendrier legacy (`.date-picker`) ----------------------
   Meme defaut que la croix d effacement ci-dessus, et meme correctif.
   `sitecss.css` porte `.date-picker .hrmps { color: #018acc; font-size: 26px }`
   — un bleu EN DUR, identique dans les deux themes, et un glyphe de 26px la
   ou le design system tient ses icones de champ a --icon-md. Le span
   `<span class="date-picker">` accompagne un champ de date dans une trentaine
   de gabarits : on repeint la classe que ces gabarits emettent deja, plutot
   que d en changer le nom partout.

   Le POSITIONNEMENT reste celui du legacy (`absolute; top: 5px; right: 6px`) :
   il fonctionne, et le §21 du skill dit de ne corriger que ce qui est casse.
   En revanche il exige un ancetre positionne — c etait `.input-group` de
   Bootstrap. Les gabarits migres remplacent ce conteneur par `.input-icon`,
   qui porte `position: relative` : le repere garde donc son ancre. */
.ds .date-picker .hrmps {
  color: var(--text-muted) !important;
  font-size: var(--icon-md, 16px) !important;
}
/* ---- plage de dates ---------------------------------------------------
   Deux champs separes par une fleche, et non un seul champ a double valeur :
   c est ce que montre le specimen `audit/formulaires.html`. `align-items: end`
   pose la fleche au niveau des CHAMPS et non des libelles qui les surmontent ;
   le `padding-bottom` la recentre sur la ligne de saisie. */
.ds .daterange {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: end;
  gap: 10px;
}
.ds .daterange .arrow {
  padding-bottom: 10px;
  color: var(--text-muted);
}
@media (max-width: 860px) {
  .ds .daterange { grid-template-columns: 1fr; }
  .ds .daterange .arrow { display: none; }
}

/* ---- icone calendrier des champs de date ------------------------------
   Le specimen met une icone calendrier EN TETE de tout champ de date. Les
   trois directives du portail (`hm-date-picker`, `hm-date-range-picker`,
   `hm-time-picker`) rendent un `<input class="form-control date-range">` sans
   conteneur `.input-icon` — on ne peut donc pas y glisser un `<i>` sans
   toucher a leur template, qui sert 134 ecrans. L icone est donc posee en
   `background-image`, exactement comme le chevron du select juste au-dessus :
   meme resultat visuel, zero changement de structure, aucun risque pour le JS
   qui fait `$element.find('input')`.

   Les `!important` sont necessaires : `sitecss.css` pose
   `input[type=text] { background-image: initial !important }`, et le
   `padding: 0 12px !important` de la recette de champ ecraserait la place
   reservee a l icone. */
/* LA CLASSE EST REPETEE, et ce n est pas cosmetique. `sitecss.css` porte
   `.v2 input[disabled="disabled"], …:focus, …:hover { background: var(--input-disabled-bg) !important }`
   — un RACCOURCI `background`, donc il remet `background-image` a `none`, et il
   pese (0,2,1) : une clause d attribut de plus que notre `.ds .date-range`
   (0,2,0). Sur un champ de date desactive — celui de la demande RH l est —
   l icone calendrier disparaissait. `.date-range.date-range` monte a (0,3,0)
   et repasse devant. On ne repose QUE `background-image` : la couleur de fond
   du desactive reste celle du legacy, chaque longhand se resolvant
   separement. */
/* MEME TRAITEMENT QUE LE CHEVRON DU SELECT : couleur primaire, et plus aucun
   hex dans l image.

   Le calendrier etait pose en `background-image` sur l `<input>` lui-meme, avec
   un `stroke` en dur. Deux consequences mesurees sur la modale d absence :
     champ de date actif      texte rgb(23, 26, 33)      icone #7A8294
     champ de date desactive  texte rgb(167, 174, 189)   icone #7A8294
   Sur un champ desactive, l icone restait donc PLUS SOMBRE que le texte
   qu elle accompagne — elle se lisait comme noire. Et elle ne changeait pas
   non plus en theme sombre.

   Un `<input>` ne porte pas de pseudo-element : l icone passe sur l HOTE de la
   directive (`hm-date-picker`, `hm-date-range-picker`, `hm-time-picker`), qui
   enveloppe deja l input et la croix d effacement. Le SVG ne sert plus que de
   pochoir, la couleur vient de `background-color`.

   L input garde son `padding-left` : la place reservee ne change pas, donc le
   calage du texte non plus.

   La portee passe de `.field > hm-date-picker` a `.ds hm-date-picker` : l icone
   doit suivre le champ ou qu il soit, y compris dans un `.input-icon`. Cela
   reste borne aux ecrans migres par le `.ds`. */
/* `hm-month-picker` FAIT PARTIE DE LA LISTE. Son gabarit rend lui aussi un
   `<input class="form-control date-range">`, il recevait donc l ancienne image
   de fond comme les trois autres. L oublier ici lui retirait son icone : 18
   gabarits concernes, defaut introduit puis rattrape. */
/* `flow-root` et non `block` : dans un `.input-group`, Bootstrap fait flotter
   l input, l hote retombait a 0px de haut et l icone collait au bord du haut. */
.ds hm-date-picker,
.ds hm-date-range-picker,
.ds hm-month-picker,
.ds hm-time-picker {
  position: relative;
  display: flow-root;
}
.ds hm-date-picker::before,
.ds hm-date-range-picker::before,
.ds hm-month-picker::before,
.ds hm-time-picker::before {
  content: "";
  position: absolute;
  left: 11px;
  top: 50%;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  background-color: var(--text-muted);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='16' height='16' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2'/%3E%3Cpath d='M16 2v4M8 2v4M3 10h18'/%3E%3C/svg%3E") no-repeat center / 16px 16px;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='16' height='16' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2'/%3E%3Cpath d='M16 2v4M8 2v4M3 10h18'/%3E%3C/svg%3E") no-repeat center / 16px 16px;
  pointer-events: none;
  z-index: 1;
}
/* Meme regle sur un champ DESACTIVE. Mesure du defaut d origine, modale
   d absence : texte rgb(167, 174, 189) et icone #7A8294 — l icone plus foncee
   que le texte qu elle accompagne. */
.ds hm-date-picker:has(input:disabled)::before,
.ds hm-date-range-picker:has(input:disabled)::before,
.ds hm-month-picker:has(input:disabled)::before,
.ds hm-time-picker:has(input:disabled)::before {
  background-color: var(--text-disabled);
}
/* … SAUF L HEURE, QUI PREND UNE HORLOGE. La reference (§04) donne une icone
   par nature de champ : calendrier pour une date, horloge pour une heure. Les
   quatre directives partageaient le meme calendrier parce qu elles partagent la
   classe `date-range` — un heritage de nommage, pas une intention. */
.ds hm-time-picker::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='16' height='16' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpolyline points='12 6 12 12 16 14'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='16' height='16' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpolyline points='12 6 12 12 16 14'/%3E%3C/svg%3E");
}

/* LE CHAMP D HEURE EST UN `input[type=time]` NATIF.

   La reference (§04) dit de les EVITER : ils ajoutent leur propre indicateur et
   imposent le format de la locale du navigateur. `hmTimePicker` en pose un
   quand meme — c est son gabarit, partage par tous les ecrans, on ne le change
   pas ici. La reference prevoit ce cas et donne la recette de repli (§08) :
   masquer l indicateur natif et fixer la hauteur au jeton.

   Sans cela, le champ portait DEUX icones : la notre a gauche et celle de
   Chrome a droite.

   La hauteur est fixee parce qu un champ natif de date ou d heure ne la calcule
   pas comme les autres : `min-height` seul ne suffit pas. */
.ds input[type="time"]::-webkit-calendar-picker-indicator,
.ds input[type="date"]::-webkit-calendar-picker-indicator {
  display: none;
}
.ds input[type="time"],
.ds input[type="date"] {
  height: var(--control-h) !important;
}
/* L ancienne image est retiree, la place qu elle reservait est gardee. Le
   `!important` sur `background-image` reste necessaire : `sitecss.css` porte
   `.v2 input[disabled="disabled"] { background: var(--input-disabled-bg)
   !important }`, un raccourci qui repose une image. */
.ds .date-range.date-range,
.ds input.date-range.date-range {
  background-image: none !important;
  padding-left: 35px !important;
}

/* Le PLACEHOLDER des trois directives porte le codepoint FontAwesome du
   calendrier (`placeholder="&#xf073"`), qui FAISAIT office d icone tant que le
   champ heritait de la police "Font Awesome 5 Pro". Sous `.ds` la police est
   Roboto et l icone est dessinee juste au-dessus en `background-image` : le
   codepoint n a plus de glyphe, et Chrome affiche le carre du caractere
   manquant a cote de l icone du design system — sur TOUT champ de date vide de
   tout ecran migre. Le design system a remplace le mecanisme d icone sans
   neutraliser l ancien ; on neutralise ici le placeholder plutot que les
   templates des directives, qui servent 134 ecrans.
   `opacity` en plus de `color` : Firefox applique sa propre opacite au
   placeholder, que `color` seul ne suffit pas a effacer. Les `!important`
   battent le `*, ::after, ::before { color: #000 !important }` de `sitecss.css`.
   Mesure, modale ouverte : couleur rgba(0, 0, 0, 0), opacite 0, icone DS
   toujours en `11px 50%`, champ toujours a 38px. */
.ds .date-range::placeholder {
  color: transparent !important;
  opacity: 0 !important;
}

/* Le POSITIONNEMENT de la croix d effacement est laisse au CSS existant, qui
   s en occupe deja correctement. Le template de la directive pose
   `position: relative` sur l INPUT et non sur son parent : une regle
   `position: absolute; right: 11px` ici se calerait donc sur un ancetre
   quelconque et deplacerait la croix hors du champ — constate a l ecran. On ne
   corrige que ce qui est casse, c est-a-dire la couleur (voir plus haut). */

/* ---- panneau déroulant du cs-select -------------------------------------
   `cs-select` est un plugin (`app/directives/select.js`) : il ne rend pas une
   liste déroulante NATIVE mais son propre panneau, `<div class="cs-options">
   <ul><li>…`. Le trigger (le champ fermé) est déjà couvert par la recette de
   champ plus haut ; le panneau ouvert, lui, n'était pas repris — il gardait une
   ombre `#ccc` en dur, aucune bordure, aucun rayon, et des items à
   `line-height: 38px` sans padding, d'où un espacement énorme.

   Le design system décrit déjà cet objet : c'est le `.ac-menu` / `.ac-item` de
   `audit/formulaires.html` (le menu de résultats de l'autocomplétion). Un
   panneau flottant de choix, c'est le même objet — donc les mêmes valeurs, et
   pas une seconde recette.

   Scopé `.ds` : les écrans non encore migrés gardent leur rendu actuel. */
/* `.cs-select` est repete pour le poids : `sitecss.css` porte
   `.v2 .cs-select.cs-active .cs-options` a (0,4,0), qui posait une ombre
   `#ccc` en dur. */
.ds .cs-select.cs-select.cs-select .cs-options {
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--popover-shadow);
  /* CALAGE SUR LE CHAMP. `sitecss.css` porte
     `.cs-skin-slide .cs-options { top: 40px; left: 0; width: 100% }` :

     - `top: 40px` est une valeur EN DUR, celle de l ancienne hauteur de champ.
       Le champ fait maintenant 38px (`--control-h`), donc le panneau ouvrait a
       3px sous le champ au lieu des 6px de la recette — et le jour ou
       `--control-h` bougera, l ecart bougera encore. Meme famille que le
       `margin-top: -30px` de la croix des champs de date. On repart de
       `100%`, donc de la hauteur reelle du champ.
     - `width: 100%` sur un element en `position: absolute` se calcule sur la
       BOITE DE PADDING du parent, bordures exclues : le panneau sortait a
       413px sous un champ de 415px, donc 1px trop court de chaque cote.
       La reference pose `left: 0; right: 0`, qui s etire sur la boite de
       bordure et donne exactement la largeur du champ.

     Les trois offsets se calculent sur la BOITE DE PADDING du declencheur,
     qui est ici l ancetre positionne ET porte la bordure du champ : `100%`
     vaut donc 36px pour un champ de 38, et `left/right: 0` s arretent a
     413px pour un champ de 415. D ou le 1px de bordure ajoute aux trois
     valeurs — la reference n a pas ce terme parce que son `.ac` est un
     conteneur SANS bordure. `--field-border-width` n existe pas comme jeton ;
     la bordure est posee a 1px dans la recette de champ juste au-dessus, le
     litteral suit donc la meme source.

     Mesure, panneau ouvert dans un champ de 415px :
       ecart au champ   3px  -> 6px
       largeur       413px  -> 415px
       bords          1px trop court des deux cotes -> alignes */
  top: calc(100% + 6px + 1px);
  left: -1px;
  right: -1px;
  width: auto;
}

.ds .cs-select .cs-options ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* `line-height` ramené à la normale : c'est lui qui donnait 38px de hauteur à
   chaque option, sans qu'aucun padding ne le décide. La hauteur vient
   maintenant du padding, comme pour `.ac-item`. */
.ds .cs-select .cs-options li {
  display: flex;
  align-items: center;
  /* `.cs-skin-slide .cs-options li` posait `margin: 5px`, soit 10px de plus
     par option : la hauteur passait de 35 a 49px. */
  margin: 0;
  padding: 8px 10px;
  /* `ng-options` ajoute une option VIDE tant que rien n est choisi. Sans
     hauteur minimale elle se dessinait sur 16px au lieu de 37 — une bande
     grise en haut du panneau, puisqu elle porte `.cs-selected`. La hauteur
     minimale la rend semblable aux autres : une ligne « aucun choix », et non
     un defaut d affichage. */
  min-height: 37px;
  border-radius: var(--radius-sm);
  font: var(--font-body-sm);
  line-height: var(--leading-normal);
  color: var(--text-primary);
  cursor: pointer;
}

/* Le plugin enveloppe le libelle dans un <span>, auquel sitecss.css donne son
   propre `padding: 6px`. Additionne a celui de l option, chaque ligne montait a
   49px au lieu de 35. Le padding se porte a UN seul niveau : l option. */
.ds .cs-select .cs-options li span {
  padding: 0;
  /* `font: inherit` et pas seulement `line-height` : le texte de l option vit
     dans ce <span>, et une regle legacy le remettait a 14px. Le `font:
     var(--font-body-sm)` pose sur le <li> ci-dessus ne l atteignait donc
     jamais — mesure : li a 13px, span a 14px, et c est le span qu on lit.
     Meme famille de defaut que l asterisque du §35 : une declaration qui
     n arrive pas jusqu au pixel. */
  font: inherit;
}

/* Le SURVOL — et le curseur clavier — prennent la meme BANDE PLEINE que la
   selection.

   Decision produit, sur capture du rendu vise : dans une liste native, le
   bandeau bleu SUIT LE CURSEUR. Survol, curseur clavier et option retenue
   partagent donc une seule couleur, `--primary`, et le seul etat au repos qui
   se distingue est l option retenue.

   Historique, pour ne pas refaire le tour : cette regle a d abord porte la
   bande pleine, puis `--surface-hover` au motif que la reference developpeur
   decrit son panneau de choix (`.ac-item:hover`) en gris. Mais cette reference
   ne decrit AUCUN panneau de select : sa liste deroulante (§03) est un
   <select> NATIF, dont le panneau est dessine par le navigateur et hors de
   portee du CSS. Il n y avait donc rien a lire, et c est une decision.

   Contraste mesure : blanc sur `--primary` rgb(63, 81, 181) = 6.87:1,
   au-dessus du seuil AA de 4.5. */
.ds .cs-select .cs-options li:hover,
.ds .cs-select .cs-options li.cs-focus,
.ds .cs-select .cs-options li.cs-selected:hover,
.ds .cs-select .cs-options li.cs-selected.cs-focus {
  /* !important : `sitecss.css` porte
     `.v2 .cs-skin-slide .cs-options li:hover { background: var(--wrapper-bg)
     !important }`. Le texte est repose avec, pour ne dependre d aucune regle
     legacy sur sa couleur. */
  background: var(--primary) !important;
  color: var(--on-primary) !important;
}

/* La SELECTION est une BANDE PLEINE en couleur client.

   Decision produit, prise sur capture du rendu vise : dans un <select> NATIF,
   l option retenue est peinte par le systeme en bleu sature avec du texte
   blanc, et c est ce reperage-la qui est attendu. Ce bleu systeme n est pas un
   jeton et reste hors de portee du CSS ; on en reprend l INTENTION avec
   `--primary`, donc la couleur du client, surchargeable sans retoucher un
   ecran.

   La teinte `--primary-soft` posee ici auparavant (10 %) se lisait a peine
   parmi 18 options.

   `:hover` et `.cs-focus` font partie du selecteur, et les deux declarations
   portent `!important` : la regle de survol juste au-dessus en porte aussi, et
   le plugin pose `cs-focus` sur l option retenue DES L OUVERTURE du panneau.
   Sans cela le panneau s ouvrait avec son option retenue en GRIS. Survoler une
   AUTRE option la grise normalement ; l option retenue reste reperable.

   Contraste mesure : blanc sur `--primary` rgb(63, 81, 181) = 6.87:1, au-dessus
   du seuil AA de 4.5. */
.ds .cs-select .cs-options li.cs-selected,
.ds .cs-select .cs-options li.cs-selected:hover,
.ds .cs-select .cs-options li.cs-selected.cs-focus {
  background: var(--primary) !important;
  color: var(--on-primary) !important;
}

/* UN SEUL BANDEAU A LA FOIS : celui du curseur.

   Le bandeau de selection et celui du survol etant de la meme couleur, des
   qu on survolait une autre option on en lisait DEUX en meme temps. Une liste
   native n en montre jamais qu un : le bleu SUIT le curseur, il ne s ajoute
   pas a la selection. L option retenue rend donc son bandeau tant que le
   curseur est ailleurs dans le panneau, et le reprend des qu il en sort.

   `:has(li:hover)` sur le panneau plutot qu un `:not()` sur l option : c est
   le PANNEAU qui sait si le curseur est dedans. Specificite (0,6,2), donc
   au-dessus des trois selecteurs de selection ci-dessus (0,5,1 au plus). */
.ds .cs-select .cs-options:has(li:hover) li.cs-selected:not(:hover) {
  background: transparent !important;
  color: var(--text-primary) !important;
}

/* MAIS PAS l option VIDE. `ng-options` en ajoute une tant que rien n est
   choisi, et le plugin la rend en `<li class="cs-selected"><span></span></li>` :
   elle se peignait donc en bande pleine, donnant un gros bloc de couleur vide
   en tete de panneau. Le specimen, lui, ne montre que de vraies options.

   Elle est rendue INERTE, pas supprimee : c est par elle qu on vide un champ
   non obligatoire (le type d un document sans fichier, par exemple). Le survol
   lui reste acquis, elle est cliquable.

   `:has()` compte pour la specificite de son argument, ces selecteurs passent
   donc largement au-dessus de la bande pleine et du survol generique.

   `cs-focus` est inclus : le plugin le pose sur l option courante DES L
   OUVERTURE du panneau. Sans cette exclusion, l option vide reprenait la bande
   pleine du curseur — le gros bloc de couleur revenait. Le `!important` est
   necessaire pour battre celui de la regle de survol ci-dessus. */
/* `[data-value=""]` EN PLUS de `:has(> span:empty)`. Le test sur le span vide
   ne suffit pas : selon le gabarit, l option de vidage porte un libelle. Sur la
   demande RH, `ng-options` sort `<li data-value=""><span>—</span></li>` — un
   tiret cadratin, donc un span NON vide, donc la garde ne s appliquait pas et
   la ligne « — » s ouvrait en bande primaire pleine. Le marqueur fiable est la
   VALEUR vide, que le plugin recopie dans `data-value`. Les deux formes sont
   gardees : un gabarit peut rendre l un ou l autre. */
.ds .cs-select .cs-options li.cs-selected:has(> span:empty),
.ds .cs-select .cs-options li.cs-selected.cs-focus:has(> span:empty),
.ds .cs-select .cs-options li.cs-selected[data-value=""],
.ds .cs-select .cs-options li.cs-selected.cs-focus[data-value=""] {
  background: transparent !important;
  color: var(--text-primary) !important;
}

/* Et sur un champ OBLIGATOIRE, elle disparait tout court.

   Rendue inerte elle laissait quand meme une ligne blanche de 37px en tete du
   panneau, ce qui se lit comme un defaut — le specimen ouvre directement sur la
   premiere vraie option. La masquer entierement serait pourtant faux partout :
   sur un champ FACULTATIF, c est par elle qu on remet le champ a vide.

   Le `:has(select[required])` fait la difference, et il suit `ng-required` :
   verifie sur le type d un document, obligatoire seulement quand un fichier est
   joint. Le <select> natif est masque par le plugin mais reste dans le DOM,
   frere du declencheur — c est lui qui porte l attribut. */
.ds .field:has(select[required]) .cs-options li:has(> span:empty),
.ds .field:has(select[required]) .cs-options li[data-value=""] { display: none; }
/* Au SURVOL, en revanche, elle s allume comme n importe quelle autre option :
   inerte AU REPOS ne veut pas dire inactive. C est par elle qu on remet un
   champ facultatif a vide, elle est donc un choix, et le bandeau suit le
   curseur partout de la meme facon. */
.ds .cs-select .cs-options li.cs-selected:has(> span:empty):hover,
.ds .cs-select .cs-options li.cs-selected[data-value=""]:hover {
  background: var(--primary) !important;
  color: var(--on-primary) !important;
}

/* Barre de défilement du panneau : fine et tokenisée, à la place de celle du
   système qui prenait 17px de large et cassait l'objet. Cantonnée au panneau —
   changer la barre de toute l'application est une autre décision. */
.ds .cs-select .cs-options::-webkit-scrollbar {
  width: 10px;
}
.ds .cs-select .cs-options::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--text-muted) 32%, transparent);
  border: 2px solid transparent;
  border-radius: var(--radius-pill);
  background-clip: padding-box;
}
.ds .cs-select .cs-options::-webkit-scrollbar-thumb:hover {
  background: color-mix(in srgb, var(--text-muted) 50%, transparent);
  background-clip: padding-box;
}
.ds .cs-select .cs-options::-webkit-scrollbar-track {
  background: transparent;
}

/* ---- le gel anti-autofill, et pourquoi il faut le lever ------------------
   `sitecss.css` porte sur tous les champs de la v2 :

     transition: background-color 0s 600000s, color 0s 600000s !important;

   C'est la vieille recette anti-autofill de Chrome : un délai de transition de
   600 000 secondes — presque sept jours — pour que le fond jaune de
   l'autocomplétion n'ait jamais le temps d'apparaître.

   L'effet de bord est total : une transition EN COURS est au sommet de la
   cascade, au-dessus même d'un `!important` inline. Donc `background-color` et
   `color` d'un champ restent **figés à leur valeur du premier rendu**, quoi
   qu'on écrive ensuite. Concrètement, cela rendait impossible :
     · le passage d'un champ au mode sombre,
     · l'affichage de l'état désactivé (fond en creux),
     · et tout changement de couleur client sur un champ déjà rendu.

   C'est ce qui expliquait un symptôme incompréhensible : un
   `background-color: red !important` posé en inline ne changeait même pas la
   valeur calculée de l'élément.

   On remplace le gel par une vraie transition courte. La protection
   anti-autofill n'est pas perdue pour autant : Chrome expose
   `:-webkit-autofill`, traité juste en dessous — c'est la façon propre de
   reprendre la main sur ce fond, plutôt que de geler la propriété pour tout le
   monde pendant une semaine. */
/* Classe repetee : le selecteur legacy est `.v2 input[type=text]`, soit
   (0,2,1). Un `.ds .input` vaut (0,2,0) et perd sur le compte des elements. */
.ds .input.input,
.ds .select.select,
.ds .textarea.textarea,
.ds .form-control.form-control,
.ds .cs-select.cs-select {
  /* `background-color` et `color` sont VOLONTAIREMENT hors de la transition.
     Les animer n apporte rien — on ne regarde pas un champ changer de fond —
     et cela rouvre la porte au gel : tant qu une transition est en cours sur
     une propriete, sa valeur est figee et rien ne peut la deplacer, pas meme un
     !important inline. C est ce qui empechait le mode sombre et l etat
     desactive de s afficher. Un changement de theme doit etre instantane.
     Restent animees les deux proprietes du FOCUS, ou la transition se voit. */
  transition: border-color var(--duration-fast) var(--ease-standard),
              box-shadow var(--duration-fast) var(--ease-standard) !important;
}

/* Le fond d'autocomplétion, repris proprement. Chrome ne laisse pas peindre
   `background-color` sur un champ auto-rempli : l'usage est de le recouvrir
   avec une ombre interne, seule propriété qu'il respecte ici. */
.ds .input:-webkit-autofill,
.ds .select:-webkit-autofill,
.ds .textarea:-webkit-autofill,
.ds .form-control:-webkit-autofill {
  box-shadow: 0 0 0 100px var(--field-bg) inset !important;
  -webkit-text-fill-color: var(--text-primary) !important;
  caret-color: var(--text-primary);
}

/* ---- MESSAGES (`.alert`) ------------------------------------------------
   Recette transcrite de `audit/composants.html` §05 « Onglets & messages » :

     .alert { display:flex; gap:11px; padding:13px 15px;
              border-radius:var(--radius-md); font:var(--font-body-sm);
              align-items:flex-start; }
     .alert-success { background: var(--success-soft); color: var(--success-fg); }
     .alert-warning { background: var(--warning-soft); color: var(--warning-fg); }
     .alert-danger  { background: var(--danger-soft);  color: var(--danger-fg);  }
     .alert-info    { background: var(--info-soft);    color: var(--info-fg);    }

   C est la meme paire « fond doux + texte fonce » que les badges (§02) et que
   les badges `b-*` : une seule convention semantique pour tout ce qui
   porte une couleur de sens.

   POURQUOI CETTE RECETTE MANQUAIT. La famille `.alert` de Bootstrap etait la
   derniere a n avoir aucune cible dans le design system : ses quatre variantes
   gardaient donc leurs couleurs Bootstrap en theme sombre — mesure sur
   `trainingDesiredCancelModal`, fond rgb(252,248,227) et texte rgb(138,109,59),
   soit un ilot clair dans un panneau sombre.

   SCOPEE SOUS `.ds`, parce que `.alert` est un nom Bootstrap (§9) : seuls les
   ecrans deja migres suivent, les autres gardent leur rendu d avant.

   TROIS REGLES LEGACY A BATTRE, toutes dans `sitecss.css` :
     `.alert { border: 1px solid transparent; border-radius: 4px }`  (0,1,0)
     `.alert, .thumbnail { margin-bottom: 20px }`                    (0,1,0)
     `.alert-success { background-color: transparent !important;
                       border-color: var(--whiteColor) !important }` (0,1,0)
     `.alert-danger  { background-color: transparent !important;
                       border-color: #fff !important }`              (0,1,0)
   `.ds .alert` vaut (0,2,0) et gagne sur les deux premieres au poids. Les deux
   dernieres portent un `!important` : seul un `!important` le bat — d ou celui
   pose sur le fond de `success` et `danger`, et LA SEULEMENT. `warning` et
   `info` n en ont pas besoin, et n en prennent pas. */
.ds .alert {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 13px 15px;
  border-radius: var(--radius-md);
  font: var(--font-body-sm);
  /* La reference ne met AUCUN filet. `border: 0` neutralise d un coup le
     `1px solid transparent` de Bootstrap et les `border-color` par variante,
     qui n ont plus de largeur a peindre. */
  border: 0;
  /* La gouttiere vient du `.stack` ou du `.modal-body` qui contient le
     message ; le `margin-bottom: 20px` de Bootstrap s y AJOUTAIT. */
  margin: 0;
}
/* La reference met un glyphe devant chaque message
   (`<i data-lucide="check-circle-2">`). AUCUN gabarit du portail n en affiche,
   et en ajouter serait ajouter une information (§1) : la regle est ecrite pour
   le jour ou un ecran en portera un, elle ne change rien d ici la. */
.ds .alert > i,
.ds .alert > svg {
  flex: none;
  margin-top: 1px;
  width: var(--icon-md, 16px);
  height: var(--icon-md, 16px);
}
.ds .alert b { font-weight: var(--weight-medium); }

.ds .alert.alert-success { background-color: var(--success-soft) !important; color: var(--success-fg); }
.ds .alert.alert-danger  { background-color: var(--danger-soft)  !important; color: var(--danger-fg);  }
.ds .alert.alert-warning { background-color: var(--warning-soft); color: var(--warning-fg); }
.ds .alert.alert-info    { background-color: var(--info-soft);    color: var(--info-fg); }

/* ---- HISTORIQUE DE WORKFLOW (`.wf-history`) -----------------------------
   Cet ecran avait sa PROPRE feuille de style, prefixee `wfh-` et posee dans
   `Content/css/CustomHrMaps.css` : 52 regles, 24 couleurs EN DUR, aucune
   variante sombre. Resultat mesure sur le panneau, en theme sombre : deux
   ilots blancs (`#f5f6f8`), des pastilles illisibles (« Valider » en `#333`
   sur fond sombre, « Refus de demande » en `#a3312e` sur rouge sombre) et un
   soulignement d onglet fige a `#3f51b5`, donc insensible a la couleur du
   client. Le bloc a ete retire de `CustomHrMaps.css` et rendu ici.

   Le design system decrit cet ecran en entier — `audit/historique-workflow.html`.
   On en reprend LE VOCABULAIRE (`.status`, `.count`, `.tl`, `.step`,
   `.marker`, `.dot`, `.content`, `.line1`, `.nm`, `.meta`, `.diff`, `.quote`,
   `.halt`) et LES JETONS. Les onglets sont ceux du portail (`.tabs` / `.tab`).

   Decisions du 15/10/2026, toutes les fenetres d historique : la demande sort
   de la frise et se lit en tete (`.status`), avec le statut REEL de l objet,
   seul badge de la fenetre (`ds-badge` + classe de statut `b-*` de l ecran).
   Dessous, le circuit (`.tl`) : chaque etape en texte (`.nm`), sans badge ;
   son etat se lit sur la pastille, couleur ET icone (`step.displayInfo.kind` :
   `todo`, `current`, `done`, `back`, `cancel`, `refused`, `planned`), en
   teinte `--status-*`. Etape a venir : neutre. Un refus ou une annulation
   reste sur l etape ou il a eu lieu. */
.wf-history .status {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-3);
  margin-bottom: 14px;
  border-radius: var(--radius-md);
  background: var(--surface-sunken);
}
.wf-history .count { font: var(--font-meta); color: var(--text-muted); }

/* la frise */
.wf-history .tl { display: flex; flex-direction: column; }
.wf-history .step { display: flex; gap: var(--space-3); }
.wf-history .marker { position: relative; width: 28px; flex: none; display: flex; justify-content: center; }
/* le rail relie les pastilles : il part sous la pastille et descend */
.wf-history .marker::before {
  content: "";
  position: absolute;
  top: 30px;
  bottom: -6px;
  left: 50%;
  width: 2px;
  transform: translateX(-50%);
  background: var(--border);
}
.wf-history .step:last-child .marker::before { display: none; }
.wf-history .dot {
  position: relative;
  z-index: 1;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  background: var(--surface);
  border: 2px solid var(--border-strong);
  color: var(--text-muted);
  font-size: var(--text-xs);
}
.wf-history .step.done .dot    { background: var(--status-done); border-color: var(--status-done); color: var(--on-primary); }
.wf-history .step.refused .dot { background: var(--status-refused); border-color: var(--status-refused); color: var(--on-primary); }
.wf-history .step.current .dot { background: var(--status-progress-soft); border-color: var(--status-progress); color: var(--status-progress-fg); }
.wf-history .step.back .dot    { background: var(--status-draft-soft); border-color: var(--status-draft); color: var(--status-draft-fg); }
.wf-history .step.cancel .dot  { background: var(--status-cancelled-soft); border-color: var(--status-cancelled); color: var(--status-cancelled-fg); }
.wf-history .step.planned .dot { background: var(--status-planned-soft); border-color: var(--status-planned); color: var(--status-planned-fg); }
.wf-history .content { flex: 1; min-width: 0; padding-bottom: 18px; }
.wf-history .step:last-child .content { padding-bottom: 0; }
.wf-history .line1 { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; min-height: 28px; }
.wf-history .nm { font: var(--weight-medium) var(--text-base) var(--font-sans); color: var(--text-primary); }
.wf-history .step.todo .nm { color: var(--text-muted); }
.wf-history .super { font: var(--font-meta); color: var(--text-muted); font-style: italic; }
.wf-history .meta { margin-top: 4px; font: var(--font-body-sm); color: var(--text-muted); }

.wf-history .diff {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 6px;
  font: var(--font-body-sm);
  word-break: break-word;
}
.wf-history .diff .k    { color: var(--text-muted); }
.wf-history .diff .from { color: var(--text-muted); text-decoration: line-through; }
.wf-history .diff .arw  { font-size: 10px; color: var(--text-disabled); }
.wf-history .diff .to   { font-weight: var(--weight-medium); color: var(--text-primary); }
.wf-history .diff .by   { flex-basis: 100%; font: var(--font-meta); color: var(--text-muted); }

.wf-history .quote {
  margin-top: var(--space-2);
  padding: 8px 10px;
  border-radius: var(--radius-md);
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  font: var(--font-body-sm);
  color: var(--text-secondary);
  white-space: pre-wrap;
}
.wf-history .halt {
  margin-top: var(--space-2);
  padding: 8px 10px;
  border-radius: var(--radius-md);
  border: 1px dashed var(--border-strong);
  font: var(--font-body-sm);
  color: var(--text-muted);
}
.wf-history > .ds-modal-body > .card { margin-bottom: 18px; }

/* ---- CARTE REPLIABLE (`.card-head.is-toggle`) ---------------------------
   L en-tete d une carte devient le bouton qui la deplie, comme le widget
   « Solde de conges » de l accueil : pastille, titre, resume sous le titre
   (`.card-title-sub`), chevron (`.ds-chevron`) en bout de ligne. Le corps
   n est rendu que deplie ; replie, l en-tete perd son filet bas.
   Premier usage : la carte des preparations de l historique d entretien. */
.ds .card-head.is-toggle {
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  margin: 0;
  border: 0;
  border-bottom: 1px solid var(--border-subtle);
  border-radius: var(--radius-card) var(--radius-card) 0 0;
  background-color: transparent;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.ds .card-head.is-toggle[aria-expanded="false"] { border-bottom-color: transparent; border-radius: var(--radius-card); }
.ds .card-head.is-toggle:hover { background-color: var(--surface-hover); }
.ds .card-head.is-toggle:focus-visible { outline: none; box-shadow: var(--ring); }
.ds .card-title-sub {
  display: block;
  margin-top: 2px;
  font: var(--font-body-sm);
  color: var(--text-muted);
}
/* Le chevron d un bouton qui deplie : il se retourne quand le bouton est ouvert. */
.ds .ds-chevron { flex: none; color: var(--text-muted); transition: transform var(--duration-fast); }
.ds [aria-expanded="true"] > .ds-chevron { transform: rotate(180deg); }

/* ---- LISTE A BARRE DE COULEUR (`.ds-bar-list`) -------------------------
   Une ligne par element, une barre de 3px a gauche dans la teinte de son
   etat (classes `st-*` ; grise sans teinte), comme les evenements de l agenda
   de l accueil (`hm-wa-event`). La ligne est un bouton qui deplie son detail ;
   l etat se dit aussi en badge, jamais par la couleur seule.
     .ds-bar-item          la ligne et son detail
       .ds-bar-item-head     le bouton : barre, texte, badge, chevron
         .ds-bar-item-mark     la barre
         .ds-bar-item-text     titre (`.ds-bar-item-title`) et precision (`.ds-bar-item-meta`)
       .ds-bar-item-body     le detail deplie, aligne sur le texte */
.ds .ds-bar-list { display: flex; flex-direction: column; gap: var(--space-2); }
.ds .ds-bar-item {
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background-color: var(--surface);
}
.ds .ds-bar-item-head {
  appearance: none;
  -webkit-appearance: none;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  margin: 0;
  padding: 11px var(--space-3);
  border: 0;
  border-radius: var(--radius-md);
  background-color: transparent;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.ds .ds-bar-item-head:hover { background-color: var(--surface-hover); }
.ds .ds-bar-item-head:focus-visible { outline: none; box-shadow: var(--ring); }
.ds .ds-bar-item-mark {
  flex: none;
  align-self: stretch;
  width: 3px;
  border-radius: var(--radius-pill);
  background-color: var(--st, var(--border-strong));
}
.ds .ds-bar-item-text { flex: 1 1 auto; min-width: 0; }
.ds .ds-bar-item-title { display: block; font: var(--weight-medium) var(--text-sm) var(--font-sans); color: var(--text-primary); }
.ds .ds-bar-item-meta { display: block; margin-top: 2px; font: var(--font-meta); color: var(--text-muted); }
/* 27px = retrait de la ligne + barre + ecart : le detail s aligne sur le texte. */
.ds .ds-bar-item-body { padding: 0 var(--space-3) var(--space-3) calc(var(--space-3) * 2 + 3px); }

/* ============================================================
   FIL D'ARIANE  (`.crumbs`)
   Recette reprise telle quelle du specimen `audit/patterns.html`
   §06 Navigation. 24px de haut, reutilisable partout : page,
   panneau lateral, fenetre.

   Rendue par `Views/Templates/hmBreadcrumb.html`, pose une seule
   fois sur `$breadcrumbProvider`. Elle REMPLACE `.file-ariane`,
   qui habillait le `<ol class="breadcrumb">` de Bootstrap depuis
   quatre feuilles a la fois ; ces regles ne visent plus rien une
   fois les vues migrees, mais trois des quatre fichiers sont
   generes (`ds-bridge.css`) ou compiles depuis du SCSS
   (`css-client.css`), donc elles restent en place.
   ============================================================ */
.ds .crumbs {
  display: flex;
  align-items: center;
  gap: 2px;
  /* 20px, et non les 24 du specimen : le fil est une ligne de reperage, pas une
     ligne de contenu, et chaque pixel rendu ici est gagne par la liste qui suit
     sur TOUS les ecrans du portail. La hauteur reste FIXE — c'est elle qui
     garantit que la page ne descend pas d'un cran quand un libelle s'allonge. */
  height: 20px;
  min-width: 0;
  /* LA PLUS PETITE TAILLE DU SYSTEME EST `--text-xs` (12px) : l'echelle s'arrete
     la, et le fil d'Ariane est le texte le plus secondaire de la page. Seule la
     TAILLE descend d'un cran sous l'echelle ; la graisse, l'interlignage et la
     famille restent des tokens. Le jour ou un `--text-2xs` entre dans
     `tokens/typography.css`, cette ligne redevient `var(--font-meta)`. */
  font: var(--weight-regular) 11px/var(--leading-snug) var(--font-sans);
  color: var(--text-muted);
}

/* Tout ne se comprime pas : seul le niveau courant cede, plus bas. */
.ds .crumbs > * {
  flex: none;
}

.ds .crumbs a,
.ds .crumbs .cur {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 20px;
  padding: 0 6px;
  border-radius: var(--radius-sm);
  text-decoration: none;
  white-space: nowrap;
  min-width: 0;
}

/* `!important` SUR LA COULEUR, ET C EST LA SEULE FACON. `ds-bridge.css` pose
   `a { color: var(--primary) !important }` sur TOUS les liens de l application
   (ligne 39, feuille generee). Une declaration normale perd contre un
   `!important` quelle que soit sa specificite et quel que soit l ordre des
   feuilles : sans cela le fil d Ariane se peint en bleu de marque au lieu du
   gris du systeme, et le survol ne change rien non plus. C est exactement la
   meme parade que pour les badges. */
.ds .crumbs a {
  color: var(--text-secondary) !important;
}

/* LE SURVOL PASSE AU BLEU DE MARQUE, ET NON AU NOIR DU SPECIMEN. Un niveau de
   fil d'Ariane est un LIEN : le portail entier signale ses liens par
   `var(--primary)`, et c'est d'ailleurs ce que le pont impose a tous les `<a>`.
   Repeindre le survol en `--text-primary` aurait fait du fil le seul endroit de
   l'application ou pointer un lien ne dit pas qu'il en est un. Le fond
   `--surface-hover` du specimen reste : il dessine la cible, le bleu dit la
   nature. */
.ds .crumbs a:hover {
  background: var(--surface-hover);
  color: var(--text-link) !important;
}

.ds .crumbs a:focus-visible {
  outline: none;
  box-shadow: var(--ring);
}

/* LE NIVEAU COURANT S ABREGE, IL NE PASSE PAS A LA LIGNE : c'est ce qui tient
   la hauteur du fil a 20px quoi qu il arrive. Un nom de collaborateur ou un
   intitule de poste long tirait autrefois le bloc sur deux lignes, et toute la
   page descendait d'un cran. Seul lui porte `flex: 0 1 auto`. */
.ds .crumbs .cur {
  color: var(--text-primary);
  font-weight: var(--weight-medium);
  flex: 0 1 auto;
  overflow: hidden;
}

.ds .crumbs .cur span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ds .crumbs .sep {
  color: var(--border-strong);
  display: inline-flex;
}

/* Le separateur de l'accueil n'a rien a separer : il precede le premier
   niveau, qui est l'icone elle-meme. Masque par la feuille et non par un
   `ng-if`, parce que `ng-if` et `ng-repeat` se disputent la transclusion du
   meme noeud — le gabarit l'explique. */
.ds .crumbs .sep.is-home {
  display: none;
}

.ds .crumbs .sep svg {
  width: 1em;
  height: 1em;
}

.ds .crumbs .home {
  padding: 0 5px;
}

/* TROIS PLANS, ET LA MAISON EST AU MILIEU. Les chevrons sont de la ponctuation
   (`--border-strong`, le plus clair), les mots portent le sens (`--text-primary`
   pour le niveau courant), et le pictogramme d'accueil se tient entre les deux :
   assez present pour se voir, assez efface pour ne pas peser plus lourd que le
   nom de la page. Sans cette regle il heritait du gris des liens et pesait
   autant qu'un mot.

   La couleur est posee sur le GLYPHE et non sur le lien : le pont impose
   `a { color: var(--primary) !important }`, qu'une declaration sur `<a>` ne peut
   pas reprendre sans un `!important` de plus. Sur le `<i>`, aucune regle
   concurrente — le poids normal suffit. */
.ds .crumbs .home i {
  color: var(--text-muted);
}

/* Au survol, le glyphe suit son lien : c'est un seul objet cliquable, il ne se
   colore pas en deux temps. */
.ds .crumbs .home:hover i {
  color: inherit;
}

.ds .crumbs .home svg,
.ds .crumbs a > svg {
  width: 1em;
  height: 1em;
}

/* UNE ICONE DE FONTE NE SE DIMENSIONNE PAS EN `width`/`height`. Le specimen
   dessine ses marques en SVG (Lucide), ou `13px` de cote est la taille du
   trace ; le portail les dessine en Font Awesome, c est-a-dire un GLYPHE pose
   par un `::before` sur un `<i>` en ligne. Sur un element en ligne, `width` et
   `height` n ont aucun effet : les regles reprises telles quelles du specimen
   etaient donc inertes, et les chevrons comme la maison heritaient des 12px de
   `--font-meta`. La taille du trace vient de `font-size`, et c est le seul
   endroit du composant ou une valeur en dur est juste : elle reproduit la
   mesure du specimen, qui n a pas de token. */
/* EN `em`, PAS EN PIXELS : les glyphes sont indexes sur le texte de la ligne.
   Deux valeurs en dur cote a cote — 11px pour le mot, 12px pour l'icone — sont
   deux nombres qui derivent des qu'on touche a l'un : c'est exactement ce qui
   s'etait produit, l'icone se retrouvant plus grande que le mot qu'elle
   accompagne. `1em` vaut la taille de `.crumbs`, donc la taille du texte ; il
   n'y a plus qu'UN nombre a tourner dans tout le composant.

   Toutes les marques au meme `1em`, sans compensation par glyphe : la maison,
   l'angle et les points de suspension sont dessines sur la meme grille dans
   Font Awesome, comme ils l'etaient dans Lucide chez le specimen. Compenser
   l'un ferait mentir les autres. */
.ds .crumbs .sep i,
.ds .crumbs .home i,
.ds .crumbs a > i,
.ds .crumbs .fold i {
  font-size: 1em;
  line-height: 1;
}

.ds .crumbs .fold {
  display: none;
  color: var(--text-muted);
  padding: 0 6px;
  height: 20px;
  align-items: center;
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.ds .crumbs .fold:hover {
  background: var(--surface-hover);
  color: var(--text-primary);
}

.ds .crumbs .fold:focus-visible {
  outline: none;
  box-shadow: var(--ring);
}

/* LE REPLI SE DECIDE SUR LA LARGEUR DU COMPOSANT, PAS SUR CELLE DE LA FENETRE.
   C'est tout l'interet du conteneur de requete : le meme fil sert dans une page
   pleine largeur, dans un panneau lateral et dans une fenetre, ou la place
   disponible n'a rien a voir avec la taille de l'ecran. Une `@media` aurait
   replie le fil d'une fenetre etroite sur un grand ecran, et pas l'inverse. */
.ds.crumbs-host {
  container: crumbs / inline-size;
  min-width: 0;
  /* L ECART SOUS LE FIL APPARTIENT AU COMPOSANT, ET IL VIENT D UN TOKEN. C est
     `.file-ariane` qui le posait, en dur (`padding: 8px 8px 8px 0`, styles.css),
     et chaque ecran le rattrapait ensuite a la main : `style="padding-top:5px"`,
     `class="p-t-5"`, `p-t-15`, une regle `.team-btn .p-0 { padding-bottom: 10px }`
     sous media query... Meme valeur que la marge basse de
     `ds-toolbar` : ce sont deux blocs de tete du meme ecran, ils n ont pas de raison
     de se separer differemment.

     Descendu d un cran de l echelle, comme la hauteur de la ligne : le fil
     repere, il n introduit pas. Les 4px rendus ici s ajoutent aux 4 de la ligne
     elle-meme, sur chacun des 267 ecrans. */
  margin-bottom: var(--space-3);
}

@container crumbs (max-width: 520px) {
  .ds .crumbs .mid,
  .ds .crumbs .sep:has(+ .mid) {
    display: none;
  }

  .ds .crumbs .fold {
    display: inline-flex;
  }

  /* Le bouton « … » rend les niveaux caches : sans cela il annoncerait par son
     `aria-expanded` une ouverture qui n'arrive jamais. */
  .ds .crumbs.is-unfolded .mid {
    display: inline-flex;
  }

  .ds .crumbs.is-unfolded .sep:has(+ .mid) {
    display: inline-flex;
  }
}

/* VARIANTE SUR FOND COLORE : bandeau de fenetre ou d'en-tete peint en
   `--primary`. Meme recette, seules les couleurs changent. */
/* `!important` ICI AUSSI, ET PAR CONTAGION : la regle de base a du le prendre
   pour battre le `a { color: ... !important }` de `ds-bridge.css`. Une
   declaration importante l emporte sur une declaration normale meme MOINS
   specifique — sans ce rappel, la variante sur fond colore serait repeinte en
   gris par sa propre regle de base. */
.ds .crumbs.on-dark a {
  color: color-mix(in srgb, var(--on-primary) 78%, transparent) !important;
}

.ds .crumbs.on-dark a:hover {
  background: color-mix(in srgb, var(--on-primary) 14%, transparent);
  color: var(--on-primary) !important;
}

.ds .crumbs.on-dark .cur {
  color: var(--on-primary);
}

.ds .crumbs.on-dark .sep {
  color: color-mix(in srgb, var(--on-primary) 45%, transparent);
}


/* ============================================================
   FENETRE DE SAISIE — VOCABULAIRE `ds-` (12/10/2026)
   ------------------------------------------------------------
   Meme dessin que la section « FENETRE DE SAISIE ET FORMULAIRE » plus haut,
   avec trois regles d ecriture :

   1. Chaque classe porte le prefixe `ds-`. Aucun fichier legacy ni Bootstrap
      n utilise ce prefixe : il n y a rien a battre.
   2. Une classe par selecteur (plus un etat `is-…` / `has-…` pose sur le
      composant). Pas de `.ds .x`, pas de chaine de parents.
   3. Aucun `!important`. Les regles legacy qui visent des BALISES (`input`,
      `label`, `textarea`…) ignorent ces classes a la source :
      `:not(.ds-input)` etc. dans les fichiers legacy.

   Ecran modele : Views/Templates/Modals/Vacation/vacationModal.html.
   Les anciennes recettes (`.ds .field`, `.ds .input`…) restent tant que des
   ecrans les utilisent ; elles partiront quand tout sera passe en `ds-`.

   Exceptions, en attendant leur propre passage : le plugin `cs-select` (son
   JavaScript exige ces noms) et le champ des directives de date
   (`.form-control.date-range`, 134 ecrans). Ils gardent leurs recettes
   `.ds`, mais lisent les variables posees ici (`--field-border`), donc
   suivent les etats du `.ds-field`.
   ============================================================ */

/* ---- la fenetre ---------------------------------------------------- */
.ds-modal-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  background: var(--primary);
}
.ds-modal-title {
  flex: 1;
  min-width: 0;
  margin: 0;
  font: var(--weight-bold) var(--text-lg) var(--font-sans);
  color: var(--on-primary);
}
.ds-modal-close {
  flex: none;
  display: inline-flex;
  color: var(--on-primary);
  opacity: .8;
  cursor: pointer;
}
.ds-modal-close:hover { opacity: 1; }

.modal:not(.ng-aside) .ds-modal-head {
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.modal:not(.ng-aside) .ds-modal-title { color: var(--text-primary); }
.modal:not(.ng-aside) .ds-modal-close { color: var(--text-muted); }

/* In a window that slides in from the right, the × sits in the screen's top-right corner: the default
   tooltip above it is cut off by the screen edge, so it is pinned to the left of the × instead.
   `!important` beats the top/left that ui-bootstrap writes inline. */
.modal.ng-aside.right .ds-modal-head { position: relative; }
.modal.ng-aside.right .ds-modal-head > .tooltip {
  top: 50% !important;
  left: auto !important;
  right: calc(var(--space-5) + 1em);
  margin: 0;
  padding: 0 5px;
  transform: translateY(-50%);
}
.modal.ng-aside.right .ds-modal-head > .tooltip .tooltip-arrow {
  top: 50%;
  bottom: auto;
  left: auto;
  right: 0;
  margin: -5px 0 0;
  border-width: 5px 0 5px 5px;
  border-color: transparent;
  /* same black as the tooltip body, which has no token yet */
  border-left-color: #000;
}
.ds-modal-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-5);
}
/* Assistant en fenetre : la barre du libre-service colle sous la barre d etapes, comme
   le fait `.ds.hm-wizard-modal .modal-body …` (CustomHrMaps.css) pour l ancien corps. */
.ds.hm-wizard-modal .ds-modal-body .self-service > .ds-toolbar { top: 65px; }
/* Le pied passe a la ligne quand ses actions ne tiennent pas (telephone). */
.ds-modal-foot {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-top: 1px solid var(--border);
}
/* Pousse ce qui suit au bout de la ligne. */
.ds-spacer { margin-left: auto; }

/* ---- mise en page -------------------------------------------------- */
.ds-stack { display: flex; flex-direction: column; gap: var(--space-4); }
.ds-stack.is-tight { gap: var(--space-2); }
/* `minmax(0, 1fr)` : un champ en `width: 100%` ne force pas la colonne a s elargir. */
.ds-grid2 { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--space-4) var(--space-5); }
.ds-span2 { grid-column: span 2; }
.ds-hstack { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2); min-width: 0; }
@media (max-width: 860px) {
  .ds-grid2 { grid-template-columns: minmax(0, 1fr); }
  .ds-span2 { grid-column: auto; }
}

/* ---- le champ ------------------------------------------------------ */
.ds-field { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.ds-label {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin: 0;
  padding: 0;
  font: var(--font-label);
  color: var(--text-secondary);
}
.ds-hint { font: var(--font-meta); color: var(--text-muted); }
/* Le message d erreur dit quoi faire ; il se range apres le controle, y compris
   quand le plugin `cs-select` ajoute son declencheur en fin de champ. */
.ds-err-msg {
  display: flex;
  align-items: center;
  gap: 5px;
  font: var(--font-meta);
  color: var(--danger-fg);
}
.ds-field > .ds-err-msg { order: 2; }

/* L obligation : un asterisque rouge, une coche verte une fois le champ valide.
   Ecrit par le `::before`, comme l ancien `symbol required`, pour garder la
   coche. La meme classe sert dans la legende du pied (« * Champs obligatoires »). */
.ds-req { font-size: 1em; line-height: inherit; }
.ds-req::before { content: "*"; color: var(--danger-fg); }
.ds-field.is-valid .ds-req::before {
  content: "\f00c";
  font-family: "Font Awesome 5 Pro";
  margin-left: 3px;
  color: var(--success-fg);
}
/* Colonne d une ligne repetable : la coche attend que TOUTES les lignes soient
   remplies ; et un champ qui affiche une erreur garde l asterisque. */
.ds-field.is-valid .ds-req.is-incomplete::before,
.ds-field.is-valid:has(.ds-err-msg:not(.ng-hide)) .ds-req::before {
  content: "*";
  font-family: inherit;
  margin-left: 0;
  color: var(--danger-fg);
}

/* ---- les controles ------------------------------------------------- */
.ds-input,
.ds-select,
.ds-textarea {
  box-sizing: border-box;
  width: 100%;
  min-height: var(--control-h);
  height: var(--control-h);
  padding: 0 12px;
  font: var(--font-body);
  line-height: calc(var(--control-h) - 2px);
  color: var(--text-primary);
  background-color: var(--field-bg);
  border: 1px solid var(--field-border);
  border-radius: var(--radius-input);
  appearance: none;
  -webkit-appearance: none;
  transition: border-color var(--duration-fast), box-shadow var(--duration-fast);
}
.ds-textarea {
  min-height: var(--space-20);
  height: auto;
  padding: 7px 12px;
  line-height: var(--leading-normal);
  resize: vertical;
}
/* Liste deroulante NATIVE (entretien : evaluation du poste) : meme champ que `ds-input`, plus le chevron
   du design system. */
.ds-select {
  padding-right: 34px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' stroke='%237A8294' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4 6 4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 11px center;
}
.ds-input::placeholder,
.ds-textarea::placeholder { color: var(--field-placeholder); opacity: 1; }
.ds-input:hover,
.ds-select:hover,
.ds-textarea:hover { border-color: var(--field-border-hover); }
.ds-input:focus,
.ds-select:focus,
.ds-textarea:focus { outline: none; border-color: var(--primary); box-shadow: var(--ring); }
.ds-input[readonly] { color: var(--text-secondary); }
/* Desactive = a LIRE (entretien en lecture seule, valeur verrouillee) : texte secondaire et non
   `--text-disabled` (2,0:1 sur le creux, illisible ; --text-secondary 5,6:1, mesure du 28/09/2026,
   meme decision que le libre-service). Curseur `not-allowed` (19/10/2026, demande produit) :
   le survol annonce que le champ ne se saisit pas. */
.ds-input:disabled,
.ds-select:disabled,
.ds-textarea:disabled {
  color: var(--text-secondary);
  background-color: var(--surface-sunken);
  border-color: var(--border);
  cursor: not-allowed;
}
/* Un champ desactive ne s eclaire pas au survol. `ds-bridge.css` repeint la bordure de tout
   champ survole en `--primary` avec `!important` (`input[type="text"]:hover`, poids 0,2,1) :
   seul un `!important` le bat, et la classe repetee monte a 0,3,0. */
.ds-input.ds-input:disabled,
.ds-select.ds-select:disabled,
.ds-textarea.ds-textarea:disabled { border-color: var(--border) !important; }
/* Chrome pose de lui-meme `opacity: 0.7` sur un <select> desactive : il repalissait la valeur. */
.ds-select:disabled { opacity: 1; }

/* ETATS, poses sur le `.ds-field`. Le champ en erreur (classe, ou message
   affiche) passe sa bordure en rouge ; les variables servent aussi aux
   controles qui ne sont pas encore en `ds-` (`cs-select`, champ de date). */
.ds-field.is-error,
.ds-field:has(.ds-err-msg:not(.ng-hide)) {
  --field-border: var(--danger-border);
  --input-border: var(--danger-border);
}
.ds-field.is-error .ds-input,
.ds-field.is-error .ds-input:hover,
.ds-field.is-error .ds-select,
.ds-field.is-error .ds-select:hover,
.ds-field.is-error .ds-textarea,
.ds-field.is-error .ds-textarea:hover { border-color: var(--danger-border); }
/* Un champ en erreur garde sa bordure rouge au survol. Meme cause que le champ desactive :
   `ds-bridge.css` repeint la bordure de tout champ survole en `--primary` avec `!important`
   (`textarea:hover`, `input[type="text"]:hover`, poids 0,2,1) ; ces selecteurs pesent 0,4,0. */
.ds-field.is-error .ds-input:hover,
.ds-field.is-error .ds-select:hover,
.ds-field.is-error .ds-textarea:hover { border-color: var(--danger-border) !important; }
.ds-field.is-error .ds-input:focus,
.ds-field.is-error .ds-select:focus,
.ds-field.is-error .ds-textarea:focus { box-shadow: 0 0 0 3px var(--danger-soft); }
/* Un champ en lecture seule (rempli par une liste) passe lui aussi en rouge : la regle du champ
   desactive pese 0,3,0 avec `!important`, celles-ci 0,4,1. */
.ds-field.is-error .ds-input.ds-input:disabled,
.ds-field.is-error .ds-select.ds-select:disabled,
.ds-field.is-error .ds-textarea.ds-textarea:disabled { border-color: var(--danger-border) !important; }
/* Les boutons accoles (liste, corbeille) lisent `--field-border`, que l erreur passe au rouge :
   ils reprennent la valeur normale, relevee sur le conteneur `.ds` pour suivre le theme. */
.ds { --field-border-rest: var(--field-border); }
.ds-field.is-error .ds-repeat-actions,
.ds-field:has(.ds-err-msg:not(.ng-hide)) .ds-repeat-actions { --field-border: var(--field-border-rest); }

/* Les directives de date posent leur croix d effacement en absolu : leur hote
   sert de repere. */
.ds-field > hm-date-picker,
.ds-field > hm-date-range-picker,
.ds-field > hm-time-picker { position: relative; display: block; }

/* ---- prefixe / suffixe : « 4,5 | jours » ---------------------------- */
.ds-affix { display: flex; align-items: stretch; min-width: 0; }
.ds-affix > .ds-input { flex: 1 1 auto; min-width: 0; }
/* Boutons accoles au champ (entretien : - / + de la question nombre). */
.ds-affix > .ds-repeat-actions { margin-left: var(--space-2); }
.ds-input.has-end { border-radius: var(--radius-input) 0 0 var(--radius-input); }
.ds-affix-tag {
  flex: none;
  display: inline-flex;
  align-items: center;
  padding: 0 11px;
  min-height: var(--control-h);
  font: var(--font-body-sm);
  color: var(--text-muted);
  white-space: nowrap;
  background: var(--surface-sunken);
  border: 1px solid var(--field-border);
}
.ds-affix-tag.is-end { border-left: none; border-radius: 0 var(--radius-input) var(--radius-input) 0; }

/* ---- choix : cases et radios --------------------------------------- */
.ds-optgroup { display: flex; flex-direction: column; gap: 2px; }
.ds-optgroup.is-inline { flex-direction: row; flex-wrap: wrap; gap: var(--space-5); }
.ds-opt {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--control-h-sm);
  margin: 0;
  font: var(--font-body);
  color: var(--text-primary);
  cursor: pointer;
}
/* Natifs : c est eux qui portent la couleur du client (`accent-color`) et le clavier. */
.ds-check,
.ds-radio {
  display: inline-block;
  flex: none;
  width: 17px;
  height: 17px;
  margin: 0;
  accent-color: var(--primary);
  cursor: pointer;
}
/* Bootstrap (bibliotheque, non modifiee) pose `input[type="checkbox"]` et `[type="radio"]`
   a `margin: 4px 0 0`, plus lourd qu une classe seule : dans son option, la case reprend
   sa marge nulle. */
.ds-opt > .ds-check,
.ds-opt > .ds-radio { margin: 0; }
/* DESACTIVEES (entretien en lecture seule) : dessinees ici. Le navigateur peint une case native
   desactivee en gris pale, sans la couleur du client : cochee ou non, on ne voyait plus la reponse.
   Cadre sur fond en creux, point ou coche en texte secondaire : la reponse se lit, et la case reste
   visiblement non modifiable. Les cases actives gardent le rendu natif. */
.ds-check:disabled,
.ds-radio:disabled,
.ds .chk:disabled,
.ds .rad:disabled {
  appearance: none;
  -webkit-appearance: none;
  display: inline-grid;
  place-content: center;
  border: 1.5px solid var(--border-strong);
  background: var(--surface-sunken);
  cursor: default;
}
.ds-radio:disabled, .ds .rad:disabled { border-radius: 50%; }
.ds-check:disabled, .ds .chk:disabled { border-radius: var(--radius-xs); }
.ds-check:disabled:checked,
.ds-radio:disabled:checked,
.ds .chk:disabled:checked,
.ds .rad:disabled:checked { border-color: var(--text-secondary); }
.ds-radio:disabled:checked::before,
.ds .rad:disabled:checked::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--text-secondary);
}
.ds-check:disabled:checked::before,
.ds .chk:disabled:checked::before {
  content: "";
  width: 9px;
  height: 5px;
  border-left: 2px solid var(--text-secondary);
  border-bottom: 2px solid var(--text-secondary);
  transform: translateY(-1px) rotate(-45deg);
}
.ds-opt:has(> :disabled) { cursor: default; }

/* ---- messages ------------------------------------------------------ */
.ds-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  font: var(--weight-medium) var(--text-xs)/1 var(--font-sans);
  white-space: nowrap;
  border: 1px solid transparent;
  border-radius: var(--radius-badge);
}
.ds-badge.is-warning { background: var(--warning-soft); color: var(--warning-fg); }
.ds-alert {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 13px 15px;
  font: var(--font-body-sm);
  border-radius: var(--radius-md);
}
.ds-alert.is-warning { background-color: var(--warning-soft); color: var(--warning-fg); }
.ds-alert.is-danger  { background-color: var(--danger-soft);  color: var(--danger-fg); }
.ds-alert.is-success { background-color: var(--success-soft); color: var(--success-fg); }
/* `is-compact` : une consigne d une ligne posee dans un formulaire (« Selectionnez au moins
   une formation… », createRequestTrainingModal). Retrait et texte d une aide, la bande ne
   prend que la largeur de son texte. */
.ds-alert.is-compact {
  align-self: flex-start;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  font: var(--font-meta);
}
.ds-alert.is-compact > i,
.ds-alert.is-compact > svg { width: 12px; height: 12px; flex: none; }

/* ---- documents ----------------------------------------------------- */
.ds-repeat {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
.ds-legend {
  font: var(--weight-bold) var(--text-xs)/1 var(--font-sans);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-muted);
}
/* La zone de depot : toute sa surface ouvre le choix de fichier (l input
   transparent la recouvre, la zone est son repere). */
.ds-drop {
  position: relative;
  padding: 18px;
  text-align: center;
  font: var(--font-body-sm);
  color: var(--text-muted);
  background: var(--surface-sunken);
  border: 1.5px dashed var(--border-strong);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: border-color var(--duration-fast), background var(--duration-fast);
}
.ds-drop:hover { color: var(--primary-700); background: var(--primary-soft); border-color: var(--primary); }
.ds-drop > i,
.ds-drop > svg { display: block; width: 20px; height: 20px; margin: 0 auto 7px; }
.ds-drop-pick { display: block; font-weight: var(--weight-medium); color: var(--primary-700); }
.ds-drop-pick > input[type="file"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}

.ds-drop-alt {
  position: relative;
  z-index: 1;
  display: inline-block;
  margin-top: var(--space-1);
  color: var(--primary-700);
  cursor: pointer;
}
.ds-drop-alt:hover { text-decoration: underline !important; }
.ds-drop:has(.ds-drop-alt):hover:not(:has(.ds-drop-alt:hover)) .ds-drop-pick { text-decoration: underline; }
/* Une ligne par fichier depose : nom, type, suppression. */
.ds-repeat-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
  gap: var(--space-3);
  align-items: start;
}
/* Dans la colonne de son champ, les actions descendent au niveau des controles. */
.ds-repeat-actions { display: flex; align-items: center; gap: var(--space-2); flex: none; margin-top: auto; }
/* Ecran etroit : la corbeille se range a droite du nom du fichier, le type passe dessous
   sur toute la largeur. Empilee seule, elle prenait une ligne entiere sous un libelle vide. */
@media (max-width: 860px) {
  .ds-repeat-grid { grid-template-columns: minmax(0, 1fr) auto; }
  .ds-repeat-grid > .ds-field:has(> .ds-repeat-actions) { grid-column: 2; grid-row: 1; align-self: end; }
  .ds-repeat-grid > .ds-field:has(> .ds-repeat-actions) > .ds-label { display: none; }
  .ds-repeat-grid > .ds-field:not(:first-child):not(:has(> .ds-repeat-actions)) { grid-column: 1 / -1; }
}
/* Un document deja joint : son nom (lien), son type, la suppression. */
.ds-filerow {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 7px 10px;
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
.ds-filerow-name {
  flex: 1;
  min-width: 0;
  font: var(--font-body-sm);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---- champ a bouton de fin : l oeil du mot de passe ---------------- */
/* Le bouton se pose DANS le champ, a droite ; le champ lui reserve sa place. */
.ds-input-wrap { position: relative; display: block; min-width: 0; }
.ds-input-wrap > .ds-input { padding-right: 36px; }
/* Edge ajoute son propre oeil (et sa croix) dans un champ mot de passe : il
   doublait le notre. Le bouton du design system est le seul. */
.ds-input::-ms-reveal,
.ds-input::-ms-clear { display: none; }
.ds-input-trail {
  position: absolute;
  top: 50%;
  right: 11px;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--icon-md, 16px);
  height: var(--icon-md, 16px);
  color: var(--text-muted);
  cursor: pointer;
}
.ds-input-trail:hover { color: var(--text-primary); }
.ds-input-trail > i,
.ds-input-trail > svg { width: var(--icon-md, 16px); height: var(--icon-md, 16px); }

/* ============================================================
   CONNEXION — `hm-wrapper-login` (13/10/2026)
   ------------------------------------------------------------
     .ds-auth          la page : la carte centree sur toute la hauteur
     .ds-auth-card     la carte ; son fond (couleur ou image du client) est
                       une DONNEE, pose par `ng-style` (getDynamicBackground)
     .is-compact       telephone ou captcha affiche : marges reduites
     .ds-auth-panel    le volet blanc du formulaire (40 %)
     .ds-auth-logo     le logo du client, contenu dans sa boite
     .ds-auth-title    le titre d accueil (couleur du client en `ng-style`)
     .ds-auth-foot     l annee et le nom, en bas du volet
     .ds-auth-captcha  l image du captcha
   Le chargeur des connexions SSO (check, checksso) est l anneau du DS, `ds-loader-ring is-lg`.
   ============================================================ */
/* Le haut de la carte est FIXE, a la place qu aurait une carte de 650px
   centree : quand le contenu grandit pendant la saisie (jauge, regles,
   message d erreur), la carte s allonge vers le bas au lieu de sauter. */
.ds-auth {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  box-sizing: border-box;
  min-height: 100vh;
  padding: max(var(--space-4), calc((100vh - 650px) / 2)) var(--space-4) var(--space-4);
  background: var(--bg-canvas);
}
/* Le chargeur, lui, ne grandit pas : il reste au centre de l ecran. */
.ds-auth.is-centered { align-items: center; padding: var(--space-4); }
.ds-auth-card {
  display: flex;
  box-sizing: border-box;
  width: 90%;
  max-width: 1000px;
  /* Hauteur MINIMALE : la carte grandit avec son contenu (texte d accueil
     sur deux lignes, captcha) au lieu de faire defiler le volet. */
  min-height: 650px;
  padding: var(--space-8);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.ds-auth-panel {
  flex: 0 0 40%;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  box-sizing: border-box;
  min-width: 0;
  padding: var(--space-10);
  border-radius: var(--radius-xl);
  background: var(--surface);
}
.ds-auth-card.is-compact { padding: var(--space-4); }
.ds-auth-card.is-compact .ds-auth-panel { padding: 0 var(--space-5); }
.ds-auth-logo {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  max-width: 200px;
  height: 150px;
  margin: 0 auto;
}
.ds-auth-logo > img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}
.ds-auth-title {
  margin: 0;
  font: var(--weight-bold) var(--text-xl) var(--font-sans);
  color: var(--text-primary);
}
.ds-auth-foot {
  margin-top: auto;
  padding: var(--space-2) 0;
  text-align: center;
  font: var(--font-meta);
  color: var(--text-muted);
}
.ds-auth-captcha { display: block; width: 200px; height: 50px; cursor: pointer; }

/* ---- force du mot de passe ----------------------------------------- */
/* Trois segments et le libelle du niveau. Le niveau se pose sur chaque
   segment allume (`is-weak`, `is-medium`, `is-strong`). */
.ds-strength { display: flex; align-items: center; gap: var(--space-3); }
.ds-strength-bar { flex: 1; display: flex; gap: 6px; }
.ds-strength-seg {
  flex: 1;
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
}
.ds-strength-seg.is-weak   { background: var(--danger); }
.ds-strength-seg.is-medium { background: var(--warning); }
.ds-strength-seg.is-strong { background: var(--success); }
.ds-strength-label { font: var(--font-meta); color: var(--text-muted); white-space: nowrap; }

/* ---- regles a respecter (mot de passe) ----------------------------- */
/* Neutre tant que rien n est saisi ; vert une fois la regle tenue, rouge sinon. */
.ds-rules { display: flex; flex-direction: column; gap: var(--space-1); margin: 0; padding: 0; list-style: none; }
.ds-rule {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font: var(--font-body-sm);
  color: var(--text-secondary);
}
.ds-rule.is-valid { color: var(--success-fg); }
.ds-rule.is-error { color: var(--danger-fg); }
.ds-rule-icon { flex: none; display: inline-flex; width: 14px; height: 14px; }
.ds-rule-icon > i,
.ds-rule-icon > svg { width: 100%; height: 100%; }
/* Ecran etroit : le volet prend toute la carte, l image du client ne se voit plus. */
@media (max-width: 768px) {
  .ds-auth-card { width: 100%; min-height: 0; }
  .ds-auth-panel { flex-basis: 100%; }
  .ds-auth-logo { height: 120px; }
}
/* Remplissage automatique du navigateur : Chrome ne declenche pas `change`.
   Cette animation vide sert de signal : `LoginCtrl` ecoute `animationstart`
   sur #username et active alors le bouton de connexion. Le nom de l animation
   est lu par le JavaScript : ne pas le changer. */
@keyframes onAutoFillStart { from { opacity: 1; } to { opacity: 1; } }
.ds-input:-webkit-autofill {
  animation-name: onAutoFillStart;
  animation-duration: 0.01s;
}
